在同一个端口接收多个多播提要——C, Linux

时间:2022-03-08 14:27:55

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.

我有一个应用程序,它从同一个端口上的多个多播源接收数据。我能收到数据。但是,我正在尝试计算每个组的统计数据(即接收到的msgs、接收到的字节),所有的数据都混合在一起了。有人知道怎么解决这个问题吗?如果我试图查看发送者的地址,它不是多播地址,而是发送机的IP。

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));

8 个解决方案

#1


10  

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

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

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.

因此,应用程序正在加入几个多播组,并接收发送到其中任何一个组的消息,发送到同一个端口。SO_REUSEPORT允许您将多个套接字绑定到同一个端口。除了端口之外,bind()还需要一个IP地址。INADDR_ANY是一个通用地址,但是也可以使用一个IP地址,包括一个多播地址。在这种情况下,只有发送到该IP的数据包才会被发送到套接字。也就是说,您可以为每个多播组创建多个套接字。绑定()每个套接字到(group_addr、端口)和join group_addr。然后,针对不同组的数据将显示在不同的套接字上,这样您就能够区分它们。

I tested that the following works on FreeBSD:

我测试了以下在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.

如果您运行几个这样的进程,针对不同的多播地址,并向其中一个地址发送消息,那么只有相关的进程会接收到它。当然,在您的例子中,您可能希望在一个进程中拥有所有的套接字,并且您必须使用select或poll或等效的方法来读取所有的套接字。

#2


9  

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 :

在面对这种linux奇怪的行为几年之后,使用前面回答中描述的bind workaround,我意识到ip(7) manpage描述了一种可能的解决方案:

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.

ip_multi - ast_all(从Linux 2.6.31开始)可以使用此选项将多播消息的传递策略修改为与通配符INADDR_ANY地址绑定的套接字。参数是一个布尔整数(默认值为1),如果设置为1,套接字将接收来自整个系统上已加入的所有组的消息。否则,它只会从已显式连接的组(例如通过IP_ADD_MEMBERSHIP选项)在这个特定的套接字上传递消息。

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.

在Redhat Bug 231899中讨论了这个问题以及解决ip_multi - ast_all的方法,这个讨论包含了重现问题并解决它的测试程序。

#3


5  

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.

根据您的平台使用setsockopt()和IP_PKTINFO或IP_RECVDSTADDR(假设是IPv4)。结合recvmsg()或WSARecvMsg(),您可以找到每个包的源和目标地址。

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

请注意,FreeBSD使用IP_RECVDSTADDR,而两者都支持IPv6的IP6_PKTINFO。

Windows, also has IP_ORIGINAL_ARRIVAL_IF

窗户,还有IP_ORIGINAL_ARRIVAL_IF

#4


3  

Replace

取代

mc_addr.sin_addr.s_addr = htonl(INADDR_ANY);

mc_addr.sin_addr。s_addr = htonl(INADDR_ANY);

with

mc_addr.sin_addr.s_addr = inet_addr (mc_addr_str);

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.

这对我(linux)很有帮助,对于每个应用程序,我都在一个端口上从不同的mcast组接收到不同的mcast流。

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.

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

#5


1  

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:

有一点也让我花了一段时间——当我像大多数python示例那样将每个套接字绑定到一个空地址时:

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.

然后它工作。

#6


0  

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

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

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

您还可以在每个标识源发送方的包中放置一个标头。

#7


0  

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

多播地址将是收件人的地址,而不是寄件人的地址。看看接收者的IP地址。

#8


0  

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:

您可以通过查看收到的数据包的目标IP地址来分隔多播流(这将永远是多播地址)。这样做有点复杂:

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:

绑定到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 */
};

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.

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

#1


10  

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

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

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.

因此,应用程序正在加入几个多播组,并接收发送到其中任何一个组的消息,发送到同一个端口。SO_REUSEPORT允许您将多个套接字绑定到同一个端口。除了端口之外,bind()还需要一个IP地址。INADDR_ANY是一个通用地址,但是也可以使用一个IP地址,包括一个多播地址。在这种情况下,只有发送到该IP的数据包才会被发送到套接字。也就是说,您可以为每个多播组创建多个套接字。绑定()每个套接字到(group_addr、端口)和join group_addr。然后,针对不同组的数据将显示在不同的套接字上,这样您就能够区分它们。

I tested that the following works on FreeBSD:

我测试了以下在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.

如果您运行几个这样的进程,针对不同的多播地址,并向其中一个地址发送消息,那么只有相关的进程会接收到它。当然,在您的例子中,您可能希望在一个进程中拥有所有的套接字,并且您必须使用select或poll或等效的方法来读取所有的套接字。

#2


9  

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 :

在面对这种linux奇怪的行为几年之后,使用前面回答中描述的bind workaround,我意识到ip(7) manpage描述了一种可能的解决方案:

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.

ip_multi - ast_all(从Linux 2.6.31开始)可以使用此选项将多播消息的传递策略修改为与通配符INADDR_ANY地址绑定的套接字。参数是一个布尔整数(默认值为1),如果设置为1,套接字将接收来自整个系统上已加入的所有组的消息。否则,它只会从已显式连接的组(例如通过IP_ADD_MEMBERSHIP选项)在这个特定的套接字上传递消息。

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.

在Redhat Bug 231899中讨论了这个问题以及解决ip_multi - ast_all的方法,这个讨论包含了重现问题并解决它的测试程序。

#3


5  

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.

根据您的平台使用setsockopt()和IP_PKTINFO或IP_RECVDSTADDR(假设是IPv4)。结合recvmsg()或WSARecvMsg(),您可以找到每个包的源和目标地址。

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

请注意,FreeBSD使用IP_RECVDSTADDR,而两者都支持IPv6的IP6_PKTINFO。

Windows, also has IP_ORIGINAL_ARRIVAL_IF

窗户,还有IP_ORIGINAL_ARRIVAL_IF

#4


3  

Replace

取代

mc_addr.sin_addr.s_addr = htonl(INADDR_ANY);

mc_addr.sin_addr。s_addr = htonl(INADDR_ANY);

with

mc_addr.sin_addr.s_addr = inet_addr (mc_addr_str);

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.

这对我(linux)很有帮助,对于每个应用程序,我都在一个端口上从不同的mcast组接收到不同的mcast流。

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.

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

#5


1  

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:

有一点也让我花了一段时间——当我像大多数python示例那样将每个套接字绑定到一个空地址时:

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.

然后它工作。

#6


0  

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

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

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

您还可以在每个标识源发送方的包中放置一个标头。

#7


0  

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

多播地址将是收件人的地址,而不是寄件人的地址。看看接收者的IP地址。

#8


0  

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:

您可以通过查看收到的数据包的目标IP地址来分隔多播流(这将永远是多播地址)。这样做有点复杂:

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:

绑定到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 */
};

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.

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