http://blog.csdn.net/simbi/article/details/3738933
http://www.cnblogs.com/BeginGame/archive/2011/09/22/2185164.html
及时监测连接被动关闭
除非有特别要求,否则你应该总是对每个连接保持一个挂起的接收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.,尤其在大连接数的情况下。所有在编写网络库时需要注意socket唯一性问题,例如应该避免多次对同一个套结字调用closesocket。
是我在去年写服务器网络库遇到的一个问题,当时令我非常意外,那时一直以为是程序代码编写的问题。后来查阅了大量英文邮件列表,才发现是IOCP本身的一个问题。为避免读者陷于同样的问题,这里把它列出来,并给出解决方法。
对每个使用AcceptEx接受的连接套结字使用setsockopt设置SO_UPDATE_ACCEPT_CONTEXT选项,这个选项原义是把listen套结字一些属性(包括socket内部接受/发送缓存大小等等)拷贝到新建立的套结字,却可以使后续的shutdown调用成功。
/* SO_UPDATE_ACCEPT_CONTEXT is required for shutdown() to work fine*/
setsockopt( sockClient,
SOL_SOCKET,
SO_UPDATE_ACCEPT_CONTEXT,
(char*)&m_sockListen,
sizeof(m_sockListen) ) ;
1.完成端口针对的是句柄,只要句柄可进行重叠IO操作的都可以使用。
2.基本流程为
用无参数的createIOcompleteport方法建立完成端口。
建立GetQueuedCompletionStatus循环线程
创建关联句柄。
用带参数的createIOcompleteport方法绑定关联句柄和完成端口。
执行带重叠io的操作
3.通过区分GetQueuedCompletionStatus所带的2个参数,可确定来源。
4.如果操作失败,GetQueuedCompletionStatus会直接返回,但如果在GetQueuedCompletionStatus处理过程中再次操作重叠io并失败,GetQueuedCompletionStatus不会处理。
5.如果用AcceptEX处理连接,如果对方在AcceptEX处理之前连接上又断掉(这些连接请求会保存在Listen socket的连接请求队列中),GetQueuedCompletionStatus也会返回,这时就需要判断第一个WSARecv是否成功,如果失败的话就直接closesocket,需要继续接受处理的话就再建立一个socket并acceptex,最后绑定到完成端口
6.如果使用disconnectEX,它也会导致GetQueuedCompletionStatus返回,所以要对其专门做处理。
7.一般的断线检测
一是检查WSARecv导致GetQueuedCompletionStatus返回处理字节为0的时候
二是检测WSARecv是否正常开启重叠IO的时候。
8.overlapped结构,acceptec和WSARecv可以都用同一个,可以在断线后释放,而send,disconnectex的必须自己new一个新的,并在处理完后立即释放。