2010-01-26 9 views
8

Ich versuche, eine Reihe von Anwendungen zu erkennen, die UDP verwenden und Nachrichten senden. Die Anwendungen senden regelmäßig ein UDP-Paket aus, in dem sie angeben, wer sie sind und was sie tun können. Zunächst verwenden wir nur zum Senden an INADDR_BROADCAST.Empfang von UDP-Paketen, die an 127.0.0.1 gesendet werden, wenn SO_REUSEADDR verwendet wird

Alle Anwendungen haben den gleichen zu empfangenden Port (daher SO_REUSEADDR). Ein Ereignis-Kernel-Objekt wird an den Socket angehängt, so dass wir benachrichtigt werden, wenn wir ein neues Paket holen und dieses in einer WaitFor-Schleife verwenden können. Der Socket wird asynchron verwendet.

Öffnen der Steckdose:

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

Daten senden, um eine angegebene Liste von Adressen:

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; 

Empfangen von Paketen:

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; 

Wenn wir die Ausstrahlungsdaten aussenden mit INADDR_BROADCAST, die lokale Broadcast-Adresse (192.168.1.255) oder die lokale IP-Adresse funktioniert einwandfrei. In dem Moment, in dem wir 127.0.0.1 zum "Senden" verwenden, ist der Empfang sporadisch, funktioniert aber im Allgemeinen nicht.

Hat jemand eine Idee, wie man das löst (die Adressliste ist änderbar)? Wenn alles andere fehlschlägt, suche ich alle lokalen IP-Adressen und ersetze einfach 127.0.0.1, aber das lässt Probleme übrig, wenn IP-Adressen sich ändern.

Update: Wenn Sie App1 zum ersten Mal starten, empfängt App1 Pakete. Als nächstes starten Sie App2. Jetzt wird App1 weiterhin Pakete empfangen, App2 jedoch nicht. Wenn Sie App1 beenden, wird App2 Pakete empfangen. Wenn Sie App3 starten, empfängt App2 seine Pakete, App3 jedoch nicht.

Also: nur eine Anwendung wird die Pakete empfangen, wenn 127.0.0.1 verwendet wird.

Auch das Setzen von IPPROTO_IP, IP_MULTICAST_LOOP auf eins mit setsocketopt ändert nichts.

Antwort

3

Es scheint, als ob Sie die Broadcast-Adresse fest codieren möchten, ohne sich Gedanken darüber machen zu müssen, welche tatsächlichen IP-Adressen das Gerät verwendet. Ihr erstes Problem ist, dass, da dies eine neue Anwendung ist, Sie Multicast statt Broadcast verwenden sollten. Dann können Sie eine spezielle Multicast-Adresse verwenden, die überall gleich sein kann, unabhängig davon, welche Adresse die Maschine tatsächlich hat. Ich nehme an, dass alle diese Apps auf demselben Computer ausgeführt werden.

Hier ist ein Beispielprogramm in Perl geschrieben. Sie sollten den Code relativ einfach anpassen können. Starten Sie ein paar Kopien in verschiedenen Fenstern, um zu sehen, wie es funktioniert. Im Grunde gibt es einen Sender und einen Empfänger und sendet die Datetime und Pid des Senders. Sie müssen das Socket :: Multicast-Paket von CPAN installieren, um es auszuführen.

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

Ich werde Multicast statt Broadcast betrachten. Was ich an Ihrem Beispiel sehe, ist, dass ich in IP_ADD_MEMBERSHIP/IP_MULTICAST_LOOP schauen sollte. Danke für das Beispiel. –

+1

Nach dem Ausprobieren funktionierte es, Multicasting statt Broadcasting zu verwenden. –