У меня есть приложение, которое принимает данные из нескольких многоадресных источников на одном и том же порту. Я могу получить данные. Тем не менее, я пытаюсь учитывать статистику каждой группы (т. Е. Полученные сообщения, полученные байты), и все данные смешиваются. Кто-нибудь знает, как решить эту проблему? Если я попытаюсь посмотреть адрес отправителя, это не адрес многоадресной рассылки, а 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));
[Отредактировано, чтобы уточнить, что bind()
может фактически включать многоадресный адрес.]
Таким образом, приложение объединяет несколько групп многоадресной передачи и принимает сообщения, отправленные любому из них, в один и тот же порт. SO_REUSEPORT
позволяет связывать несколько сокетов с одним и тем же портом. Помимо порта, bind()
нуждается в IP-адресе. INADDR_ANY
– это INADDR_ANY
адрес, но также может использоваться IP-адрес, в том числе многоадресный. В этом случае только пакеты, отправленные на этот IP-адрес, будут доставлены в сокет. Т.е. вы можете создать несколько сокетов, по одному для каждой группы мультивещания. bind()
каждый сокет для (group_addr, port) и join group_addr. Затем данные, адресованные различным группам, будут отображаться в разных сокетах, и вы сможете отличить их таким образом.
Я тестировал, что следующие работы над FreeBSD:
#include #include #include #include #include #include #include #include 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
или эквивалент, чтобы прочитать их все.
Спустя несколько лет, столкнувшись с этим странным поведением в Linux, и используя обходное решение bind, описанное в предыдущих ответах , я понимаю, что справочная страница 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 , в этом обсуждении содержатся тестовые программы для воспроизведения проблемы и ее решения.
Используйте setsockopt()
и IP_PKTINFO
или IP_RECVDSTADDR
зависимости от вашей платформы, предполагая IPv4. Это в сочетании с recvmsg()
или WSARecvMsg()
позволяет вам найти адрес источника и получателя для каждого пакета.
Unix / Linux, обратите внимание, что FreeBSD использует IP_RECVDSTADDR
а оба поддерживают IP6_PKTINFO
для IPv6.
Windows также имеет IP_ORIGINAL_ARRIVAL_IF
замещать
mc_addr.sin_addr.s_addr = htonl(INADDR_ANY);
с
mc_addr.sin_addr.s_addr = inet_addr (mc_addr_str);
это помогает мне (linux), для каждого приложения я получаю отдельный stream mcast из отдельной группы mcast на одном порту.
Также вы можете посмотреть в источник проигрывателя VLC, он показывает много каналов mcast iptv из разных групп mcast на одном порту, но я не знаю, как он разделяет канал.
Мне пришлось использовать несколько сокетов, каждый из которых смотрел разные групповые адреса многоадресной рассылки, а затем подсчитывал статистику по каждому сокету отдельно.
Если есть способ увидеть «адрес получателя», как указано в ответе выше, я не могу понять это.
Один важный момент, который также занял меня некоторое время – когда я привязывал каждый из своих индивидуальных сокетов к пустому адресу, как это делают большинство примеров python:
sock[i].bind(('', MC_PORT[i])
Я получил все многоадресные пакеты (из всех групп многоадресной рассылки) в каждом сокете, что не помогло. Чтобы исправить это, я привязал каждый сокет к своей собственной многоадресной группе
sock[i].bind((MC_GROUP[i], MC_PORT[i]))
И это сработало.
IIRC recvfrom () дает вам другой адрес / порт для каждого отправителя.
Вы также можете поместить заголовок в каждый пакет, идентифицирующий отправителя источника.
Адрес многоадресной рассылки будет адресом получателя, а не адресом отправителя в пакете. Посмотрите на IP-адрес получателя.
Вы можете разделить streamи многоадресной рассылки, просмотрев IP-адреса получателей принятых пакетов (которые всегда будут адресами многоадресной передачи). Это несколько связано с этим:
Привяжите к INADDR_ANY
и установите IP_PKTINFO
сокета 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. Теперь вы можете обрабатывать принятые пакеты, специфичные для каждого многоадресного streamа (многоадресного адреса), который вы получаете.