2010-01-26 22 views
8

Estoy tratando de hacer que un conjunto de aplicaciones se descubran usando UDP y transmitiendo mensajes. Las aplicaciones envían periódicamente un paquete UDP que dice quiénes son y qué pueden hacer. Inicialmente solo usamos para transmitir a INADDR_BROADCAST.recepción de paquetes UDP enviar a 127.0.0.1 cuando se utiliza SO_REUSEADDR

Todas las aplicaciones comparten el mismo puerto para escuchar (de ahí el SO_REUSEADDR). Un objeto kernel de evento se adjunta al socket para que nos notifiquen cuándo podemos obtener un nuevo paquete y usarlo en un bucle WaitFor. El socket se usa como sincronización.

de abrir el aparato:

FBroadcastSocket := socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); 
if FBroadcastSocket = INVALID_SOCKET then Exit; 
i := 1; 
setsockopt(FBroadcastSocket, SOL_SOCKET, SO_REUSEADDR, Pointer(@i), sizeof(i)); 
i := 1; 
setsockopt(FBroadcastSocket, SOL_SOCKET, SO_BROADCAST, Pointer(@i), sizeof(i)); 
System.FillChar(A, sizeof(A), 0); 
A.sin_family  := AF_INET; 
A.sin_port  := htons(FBroadcastPort); 
A.sin_addr.S_addr := INADDR_ANY; 
if bind(FBroadcastSocket, A, sizeof(A)) = SOCKET_ERROR then begin 
    CloseBroadcastSocket(); 
    Exit; 
end; 
WSAEventSelect(FBroadcastSocket, FBroadcastEvent, FD_READ); 

El envío de datos a una lista específica de direcciones:

for i := 0 to High(FBroadcastAddr) do begin 
    if sendto(FBroadcastSocket, FBroadcastData[ 0 ], Length(FBroadcastData), 0, FBroadcastAddr[ i ], sizeof(FBroadcastAddr[ i ])) < 0 then begin 
     TLogging.Error(C_S505, [ GetWSAError() ]); 
    end; 
end; 

Recibiendo paquetes:

procedure TSocketHandler.DoRecieveBroadcast(); 
var 
    RemoteAddr: TSockAddrIn; 
    i, N:   Integer; 
    NetworkEvents: WSANETWORKEVENTS; 
    Buffer:  TByteDynArray; 
begin 
    // Sanity check. 
    FillChar(NetworkEvents, sizeof(NetworkEvents), 0); 
    WSAEnumNetworkEvents(FBroadcastSocket, 0, @NetworkEvents); 
    if NetworkEvents.ErrorCode[ FD_READ_BIT ] <> 0 then Exit; 

    // Recieve the broadcast buffer 
    i := sizeof(RemoteAddr); 
    SetLength(Buffer, MaxUDPBufferSize); 
    N := recvfrom(FBroadcastSocket, Buffer[ 0 ], Length(Buffer), 0, RemoteAddr, i); 
    if N <= 0 then begin 
     N := WSAGetLastError(); 
     if N = WSAEWOULDBLOCK then Exit; 
     if N = WSAEINTR then Exit; 
     TLogging.Error(C_S504, [ GetWSAError() ]); 
     Exit; 
    end; 

    DoProcessBroadcastBuffer(Buffer, N, inet_ntoa(RemoteAddr.sin_addr)); 
end; 

Cuando enviamos los datos de difusión que utilizan INADDR_BROADCAST, la dirección de difusión local (192.168.1.255) o la dirección IP local todo funciona bien. En el momento en que usamos 127.0.0.1 para "transmitir" a, la recepción es esporádica pero generalmente no funciona.

¿Alguien tiene idea de cómo resolver esto (la lista de direcciones es cambiante)? Si todo lo demás falla, buscaré todas las direcciones IP locales y simplemente reemplazaré 127.0.0.1 con eso, pero eso deja problemas cuando cambian las direcciones IP.

Actualización: Al empezar App1, App1 recibirá los paquetes. Luego, comienza la aplicación 2. Ahora App1 aún recibirá paquetes, pero App2 no lo hará. Si detiene la aplicación 1, App2 comenzará a recibir paquetes. Si inicia App3, App2 recibirá sus paquetes, pero App3 no lo hará.

Así: sólo una aplicación recibirá los paquetes cuando se utiliza 127.0.0.1.

también establecer IPPROTO_IP, IP_MULTICAST_LOOP a uno con setsocketopt no cambia nada.

Respuesta

3

Parece que lo que quiere es codificar la dirección de difusión sin preocuparse por lo que las direcciones IP reales están en uso por la máquina. Su primer problema es que, dado que esta es una nueva aplicación, debería usar multicast en lugar de broadcast. Luego puede usar una dirección de multidifusión especial que puede ser la misma en todas partes, independientemente de la dirección que tenga la máquina. Supongo que todas estas aplicaciones se ejecutan en la misma máquina.

Aquí hay un programa de ejemplo escrito en Perl. Debería poder adaptar el código bastante fácilmente. Comience algunas copias en diferentes ventanas para ver cómo funciona. Básicamente, tenedor de un emisor y el receptor y envía el datetime y pid del remitente. Tendrá que instalar el paquete Socket :: Multicast de CPAN para poder ejecutarlo.

#!/usr/bin/perl -w 
# This example is a reimplementation of Steven's sendrecv Multicast example from UNP 
use strict; 
use diagnostics; 
use Socket; 
use Socket::Multicast qw(:all); # Has to be installed from CPAN 

my $sendSock; 

socket($sendSock, PF_INET, SOCK_DGRAM, getprotobyname('udp')) 
    || die "socket: $!"; 
setsockopt($sendSock, SOL_SOCKET, SO_REUSEADDR, pack("l", 1)) 
    || die "setsockopt: $!"; 
# create socket with ephemeral port for sending $port = 0 
bind($sendSock, sockaddr_in(0, INADDR_ANY)) || die "bind: $!"; 

# create socket for multicast receive 
my $recvSock; 
my $mcastIP = '239.255.1.2'; 
my $mcastPort = 9999; 

socket($recvSock, PF_INET, SOCK_DGRAM, getprotobyname('udp')) 
    || die "socket: $!"; 
setsockopt($recvSock, SOL_SOCKET, SO_REUSEADDR, pack("l", 1)) 
    || die "setsockopt: $!"; 

# join to specific port and IPV4 address to select mcast interface 
my $imr_multicast = inet_aton($mcastIP); 
my $imr_interface = INADDR_ANY; 
my $ip_mreq = pack_ip_mreq($imr_multicast, $imr_interface); 
my $ip = getprotobyname('ip'); 

setsockopt($recvSock, $ip, IP_ADD_MEMBERSHIP, $ip_mreq)  
    || die "setsockopt IP_ADD_MEMBERSHIP failed: $!"; 

# bind to multicast address to prevent reception of unicast packets on this port 
bind($recvSock, sockaddr_in($mcastPort, inet_aton($mcastIP))) || die "bind: $!"; 

# disable multicast loopback so I don't get my own packets 
# only do this if you're running instances on seperate machines otherwise you won't 
# get any packets 
# setsockopt($recvSock, $ip, IP_MULTICAST_LOOP, pack('C', $loop)) 
    # || die("setsockopt IP_MULTICAST_LOOP failed: $!"); 

# fork sender and receiver 
my $pid = fork(); 
if ($pid == 0) { 
    mrecv(); 
} else { 
    msend(); 
}  

sub msend { 
    close($recvSock); 
    while (1) { 
     my $datastring = `date`; chomp($datastring); 
     $datastring = "$datastring :: $pid\n"; 
     my $bytes = send($sendSock, $datastring, 0, 
         sockaddr_in($mcastPort, inet_aton($mcastIP))); 
     if (!defined($bytes)) { 
      print("$!\n"); 
     } else { 
      print("sent $bytes bytes\n"); 
     } 
     sleep(2); 
    } 
} 

# just loop forever listening for packets 
sub mrecv { 
    close($sendSock); 
    while (1) { 
     my $datastring = ''; 
     my $hispaddr = recv($recvSock, $datastring, 64, 0); # blocking recv 
     if (!defined($hispaddr)) { 
      print("recv failed: $!\n"); 
      next; 
     } 
     print "$datastring"; 
    } 
} 
+0

Investigaré la multidifusión en lugar de la transmisión. Lo que veo en su ejemplo es que debería consultar IP_ADD_MEMBERSHIP/IP_MULTICAST_LOOP. Gracias por el ejemplo. –

+1

Después de probar cosas, funcionó usar multicasting en lugar de broadcasting. –

Cuestiones relacionadas