服务端开启了TCP服务,客户端连接,正常的情况下当服务端closesocket后向客户端发送FIN,客户端回应ACK,此时服务端进入FIN_WAIT2状态,等待客户端发送FIN然后进入CLOSED。
这时,客户端还可以send一次数据,send完后,服务端收到后就会向客户端发送RST并进入CLOSED
若客户端始终不发送数据,并且也不Close,那么服务端的FIN_WAIT2会持续要一个默认超时(一般是60s),这个种残留链接太多的时候可能会消耗服务端资源,降低性能,甚至无法正常工作。
我们可以使用SO_LINGER来改变关闭连接的行为
struct linger lg;
lg.l_onoff = 1;
lg.l_linger = 0;
setsockopt(sk, SOL_SOCKET, SO_LINGER, (char*)&lg, sizeof(lg));
l_onoff | l_linger | closesocket行为 | 发送队列 | 底层行为 |
l零 | 忽略 | 立即返回。 | 保持直至发送完成。 | 系统接管套接字并保证将数据发送至对端。 |
非零 | 零 | 立即返回。 | 立即放弃。 | 直接发送RST包,自身立即复位,不用经过2MSL状态。对端收到复位错误号。 |
非零 | 非零 | 阻塞直到l_linger时间超时或数据发送完成。(套接字必须设置为阻塞zhuan) | 在超时时间段内保持尝试发送,若超时则立即放弃。 | 超时则同第二种情况,若发送完成则皆大欢喜。 |
另外,除了SO_LINGER的方式,还有一种知其然不知其所以然的方法,这是在调试一个服务程序时发现的,没搞清楚其内部原由:
使用ReadFile对Socket发起一次读取,其中使用到OVERLAPPED数据结构,这里非常关键
OVERLAPPED ov;
ov.hEvent = 0; //必须置零
ov.OffsetHigh=0; //必须置零
DWORD nRead = 0;
char chBuf[100];
BOOL blRet = ReadFile(sk, chBuf, 10,(LPDWORD)(&nRead), &ov);//肯定读不到数据
closesocket(sk); //此时关闭socket,可以直接发送RST,而不经过四次握手