IOCP在网络应用中常见错误分析
原创文章 JohnsonFeng@yeah.net
致力于网络游戏服务器.客户端引擎开发
IOCP是一种异步套接字体机制,它是Windows系统中管理异步IO操作的API。使用IOCP,应用程序可以发送一个耗时的请求,然后执行其他的任务,而这个请求在内核执行,调用相应的驱动程序完成IO操作各个步骤。在大规模C/S架构的系统中,完成端口经常用于管理大量Socket连接。最近在一些网上代码和书店的部分参考书中,发现了一些常见的错误,这里经过简单总结,列出以下常见问题,供博友参考。
1.错误处理已取消的ACCEPT完成通知
在某些时候,应用层尚未对连入请求进行处理,这个连接还处于等待状态,如果在请求被处理前客户端由于某种原因断开了,那么GetQueuedCompletionStatus会返回FALSE。在此,某些参考书中对此返回值的处理是忽略,但这是错误的,首先,返回false并不表明完成端口出问题了,其次如果忽略,会导致一个ACCEPT异步请求丢失。通常,一个监听端口上,应用程序会创建很多个ACCEPT请求,以提高连接成功率。每当应用程序处理了一个连接请求时,应该再投递一个请求,以便完成端口总是在异步地接受连接,这样,在监听端口上进行的异步ACCEPT请求是固定的。如果应用程序经常因为GetQueuedCompletionStatus返回FALSE而忽略这些连接请求的完成结果,将会导致ACCEPT请求数减少,当这个数字减少到0之后,应用程序将得不到任何ACCEPT通知,也就是不会知道有客户端连入,当然,可能一开始还有少数客户端能连入,但是服务器端得不到通知,当连入排队数超出设置的上限(由listen函数设置)时,任何连入的请求都直接失败。此种错误本人已经在多本参考书和网上的文章中看到过,如果你的IOCPserver连不上,可能就是这种原因了。
Tips:在连接请求队列满时,用Telnet连入,直接返回无法连接,这和端口没打开的表象是相同的,但是此时,在服务器端用netstat -a查看时,该端口却是打开的。
正确的做法是,先关闭socket,然后再投递一个请求。
2.完成端口适合短连接
例如HTTP服务器,P2P Server等,实际上,很多场合并不需要IOCP,简单使用select模型就可以了,例如vsftp。
3.memcpy使用不当
这个memcpy如果拷贝的源范围和目标地址范围有重叠,将导致未决的错误,类似的错误也发生在strcpy中。正确的做法是用memmove;
4.关闭Socket的时机
一个连接被关闭,有多种情况:
a.客户端主动断开,此时,需要注意,GetQueuedCompletionStatus可能因为断开返回一或两次,一般接受请求总是存在的,所以至少有一次是因为WSARecv异步调用返回;而WSASend可能在断开时没有发生,也可能正在进行,这时,会产生一次完成事件。所以,在处理这种情况时,需要判别是否有两次事件,避免第一次直接施放资源,第二次再视图关闭相关资源而导致服务器逻辑错误。
b.服务器端主动断开,服务器主动断开,建议使用closesocket,然后交给情况a去处理。
c.网络中断,有通知,这种情况的处理和a也是一样的。
d.网络中断,无通知,由于TCP协议的特点,没有数据需要传输时,不发送数据,所以,如果一个客户端突然死机了,server端是无法知道的,除非有数据发送出去失败了。在没有数据发送时,一般说来,可以用两种方法进行处理,一是定时发送某个同步消息,当发送或对方处理失败时,就表明连接断开了;另外一种方法是经常地检测客户端有多长时间没活动了,如果超过一定时间就断开,当然客户端可还需要数据传输,那么再建立一次连接,例如http server就使用这种方式,当webbrowser打开一个网页后长时间停留,服务器可以在超过一定时间后断开以施放服务器资源。
5.不正确地处理TCP数据流
TCP数据是按流传输的,保证数据按顺序到达,但TCP并不保证数据按调用,逐个数据包进行传输,可能出现分解也可能出现合并,这是使用TCP的常识。
如果在你的C/S程序中出现了不该出现的应用层消息,或者出现了连接不上的问题,或者出现了掉线频繁的问题,那么此文章可能对解决问题有所帮助。
参考:
GetQueuedCompletionStatus API文档 http://msdn.microsoft.com/en-us/library/aa364986(VS.85).aspx
VSFTP(very safe ftp): http://vsftpd.beasts.org/