背景:
最近在做网络IO模型时,遇到一个问题,客户端软件连接服务器端一次断开以后,很长一段时间以内不能连接第二次。后来发现,客户端(Windows)同一个端口的socket在close以后的两分钟内Windows是没有释放这个handle的,所以才会出现bind->connect失败。WSAGetLastError()返回10048错误。
10048:通常每个套接字地址(协议/网络地址/端口)只允许使用一次。
问题相关:
MSDN的说明如下:
这种错误常常发生在bind函数中,当bind已经绑定的端口被占用后,再次绑定同一端口就会发生错误,但这一错误不影响bind的执行,换句话说,bind第二次执行并不发生错误,但是在紧接着调用connect()时会出错,错误码就是10048。
应用程序调用bind在使用随机端口绑定时,如果操作系统预留的1024~5000之间的端口都被占用着的话,bind不出错,connect会失败。
什么情况下端口会被全部占用?
情况一:如果每个端口上都正在进行数据操作,那么端口就都处于占用状态。
情况二:connect的频率太快,每一个socket在调用closesocket进行关闭操作后,虽然closesocket执行完毕,但是对应的占用端口并不是立即释放,这些端口需要继续处于占用状态,等待时间大约2ML(数据包最大生存周期),默认最大值为4分钟。用netstat命令查看端口状态,端口显示TIME_WAIT,在4分钟内如果对同一个端口进行connect,就会发生10048错误。
说明:
新版的客户端在登录的时候与服务器进行连接的时候socket有bind固定的端口(旧版本没有bind固定端口,之所以这样做是因为之前你们反应登录的时候会出现用户已经登录的情况,经过与服务器端商量才决定这样的)。
在Windows下,系统会将每个连接(包含已经被关闭的连接)所占用的端口维持2个TTL(约120s),才进行资源释放进行下次使用。
也许你会问为什么不是每次出现10048的错误,原因一有可能是你连续登录的时间已经超过120S,原因二有可能是客户端logout VMS的时候VMS先将之前的链接关闭。
还有之前为什么不会出现10048的错误,是因为之前客户端socket没有绑定固定端口,而是由windos随机分配的端口,这样出现10048错误的概率就大大降低。 但是Windows限制本地端口总数为5000个,我们做测试可以发现如果连续不停的connect当windows将5000个端口都用完的话再connect也会出现10048的错误。
解决办法:
1.由Windos随机分配未使用的端口
addr.sin_port = 0; // 若port指定为0,则调用bind时,系统会为其指定一个可用的端口号
2. 如果使用了bind函数,那么可以对绑定的端口进行指定,如果调用connect失败,那么就返回bind处更改绑定端口,然后继续connect,直到成功为止。
3.如果没有使用bind函数,直接使用connect进行连接,那么可以对connect操作进行retry,在这种情况下如果connect失败,那么它下次被调用时仍会使用调用失败的端口进行连接,直到连接成功。
思路: 将一个socket 绑定到本机,使用getsockname获取此socket关联的端口号
函数声明:
bool getAvaliablePort(unsigned short &port);
// 返回:若成功则为true, 否则为false
* @brief 获取可用的端口
*
* @param port : 输出,返回可用的端口号
*
* @return 若成功则返回true,否则放回false
*/
bool getAvaliablePort(unsigned short &port)
{
bool result = true;
// 1. 创建一个socket
SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
// 2. 创建一个sockaddr,并将它的端口号设为0
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(ADDR_ANY);
addr.sin_port = 0; // 若port指定为0,则调用bind时,系统会为其指定一个可用的端口号
// 3. 绑定
int ret = bind(sock, (SOCKADDR*)&addr, sizeof addr);
if (0 != ret) {
result = false;
goto END;
}
// 4. 利用getsockname获取
struct sockaddr_in connAddr;
int len = sizeof connAddr;
ret = getsockname(sock, (SOCKADDR*)&connAddr, &len);
if (0 != ret){
result = false;
goto END;
}
port = ntohs(connAddr.sin_port); // 获取端口号
END:
if ( 0 != closesocket(sock) )
result = false;
return result;
}
参考:https://www.cnblogs.com/hdtianfu/archive/2012/10/20/2732675.html