Подтвердить что ты не робот

Получение нескольких многоадресных каналов на одном порту - C, Linux

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

Я ценю любую помощь!!!

4b9b3361

Ответ 1

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

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

Ответ 2

Спустя несколько лет, столкнувшись с этим странным поведением в Linux, и используя обходное решение bind, описанное в предыдущих ответах, я понимаю, что 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, в этом обсуждении содержатся тестовые программы для воспроизведения проблемы и решить его.

Ответ 3

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

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

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

Ответ 4

Заменить

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 на одном порту, но я не знаю, как он разделяет канал.

Ответ 5

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

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

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

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

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

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

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

Ответ 6

IIRC recvfrom() дает вам другой адрес/порт для каждого отправителя.

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

Ответ 7

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

Ответ 8

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