IO Completion Ports provide a specialised notification mechanism for completed asynchronous I/O. Completed I/O is queued at an object called a completion port which then calls a programmer-supplied call-back, known as a completion function, to process the I/O. What is unique about the completion port mechanism is that it tries to run the call-backs for multiple socket I/O in as few threads as possible, to maximise efficiency by minimising thread context switching.
IOCP的完成函数是在线程内部调用的,当一个请求完成后,完成端口会检查是否含有IO请求在排队,如果有,继续像前面一样在这个线程中
调用完成端口,这样所有的请求都可以在一个线程中处理完成,避免了线程切换。
比如: 一个SMTP服务器从客户端接收数据,然后根据SMTP协议返回数据,在等待进一步响应的时候,第二个客户端的IO来了,这时,IOCP
会在处理第一个客户端的线程中处理当前的IO事件。No context switch to a second thread is necessary.
IOCP的优点:
1.避免创建大量线程
因为同一个线程里面会处理多个socket,一个完成后会立马循环取下一个queue中的请求,避免了线程之间的切换
2.当需要再启动一个线程时,系统会取最近刚用过的线程
3.避免了自己写线程池,使用windows提供的BindIoCompletionCallback就行
IOCP的问题:
完成端口不能有超时(阻塞),否则导致线程池中的线程被切换(queue中还有pending io时, IOCP不会闲坐)
如果每一个客户端请求都因为超时阻塞了,这就会退回到每一个连接创建一个线程的情况
1.定时器问题
我们可以用一个单独的线程来监视执行线程的timeout,在这个线程里,我们用一个list保存每个socket关联的overlapped数据,里面保存了超时信息,
线程每秒查看一次list中的overlapped有没有超时的,如果有,我们就向程序报告这个IO出错了,即使后面这个我们报告超时的socket最终返回,我们
也会把返回的数据丢弃掉,这样做的一个坏处是我们必须保存overlapped的list直到socket关闭
We don't want any thread to start cleaning up until the other thread has finished with the data, so what is relevant to cleanup is not which thread enters the timeout handling code first – that will be given the task of reporting the timeout -- but which thread gets to the cleanup part of the code last -- that will be given the task of cleaning up.
2.socket关闭的问题
Imagine this scenario:
1. We delete a Socket class object
2. The destructor of the object causes close() on the socket
3. Closing a socket causes any outstanding i/o on the socket to be reported back to the completion routine as having failed with an error
4. In the completion routine we try to access the Socket class object. However the object has already been deleted in step 2, so we are accessing an invalid object, and the code fails.
(并行运行的负责清理对象的线程被唤醒,设置事件,析构函数一直等待直到这个事件被设置,然后析构完成)
另一种复杂点的办法:
1.声明socket所在类对象的destructor为protected,可以阻止对象被直接调用delete destroyed.
同时,这也意味着我们封装的socket类对象必须是在堆上创建的,原因不用讲。。
2.我们可以利用这样一个事实:关闭socket会导致所有此socket的pending的I/O在IOCP上排队,并带上一个abort错误
我们可以记下pending i/o的数目,当被IOCP报告的数目减少为0时,我们就可以删除此对象了。
3.当所有pending io返回后,在完成例程内部,我们可以删除对象。因为destructor是private的,我们要
另外定一个函数来实现次功能
4.destructor被定义为protected而不是private,是为了继承者可以直接调用。
TCP关闭时会有一个"Delay",就是TIME_WAIT,主动关闭方会进入TIME_WAIT状态,
服务器要高效使用SOCKET,需要SOCKET关闭后马上就可以使用,所以不能有这种情况
解决方法:
让客户端主动关闭,服务器被动关闭
RECV时可以提高性能的一个方法:
每次循环接受数据时加倍当前的缓冲区大小
在接受数据时,如果多个线程都提交了缓冲区,而这个缓冲区是会被locked的,如果locked的缓冲区达到一定数量就会失败,
一个方法是初次提交时设置接受缓冲区长度为0,从而可以接受所有的连接
关于缓冲区:
可以设置一个区间缓冲区,如 0 - 100 对应一个缓冲区, 100 - 200 对应一个缓冲区,。。。,提供缓冲区的读写效率和快速查找缓冲区