一、说明
1.1 标准组播解释
通信分为单播、多播(即组播)、广播三种方式
单播指发送者发送之后,IP数据包被路由器发往目的IP指定的唯一一台设备的通信形式,比如你现在与web服务器通信就是单播形式
广播指发送者发送之后,IP数据包被路由器发给与其连接的所有设备的通信形式
组播指发送者发送之后,IP数据包被路由器发往目的IP对应组播组名下所有主机的通信形式
1.2 个人理解组播解释
对于标准的组播解释,说明似乎还算是清楚的,但具体到技术就有很多问题。比如我将数据包发往一个组播地址,这个组播地址对应一台物理设备吗?如果不是一台物理设备那谁依据什么向哪些主机发送该数据包等等。
结合各资料和自己测试的情况总结出了以下几点:
1) 编写发送程序:组播数据包是且只是目的IP是组播保留地址的UDP数据包,与正常UDP数据包的区别只是其目的IP是组播保留IP
2) 发送数据包主机:网卡在看到目的IP是组播保留IP后,自动将目的mac地址改成组播mac地址然后向其各端口都发送出去
3) 交换机:交换机在接收到数据包之后,通过目的mac地址认识到这是一个组播数据包,修改源mac为自己mac、保持目的mac为组播mac不变向其各端口都发送出去(交换机对组播包的处理和广播包应该是一样的,或者说对于交换机只有单播包和广播包)
4) 路由器:路由器在接收到数据包之后,通过目的mac地址或目的ip地址认识到这是一个组播数据包,修改源mac为自己mac、保持目的mac为组播mac不变,保持源IP不变、保持目的IP为组播保留IP不变,依照与路由表类似的“组播组地址表”向与目的IP匹配的一个或多端口将数据包发送出去
5) 接收数据包主机:接收数据包主机要想接收到发送主机发送的数据包,首先他要(向路由器说明)加入发送者发往的组播组,然后他要在本地启动一个进程监听发送者发往的端口
6) 组播需要硬件支持,有些路由器是不支持组播的,就直观感受看如果在全球实现组播那维护“组播组地址表”会给路由器带来很大负担路由大厂商应该也不是很愿意支持组播;也就是说理论上组播可以在广域网上实现,但其实一般只在局域网中(能够)使用。
通信举例如下:
发送者S(假设使用组播地址为234.2.2.2,使用组播端口为23456)----发送者向234.2.2.2:23456发送一个UDP数据包Packet1
接收者R1(假设其IP地址为192.168.220.128)----第一步通过setsockopt加入组播组(234.2.2.2);第二步启动进程监听192.168.220.128:23456
接收者R2(假设其IP地址为192.168.220.129)----第一步通过setsockopt加入组播组(234.2.2.2);第二步启动进程监听192.168.220.129:23456
最终效果----发送者发往234.2.2.2:23456的udp数据包,R1的23456端口收到一份Packet1,R2的23456端口收到一份Packet1
1.3 谁是服务端引发的混乱
我在前面一直使用“发送者”、“接收者”,而没有使用“服务端”、“客户端”,因为“服务端”和“客户端”在组播中容易引发混乱。
在我们一般的socket编程中都是服务端去bind;但在组播中是反过来,客户端(接收者)去bind,而发送者(服务端)是不用去bind的(注意是不用而不是不能,你非要bind也是可以的,bind和不bind只是使用固定端口还是使用随机端口的区别)。
有些小伙伴意识到了这个问题,为了与习惯一致,所以直接将发送者称为客户端,接收者称为服务端。
其实这种叫法是不合适的,以初高中常见的电脑课场景为例:老师控制所有电脑显示老师的电脑操作,这时作为发送者的老师电脑从认知上确实应该是服务端而不是客户端才对。
如果你读了半天没听懂这里在说什么,那不必在意,记得组播中尽里使用“发送者”和“接收者”,少用“服务端”和“客户端”就对了。
二、程序实现
2.1 程序代码
发送者代码:
import time import socket # 组播组IP和端口 mcast_group_ip = ‘234.2.2.2‘ mcast_group_port = 23456 def sender(): # 建立发送socket,和正常UDP数据包没区别 send_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) # 每十秒发送一遍消息 while True: message = "this message send via mcast !" # 发送写法和正常UDP数据包的还是完全没区别 # 猜测只可能是网卡自己在识别到目的ip是组播地址后,自动将目的mac地址设为多播mac地址 send_sock.sendto(message.encode(), (mcast_group_ip, mcast_group_port)) print(f‘{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())}: message send finish‘) time.sleep(10) if __name__ == "__main__": sender()
接收者代码:
import struct import time import socket # 组播组IP和端口 mcast_group_ip = ‘234.2.2.2‘ mcast_group_port = 23456 def receiver(): # 建立接收socket,和正常UDP数据包没区别 sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) # 获取本地IP地址 local_ip = socket.gethostbyname(socket.gethostname()) # 监听端口,已测试过其实可以直接bind 0.0.0.0;但注意不要bind 127.0.0.1不然其他机器发的组播包就收不到了 sock.bind((local_ip, mcast_group_port)) # 加入组播组 mreq = struct.pack("=4sl", socket.inet_aton(mcast_group_ip), socket.INADDR_ANY) sock.setsockopt(socket.IPPROTO_IP,socket.IP_ADD_MEMBERSHIP,mreq) # 允许端口复用,看到很多教程都有没想清楚意义是什么,我这里直接注释掉 # sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 设置非阻塞,看到很多教程都有也没想清楚有什么用,我这里直接注释掉 # sock.setblocking(0) while True: try: message, addr = sock.recvfrom(1024) print(f‘{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())}: Receive data from {addr}: {message.decode()}‘) except : print("while receive message error occur") if __name__ == "__main__": receiver()
2.2 运行截图
发送者截图:
接收者运行截图:
参考:
https://blog.csdn.net/ztb3214/article/details/19285363
https://blog.csdn.net/s_lisheng/article/details/74938534?locationNum=10&fps=1
http://blog.sina.com.cn/s/blog_69c81c3e0102x9pm.html
原文地址:https://www.cnblogs.com/lsdb/p/9408947.html