简体   繁体   English

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

[英]Receiving multiple multicast feeds on the same port - C, Linux

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 (ie 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. 如果我试着查看发件人的地址,那么它不是多播地址,而是发送机器的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));

[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. SO_REUSEPORT允许您将多个套接字绑定到同一端口。 Besides the port, bind() needs an IP address. 除端口外, bind()需要一个IP地址。 INADDR_ANY is a catch-all address, but an IP address may also be used, including a multicast one. INADDR_ANY是一个包罗万象的地址,但也可以使用IP地址,包括多播地址。 In that case, only packets sent to that IP will be delivered to the socket. 在这种情况下,只有发送到该IP的数据包才会被传送到套接字。 Ie you can create several sockets, one for each multicast group. 即,您可以创建多个套接字,每个多播组一个。 bind() each socket to the (group_addr, port), AND join group_addr. bind()每个套接字到(group_addr,port),并加入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: 我测试了以下适用于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. 当然,在您的情况下,您可能希望在一个进程中拥有所有套接字,并且您将必须使用selectpoll或等效函数来全部读取它们。

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奇怪的行为,并使用前面的答案中描述的绑定解决方法,我意识到ip(7)联机帮助页描述了一个可能的解决方案:

IP_MULTICAST_ALL (since Linux 2.6.31) IP_MULTICAST_ALL(自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. 此选项可用于将组播消息的传递策略修改为绑定到通配符INADDR_ANY地址的套接字。 The argument is a boolean integer (defaults to 1). 参数是一个布尔整数(默认为1)。 If set to 1, the socket will receive messages from all the groups that have been joined globally on the whole system. 如果设置为1,则套接字将从整个系统上全局加入的所有组接收消息。 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_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_MULTICAST_ALL,这个讨论包含重现问题并解决它的测试程序。

Use setsockopt() and IP_PKTINFO or IP_RECVDSTADDR depending on your platform, assuming IPv4. 假设使用IPv4,请使用setsockopt()IP_PKTINFOIP_RECVDSTADDR具体取决于您的平台。 This combined with recvmsg() or WSARecvMsg() allows you to find the source and destination address of every packet. 结合recvmsg()WSARecvMsg() ,您可以查找每个数据包的源目标地址。

Unix/Linux, note FreeBSD uses IP_RECVDSTADDR whilst both support IP6_PKTINFO for IPv6. Unix / Linux,注意FreeBSD使用IP_RECVDSTADDR ,同时支持IPv6的IP6_PKTINFO

Windows, also has IP_ORIGINAL_ARRIVAL_IF Windows,也有IP_ORIGINAL_ARRIVAL_IF

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. 这对我(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频道,但我不知道,它是如何分配频道的。

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. 然后它起作用了。

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. 您还可以在每个数据包中标识标识源发送方的标头。

The Multicast address will be the receiver's address not sender's address in the packet. 多播地址将是接收方的地址,而不是数据包中发送方的地址。 Look at the receiver's IP address. 查看接收方的IP地址。

You can separate the multicast streams by looking at the destination IP addresses of the received packets (which will always be the multicast addresses). 您可以通过查看收到的数据包的目标IP地址(始终是多播地址)来分离多播流。 It is somewhat involved to do this: 这有点涉及到:

Bind to INADDR_ANY and set the IP_PKTINFO socket option. 绑定到INADDR_ANY并设置IP_PKTINFO套接字选项。 You then have to use recvmsg() to receive your multicast UDP packets and to scan for the IP_PKTINFO control message. 然后,您必须使用recvmsg()接收多播UDP数据包并扫描IP_PKTINFO控制消息。 This gives you some side band information of the received UDP packet: 这为您提供了收到的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. 查看ipi_addr:这将是您刚刚收到的UDP数据包的多播地址。 You can now handle the received packets specific for each multicast stream (multicast address) you are receiving. 您现在可以处理特定于您正在接收的每个多播流(多播地址)的接收数据包。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM