IP 组播 + UDP:我应该接收所有组的数据吗?

IP Multicast + UDP: Should I receive data for all groups?

基于 UDP 的 IP 多播的正确行为是什么?

想象一下自己的场景:

  1. 我们尝试通过 UDP 与 IP 协议通信。
  2. 我们打开一个具有特定端口(例如 60001)的 UDP 套接字
  3. 然后我们将套接字加入某个多播组(例如 239.192.0.1)
  4. 我们打开另一个端口为 60002 的 UDP 套接字
  5. 然后我们将套接字加入到其他多播组(例如239.192.0.2)

在 Windows 中完成时,使用 Winsock,每个套接字仅在满足两个条件时才成为数据:

这是一些 Delphi 代码:

procedure Connect();
var err: Integer;
    wData: WsaData;
    reuse: Integer;
begin
  FillChar(wData, SizeOf(wData), 0);
  err := WSAStartup(MAKEWORD(2, 2), wData);
  if err = SOCKET_ERROR then Exit;

  _fd := socket(AF_INET, SOCK_DGRAM, 0);
  if _fd = INVALID_SOCKET then Exit;

  reuse := 1;
  if (setsockopt(_fd, SOL_SOCKET, SO_REUSEADDR, Pointer(@reuse), SizeOf(reuse)) < 0) then Exit;

  FillChar(_addr, sizeof(_addr), 0);
  _addr.sin_family := AF_INET;
  if (_listeningInterface = '0.0.0.0') or not _isIpAddress(_listeningInterface) then
    _addr.sin_addr.s_addr := htonl(INADDR_ANY)
  else
    _addr.sin_addr.s_addr := inet_addr(PAnsiChar(_listeningInterface));
  _addr.sin_port := htons(_port);

  if (bind(_fd, _addr, sizeof(_addr)) < 0) then Exit;

  if _isMulticast() then begin
    _mreq.imr_multiaddr.s_addr := inet_addr(PAnsiChar(_multicastGroup));
    if (_listeningInterface = '0.0.0.0') or not _isIpAddress(_listeningInterface) then
      _mreq.imr_interface.s_addr := htonl(INADDR_ANY)
    else
      _mreq.imr_interface.s_addr := inet_addr(PAnsiChar(_listeningInterface));
    // Joinig multicast group here. Note the _fd variable usage here.
    if (setsockopt(_fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, @_mreq, sizeof(_mreq)) < 0) then Exit;
  end;
end;

当我们尝试从用于微控制器的 Keil 库中执行此操作时,没有像 setsockopt 这样的函数。 igmp_join 函数不接受套接字变量,但只接受多播组。

因此,我们无法指定UDP端口与多播组的对应关系,每个UDP套接字都成为我们加入的所有组的数据。

两种方法中哪一种是正确的:Keil 的还是 Windows'?

两种行为都是正确的。

你在哪个套接字上接收到哪个UDP数据包,这已经超出了IPv4多播的定义范围。

你只收到你加入群组的流量的期望经常被违反。此外,所有操作系统都以不同方式实现这一点。 Linux 和 Windows 的行为不同,微控制器中的小型 IP 堆栈通常具有更简单的行为。

基准,您可以预期的最坏情况是:您将在您的 UDP 套接字上收到随机 UDP 数据包,即使来自您 加入的群组。但在实践中通常并没有那么糟糕。只是作为思考时的指导方针。原因是:OS 可能会延迟会员资格下降。本地路由器延迟成员资格下降。本地路由器将包含错误。

您无论如何都需要处理意外接收到的 UDP 数据包,因为您网络中的任何人都可以伪造 UDP 数据包并以其他人的名义将它们发送到您的套接字。此外,其他任何人都可以伪造 IGMP 数据包和 "join you" 到任何多播组。

这意味着:在实践中,您总是需要进行应用程序级别的过滤并默默地忽略意外的 UDP 数据包。在大多数 IP 堆栈上,有一个(重要的)选项来确定接收到的 UDP 数据包的 目标地址 。这对于拆分多个多播组的流量最有用。

而且由于您现在知道了 IP 堆栈的行为,因此您可以节省一些内存并仅使用单个 UDP 套接字接收所有 UDP 流量并在应用程序端对其进行多路分解。当然,这在一定程度上取决于您的应用程序。

但是,是的,我理解您对 IP 多播定义如此不明确感到失望。我和你有同感! :-)