组播:从理论到实践

作者:Juan-Mariano de Goyeneche

随着互联网的成长,新的通信需求随之出现。最初,电子邮件和 FTP 对于大多数人来说已经足够了。然后万维网出现了,人们希望看到图形,而不仅仅是纯文本。现在,即使是静态图形也无法满足需求;实时视频和音频成为新的诉求。

随着通信需求的演变,最初为处理电子邮件和 FTP 而设计的通信范式也需要发展。一种新的范式已经发展起来,那就是“组播”。

问题

想象一下通过互联网传输一个事件(也许是 Linus Torvalds 的会议),但组播不可用。一个单一的信息源,可能是一台连接到互联网以及 Linus 正在对着讲话的摄像机和麦克风的计算机,正在向分布在互联网上的多个主机传输多媒体流。当然,流量应该尽可能高效地发送——使用的带宽越少越好。

在使用组播技术之前,有两种通信范式可用,但两者都不够充分。

第一种是单播。TELNET、FTP、SMTP 和 HTTP 是基于单播的协议,具有一个源和一个目的地。要发送到多个目的地,需要在源和每个目的地之间建立不同的通信路径(见图 1.a)。因此,每个音频和视频流都需要复制一份,并分别发送给每个接收者。显然,这是不可行的。即使您复制实时音频和视频流的速度足够快,您的网络和互联网也会崩溃。音频和视频非常消耗带宽。显然,TCP 不能用于组播应用,因为它显然是面向单播的。

图 1。

第二种选择是广播。与单播相比,广播范式(见图 1.b)节省了大量带宽。如果您想将某些内容发送到局域网上的所有计算机,则无需为每台计算机单独复制一份。相反,只需向线路发送一份副本,所有连接到线路的计算机都会收到该副本。这种解决方案对于我们的问题来说更好,但仍然不足,因为我们可能只需要广播到我们的一些计算机,而不是全部。更糟糕的是,很可能许多对您的会议感兴趣的主机都在您的局域网之外。虽然广播在局域网内表现良好,但当广播数据包跨越不同的局域网路由时,问题就出现了。因此,广播适用于不需要跨越局域网限制的应用和协议(例如 ARP、BOOTP、DHCP 甚至 routed),但它对于我们的问题来说还不够好。最后,很可能人们希望同时进行多个视频会议,而只有一个广播地址可用。

解决方案:组播

在研究了问题之后,很明显我们需要一个提供以下功能的解决方案

  • 允许以有效的方式将数据发送到多个接收者,避免每个接收者都复制一份。

  • 不受任意网络限制的约束,因此可以到达互联网上的任何人、任何地方。

  • 区分多个不相关的传输,以便计算机可以知道哪些传输对应用程序感兴趣。

第三点与无线电或电视频道(非有线电视)非常相关。如果您对某个特定频道感兴趣,您可以调谐接收器以收听特定频率范围,并丢弃其余频率。

满足所有三个要求的解决方案是组播。图 1.c 显示,主机 1 只发送一次数据(即,不进行每个接收者的复制),只有对该数据感兴趣的主机(主机 3、5 和 6)才接收它。

组播地址

无线电/电视的例子将仍然是本文其余部分的一个很好的起点。正如多个频率简化了识别和隔离不同电视频道的过程一样,多个组播地址简化了识别感兴趣的组播流量的过程。

IP 地址的范围根据 IP 地址的高阶位划分为类。组播地址是 D 类地址:那些前三位设置为 1,第四位设置为 0 的地址。这意味着组播地址的范围从 224.0.0.0 到 239.255.255.255。这是您可以传输或收听流量的“频率”范围。每个“频率”标识一个不同的特定组播组。

其中一些组播地址是众所周知的,为特定目的而保留。例如,224.0.0.1 是所有主机组。只需“ping”这个地址,网络上所有支持组播的主机都应该响应。任何支持组播的主机都必须在其所有支持组播的接口上启动时加入此组。224.0.0.2 是所有路由器组,依此类推。在任何情况下,您的组播应用程序都不应将数据报发送到 224.0.0.0 到 224.0.0.255 的地址,因为它们不会跨组播路由器转发。同样,您应该避免使用 239.0.0.0 到 239.255.255.255 的组,因为它们被保留用于管理范围。有关更多详细信息,请参阅“TCP/IP 上的组播 HOWTO”(包含在 Linux 文档项目中)。

配置您的 GNU/Linux 组播盒

为了使用组播,您的 GNU/Linux 盒需要特殊配置。您的内核必须编译时启用 IP: multicasting。这将为 IGMP 协议(互联网组管理协议)添加支持,以发送和接收组播流量。如果您继续使用组播,您很可能需要将您的盒子用作组播路由器,因为旧的路由器不支持组播。在这种情况下,请查看 HOWTO 以获取必须启用的几个附加编译选项(即,选择 YES)。您还需要 mrouted 应用程序,这是一个守护进程,用于指示内核在充当组播路由器 (mrouter) 时如何转发组播数据报。

最后,您需要为传出的组播数据报设置默认路由。假设 eth0 网络接口将充当该传出路由(如果需要,您的应用程序可以指示内核使用不同的接口发送其数据报),您需要使用

route add -net 224.0.0.0 netmask 240.0.0.0 dev eth0
编写完整的组播应用程序

现在组播已经定义并且您的主机已设置好,我将解释如何在编写组播应用程序的同时开发一个应用程序。其目的是成为一个既有教育意义又实用的工具。读者需要具备使用套接字 API 进行网络编程的基本背景知识。UNIX 网络编程(W. Richard Stevens 著)、TCP/IP 互联卷 3(Douglas E. Comer 著)以及 setsockopt 手册页是有用的参考资料。

清单 1 中应用程序的想法来自西班牙的一个流行的电视广告:一个小男孩拿起他父亲的手机,开始随机拨打电话号码并说:“你好,我是 Edu。圣诞快乐!” 当他发现时,他的父亲倒吸一口凉气,当然,教训是这家公司的手机通话有多么便宜(在欧洲,本地通话非常昂贵)。

清单 1。

我们的程序(见清单 1)将做同样的事情:它将向作为命令行参数传递的组播组和端口发送字符串“你好,我是 机器名。圣诞快乐!” 以及消息的生存时间 (TTL)。该程序简短而简单,但它也非常有用。我在配置组播网络时多次使用它。您可以在所有机器上运行它,以查看它们是否正在发送和/或接收流量。当使用组播路由器和/或隧道时,TTL 非常方便,因为它使您可以轻松确定到达给定目的地所需的最低 TTL。

程序的第一行是常用的 include 语句。我尝试添加注释以指出哪些函数和/或数据结构需要它们。在 main 函数中,变量定义和基本初始化在第 27 行到 44 行完成。稍后,我们使用一个专用的套接字用于发送 (send_s),另一个用于接收 (recv_s)。这些套接字必须是 SOCK_DGRAM (UDP),因为 TCP 不支持组播范式。

发送流量

当实现组播时,套接字层进行了一些扩展以支持它。该支持通过 setsockopt/getsockopt 系统调用实现。

五个新的 optnames 中的三个(参见 setsockopt 手册页)旨在在发送数据时使用:IP_MULTICAST_LOOPIP_MULTICAST_TTLIP_MULTICAST_IF。它们都位于 IPPROTO_IP 级别。

如果设置了 IP_MULTICAST_LOOP,则从此套接字发送的所有组播数据包都将在内核内部环回。这样,其余等待接收此组流量的应用程序将看到它,就像它已由网卡接收一样。我们的应用程序不对此行为感兴趣,因此在第 65 行到 69 行中禁用了它。默认情况下,环回是启用的。

IP 标头的 TTL 字段在组播中起着主要作用。它避免由于路由错误而导致数据包永远循环的原始作用得以保留,但添加了一个新的作用:该字段还与“阈值”的含义相关联。它充当分隔符,以防止组播数据包在不受控制的情况下跨互联网转发。您可以通过指定组播数据包只有在其 TTL 字段大于特定值时才会跨越您的组播路由器来建立边界。这样,您可以组播一个会议,将其范围限制在您的局域网(TTL 为 1)、您的本地站点(TTL<32)、您的国家/地区(TTL<64)或允许其范围不受限制(TTL<256)。我们的测试程序允许您在命令行上指定 TTL,然后使用 IP_ MULTICAST_TTL 选项设置它。如果未指定,则假定 TTL 为 1(参见第 52 行到 62 行)。如果您正在使用组播隧道或您的应用程序被组播路由器分隔,则可以通过增加 TTL 字段的值在两端运行该程序,直到两个程序“看到”彼此。这样,您可以轻松发现应用程序通信所需的最小 TTL。

如果未另行指定,则传出的组播数据报将遵循系统管理员设置的默认组播路由发送。如果这不是您想要的,您可以为该套接字指定另一个输出接口。我们的示例程序非常简单,不需要此功能,因此我们没有使用 IP_MULTICAST_IF 选项。相反,我们让内核选择正确的路由。如果您需要它,请编写如下代码

struct in_addr interface_addr;
setsockopt (socket, IPPROTO_IP, IP_MULTICAST_IF,
   &interface_addr, sizeof(interface_addr));

使用合适的值填充 interface_addr 结构。如果稍后您想恢复到原始行为,只需再次调用 setsockopt,并将 INADDR_ANY 用作接口字段。

接收流量

您的无线电或电视必须调谐到接收您想要收听的频道。以类似的方式,您必须“调谐”您的内核,以便它知道哪些组播组是感兴趣的。这被称为“订阅”主机到特定组。请注意,是主机,而不是进程,被订阅。进程使用 bind 绑定到特定的组播组/端口对,并且必须告诉内核它们想要接收该组的流量。然后内核知道它不能丢弃该组的数据包。当内核收到它们时,它会为绑定到该组播地址和端口对的所有进程复制副本。当最后一个仍然订阅该组的进程“放弃成员资格”时,内核会停止将这些数据包发送到上层协议,并再次忽略它们。

简而言之,如果您想接收来自组播组的流量,您必须执行以下步骤

  • 创建套接字(第 71 行到 74 行)。

  • 绑定组/端口(第 81 行到 84 行)。

  • 可选地,使用 SO_REUSEADDR 选项(第 76 行到 79 行),以便多个进程可以在同一台机器上绑定相同的组和端口,即,拥有多个接收者。

  • 加入组(第 87 行到 92 行)。

IP_ADD_MEMBERSHIP 选项需要指向 struct ip_mreq 的指针。此结构在 netinet/in.h 中定义。第一个字段 imr_multiaddr 包含您要加入的组地址。第二个字段 imr_interface 包含组将要加入的接口的 IP 地址。这是一个关键点:成员资格与组接口相关联。您不仅仅是加入一个组;您是在一个网络接口加入一个组。如果您的主机是多宿主的,您可以加入所有网络接口上的同一组,或者其中一个接口上的同一组,甚至其中一些接口上的同一组。这样,应用程序将获得为该组发送的在该特定接口上接收的数据包。

通常,您希望接收该组的流量,并且您不关心哪个接口接收了它。在这些情况下,请使用 INADDR_ANY 通配符填充 imr_interface 字段(参见第 88 行)。

完成后,您可能想要放弃成员资格(停止成为该组的成员),尽管如果您要在此之后立即关闭套接字,则不是绝对必要的。当您关闭套接字时,内核将为您放弃套接字已订阅的所有组的成员资格。

如果您的进程放弃了特定组的成员资格,但保持套接字绑定,只要主机中的任何其他进程仍然是成员,它将继续接收该组的流量。加入组播组只是告诉 IP 和数据链路层(在某些情况下明确告诉硬件)接受发往该组的组播数据报;它不是每个进程的成员资格,而是每个主机的成员资格。

其余部分很简单;我们 fork 并让父进程发送消息(第 123 行到 137 行),子进程接收消息(第 104 行到 122 行)。正如我们告诉它不要环回一样,我们看不到我们自己的消息。更改 IP_MULTICAST_LOOP 选项,您会发现您在和自己说话。

结论

请随意测试、修改和增强此示例程序。您可能会看到文本中没有完全解决某些细微之处。在一篇短文中涵盖所有内容是很困难的,但您可以通过阅读组播 HOWTO 来检查和完善它 (tldp.org/HOWTO/Multicast-HOWTO.html)。

本文中引用的所有清单都可以通过匿名下载文件 ftp.linuxjournal.com/pub/lj/listings/issue65/3041.tgz 获得。

Multicast: From Theory to Practice
Juan-Mariano de Goyeneche (jmseyas@dit.upm.es) 在意识到拥有源代码时调试和修改程序要容易得多后,迅速转向 GNU/Linux。在他完成他的教育生涯的同时,他与西班牙 UPM 的远程信息处理系统部门 (DIT) 合作,从事 CSCW 组播应用程序的工作。他是“TCP/IP 上的组播 HOWTO”的作者。
加载 Disqus 评论