在同一端口上接收多个多播源 - C、Linux

发布于 2024-08-31 00:34:58 字数 494 浏览 11 评论 0原文

我有一个应用程序正在同一端口上从多个多播源接收数据。我能够接收数据。然而,我试图解释每组的统计数据(即收到的消息、收到的字节),并且所有数据都变得混乱。有谁知道如何解决这个问题?如果我尝试查看发送者的地址,它不是多播地址,而是发送机器的 IP。

我正在使用以下套接字选项:

struct ip_mreq mreq;         
mreq.imr_multiaddr.s_addr = inet_addr("224.1.2.3");         
mreq.imr_interface.s_addr = INADDR_ANY;         
setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq));

以及:

setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &reuse, sizeof(reuse));

I have an application that is receiving data from multiple multicast sources on the same port. I am able to receive the data. However, I am trying to account for statistics of each group (i.e. msgs received, bytes received) and all the data is getting mixed up. Does anyone know how to solved this problem? If I try to look at the sender's address, it is not the multicast address, but rather the IP of the sending machine.

I am using the following socket options:

struct ip_mreq mreq;         
mreq.imr_multiaddr.s_addr = inet_addr("224.1.2.3");         
mreq.imr_interface.s_addr = INADDR_ANY;         
setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq));

and also:

setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &reuse, sizeof(reuse));

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

-小熊_ 2024-09-07 00:34:58

经过几年面对这种linux奇怪的行为,并使用前面答案中描述的绑定解决方法,我意识到ip(7) 手册页 描述了一个可能的解决方案:

IP_MULTICAST_ALL(自 Linux 2.6.31 起)
该选项可用于修改
向绑定到通配符 INADDR_ANY 的套接字多播消息
地址。参数是一个布尔整数(默认为 1)。
如果设置为 1,则套接字将接收来自所有套接字的消息
整个系统已在全球范围内加入的团体。
否则,它将仅传递来自以下组的消息:
已明确加入(例如通过
此特定套接字上的 IP_ADD_MEMBERSHIP 选项)。

然后,您可以使用以下命令激活过滤器以接收已加入组的消息:

int mc_all = 0;
if ((setsockopt(sock, IPPROTO_IP, IP_MULTICAST_ALL, (void*) &mc_all, sizeof(mc_all))) < 0) {
    perror("setsockopt() failed");
}

此问题以及启用 IP_MULTICAST_ALL 的解决方法在 Redhat Bug 231899,此讨论包含重现该问题并解决该问题的测试程序。

After some years facing this linux strange behaviour, and using the bind workaround describe in previous answers, I realize that the ip(7) manpage describe a possible solution :

IP_MULTICAST_ALL (since Linux 2.6.31)
This option can be used to modify the delivery policy of
multicast messages to sockets bound to the wildcard INADDR_ANY
address. The argument is a boolean integer (defaults to 1).
If set to 1, the socket will receive messages from all the
groups that have been joined globally on the whole system.
Otherwise, it will deliver messages only from the groups that
have been explicitly joined (for example via the
IP_ADD_MEMBERSHIP option) on this particular socket.

Then you can activate the filter to receive messages of joined groups using :

int mc_all = 0;
if ((setsockopt(sock, IPPROTO_IP, IP_MULTICAST_ALL, (void*) &mc_all, sizeof(mc_all))) < 0) {
    perror("setsockopt() failed");
}

This problem and the way to solve it enabling IP_MULTICAST_ALL is discussed in Redhat Bug 231899, this discussion contains test programs to reproduce the problem and to solve it.

魔法少女 2024-09-07 00:34:58

[编辑以澄清 bind() 实际上可能包含多播地址。]

因此,应用程序正在加入多个多播组,并接收发送到其中任何一个的消息到同一端口。 SO_REUSEPORT 允许您将多个套接字绑定到同一端口。除了端口之外,bind() 还需要一个 IP 地址。 INADDR_ANY 是一种包罗万象的地址,但也可以使用 IP 地址,包括多播地址。在这种情况下,只有发送到该 IP 的数据包才会传递到套接字。即,您可以创建多个套接字,每个多播组一个。 bind() 每个套接字到 (group_addr, port),并加入 group_addr。然后,发送给不同组的数据将显示在不同的套接字上,您将能够通过这种方式进行区分。

我测试了以下方法在 FreeBSD 上的工作原理:

#include <sys/socket.h>
#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/param.h>
#include <unistd.h>
#include <errno.h>

int main(int argc, const char *argv[])
{
    const char *group = argv[1];

    int s = socket(AF_INET, SOCK_DGRAM, 0);
    int reuse = 1;
    if (setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &reuse, sizeof(reuse)) == -1) {
        fprintf(stderr, "setsockopt: %d\n", errno);
        return 1;
    }

    /* construct a multicast address structure */
    struct sockaddr_in mc_addr;
    memset(&mc_addr, 0, sizeof(mc_addr));
    mc_addr.sin_family = AF_INET;
    mc_addr.sin_addr.s_addr = inet_addr(group);
    mc_addr.sin_port = htons(19283);

    if (bind(s, (struct sockaddr*) &mc_addr, sizeof(mc_addr)) == -1) {
        fprintf(stderr, "bind: %d\n", errno);
        return 1;
    }

    struct ip_mreq mreq;
    mreq.imr_multiaddr.s_addr = inet_addr(group);
    mreq.imr_interface.s_addr = INADDR_ANY;
    setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq));

    char buf[1024];
    int n = 0;
    while ((n = read(s, buf, 1024)) > 0) {
        printf("group %s fd %d len %d: %.*s\n", group, s, n, n, buf);
    }
}

如果您针对不同的多播地址运行多个此类进程,并向其中一个地址发送消息,则只有相关进程才会收到该消息。当然,在您的情况下,您可能希望在一个进程中拥有所有套接字,并且您必须使用 selectpoll 或等效方法来读取所有套接字。

[Edited to clarify that bind() may in fact include a multicast address.]

So the application is joining several multicast groups, and receiving messages sent to any of them, to the same port. SO_REUSEPORT allows you to bind several sockets to the same port. Besides the port, bind() needs an IP address. INADDR_ANY is a catch-all address, but an IP address may also be used, including a multicast one. In that case, only packets sent to that IP will be delivered to the socket. I.e. you can create several sockets, one for each multicast group. bind() each socket to the (group_addr, port), AND join group_addr. Then data addressed to different groups will show up on different sockets, and you'll be able to distinguish it that way.

I tested that the following works on FreeBSD:

#include <sys/socket.h>
#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/param.h>
#include <unistd.h>
#include <errno.h>

int main(int argc, const char *argv[])
{
    const char *group = argv[1];

    int s = socket(AF_INET, SOCK_DGRAM, 0);
    int reuse = 1;
    if (setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &reuse, sizeof(reuse)) == -1) {
        fprintf(stderr, "setsockopt: %d\n", errno);
        return 1;
    }

    /* construct a multicast address structure */
    struct sockaddr_in mc_addr;
    memset(&mc_addr, 0, sizeof(mc_addr));
    mc_addr.sin_family = AF_INET;
    mc_addr.sin_addr.s_addr = inet_addr(group);
    mc_addr.sin_port = htons(19283);

    if (bind(s, (struct sockaddr*) &mc_addr, sizeof(mc_addr)) == -1) {
        fprintf(stderr, "bind: %d\n", errno);
        return 1;
    }

    struct ip_mreq mreq;
    mreq.imr_multiaddr.s_addr = inet_addr(group);
    mreq.imr_interface.s_addr = INADDR_ANY;
    setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq));

    char buf[1024];
    int n = 0;
    while ((n = read(s, buf, 1024)) > 0) {
        printf("group %s fd %d len %d: %.*s\n", group, s, n, n, buf);
    }
}

If you run several such processes, for different multicast addresses, and send a message to one of the addresses, only the relevant process will receive it. Of course, in your case, you probably will want to have all the sockets in one process, and you'll have to use select or poll or equivalent to read them all.

夏花。依旧 2024-09-07 00:34:58

根据您的平台(假定为 IPv4)使用 setsockopt()IP_PKTINFOIP_RECVDSTADDR。与 recvmsg()WSARecvMsg() 结合使用,您可以找到每个数据包的源目标地址。

Unix/Linux,请注意 FreeBSD 使用 IP_RECVDSTADDR,同时两者都支持 IPv6 的 IP6_PKTINFO

Windows,还有 IP_ORIGINAL_ARRIVAL_IF

Use setsockopt() and IP_PKTINFO or IP_RECVDSTADDR depending on your platform, assuming IPv4. This combined with recvmsg() or WSARecvMsg() allows you to find the source and destination address of every packet.

