SOME/IP用到了组播, 如华为MDC300F系统101版本的SOME/IP组播域地址为239.192.255.251
, 端口号 30491. 本篇不讲SOME/IP, 只看下组播的概念.
组播(Multicast, 又称多播)是UDP专有的, 关于UDP的单播(Unicast), 组播, 广播(Broadcast)的区别, 网络上有张图有些形象:
D类地址空间分配给了IP组播, 范围 224.0.0.0~239.255.255.255, 二进制表示前四位均为1110, 细分下来又有如下区别:
这里用如下系统测试组播:
Win10能ping通U18和U20, U18和U20彼此可以ping通
先来看下单播, 用Ubuntu18发送数据给Win10和Ubuntu20, python3的发送程序 tx.py
import socket
import time
win10_addr = ("192.168.65.1", 34567)
u20_addr = ("192.168.65.128", 34567)
# local_addr = ("192.168.65.156", 34568)
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# sock.bind(local_addr)
cnt = 0
while True:
MSG = bytes(f'{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())}' + \
", it's unicast, " + str(cnt), encoding="utf8")
print(MSG)
sock.sendto(MSG, win10_addr)
sock.sendto(MSG, u20_addr)
cnt = cnt + 1
time.sleep(1)
# $ python3 tx.py
# b"2021-10-12 14:48:24, it's unicast, 0"
# b"2021-10-12 14:48:25, it's unicast, 1"
Win10这边python3的接收程序, Ubuntu20把addr改为addr = ("192.168.65.128", 34567)
import socket
addr = ("192.168.65.1", 34567)
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(addr)
while True:
r_data, r_addr = sock.recvfrom(1024)
print(r_addr, r_data)
# ('192.168.65.156', 37053) b"2021-10-12 14:59:36, it's unicast, 120"
# ('192.168.65.156', 37053) b"2021-10-12 14:59:37, it's unicast, 121"
这样Win10和Ubuntu20都能接收到Ubuntu18发出的消息, 但注意发送程序中的两个 sock.sendto
, 如果再增加一个设备接收消息, 那个发送程序还是要修改, 这是很不方便且耗费网络资源的(如果直播按这种方式多一个用户观看要多一份视频带宽, 这将是不能忍受的), 由Wireshark也能看出来, 一份消息被投递了两次
有没有方法只发送一次, 就能让两个设备都接收到, 并且即便再增加设备接收消息, 也不需要修改发送程序? 来, 组播上场
先来看Ubuntu18的发送程序:
import socket
import time
multicast_addr = ("239.255.255.252", 34567)
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
cnt = 0
while True:
MSG = bytes(f'{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())}' + \
", it's unicast, " + str(cnt), encoding="utf8")
print(MSG)
sock.sendto(MSG, multicast_addr)
cnt = cnt + 1
time.sleep(1)
Win10的接收程序
import socket
win10_addr = ("192.168.65.1", 34567)
multicast_addr = ("239.255.255.252", 34567)
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
# allow multiple sockets to use the same PORT number
# sock.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
sock.bind(win10_addr)
sock.setsockopt(socket.IPPROTO_IP,\
socket.IP_ADD_MEMBERSHIP,\
socket.inet_aton(multicast_addr[0]) + socket.inet_aton(win10_addr[0]))
#sock.setblocking(0)
while True:
r_data, r_addr = sock.recvfrom(1024)
print(r_addr, r_data)
# try:
# r_data, r_addr = sock.recvfrom(1024)
# except socket.error:
# pass
# else:
# print(r_addr, r_data)
# ('192.168.65.156', 43223) b"2021-10-12 15:43:06, it's unicast, 1515"
# ('192.168.65.156', 43223) b"2021-10-12 15:43:07, it's unicast, 1516"
Ubuntu20的接收程序(与Win10不同)
import socket
import struct
MCAST_GRP = '239.255.255.252'
MCAST_PORT = 34567
IS_ALL_GROUPS = True
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
if IS_ALL_GROUPS:
# on this port, receives ALL multicast groups
sock.bind(('', MCAST_PORT))
else:
# on this port, listen ONLY to MCAST_GRP
sock.bind((MCAST_GRP, MCAST_PORT))
mreq = struct.pack("4sl", socket.inet_aton(MCAST_GRP), socket.INADDR_ANY)
sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
while True:
print(sock.recv(10240))
# b"2021-10-12 16:18:09, it's unicast, 3614"
# b"2021-10-12 16:18:10, it's unicast, 3615"
# b"2021-10-12 16:18:11, it's unicast, 3616"
# 复制一份程序继续运行, 测试发现组播还能被同一个设备的两个进程同时使用
Ubuntu下也有一种是直接绑定网卡的方式
# 一种是直接绑定网卡的
import struct
import socket
nic_name = 'ens33' #网卡名
# ubuntu20_addr = ("192.168.65.128", 34567)
multicast_addr = ("239.255.255.252", 34567)
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
# 25是linux上的socket.SO_BINDTODEVICE的宏定义
sock.setsockopt(socket.SOL_SOCKET,25,nic_name.encode())
sock.bind(multicast_addr)
# 加入组播组
mreq = struct.pack("=4sl", socket.inet_aton(multicast_addr[0]), socket.INADDR_ANY)
sock.setsockopt(socket.IPPROTO_IP,socket.IP_ADD_MEMBERSHIP,mreq)
while True:
r_data, r_addr = sock.recvfrom(1024)
print(r_addr, r_data)
用wireshark查看, 确实是Ubuntu18只投递了一次数据, 两个设备都能接收到, 即便再增加设备收消息, 发送程序也无需改动
直接搬参考代码
/* Receiver/client multicast Datagram example. */
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define PORT 34567
#define LOCAL_IP "192.168.65.128"
#define MCAST_IP "239.255.255.252"
struct sockaddr_in localSock;
struct ip_mreq group;
int sd;
int datalen;
char databuf[1024];
int main(int argc, char *argv[])
{
/* Create a datagram socket on which to receive. */
sd = socket(AF_INET, SOCK_DGRAM, 0);
if (sd < 0)
{
perror("Opening datagram socket error");
exit(1);
}
else
{
printf("Opening datagram socket....OK.\n");
}
/* Enable SO_REUSEADDR to allow multiple instances of this */
/* application to receive copies of the multicast datagrams. */
{
int reuse = 1;
if (setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, (char *)&reuse, sizeof(reuse)) < 0)
{
perror("Setting SO_REUSEADDR error");
close(sd);
exit(1);
}
else
{
printf("Setting SO_REUSEADDR...OK.\n");
}
}
/* Bind to the proper port number with the IP address */
/* specified as INADDR_ANY. */
memset((char *)&localSock, 0, sizeof(localSock));
localSock.sin_family = AF_INET;
localSock.sin_port = htons(PORT);
localSock.sin_addr.s_addr = INADDR_ANY;
if (bind(sd, (struct sockaddr *)&localSock, sizeof(localSock)))
{
perror("Binding datagram socket error");
close(sd);
exit(1);
}
else
{
printf("Binding datagram socket...OK.\n");
}
/* Join the multicast group 226.1.1.1 on the local 203.106.93.94 */
/* interface. Note that this IP_ADD_MEMBERSHIP option must be */
/* called for each local interface over which the multicast */
/* datagrams are to be received. */
group.imr_multiaddr.s_addr = inet_addr(MCAST_IP);
group.imr_interface.s_addr = inet_addr(LOCAL_IP);
if (setsockopt(sd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *)&group, sizeof(group)) < 0)
{
perror("Adding multicast group error");
close(sd);
exit(1);
}
else
{
printf("Adding multicast group...OK.\n");
}
/* Read from the socket. */
datalen = sizeof(databuf);
while(1)
{
if (read(sd, databuf, datalen) < 0)
{
perror("Reading datagram message error");
close(sd);
exit(1);
}
else
{
printf("Reading datagram message...OK.\n");
printf("The message from multicast server is: \"%s\"\n", databuf);
}
}
return 0;
}
/* Send Multicast Datagram code example. */
#include
#include
#include
#include
#include
#include
#include
#include
#define PORT 34567
#define LOCAL_IP "192.168.65.128"
#define MCAST_IP "239.255.255.252"
struct in_addr localInterface;
struct sockaddr_in groupSock;
int sd;
char databuf[1024] = "Multicast test message lol!";
int datalen = sizeof(databuf);
int main(int argc, char *argv[])
{
/* Create a datagram socket on which to send. */
sd = socket(AF_INET, SOCK_DGRAM, 0);
if (sd < 0)
{
perror("Opening datagram socket error");
exit(1);
}
else
{
printf("Opening the datagram socket...OK.\n");
}
/* Initialize the group sockaddr structure with a */
/* group address of 225.1.1.1 and port 5555. */
memset((char *)&groupSock, 0, sizeof(groupSock));
groupSock.sin_family = AF_INET;
groupSock.sin_addr.s_addr = inet_addr(MCAST_IP);
groupSock.sin_port = htons(PORT);
/* Disable loopback so you do not receive your own datagrams.
{
char loopch = 0;
if(setsockopt(sd, IPPROTO_IP, IP_MULTICAST_LOOP, (char *)&loopch, sizeof(loopch)) < 0)
{
perror("Setting IP_MULTICAST_LOOP error");
close(sd);
exit(1);
}
else
{
printf("Disabling the loopback...OK.\n");
}
}
*/
/* Set local interface for outbound multicast datagrams. */
/* The IP address specified must be associated with a local, */
/* multicast capable interface. */
localInterface.s_addr = inet_addr(LOCAL_IP);
if (setsockopt(sd, IPPROTO_IP, IP_MULTICAST_IF, (char *)&localInterface, sizeof(localInterface)) < 0)
{
perror("Setting local interface error");
exit(1);
}
else
{
printf("Setting the local interface...OK\n");
}
/* Send a message to the multicast group specified by the*/
/* groupSock sockaddr structure. */
/*int datalen = 1024;*/
while(1)
{
if (sendto(sd, databuf, datalen, 0, (struct sockaddr *)&groupSock, sizeof(groupSock)) < 0)
{
perror("Sending datagram message error");
}
else
{
printf("Sending datagram message...OK\n");
}
sleep(1);
}
/* Try the re-read from the socket if the loopback is not disable
if(read(sd, databuf, datalen) < 0)
{
perror("Reading datagram message error\n");
close(sd);
exit(1);
}
else
{
printf("Reading datagram message from client...OK\n");
printf("The message is: %s\n", databuf);
}
*/
return 0;
}
在Ubuntu20运行, Wireshark可以看到, 两个组播发送都可以发出来, 在Win10上也可以接收到所有的组播数据
How do you UDP multicast in Python? - Stack Overflow
The Linux socket and network programming on multicasting client-server with C program example (tenouk.com)
https://web.cs.wpi.edu/~claypool/courses/4514-B99/samples/multicast.c
Python3组播通信编程实现教程(发送者+接收者) - 诸子流 - 博客园 (cnblogs.com)
通过python下的socket实现组播数据的发送和接收_ztb3214的专栏-CSDN博客_python 组播
组播的时候到底该如何绑定网卡_holly的专栏-CSDN博客
组播_百度百科 (baidu.com)
UDP 用户数据报格式(单播+组播)_u010018619的博客-CSDN博客
欢迎扫描二维码关注本人微信公众号, 及时获取最新文章: