我试图使一组应用程序使用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,SO_BROADCAST,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 ),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,@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 ),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也不会改变任何内容.
解决方法
看起来你想要的是硬编码广播地址而不用担心机器正在使用什么实际的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,getprotobyname('udp')) || die "socket: $!"; setsockopt($sendSock,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,getprotobyname('udp')) || die "socket: $!"; setsockopt($recvSock,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_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,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,64,0); # blocking recv if (!defined($hispaddr)) { print("recv Failed: $!\n"); next; } print "$datastring"; } }