套接字关闭后地址已在使用错误绑定 UDP 套接字

Address already in use error binding UDP socket after socket closed

所以我打开一个udp套接字(SOCK_DGRAM)并绑定它。发送流量后,套接字关闭。相同的代码用于创建套接字和绑定(相同的地址端口)。调用失败,errno 98(地址已被使用)。对于我来说有意义的 TCP,因为它进入 TIME_WAIT 状态,但对于 UDP?为什么会这样?

请注意,这仅在服务器忙于以高速率(例如 10Gb/s)发送时发生。套接字包装在提供 RAII 的 c++ class 中。代码如下:

Socket::Socket(uint32_t srcIp, uint16_t port)
{
   fd = socket(AF_INET, SOCK_DGRAM, 0);
   sockaddr_in addr = {};
   addr.sin_addr.s_addr = srcIp;
   addr.sin_port = port;
   addr.sin_family = AF_INET;
   socklen_t len = sizeof(addr);
   
   
   if (bind(fd, reinterpret_cast<sockaddr*>(&addr), len) != 0) {
      // throw error
   }

}

Socket::~Socket()
{
   close(fd);
}

有趣的是,在销毁并重新创建套接字后,套接字(AF_INET, SOCK_DGRAM, 0) 为文件描述符调用returns 相同的值。我认为这表明 OS 已经回收了 FD 并处理了关闭。然而,它不喜欢绑定。对我来说很奇怪,因为 UDP 绑定是无连接的,所以它会表现得那样。

我不想使用 SO_REUSEADDR,因为我不想绑定到已在使用的端口。我想知道我是否已经有一个套接字在该端口上侦听。或者如果这是唯一的方法,那么我怎么知道套接字是否已关闭并且 UDP 处于它所处的任何状态(它甚至可以进入 TIME_WAIT 状态吗?)。这个问题的本质是一个竞争条件,它发生得足够快,以至于我无法查询 netstat 来查看僵尸套接字处于什么状态,因为到那时它就会消失。

我确实在析构函数上设置了 gdb 断点,然后在“// throw error”行上设置了断点。我看到调用了析构函数,所以我知道调用了 close;然后下一个要命中的断点在构造函数中(绑定失败)。

好久没给官方答复了。但这种行为的原因是由于该进程在其他代码中被分叉。正如 Barmar 指出的那样,这导致套接字继承了分叉进程中的绑定。事实上,boost 被用来分叉进程并且它没有关闭文件描述符,我认为这是一个很好的做法,这样你就不会遇到这样的场景。