9

UDPとブロードキャストメッセージを使用して、一連のアプリケーションがお互いを検出できるようにしようとしています。アプリケーションは定期的にUDPパケットを送信し、自分が誰で何ができるかを示します。最初は、INADDR_BROADCASTへのブロードキャストにのみ使用します。

すべてのアプリケーションは、リッスンする同じポートを共有します(したがって、SO_REUSEADDR)。イベントカーネルオブジェクトがソケットにアタッチされているため、新しいパケットをフェッチしてそれをWaitForループで使用できるようになると通知が届きます。ソケットは非同期で使用されます。

ソケットを開く:

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

指定されたアドレスのリストにデータを送信します。

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;

パケットの受信:

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が使用されている場合、1つのアプリケーションのみがパケットを受信します。

また、IPPROTO_IP、IP_MULTICAST_LOOPをsetsocketoptを使用して1に設定しても、何も変更されません。

4

1 に答える 1

3

マシンが使用している実際の IP アドレスを気にせずに、ブロードキャスト アドレスをハード コーディングしたいようです。最初の問題は、これが新しいアプリケーションであるため、ブロードキャストではなくマルチキャストを使用する必要があることです。次に、マシンが実際に持っているアドレスに関係なく、どこでも同じになる特別なマルチキャスト アドレスを使用できます。これらのアプリはすべて同じマシンで実行されていると思います。

これは Perl で書かれたプログラムの例です。コードをかなり簡単に適応させることができるはずです。異なるウィンドウでいくつかのコピーを開始して、どのように機能するかを確認してください。基本的に、送信者と受信者をフォークし、送信者の日時と 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";
    }
} 
于 2010-01-27T09:34:55.967 に答える