网络_UDP
UDP组播的概念
单播是指将数据包从1个发送方到1个特定的接收方。
组播是指将数据包从1个发送方到1组特定的接收方。这个组是预先定义的,这些接收方共享同一个组播地址。
在IPv4中,组播地址是有范围和规定的,不能随便写。IPv4的组播地址范围是224.0.0.0
到239.255.255.255
。
其中,224.0.0.0
到224.0.0.255
是预留的,用于本地链接的多播地址(Link-Local Multicast Addresses),而其他范围的地址才可以用于全局组播。
在选择组播地址时,应该遵循规范,避免使用预留的地址或者其他可能会引起冲突的地址。
通常建议从239.0.0.0
开始向上选择,确保不会与其他组播组冲突。
setsockopt()
setsockopt()
函数是系统调用,用于设置套接字选项,它允许程序员为打开的套接字设置不同的选项和参数
1 | int setsockpt(int sockfd, int level, int optname, const void *optval, socklen_t optlen); |
参数:
sockfd
:指定要设置选项的套接字文件描述符。level
:指定选项所在的协议层。常用的有SOL_SOCKET
表示套接字级别的选项,IPPROTO_IP
表示IP层的选项,IPPROTO_TCP
表示TCP层的选项,IPPROTO_IPV6
表示IPV6层的选项等。optname
:指定要设置的选项名。IP_ADD_MEMBERSHIP
:加入组播组IP_DROP_MEMBERSHIP
:退出组播组
optval
:指向包含选项值的缓冲区的指针。optlen
:指定选项值的长度。
发送端
1 | // multicast_send.cpp |
接收端
- 与单播不同,sockaddr需要写入IP地址为
0.0.0.0
、端口号为与服务端约定好的组播端口(本例为8888) - 需要绑定创建的sock 和 上面填写好的sockaddr
- 还需要写入一个
ip_mreq
结构。- 指明
imr_multiaddr
,写入与服务端约定好的组播地址(本例为239.0.0.1
) - 指明
imr_interface
,写入0.0.0.0
(本地地址)
- 指明
- setsockopt 设置 sock 加入组播组。
- 在调用
setsockopt
函数时,参数&mreq, sizeof(mreq)
的作用是传递一个ip_mreq
结构体及其大小给内核,以便内核根据这个结构体中的信息将套接字加入到指定的组播组。
- 在调用
- 用的是recv接收,而不是单播时的recvfrom。(其实recvfrom也行,但是接收方必须绑定sock和组播端口)
imr_multiaddr
和imr_interface
有什么区别
1 | struct ip_mreq |
在组播编程中,struct ip_mreq
结构体用于管理组播组成员关系,其中两个关键字段的区别如下:
特性 | imr_multiaddr |
imr_interface |
---|---|---|
作用 | 指定要加入/离开的组播组 | 指定用于组播通信的网络接口 |
地址类型 | D类IP地址(224.0.0.0-239.255.255.255) | 本机真实IP地址或INADDR_ANY |
示例值 | 239.0.0.1 | 192.168.1.100 或 INADDR_ANY |
必要性 | 必须正确设置 | 可选(不设置时系统自动选择) |
用途 | 标识通信的目标组 | 选择收发组播数据的物理/虚拟网卡 |
imr_multiaddr
(组播组地址)
- 定义:表示要加入或离开的组播组
- 组播地址范围:224.0.0.0 到 239.255.255.255(D类IP)
- 永久组播地址:224.0.0.1(所有主机)、224.0.0.2(所有路由器)
- 临时组播地址:239.x.x.x(管理员定义范围)
imr_interface
(本地接口地址)
- 定义:指定用于组播通信的网络接口
- 特点:
- 可以是本机具体IP(192.168.1.100)
- 也可以是特殊值:
INADDR_ANY
(0.0.0.0):让系统自动选择默认路由接口INADDR_LOOPBACK
(127.0.0.1):限定为本地环回
1 | // 自动选择最优网卡(推荐) |
不能指定127.0.0.1
换回接口。当接收外部组播时,即使发送方是在本机上,但指定环回地址会导致接收方不能接收本地的组播数据。
正确示例
1 | struct ip_mreq mreq; |
为什么接收端要绑定?
在组播接收端,bind
操作是必须的,因为:
- 组播数据包是通过UDP传输的,而UDP是无连接的。
- 操作系统需要知道将哪些端口上的数据传递给应用程序。
如果你不调用bind
,那么你的套接字就没有与本地端口关联,操作系统就不会将收到的数据包递送到该套接字。因此,你无法收到任何组播数据。
recvfrom
函数是用来接收数据并获取发送者的地址,但是它并不能替代bind
。实际上,recvfrom
通常是在已经绑定的套接字上使用的。
因此,正确的步骤是:
- 创建套接字。
- 绑定到指定的端口(通常还会指定地址为
INADDR_ANY
,即0.0.0.0
)。 - 加入组播组(通过
setsockopt
设置IP_ADD_MEMBERSHIP)。 - 使用
recvfrom
(或者recv
)接收数据。
所以,即使你想使用recvfrom
来获取发送者的信息,你仍然需要先绑定端口。
如果你尝试不绑定,那么你将会看到recvfrom
调用会失败,并返回一个错误(例如“Transport endpoint is not connected”或者“Invalid argument”)。
因此,结论是:不能省略bind
步骤,即使你打算使用recvfrom
。
bind()
的核心作用 - 声明端口所有权
- 作用:告知内核:“我负责处理本机所有网卡上指定端口的UDP流量”
底层原理:
1 | // 内核网络栈伪代码 |
setsockopt()
的作用 - 订阅组播频道
- 作用:告诉网络驱动:“我对 特定组播地址 的流量感兴趣”
- 触发内核进行:
- IGMP加入报文发送(通知路由器)
- 设置网卡组播过滤器
代码
1 | // multicast_recv.cpp |
问题
为什么发送端发给的是8888,接收端也要从本地的8888接收?
- 组播地址是逻辑分组(如239.0.0.1)
- 只负责标识组播组,不包含端口信息
- 相当于"广播频道",但无法单独区分不同应用
- 端口号标识具体服务
- 接收端通过绑定特定端口(8888)声明:“我是这个组播组中负责8888端口的应用”
- 发送端必须将数据发送到该组播地址+指定端口的组合
想象一个无线电系统:
- 组播地址 = 频道(如FM 239.0)
- 端口号 = 子频道(如"新闻子频道8888")
- 发送端必须在指定频道(FM 239.0)用指定子频道(8888)发射信号
- 接收端必须调到相同频道(FM 239.0)+相同子频道(8888)才能接收
端口重用问题
- 同一台机器上的不同程序不能同时绑定相同端口
- 如需多进程接收,需要设置
SO_REUSEPORT
选项
防火墙配置
- 接收端必须开放对应的UDP端口(示例中是8888)
- Linux检查:
sudo ufw allow 8888/udp