重用套接字时出现套接字正在使用错误
我正在用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 个回答
你在使用完插座后会把它关掉吗?
更新:
我把这个代码加进去后,现在似乎可以正常工作了。
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次调用才会出现一次。
我很好奇为什么会这样,尽管这个方法似乎解决了问题。如果有人对此有任何看法,我会很想听听。
这个问题是因为有些连接在关闭客户端的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状态。
有几种方法可以解决这个问题:
自己管理端口分配,使用
bind()
将客户端socket明确绑定到一个特定的端口,每次创建socket时递增这个端口。你仍然需要处理端口已被使用的情况,但这样就不再局限于动态端口了。例如:port = 5000 while True: s = socket.socket() s.bind(('your_host', port)) s.connect(('some_host', 80)) s.close() port += 1
调整SO_LINGER这个socket选项。我发现这个方法在Windows上有时有效(虽然我不太确定为什么):
s.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, 1)
我不知道这是否对你的具体应用有帮助,不过可以通过multicall方法在同一个连接上发送多个XMLRPC请求。基本上,这允许你将多个请求累积起来,然后一次性发送。你在实际发送累积的请求之前不会收到任何响应,所以可以把这看作是批处理——这是否符合你的应用设计?