多播编程... 或者编写您自己的多播应用程序。
为了支持多播,编程 API 需要一些扩展。所有这些都通过两个系统调用处理:setsockopt()
(用于向内核传递信息)和 getsockopt()
(用于检索有关多播行为的信息)。这并不意味着为了支持多播而添加了 2 个新的系统调用。setsockopt()
/getsockopt()
这对系统调用已经存在多年了。至少从 4.2 BSD 开始就有了。新增的功能是一组新的选项(多播选项),这些选项被传递给这些系统调用,内核必须理解它们。
以下是 setsockopt()
/getsockopt()
函数原型
int getsockopt(int s, int level, int optname, void* optval, int* optlen);
int setsockopt(int s, int level, int optname, const void* optval, int optlen);
第一个参数 s
是系统调用应用到的套接字。对于多播,它必须是 AF_INET
族的套接字,其类型可以是 SOCK_DGRAM
或 SOCK_RAW
。最常见的用法是使用 SOCK_DGRAM
套接字,但是如果您计划编写路由守护程序或修改一些现有的守护程序,您可能需要使用 SOCK_RAW
套接字。
第二个参数 level
标识要处理选项、消息或查询的层,无论您想称它为什么。因此,SOL_SOCKET
用于套接字层,IPPROTO_IP
用于 IP 层,等等... 对于多播编程,level
将始终是 IPPROTO_IP
。
optname
标识我们正在设置/获取的选项。它的值(由程序提供或由内核返回)是 optval
。多播编程中涉及的选项名称如下
setsockopt() getsockopt() IP_MULTICAST_LOOP yes yes IP_MULTICAST_TTL yes yes IP_MULTICAST_IF yes yes IP_ADD_MEMBERSHIP yes no IP_DROP_MEMBERSHIP yes no
optlen
携带 optval
指向的数据结构的大小。请注意,在 getsockopt()
中,它是一个值-结果,而不是一个值:内核将 optname
的值写入 optval
指向的缓冲区,并通过 optlen
通知我们该值的大小。
setsockopt()
和 getsockopt()
在成功时返回 0,在错误时返回 -1。
作为应用程序编写者,您必须决定是否要将您发送的数据环回给您的主机。如果您计划有多个进程或用户“监听”,则必须启用环回。另一方面,如果您正在发送您的摄像机正在生成的图像,您可能不想要环回,即使您想在屏幕上看到自己。在后一种情况下,您的应用程序可能会从连接到计算机的设备接收图像,并将它们发送到套接字。由于应用程序已经“拥有”该数据,因此它不太可能希望在套接字上再次接收它。环回默认情况下是启用的。
请注意 optval
是一个指针。您不能这样写
setsockopt(socket, IPPROTO_IP, IP_MULTICAST_LOOP, 0, 1);
来禁用环回。而是这样写
u_char loop;
setsockopt(socket, IPPROTO_IP, IP_MULTICAST_LOOP, &loop, sizeof(loop));
并将 loop
设置为 1 以启用环回,或设置为 0 以禁用它。要了解套接字当前是否正在环回,请使用如下代码
u_char loop;
int size;
getsockopt(socket, IPPROTO_IP, IP_MULTICAST_LOOP, &loop, &size)
如果未另行指定,则多播数据报将以默认值 1 发送,以防止它们被转发到本地网络之外。要将 TTL 更改为您 desired 的值(从 0 到 255),请将该值放入一个变量(这里我将其命名为 “ttl”),并在您的程序中的某处写入
u_char ttl;
setsockopt(socket, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl));
getsockopt()
的行为与在 IP_MULTICAST_LOOP
上看到的行为类似。
通常,系统管理员指定应从中发送多播数据报的默认接口。程序员可以覆盖此设置,并使用此选项为给定的套接字选择一个具体的传出接口。
struct in_addr interface_addr;
setsockopt (socket, IPPROTO_IP, IP_MULTICAST_IF, &interface_addr, sizeof(interface_addr));
>从现在开始,在此套接字中生成的所有多播流量都将从所选接口输出。要恢复到原始行为并让内核根据系统管理员的配置选择传出接口,只需使用相同的选项和接口字段中的 INADDR_ANY
调用 setsockopt()
即可。
在确定或选择传出接口时,以下 ioctl
可能很有用:SIOCGIFADDR
(获取接口的地址),SIOCGIFCONF
(获取所有接口的列表)和 SIOCGIFFLAGS
(获取接口的标志,从而确定接口是否具有多播功能 - IFF_MULTICAST
标志)。
如果主机有多个接口且未设置 IP_MULTICAST_IF
选项,则多播传输将从默认接口发送,尽管如果主机充当多播路由器,则其余接口可能用于多播 转发。
回想一下,您需要告诉内核您对哪些多播组感兴趣。如果没有进程对某个组感兴趣,则到达主机的、发往该组的数据包将被丢弃。为了将您的兴趣告知内核,从而成为该组的成员,您应该首先填充一个 ip_mreq
结构,该结构稍后将在 setsockopt()
系统调用的 optval
字段中传递给内核。
ip_mreq
结构(取自 /usr/include/linux/in.h
)具有以下成员
struct ip_mreq
{
struct in_addr imr_multiaddr; /* IP multicast address of group */
struct in_addr imr_interface; /* local IP address of interface */
};
(注意:该结构的“物理”定义在上面指定的文件中。但是,如果您希望您的代码具有可移植性,则不应包含 <linux/in.h>
。 相反,应包含 <netinet/in.h>
,后者又包含 <linux/in.h>
本身)。
第一个成员 imr_multiaddr
保存您要加入的组地址。请记住,成员资格也与接口相关联,而不仅仅是组。这就是您必须为第二个成员 imr_interface
提供值的原因。这样,如果您在多宿主主机中,则可以在多个接口中加入同一组。您始终可以用通配符地址 (INADDR_ANY
) 填充最后一个成员,然后内核将处理选择接口的任务。
填充此结构后(假设您将其定义为:struct ip_mreq mreq;
),您只需按以下方式调用 setsockopt()
setsockopt (socket, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq));
请注意,您可以将多个组加入到同一套接字,而不仅仅是一个。对此的限制是 IP_MAX_MEMBERSHIPS
,截至 2.0.33 版本,其值为 20。
该过程与加入组非常相似
struct ip_mreq mreq;
setsockopt (socket, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq));
其中 mreq
是与加入组时使用的数据相同的结构。如果 imr_interface
成员填充了 INADDR_ANY
,则会删除第一个匹配的组。
如果您已将许多组加入到同一套接字,则无需删除所有组的成员资格即可终止。当您关闭套接字时,与该套接字关联的所有成员资格都将由内核删除。如果打开套接字的进程被终止,也会发生同样的情况。
最后,请记住,进程删除某个组的成员资格并不意味着主机将停止接收该组的数据报。如果在之前的 IP_DROP_MEMBERSHIP
操作之前,另一个套接字在同一接口中加入了该组,则 主机 将继续成为该组的成员。
ADD_MEMBERSHIP
和 DROP_MEMBERSHIP
都是非阻塞操作。它们应立即返回,指示成功或失败。