我正在尝试让一组应用程序使用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并不会改变任何内容。
答案 0 :(得分: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";
}
}