2010-01-26 2 views
8

UDP 및 브로드 캐스트 메시지를 사용하여 서로 다른 응용 프로그램을 검색하려고합니다. 응용 프로그램은 주기적으로 자신이 누구이며 무엇을 할 수 있는지 나타내는 UDP 패킷을 보냅니다. 처음에는 INADDR_BROADCAST에 브로드 캐스트하는 용도로만 사용됩니다.UDP 패킷을 수신하면 SO_REUSEADDR을 사용할 때 127.0.0.1로 보냅니다.

모든 응용 프로그램은 수신 대기 포트 (따라서 SO_REUSEADDR)를 공유합니다. 이벤트 커널 객체가 소켓에 첨부되어 새로운 패킷을 가져 와서 WaitFor 루프에서 사용할 수있을 때 알림을받습니다. 소켓은 비동기로 사용됩니다.

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; 

수신 패킷 : 지정한 주소 목록에 데이터를 전송

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

:

소켓을 열면 우리가 사용하는 방송 데이터를 전송

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; 

INADDR_BROADCAST, 로컬 브로드 캐스트 주소 (192.168.1.255) 또는 로컬 IP 주소가 모두 올바르게 작동합니다. 127.0.0.1을 사용하여 "방송"하는 순간, 수신은 산발적이지만 일반적으로 작동하지 않습니다.

누구든지이 문제를 해결하는 방법을 알고 있습니까 (주소 목록은 변경 가능)? 다른 모든 실패하면 모든 로컬 IP 주소를 조회하고 127.0.0.1 바꾸기 만하지만 IP 주소가 변경되면 문제가 남아 있습니다.

업데이트 : App1을 처음 시작하면 App1에서 패킷을 받게됩니다. 다음 App2를 시작합니다. 이제 App1은 패킷을 계속 수신하지만 App2는 수신하지 않습니다. App1을 중지하면 App2에서 패킷을 받기 시작합니다. App3을 시작하면 App2는 패킷을 수신하지만 App3은 그렇지 않습니다.

따라서 127.0.0.1을 사용할 때 하나의 응용 프로그램에서만 패킷을 수신합니다.

setsocketopt를 사용하여 IPPROTO_IP, IP_MULTICAST_LOOP을 1로 설정해도 아무 것도 변경되지 않습니다.

답변

3

실제 IP 주소가 컴퓨터에서 사용되는 것에 대해 걱정하지 않고 브로드 캐스트 주소를 하드 코딩하는 것이 좋습니다. 첫 번째 문제는 새로운 응용 프로그램이므로 브로드 캐스트 대신 멀티 캐스트를 사용해야합니다. 그런 다음 머신이 실제로 가지고있는 주소와 상관없이 모든 곳에서 동일 할 수있는 특별한 멀티 캐스트 주소를 사용할 수 있습니다. 나는이 모든 응용 프로그램이 동일한 컴퓨터에서 실행되고 있다고 가정합니다.

다음은 Perl로 작성된 예제 프로그램입니다. 코드를 상당히 쉽게 적용 할 수 있어야합니다. 다른 창에서 몇 장을 시작하여 작동 방식을 확인하십시오. 기본적으로 보낸 사람과받는 사람을 분기하고 보낸 사람의 datetime 및 pid를 보냅니다. CPAN에서 Socket :: Multicast 패키지를 설치하여 실행해야합니다.

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

브로드 캐스트 대신 멀티 캐스트를 살펴 보겠습니다. 귀하의 예제에서 볼 수있는 것은 IP_ADD_MEMBERSHIP/IP_MULTICAST_LOOP을 조사해야한다는 것입니다. 예제를 가져 주셔서 감사합니다. –

+1

일을 시도한 후에 방송 대신 멀티 캐스팅을 사용했습니다. –

관련 문제