【问题标题】:receiving UDP packets send to 127.0.0.1 when using SO_REUSEADDR使用 SO_REUSEADDR 时接收发送到 127.0.0.1 的 UDP 数据包
【发布时间】:2011-01-09 18:06:56
【问题描述】:

我正在尝试使一组应用程序使用 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 时,只有一个应用程序会收到数据包。

同样使用 setsocketopt 将 IPPROTO_IP、IP_MULTICAST_LOOP 设置为 1 不会改变任何内容。

【问题讨论】:

    标签: delphi udp localhost broadcast


    【解决方案1】:

    您似乎想要对广播地址进行硬编码,而不用担心机器使用的实际 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";
        }
    } 
    

    【讨论】:

    • 我将研究多播而不是广播。我从您的示例中看到的是,我应该研究 IP_ADD_MEMBERSHIP / IP_MULTICAST_LOOP。谢谢你的例子。
    • 在尝试之后,使用多播而不是广播确实有效。
    猜你喜欢
    • 1970-01-01
    • 2012-10-03
    • 2012-05-20
    • 1970-01-01
    • 2019-01-04
    • 1970-01-01
    • 2014-11-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多