2010-01-26 13 views
8

Sto tentando di far scoprire una serie di applicazioni utilizzando UDP e trasmettendo messaggi. Le applicazioni inviano periodicamente un pacchetto UDP che dice chi sono e cosa possono fare. Inizialmente usiamo solo per trasmettere a INADDR_BROADCAST.ricezione di pacchetti UDP inviati a 127.0.0.1 quando si utilizza SO_REUSEADDR

Tutte le applicazioni condividono la stessa porta da ascoltare (quindi SO_REUSEADDR). Un oggetto del kernel di eventi è collegato al socket, quindi riceviamo una notifica quando possiamo recuperare un nuovo pacchetto e usarlo in un ciclo WaitFor. Il socket è usato async.

Apertura della presa:

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

L'invio di dati a un elenco specifico di indirizzi:

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; 

Ricezione pacchetti:

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; 

Quando inviamo i dati trasmessi utilizzando INADDR_BROADCAST, l'indirizzo di broadcast locale (192.168.1.255) o l'indirizzo IP locale, tutto funziona correttamente. Nel momento in cui usiamo 127.0.0.1 per "trasmettere" a, la ricezione è sporadica ma generalmente non funziona.

Qualcuno ha la più pallida idea di come risolvere questo problema (l'elenco degli indirizzi è modificabile)? Se tutto il resto fallisce, cercherò tutti gli indirizzi IP locali e sostituirò 127.0.0.1 con quello, ma questo non risolve il problema quando cambiano gli indirizzi IP.

Aggiornamento: Quando si avvia App1 per la prima volta, App1 riceve i pacchetti. Quindi si avvia App2. Ora App1 continuerà a ricevere i pacchetti, ma App2 no. Se si interrompe App1, App2 inizierà a ricevere pacchetti. Se si avvia App3, App2 riceverà i suoi pacchetti ma App3 no.

Così: solo una applicazione riceverà i pacchetti quando viene utilizzato 127.0.0.1.

Anche l'impostazione IPPROTO_IP, IP_MULTICAST_LOOP su uno con setsocketopt non cambia nulla.

risposta

3

Sembra che quello che si desidera sia codificare l'indirizzo di trasmissione senza preoccuparsi di quali indirizzi IP effettivi siano utilizzati dalla macchina. Il tuo primo problema è che poiché questa è una nuova applicazione dovresti usare multicast invece di trasmettere. Quindi è possibile utilizzare uno speciale indirizzo multicast che può essere uguale ovunque, indipendentemente dall'indirizzo che la macchina ha effettivamente. Presumo che tutte queste app siano in esecuzione sulla stessa macchina.

Ecco un esempio di programma scritto in Perl. Dovresti essere in grado di adattare il codice abbastanza facilmente. Avvia alcune copie in finestre diverse per vedere come funziona. Fondamentalmente si biforca un mittente e un destinatario e invia il datetime e il pid del mittente. Dovrai installare il pacchetto Socket :: Multicast da CPAN per eseguirlo.

#!/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

Guarderò in multicast invece di trasmissione. Quello che vedo dal tuo esempio è che dovrei esaminare IP_ADD_MEMBERSHIP/IP_MULTICAST_LOOP. Grazie per l'esempio. –

+1

Dopo aver provato, ha funzionato per utilizzare il multicasting anziché la trasmissione. –

Problemi correlati