2010-04-30 9 views
12

Tengo una aplicación que recibe datos de varias fuentes de multidifusión en el mismo puerto. Puedo recibir los datos. Sin embargo, estoy intentando contabilizar las estadísticas de cada grupo (es decir, los mensajes recibidos, los bytes recibidos) y todos los datos se mezclan. ¿Alguien sabe cómo resolver este problema? Si trato de ver la dirección del remitente, no es la dirección de multidifusión, sino la dirección IP de la máquina de envío.Recuento de varias transmisiones de multidifusión en el mismo puerto: C, Linux

estoy usando las siguientes opciones de conector:

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)); 

y también:

setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &reuse, sizeof(reuse)); 

Agradezco cualquier ayuda !!!

Respuesta

10

[Editado para aclarar que bind() puede, de hecho, incluir una dirección de multidifusión.]

lo que la aplicación es unirse a varios grupos de multidifusión, y la recepción de los mensajes enviados a cualquiera de ellos, en el mismo puerto. SO_REUSEPORT le permite vincular varios sockets al mismo puerto. Además del puerto, bind() necesita una dirección IP. INADDR_ANY es una dirección catch-all, pero también se puede usar una dirección IP, incluida una de multidifusión. En ese caso, solo los paquetes enviados a esa IP se entregarán al socket. Es decir. puede crear varios sockets, uno para cada grupo de multidifusión. bind() cada socket al (group_addr, port), AND join group_addr. Luego, los datos dirigidos a diferentes grupos aparecerán en diferentes tomas, y podrá distinguirlo de esa manera.

He probado que los siguientes trabajos en 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); 
    } 
} 

Si ejecuta varios de estos procesos, para diferentes direcciones de multidifusión, y enviar un mensaje a una de las direcciones, sólo el proceso relevante lo recibirán. Por supuesto, en su caso, es probable que desee tener todos los sockets en un proceso, y tendrá que usar select o poll o equivalente para leerlos todos.

+0

No creo que pueda vincularse a una dirección de multidifusión; debe enlazar a su interfaz local (INADDR_ANY). – Gigi

+0

Ver aquí (leer los 2 primeros comentarios): https://jira.jboss.org/jira/browse/JGRP-639 –

+0

No puedo realizar pruebas en Linux, pero he escrito una pequeña prueba (incluida en la respuesta editada)), y verificó que funciona correctamente en FreeBSD. –

0

IIRC recvfrom() le proporciona una dirección de lectura/puerto diferente para cada emisor.

También puede poner un encabezado en cada paquete que identifique al remitente de origen.

0

La dirección de multidifusión será la dirección del receptor, no la dirección del remitente en el paquete. Mira la dirección IP del receptor.

5

Use setsockopt() y IP_PKTINFO o IP_RECVDSTADDR dependiendo de su plataforma, suponiendo IPv4. Esto combinado con recvmsg() o WSARecvMsg() le permite encontrar la fuente de destino y de cada paquete.

Unix/Linux, nota FreeBSD usa IP_RECVDSTADDR, mientras que ambos admiten IP6_PKTINFO para IPv6.

de Windows, también tiene IP_ORIGINAL_ARRIVAL_IF

3

Reemplazar

mc_addr.sin_addr.s_addr = htonl(INADDR_ANY);

con

mc_addr.sin_addr.s_addr = inet_addr (mc_addr_str);

es ayuda para mí (Linux), para cada aplicación que recibo corriente mcast separado del grupo mcast separada en un puerto.

También se puede ver en el origen del reproductor VLC, muestra muchos canales iptv mcast de diferentes grupos de mcast en un puerto, pero no sé cómo separe el canal.

+0

¿Qué es diferente de esta [respuesta anterior] (http://stackoverflow.com/ a/2741989/3102264)? – mpromonet

1

He tenido que usar varios sockets, cada uno mirando diferentes direcciones de grupos de multidifusión, y luego contar las estadísticas en cada socket individualmente.

Si hay una manera de ver la "dirección del receptor" como se menciona en la respuesta anterior, no puedo resolverlo.

Un punto importante que también me llevó un tiempo - cuando unió a cada uno de mis tomas individuales a una dirección en blanco como la mayoría de los ejemplos de Python:

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

tengo todos los paquetes de multidifusión (de todos los grupos de multidifusión) en cada zócalo, lo que no ayudó. Para solucionar esto, até cada zócalo a su propio grupo de multidifusión

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

Y luego funcionó.

9

Después de algunos años se enfrenta este extraño comportamiento de Linux, y utilizando la solución se unen describen en el anterior answers, me doy cuenta de que el ip(7) manpage describen una posible solución:

IP_MULTICAST_ALL (desde Linux 2.6.31)
Este La opción se puede usar para modificar la política de entrega de los mensajes de multidifusión a los sockets vinculados a la dirección INADDR_ANY del comodín. El argumento es un entero booleano (por defecto es 1). Si se establece en 1, el socket recibirá mensajes de todos los grupos que se han unido globalmente en todo el sistema. De lo contrario, entregará mensajes solo de los grupos que se han unido explícitamente (por ejemplo, a través de la opción IP_ADD_MEMBERSHIP) en este socket en particular.

entonces se puede activar el filtro para recibir los mensajes de grupos unidos usando:

int mc_all = 0; 
if ((setsockopt(sock, IPPROTO_IP, IP_MULTICAST_ALL, (void*) &mc_all, sizeof(mc_all))) < 0) { 
    perror("setsockopt() failed"); 
} 

Este problema y la forma de resolverlo permitiendo IP_MULTICAST_ALL se discute en Redhat Bug 231899, esta discusión contiene programas de prueba para reproducir el problema y para resolverlo.

+0

Esto no es una solución para el problema descrito en absoluto, porque este método no se puede utilizar para separar múltiples flujos de multidifusión enviados al mismo socket (se menciona el mismo puerto en la pregunta). –

+1

@JohannesOvermann: esta opción permite recibir en un socket solo el grupo multicast suscrito en este socket, entonces esta opción responde a la pregunta evitando recibir todo el grupo multicast suscrito por otros socket que comparten el mismo puerto (que podría estar en otro proceso), la pregunta no menciona que solo se usa un socket. Un socket por grupo de multidifusión permite que esta opción permita que el núcleo haga el filtro apropiado. – mpromonet

+0

en mpromonet: No. La op explícitamente dice "múltiples fuentes de multidifusión en el mismo puerto". Esto significa que debe usarse un solo socket. Realmente no puede vincular múltiples sockets al mismo puerto. Establecer mc_all en 0 no le permite separar las transmisiones de multidifusión enviadas al mismo puerto. –

0

Puede separar las transmisiones de multidifusión mirando las direcciones IP de destino de los paquetes recibidos (que siempre serán las direcciones de multidifusión). Es algo complicado hacer esto:

Enlace a INADDR_ANY y configure la opción de socket IP_PKTINFO. Luego debe usar recvmsg() para recibir sus paquetes UDP de multidifusión y para buscar el mensaje de control IP_PKTINFO. Esto le da un poco de información de banda lateral del paquete UDP recibido:

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 */ 
}; 

Mira ipi_addr: Esta será la dirección de multidifusión del paquete UDP que acaba de recibir. Ahora puede manejar los paquetes recibidos específicos para cada flujo de multidifusión (dirección de multidifusión) que está recibiendo.

+1

Esto parece muy cerrado a https://stackoverflow.com/a/2753081/3102264 – mpromonet

Cuestiones relacionadas