邓立波 深圳,2007-8
作者联系方式
:
email:
libodeng@gmail.com
msn:
libodeng@gmail.com
tel: 13510275799
版权/著作权所有 (C) 2007 邓立波 保留所有权利
警告:未经作者许可,任何人或组织不得转载,公开发布,拷贝,传播本文献的全部或部分
及时监测连接被动关闭
除非有特别要求,否则你应该总是对每个连接保持一个挂起的接收
pending io
(使用
WSARecv
投递)。如果用户主动关闭连接,你的
GetQueuedCompletionStatus
调用将返回成功,但接收到的数据长度为
0
,你能根据这点检测连接是否已被对方关闭。如果连接被重置或者
io
被取消(如果你调用了
CancelIo
的话),
GetQueuedCompletionStatus
将返回失败,注意这时还应该判断
GetQueuedCompletionStatus
调用返回的
lpOverlapped
值,如果该值不为
NULL
,说明
iocp
已经检测到一个连接已经中断。
安全的关闭连接
很多人写的服务器网络库有一个难以接受的缺陷(包括我曾就职公司的一些同事),当服务器程序主动关闭连接时,刚发往客户端的包有时出现丢失,这时他们推荐的方式往往是发送数据后等待几秒再关闭连接。豪无疑问,这是一种笨拙的实现方式,他们遇到的问题根源是什么呢?
在非
IOCP
模式网络程序中,你只要简单的调用
closesocket
函数就可以确保数据在操作系统释放
socket
之前安全到达对方,但在
IOCP
模式下,如果调用
closesocket
时有未决的
pending IO
将导致
socket
被重置,所以有时会出现数据丢失。正统的解决方式是使用
shutdown
函数(指定
SD_SEND
标志),注意这时可能有未完成的发送
pengding IO
,所以你应该监测是否该连接的所有是否已完成(也许你要用一个计数器来跟踪这些
pending IO
),仅在所有
send pending IO
完成后调用
shutdown
。
当你调用
shutdown
时,也许数据仍然停留在操作系统的缓冲,操作系统将在数据发送完后发出一个
FIN
包来启动关闭进程,客户端接收完数据后,将接受到一个
0
长度的包,以此判断连接已关闭(你写的客户端肯定有检测连接关闭,不是吗?),然后调用
closesocket
,这时服务器的
GetQueuedCompletionStatus
将接收到一个数据长度为
0
的包,这时你就可以调用
closesocket
,并释放相关连接资源。
在绝大部分情况下上述的过程连接能完美的关闭。如果你特别注重服务器的安全性和健壮性,可能你还需要做一个“连接关闭队列”,对每个已调用
shutdown
的连接放到这个队列,然后定时的对这个队列扫描,如果一个连接
5
秒(你也可以自己调整)还不能关闭,那么就强制关闭它。
处理大并发短连接时如何避免
TIME_WAIT
状态
关于如何避免
TIME_WAIT
这个问题,一直没看到有效的处理方式(至少我没有),
我将在这里披露一种有效的方式。回到上一段,我们最后调用了
closesocket
关闭连接,这时仍然可能出现
TIME_WAIT
状态,但注意这时所有的数据都已经传输完毕,因此你可以强制关闭
socket
避免服务器连接进入
TIME_WAIT
(这时只会发出连接重置
RESET
包)
//
立即关闭
(
避免出现
TIME_WAIT
状态
)
LINGER linger = {1,0};
setsockopt(socket, SOL_SOCKET, SO_LINGER,
(char *)&linger, sizeof(linger));
socket唯一性问题
正常情况下
SOCKET
套结字值是唯一的,但是操作系统在分配
socket
值时有随机性,最近关闭的
socket
值可能重新分派给一个刚刚建立的新的
socket.
,尤其在大并发短连接的情况下。一个健壮的服务器
IOCP
网络库必须要考虑
socket
唯一性的问题,由于
IOCP
的排队机制,意味着当你调用
closesocket
关闭
socket
后,
IOCP
队列中可能仍然堆积了该
socket
的一些
I/O completion packet
,而此时,刚关闭的
socket
值又分派给一个刚刚建立的
socket
,所以,你必须对
GetQueuedCompletionStatus
获取到的
I/O completion packet
小心翼翼处理,避免出现数据混乱,然而,最好的方式等到所有
I/O completion packet
返回后才调用
closesocket
关闭该
socket
。