以前写过一篇用python封装IOCP的文章,这几天因朋友需要,再一次封装了iocp接口。这一次我并不是简单的把原有代码拿过来简单的修修改改,基本上是推到重来。改正第一次封装时的许多缺点,同时修正了许多bug,再回首看以前的代码,感觉自己在进步。这次封装iocp,收获了以下几点知识:
1. 数据的重复投递。一般情况下,一个Socket句柄在同一时刻最多只能有一次WSASend与SWARecv,如果多次投递WSASend或者WSARecv,有可能造成数据的重复发送或重复接收。我一般为每个Socket创建两个buffer,一个用于发送数据(send buffer),别一个用于接收数据(recv buffer),当send buffer中有数据的时候,就投递WSASend,如果send buffer为空,则不投递,且保证任何时刻该Socket只有一次WSASend投递。同样,当recv buffer满的时候,不投递WSARecv,如果recv buffer有空闲空间,就投递WSARecv投,且保证同一时刻该Socket只有一次WSARecv递。
2. 如果WSARecv或WSASend投递失败,将不会接收到通知。如:if (WSARecv(sock, …) == SOCKET_ERROR && err_no != WSA_IO_PENDING) { // 投递失败,不会为这次投递发送通知。 }
3. 关于连接断开时接收到的通知。如果iocp检测到Socket连接已经断开,程序马上会收得到通知,而且有时候会收到不至一次通知,这取决于你在该socket上投递WSASend与WSARecv的次数。例如你在一个socket上投递了一次WSASend与一次WSARecv,在这两次投递还没有被完成时,如果socket断开了连接,那么GetQueuedCompletionStatus()将会收到两次通知。一般情况下,我们会为每个连接准备两个重叠IO结构(Overlapped struct),一个用于接收数据,另一个用于发送数据。当连接断开的时候,就应该正确地释放这些资源。
4. 对于Socket的管理。一般情况下,使用字典或者列表来管理所有的连接。以字典为例,当连接创建的时候,将连接加入到字典中,而连接断开的时候,将连接从字典中移除。因为IOCP可能有多个工作者线程,所以在执行上述操作时,应该保证操作的原子性,也就是多线程同步问题。例如:一个工作者线程正在对一个连接投递WSASend操作,而同时另一个工作者线程检测到这个连接已经断开,并将该连接从字典中移除,并释放了与该连接相关的资源,此时就会出现不可预知的情况。
5. 关于IOCP的性能。网上有一些文章说IOCP能同时处理上万个连接,但我写的IOCP程序根本无法达到这个数,于是对IOCP的性能进行了怀疑。其实,经过我的调试发现,IOCP的效率确实高,处理上万个连接应该没问题,至于为什么自己的程序没有能达到这个数量级,我觉得主要是我的程序要维护成百上千个连接的缘故。我用hash_map来管理所有的连接,在程序中经常要根据key来获取相应的数据结构,如果很频繁的调用这样的操作,是会引起性能问题的。可以写一个简单的C++程序来感受一下:
// 下面这段代码在VC下是很占资源的 map<int, int> dict; for (int i = 0; i < 1000; ++i) dict[i] = i; while (1) { for (int i = 0; i < 1000; ++i) { dict[i]; } Sleep(10); }
上面这些内容是我第二次封装iocp所尝到的东西,网上还有许多关于iocp的好文章可供参考。如果您需要iocp的python封装版本,请留下您的邮箱,也欢迎与我交流。