2010-01-26 23 views
8

Tôi đang cố gắng tạo một bộ ứng dụng khám phá lẫn nhau bằng cách sử dụng UDP và phát tin nhắn. Các ứng dụng sẽ định kỳ gửi một gói UDP cho biết họ là ai và họ có thể làm gì. Ban đầu, chúng tôi chỉ sử dụng để phát sóng tới INADDR_BROADCAST.nhận các gói UDP gửi tới 127.0.0.1 khi sử dụng SO_REUSEADDR

Tất cả các ứng dụng chia sẻ cùng một cổng để nghe (do đó SO_REUSEADDR). Một đối tượng nhân sự kiện được gắn vào socket để chúng ta nhận được thông báo khi chúng ta có thể lấy một gói mới và sử dụng nó trong một vòng lặp WaitFor. Ổ cắm được sử dụng không đồng bộ.

Mở ổ cắm:

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

gửi ra dữ liệu vào một danh sách cụ thể các địa chỉ:

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; 

gói nhận:

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; 

Khi chúng tôi gửi đi dữ liệu phát sóng sử dụng INADDR_BROADCAST, địa chỉ phát sóng địa phương (192.168.1.255) hoặc địa chỉ IP cục bộ đều hoạt động tốt. Thời điểm chúng tôi sử dụng 127.0.0.1 để "phát sóng", tiếp nhận là không thường xuyên nhưng thường không hoạt động.

Có ai có manh mối về cách giải quyết vấn đề này không (danh sách địa chỉ có thể thay đổi)? Nếu mọi thứ khác thất bại, tôi sẽ tra cứu tất cả các địa chỉ IP cục bộ và chỉ thay thế 127.0.0.1 bằng địa chỉ đó nhưng điều đó sẽ khiến các vấn đề khi địa chỉ IP thay đổi.

Cập nhật: Khi bạn lần đầu khởi động App1, App1 sẽ nhận gói. Tiếp theo bạn khởi động App2. Bây giờ App1 vẫn sẽ nhận được gói nhưng App2 sẽ không. Nếu bạn dừng App1, App2 sẽ bắt đầu nhận gói. Nếu bạn khởi động App3, App2 sẽ nhận các gói của nó nhưng App3 thì không.

Do đó: chỉ một ứng dụng sẽ nhận các gói khi 127.0.0.1 được sử dụng.

Cũng đặt IPPROTO_IP, IP_MULTICAST_LOOP thành một với setsocketopt không thay đổi bất kỳ thứ gì.

Trả lời

3

Có vẻ như những gì bạn muốn là mã hóa cứng địa chỉ quảng bá mà không phải lo lắng về địa chỉ IP thực sự đang được sử dụng bởi máy. Vấn đề đầu tiên của bạn là vì đây là một ứng dụng mới, bạn nên sử dụng phát đa hướng thay vì phát sóng. Sau đó, bạn có thể sử dụng một địa chỉ multicast đặc biệt có thể giống nhau ở mọi nơi, bất kể địa chỉ máy thực sự có. Tôi cho rằng tất cả các ứng dụng này đang chạy trên cùng một máy.

Đây là một chương trình mẫu được viết bằng Perl. Bạn sẽ có thể thích ứng với mã khá dễ dàng. Bắt đầu một vài bản sao trong các cửa sổ khác nhau để xem nó hoạt động như thế nào. Về cơ bản nó dĩa một người gửi và người nhận và gửi datetime và pid của người gửi. Bạn sẽ cần cài đặt gói Socket :: Multicast từ CPAN để chạy nó.

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

Tôi sẽ xem xét phát đa hướng thay vì phát sóng. Những gì tôi thấy từ ví dụ của bạn là tôi nên xem xét IP_ADD_MEMBERSHIP/IP_MULTICAST_LOOP. Cảm ơn bạn cho ví dụ. –

+1

Sau khi thử mọi thứ, nó đã làm việc để sử dụng multicasting thay vì phát sóng. –

Các vấn đề liên quan