重用套接字时出现套接字正在使用错误

3 投票
3 回答
11637 浏览
提问于 2025-04-15 14:22

我正在用C++写一个XMLRPC客户端,目的是和一个Python的XMLRPC服务器进行通信。

不幸的是,目前这个Python XMLRPC服务器只能在一个连接上处理一个请求,然后就会关闭连接。我是通过mhawke对我之前提问的回答发现这个问题的,关于这个相关主题

因此,每次我想发送一个XMLRPC请求时,都必须重新建立一个新的socket连接到我的Python服务器。这就意味着要创建和删除很多socket。虽然一切运行得很好,但当请求数量接近4000时,我就会遇到socket错误10048,Socket正在使用中

我尝试让线程休眠,以便让winsock修复它的文件描述符,这个方法在我之前的一个Python客户端遇到类似问题时有效,但这次没有成功。我尝试了以下方法:

int err = setsockopt(s_,SOL_SOCKET,SO_REUSEADDR,(char*)TRUE,sizeof(BOOL));

但都没有成功。

我使用的是winsock 2.0,所以WSADATA::iMaxSockets应该不会影响这个问题,而且我检查过,它的值是0(我猜这意味着无限制)。

4000个请求在一个应用程序运行期间似乎并不是一个过分的数字。那么有没有办法在客户端使用SO_KEEPALIVE,而服务器不断关闭和重新打开连接呢?

我是不是遗漏了什么重要的东西?

3 个回答

0

你在使用完插座后会把它关掉吗?

1

更新:

我把这个代码加进去后,现在似乎可以正常工作了。

if(::connect(s_, (sockaddr *) &addr, sizeof(sockaddr))) 
  {
    int err = WSAGetLastError();
    if(err == 10048)   //if socket in user error,   force kill and reopen socket
    {
        closesocket(s_);
        WSACleanup();
        WSADATA info;
        WSAStartup(MAKEWORD(2,0), &info);
        s_ = socket(AF_INET,SOCK_STREAM,0);
        setsockopt(s_,SOL_SOCKET,SO_REUSEADDR,(char*)&x,sizeof(BOOL));
    }
  }

基本上,如果你遇到10048错误(表示套接字正在使用中),你可以简单地关闭这个套接字,调用清理函数,然后重启WSA,再重新设置套接字和它的选项。

(最后一个选项可能不是必须的)

我之前可能漏掉了WSACleanup和WSAStartup的调用,因为closesocket()和socket()肯定是被调用过的。

这个错误大约每4000次调用才会出现一次。

我很好奇为什么会这样,尽管这个方法似乎解决了问题。如果有人对此有任何看法,我会很想听听。

11

这个问题是因为有些连接在关闭客户端的socket后,进入了一个叫做TIME_WAIT的状态。默认情况下,socket会在这个状态下停留4分钟,之后才能被重新使用。你的客户端(可能还有其他进程的帮助)在这4分钟内消耗了所有的socket。想了解更多,可以看看这个回答,里面有个不错的解释和一个可能的非代码解决方案。

在Windows系统中,当你没有明确指定socket地址时,系统会动态分配1024到5000之间的端口号(总共3977个端口)。下面的Python代码展示了这个问题:

import socket
sockets = []
while True:
    s = socket.socket()
    s.connect(('some_host', 80))
    sockets.append(s.getsockname())
    s.close()

print len(sockets)    
sockets.sort()
print "Lowest port: ", sockets[0][1], " Highest port: ", sockets[-1][1]
# on Windows you should see something like this...
3960
Lowest port: 1025  Highest port: 5000

如果你立即再尝试运行一次,应该会很快失败,因为所有动态端口都处于TIME_WAIT状态。

有几种方法可以解决这个问题:

  1. 自己管理端口分配,使用bind()将客户端socket明确绑定到一个特定的端口,每次创建socket时递增这个端口。你仍然需要处理端口已被使用的情况,但这样就不再局限于动态端口了。例如:

    port = 5000
    while True:
        s = socket.socket()
        s.bind(('your_host', port))
        s.connect(('some_host', 80))
        s.close()
        port += 1
    
  2. 调整SO_LINGER这个socket选项。我发现这个方法在Windows上有时有效(虽然我不太确定为什么):s.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, 1)

  3. 我不知道这是否对你的具体应用有帮助,不过可以通过multicall方法在同一个连接上发送多个XMLRPC请求。基本上,这允许你将多个请求累积起来,然后一次性发送。你在实际发送累积的请求之前不会收到任何响应,所以可以把这看作是批处理——这是否符合你的应用设计?

撰写回答