最近两周的时间在写IOCP网络库,由于老版本的IOCP网络库存在的一些问题(见这篇),于是打算重新写一个用于新项目的开发,新的网络库也主要基于这几点来实现。原来使用老版本IOCP库进行开发也有比较长一段时间,在重写的过程中避免了很多问题。这期间花了些时间在考虑层次架构和性能优化上,比如分层模式,I/O投递策略,内存管理模型,最后还是没有想出理想的优化方法,决定先实现,做到程序稳定健壮,以后在实践中再做优化。也只有在自己去写一个网络库的时候会需要去考虑那么多,现在把目前知道的、遇到的一些问题技巧记录下来,以后遇上新问题补充更新。
/******** UPDATE 2012/04/25 ********/
- IOCP的工作线程的个数一般设置为processors *2+2,这是综合考虑了工作线程可能是等待/挂起/正在执行的状态。如果你测试出更好的结果,以你的为标准。
- IOCP的工作线程由系统调度和优化,不要去干预线程的调度,除非你自信能超越系统的调度。
- 在遇到奇怪的问题时,可以尝试减少IOCP工作线程数量,来定位问题所在。
- 应用层在处理收到的数据时,尽快将数据处理掉或是拷贝一份,避免阻塞IOCP工作线程。
- 在投递I/O和收到该I/O完成通知(不管成功失败)期间,注意保存好Overlapped I/O结构,以免出现奇怪的内存问题。
- 在投递I/O时,如果返回WSA_IO_PENDING,意味着投递操作成功,但是稍后才会处理完成。一般说来,投递发送或接收IO请求可能是下面三种结果, 以调用WSASend为例:
I.如果操作错误码是ERROR_SUCCESS,系统将程序缓冲区拷贝到内核缓冲区(也就是TCP/IP栈缓冲区)中,然后在网络适当的时候(比如符合Nagle算法的发送条件),将数据拷贝到网卡缓冲区,进行真正的发送;
II.如果操作错误码是WSA_IO_PENDING,意味着此时内核缓冲区空间不够,系统将锁定程序缓冲区锁定到非分页内存中,直到内核缓冲区有足够的空间来将数据拷贝走;
III.如果操作结果是其他错误码,根据具体原因,可以选择释放socket对应的资源。(该条目参考《Windows网络编程第2版》) - 尽量使I/O缓冲区的大小是系统页面大小的倍数(32位是4k),这样避免系统在拷贝程序缓冲区在不足一倍数时而占用整个页面。
- 尽量投递大型I/O操作,也就是每次发送较大的数据包,而不是多次发送一些小的数据包,且每次发送数据都固定大小。
- 使用AcceptEx异步接收的连接,如果要获取本地/对端的地址,有两种方式:
I.使用扩展API:GetAcceptExSockaddrs,可以同时获取本地/对端地址,需要I/O结构的支持;
II.使用标准API:getsockname/getpeername,由于对端socket尚未与监听socket完全绑定,需要先设置SO_UPDATE_ACCEPT_CONTEXT 选项,然后才可以正常获取到。(适用于winxp以后的版本) - 在关闭Socket时,注意处理未决的IO请求,这些请求以失败的结果(GetQueuedCompletionStatus失败)返回。
- 常见错误码:
ERROR_NETNAME_DELETED(64):对端关闭socket
ERROR_OPERATION_ABORTED(995):本端socket被关闭,操作取消