получающие пакеты 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 ничего не изменяет.

8
задан Cœur 25 April 2017 в 17:19
поделиться

1 ответ

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

Вот пример программы, написанной в Perl. Вы должны легко адаптировать код довольно легко. Начните несколько копий в разных окнах, чтобы увидеть, как это работает. В основном это вилки отправителя и приемника и отправляет DateTime и PID отправителя. Вам нужно будет установить сокет :: Multicast Package из 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";
    }
} 
3
ответ дан 6 December 2019 в 00:57
поделиться
Другие вопросы по тегам:

Похожие вопросы: