2010-01-26 17 views
8

J'essaye de faire un ensemble d'applications se découvrent en utilisant UDP et en diffusant des messages. Les applications enverront périodiquement un paquet UDP disant qui ils sont et ce qu'ils peuvent faire. Au départ, nous utilisons uniquement pour diffuser à INADDR_BROADCAST.recevoir des paquets UDP envoyer à 127.0.0.1 lors de l'utilisation SO_REUSEADDR

Toutes les applications partagent le même port à écouter (d'où le SO_REUSEADDR). Un objet kernel d'événement est attaché au socket afin que nous soyons avertis quand nous pouvons récupérer un nouveau paquet et l'utiliser dans une boucle WaitFor. Le socket est utilisé async.

Ouverture du connecteur:

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'envoi de données à une liste d'adresses:

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; 

Réception de paquets:

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; 

Lorsque nous envoyons les données de diffusion à l'aide INADDR_BROADCAST, l'adresse de diffusion locale (192.168.1.255) ou l'adresse IP locale tout fonctionne correctement. Au moment où nous utilisons 127.0.0.1 pour "diffuser", la réception est sporadique mais ne fonctionne généralement pas.

Est-ce que quelqu'un a une idée de comment résoudre ce problème (la liste d'adresses est modifiable)? Si tout le reste échoue, je rechercherai toutes les adresses IP locales et remplacerai simplement 127.0.0.1 par cela, mais cela laisse des problèmes lorsque les adresses IP changent.

Mise à jour: Lorsque vous démarrez App1, App1 reçoit des paquets. Ensuite, vous démarrez App2. Maintenant, App1 recevra toujours des paquets, mais pas App2. Si vous arrêtez App1, App2 commencera à recevoir des paquets. Si vous démarrez App3, App2 recevra ses paquets mais pas App3. Ainsi, une seule application recevra les paquets lorsque 127.0.0.1 est utilisé.

La définition de IPPROTO_IP, IP_MULTICAST_LOOP sur un avec setsocketopt ne change rien.

Répondre

3

Il semble que ce que vous voulez est de coder en dur l'adresse de diffusion sans vous soucier des adresses IP utilisées par la machine. Votre premier problème est que puisque c'est une nouvelle application, vous devriez utiliser la multidiffusion au lieu de la diffusion. Vous pouvez ensuite utiliser une adresse de multidiffusion spéciale qui peut être la même partout, quelle que soit l'adresse de la machine. Je suppose que toutes ces applications fonctionnent sur la même machine.

Voici un exemple de programme écrit en Perl. Vous devriez être capable d'adapter le code assez facilement. Commencez quelques copies dans différentes fenêtres pour voir comment cela fonctionne. Fondamentalement, il forks un expéditeur et un destinataire et envoie le datetime et le pid de l'expéditeur. Vous devrez installer le paquet Socket :: Multicast de CPAN pour l'exécuter.

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

Je vais regarder en multidiffusion au lieu de diffuser. Ce que je vois de votre exemple est que je devrais regarder dans IP_ADD_MEMBERSHIP/IP_MULTICAST_LOOP. Merci pour l'exemple. –

+1

Après avoir essayé les choses, cela a fonctionné pour utiliser la multidiffusion au lieu de la diffusion. –