Unix/Linux, note FreeBSD uses IP_RECVDSTADDR whilst both support IP6_PKTINFO for IPv6.

Windows, also has IP_ORIGINAL_ARRIVAL_IF

朦胧时间 2024-09-07 00:34:58

mc_addr.sin_addr.s_addr = htonl(INADDR_ANY);

替换为

mc_addr.sin_addr.s_addr = inet_addr (mc_addr_str);

这对我(linux)有帮助,对于我的每个应用程序在一个端口上接收来自单独多播组的单独多播流。

您还可以查看 VLC 播放器源代码,它在一个端口上显示来自不同 mcast 组的许多 mcast iptv 频道,但我不知道它如何分隔频道。

Replace

mc_addr.sin_addr.s_addr = htonl(INADDR_ANY);

with

mc_addr.sin_addr.s_addr = inet_addr (mc_addr_str);

it's help for me (linux), for each application i receive separate mcast stream from separate mcast group on one port.

Also you can look into VLC player source, it show many mcast iptv channel from different mcast group on one port, but i dont know, how it separetes channel.

山色无中 2024-09-07 00:34:58

我必须使用多个套接字,每个套接字查看不同的多播组地址,然后单独计算每个套接字的统计信息。

如果有办法查看上面答案中提到的“收件人地址”,我无法弄清楚。

一个重要的一点也花了我一段时间——当我像大多数 python 示例一样将每个单独的套接字绑定到一个空白地址时:

sock[i].bind(('', MC_PORT[i])

我在每个套接字上获取了所有多播数据包(来自所有多播组),但这没有帮助。为了解决这个问题,我将每个套接字绑定到它自己的多播组

sock[i].bind((MC_GROUP[i], MC_PORT[i]))

然后它就起作用了。

I have had to use multiple sockets each looking at different multicast group addresses, and then count statistics on each socket individually.

If there is a way to see the "receiver's address" as mentioned in the answer above, I can't figure it out.

One important point that also took me awhile - when I bound each of my individual sockets to a blank address like most python examples do:

sock[i].bind(('', MC_PORT[i])

I got all the multicast packets (from all multicast groups) on each socket, which didn't help. To fix this, I bound each socket to it's own multicast group

sock[i].bind((MC_GROUP[i], MC_PORT[i]))

And it then worked.

友谊不毕业 2024-09-07 00:34:58

IIRC recvfrom() 为每个发送者提供不同的读取地址/端口。

您还可以在每个数据包中放置一个标头来标识源发送者。

IIRC recvfrom() gives you a different read address/port for each sender.

You can also put a header in each packet identifying the source sender.

诗酒趁年少 2024-09-07 00:34:58

多播地址将是数据包中接收者的地址而不是发送者的地址。查看接收者的 IP 地址。

The Multicast address will be the receiver's address not sender's address in the packet. Look at the receiver's IP address.

风吹雨成花 2024-09-07 00:34:58

您可以通过查看接收到的数据包的目标 IP 地址(始终是多播地址)来分离多播流。执行此操作有些复杂:

绑定到 INADDR_ANY 并设置 IP_PKTINFO 套接字选项。然后,您必须使用 recvmsg() 接收多播 UDP 数据包并扫描 IP_PKTINFO 控制消息。这将为您提供接收到的 UDP 数据包的一些边带信息:

struct in_pktinfo {
    unsigned int   ipi_ifindex;  /* Interface index */
    struct in_addr ipi_spec_dst; /* Local address */
    struct in_addr ipi_addr;     /* Header Destination address */
};

查看 ipi_addr:这将是您刚刚收到的 UDP 数据包的多播地址。您现在可以处理特定于您正在接收的每个多播流(多播地址)的接收数据包。

You can separate the multicast streams by looking at the destination IP addresses of the received packets (which will always be the multicast addresses). It is somewhat involved to do this:

Bind to INADDR_ANY and set the IP_PKTINFO socket option. You then have to use recvmsg() to receive your multicast UDP packets and to scan for the IP_PKTINFO control message. This gives you some side band information of the received UDP packet:

struct in_pktinfo {
    unsigned int   ipi_ifindex;  /* Interface index */
    struct in_addr ipi_spec_dst; /* Local address */
    struct in_addr ipi_addr;     /* Header Destination address */
};

Look at ipi_addr: This will be the multicast address of the UDP packet you just received. You can now handle the received packets specific for each multicast stream (multicast address) you are receiving.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文