Получение нескольких многоадресных каналов на одном и том же порту - C, Linux - PullRequest
13 голосов
/ 30 апреля 2010

У меня есть приложение, которое получает данные из нескольких источников многоадресной рассылки через один и тот же порт. Я могу получить данные. Однако я пытаюсь учесть статистику каждой группы (то есть полученные сообщения, полученные байты), и все данные перепутаны. Кто-нибудь знает, как решить эту проблему? Если я попытаюсь посмотреть адрес отправителя, это будет не адрес многоадресной рассылки, а 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));

Ответы [ 8 ]

10 голосов
/ 30 апреля 2010

[Отредактировано, чтобы уточнить, что bind() может фактически включать адрес многоадресной рассылки.]

Итак, приложение присоединяется к нескольким группам многоадресной рассылки и получает сообщения, отправленные на любую из них, на один и тот же порт. SO_REUSEPORT позволяет привязать несколько сокетов к одному порту. Помимо порта, bind() нужен IP-адрес. INADDR_ANY - это универсальный адрес, но также можно использовать IP-адрес, включая многоадресный. В этом случае только пакеты, отправленные на этот IP, будут доставлены в сокет. То есть Вы можете создать несколько сокетов, по одному для каждой многоадресной группы. bind() каждый сокет с (group_addr, порт) И присоединение к 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);
    }
}

Если вы запустите несколько таких процессов для разных многоадресных адресов и отправите сообщение на один из адресов, только соответствующий процесс получит его. Конечно, в вашем случае вы, вероятно, захотите, чтобы все сокеты были в одном процессе, и вам придется использовать select или poll или эквивалентный, чтобы прочитать их все.

9 голосов
/ 04 января 2014

После нескольких лет столкновения с этим странным поведением в Linux и использования обходного пути, описанного в предыдущих ответах , я понимаю, что ip (7) manpage описывает возможное решение:

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 , в этом обсуждении содержатся тестовые программы для воспроизведения проблемы и ее решения.

5 голосов
/ 02 мая 2010

Используйте setsockopt() и IP_PKTINFO или IP_RECVDSTADDR в зависимости от вашей платформы, предполагая IPv4. Это в сочетании с recvmsg() или WSARecvMsg() позволяет вам найти адрес источника и назначения каждого пакета.

Unix / Linux, обратите внимание, что FreeBSD использует IP_RECVDSTADDR, в то время как оба поддерживают IP6_PKTINFO для IPv6.

Windows, также имеет IP_ORIGINAL_ARRIVAL_IF

3 голосов
/ 23 мая 2011

Заменить

mc_addr.sin_addr.s_addr = htonl(INADDR_ANY);

с

mc_addr.sin_addr.s_addr = inet_addr (mc_addr_str);

это помощь для меня (linux), для каждого приложения я получаю отдельный поток mcast из отдельной группы mcast на один порт.

Также вы можете посмотреть на источник проигрывателя VLC, он показывает много каналов mcast iptv из разных групп mcast на одном порту, но я не знаю, как он разделяет канал.

1 голос
/ 20 июля 2011

Мне приходилось использовать несколько сокетов, каждый из которых просматривал разные адреса групп многоадресной рассылки, а затем подсчитывать статистику по каждому сокету в отдельности.

Если есть способ увидеть «адрес получателя», как упомянуто в ответе выше, я не могу понять это.

Один важный момент, который также занял у меня некоторое время - когда я связывал каждый из моих индивидуальных сокетов с пустым адресом, как это делают большинство примеров Python:

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

Я получил все многоадресные пакеты (из всех многоадресных групп) на каждом сокете, что не помогло. Чтобы это исправить, я привязал каждый сокет к своей собственной многоадресной группе

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

И тогда это сработало.

0 голосов
/ 21 сентября 2017

Вы можете разделить многоадресные потоки, посмотрев 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-пакета, который вы только что получили. Теперь вы можете обрабатывать полученные пакеты, специфичные для каждого многоадресного потока (многоадресного адреса), который вы получаете.

0 голосов
/ 30 апреля 2010

Многоадресный адрес будет адресом получателя, а не адресом отправителя в пакете. Посмотрите на IP-адрес получателя.

0 голосов
/ 30 апреля 2010

IIRC recvfrom () предоставляет разные адреса / порт для чтения для каждого отправителя.

Вы также можете поместить заголовок в каждый пакет, идентифицирующий отправителя источника.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...