前一篇介绍了重叠IO的一种实现方式即基于事件通知的方式,这一篇介绍另外一种方式,即使用完成例程的方式实现重叠IO,首先声明这种方式比事件通知的方式简单多了。
用完成例程来实现重叠I/O比用事件通知简单得多。在这个模型中,主线程只用不停的接受连接即可;辅助线程判断有没有新的客户端连接被建立,如果有,就为那个客户端套接字激活一个异步的WSARecv操作,然后调用SleepEx使线程处于一种可警告的等待状态,(使辅助线程主动的放弃一段时间的CPU使用权,以防止一直处于阻塞状态)以使得I/O完成后CompletionROUTINE可以被内核调用。如果辅助线程不调用SleepEx,则内核在完成一次I/O操作后,无法调用完成例程(因为完成例程的运行应该和当初激活WSARecv异步操作的代码在同一个线程之内)。当重叠IO操作完成,SleepEx会被完成的消息唤醒,从而使得辅助线程中的完成例程得到执行。
下面是SleepEx函数的说明:
The SleepEx function causes the current thread to enter a wait state until one of the following occurs:
1)An I/O completion callback function is called
2)An asynchronous procedure call (APC) is queued to the thread.
3)The time-out interval elapses
完成例程内的实现代码比较简单,它取出接收到的数据,然后将数据原封不动的发送给客户端,最后重新激活另一个WSARecv异步操作。注意,在这里用到了“尾随数据”。我们在调用WSARecv的时候,参数lpOverlapped实际上指向一个比它大得多的结构PerSocketData,这个结构除了WSAOVERLAPPED以外,还被我们附加了缓冲区的结构信息,另外还包括客户端套接字等重要的信息。这样,在完成例程中通过参数lpOverlapped拿到的不仅仅是WSAOVERLAPPED结构,还有后边尾随的包含客户端套接字和接收数据缓冲区等重要信息。
下面是服务器端的代码,依然延续前面的编程风格。服务端的代码只在第6步进行了更改,并且在主线程中启动了一个新的辅助线程workThread,这个辅助线程用来监视客户端的连接,并且为每一个客户端的连接激活WSARecv异步调用,并提供一个完成例程函数completionRoutine,这个完成例程函数completionRoutine会在WSARecv返回时被调用。客户端的代码和前面一样,没发生变化。