Получение нескольких многоадресных каналов на одном порту - 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));
Я ценю любую помощь!!!
Ответы
Ответ 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. Теперь вы можете обрабатывать принятые пакеты, специфичные для каждого многоадресного потока (многоадресного адреса), который вы получаете.