我正在尝试实现一个基本的 UDP 协议,其中发送方将 UDP 数据报发送到服务,然后使用传入数据报中的源地址和源端口发回响应数据报。
通常,您会让 Sender 也监听该端口上的响应。但我希望响应由也在该主机上运行的单独程序(侦听器)接收。所以:
- 在主机 A 上,侦听器启动并绑定到端口 12345,并阻止
recvfrom
. - 在主机 A 上,Sender 向主机 B 上运行的 Service 发送数据报,将源地址和端口设置为主机 A,端口 12345。
- 主机 B 上的服务向主机 A 的端口 12345 发送响应。
- 响应由侦听器拾取。
设置源地址和端口是通过绑定到它们来完成的。所以我需要 Sender 和 Listener 绑定到同一个端口。在两者中设置 SO_REUSEADDR 允许这样做。请注意,我在这里没有使用多播。
但是侦听器并不能可靠地接收到响应。我观察到有两个例外:
我发现如果 Sender 在发送第一个数据报后立即关闭套接字,那么响应将到达 Listener。
或者,如果 Sender 首先启动并在 Listener 之前绑定,则响应将被 Listener 拾取。
我一直在研究互联网上的示例,但没有找到清楚描述应该发生什么的文档。但是我见过的一些地方暗示,对于单播,只有最近绑定到端口的进程才会接收发送给它的数据报。
我的问题是,我可以发送 UDP 数据报,以便另一个进程接收响应(使用源地址和端口发送)吗?如果上述过程无法工作,有没有办法在传出数据报上设置源信息而不绑定到该端口?
其他几点:
- 每个进程都应该独立启动,并且能够在不干扰其他进程的情况下重新启动。所以我不认为我可以让一个打开套接字并产生另一个。
- 我不需要从两个进程接收数据包。一个进程只发送,另一个只接收。
- 理想情况下,该解决方案将具有足够的便携性,可以在常见的 Unix 和 Windows 上运行。
- 最后,如果根本不可能,那么我将退回到使用单个进程来执行这两个功能。我对此并没有太大压力,但如果可以的话,我有兴趣这样做。:-)
网络代码如下...
发件人代码
void run(Options *options)
{
struct sockaddr_in si_me, si_other;
int s;
socklen_t slen = sizeof(si_other);
int reuse = 1;
struct hostent *he;
if ((s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1)
die("socket");
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) != 0)
die("setsockopt");
// Bind to the "listen port", so that outgoing datagrams have the correct source information
memset((char *) &si_me, 0, sizeof(si_me));
si_me.sin_family = AF_INET;
si_me.sin_port = htons(options->listen_port);
si_me.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(s, (struct sockaddr *) &si_me, sizeof(si_me)) != 0)
die("bind");
memset((char *) &si_other, 0, sizeof(si_other));
si_other.sin_family = AF_INET;
si_other.sin_port = htons(options->service_port);
if (!(he = gethostbyname2(options->service_host, AF_INET)))
die("gethostbyname2");
memmove(&si_other.sin_addr.s_addr, he->h_addr, he->h_length);
while (1)
{
int len;
char *buf;
// Create outgoing message in buf
...
if (sendto(s, buf, len, 0, (struct sockaddr *) &si_other, slen) == -1)
die("sendto");
}
close(s);
}
监听器代码
static void run(Options *options)
{
struct sockaddr_in si_me, si_other;
int s;
socklen_t slen = sizeof(si_other);
char buf[BUFLEN];
int reuse = 1;
if ((s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1)
die("socket");
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) != 0)
die("setsockopt");
// Bind to the same "listen port" to pick up responses to datagrams sent by Sender
memset((char *) &si_me, 0, sizeof(si_me));
si_me.sin_family = AF_INET;
si_me.sin_port = htons(options->listen_port);
si_me.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(s, (struct sockaddr *) &si_me, sizeof(si_me)) == -1)
die("bind");
while (1)
{
int nr;
nr = recvfrom(s, buf, BUFLEN, 0, (struct sockaddr *) &si_other, &slen);
if (nr == -1)
die("recvfrom");
// Process the received message
...
}
close(s);
}
一个相关的问题是Using netcat to send a UDP packet without binding,其中一个答案似乎表明应该可以使用 SO_SOCKADDR,但并没有完全解释它在我的情况下是如何工作的。