получение UDP-пакетов, отправляемых в 127.0.0.1 при использовании SO REUSEADDR

Я пытаюсь сделать набор приложений обнаруживать друг друга с помощью 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.

также установка IPPROTO_IP, IP_MULTICAST_LOOP на один с setsocketopt ничего не меняет.

1 ответов


похоже, что вы хотите жестко закодировать широковещательный адрес, не беспокоясь о том, какие фактические IP-адреса используются машиной. Ваша первая проблема заключается в том, что, поскольку это новое приложение, вы должны использовать многоадресную рассылку вместо широковещательной. Затем вы можете использовать специальный многоадресный адрес, который может быть одинаковым везде, независимо от того, какой адрес на самом деле имеет машина. Я предполагаю, что все эти приложения работают на одной машине.

вот пример программы написанный на Perl. Вы должны быть в состоянии адаптировать код довольно легко. Запустите несколько копий в разных окнах, чтобы узнать, как это работает. В основном это вилки отправителя и получателя и отправляет datetime и pid отправителя. Вам нужно будет установить пакет Socket:: Multicast из CPAN, чтобы запустить его.

#!/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";
    }
}