0x00
最近研究ACE中Reactor模型在windows平台中的实现,在ACE的源码中,使用了WaitForMultipleObjects()来实现同步事件分离程序,该模型中主要涉及的事件有:

FD_CONNECT
FD_ACCEPT
FD_WRITE
FD_READ
FD_CLOSE

当然还有其他一些事件,这里主要讨论这五类事件的触发条件。

0x01
FD_CONNECT

  1. 调用了connect(ConnectEx, WSAConnect, WSAConnectByList, or WSAConnectByName),并且连接建立后。

FD_ACCPET

  1. 初始阶段,当有请求建立连接时。[初始阶段:之前没有任何一次连接请求]
  2. 有连接请求,并且前一个连接请求已经调用accept()之后。

FD_WRITE

  1. 客户端socket首次调用connect(其他类connect API)连接服务器。
  2. 服务器阻塞在accept(其他类accept API)中socket成功返回。
  3. 调用send返回WSAEWOULDBLOCK,并且直到发送缓冲区准备好(为空)后。

注:3的含义其实就是当发送缓冲区满了之后,调用send会返回一个WSAEWOULDBLOCK错误,接着当发送缓冲区将数据全部发送之后,会触发FD_WRITE消息,告知上层可以继续调用send了。

测试触发代码:

while(1)
{
        char sendBuf[47368] = {0};
        sendBuf[0] = 'a';
        int err = ::send(socket_client, sendBuf, 47368, 0);
        if( err == SOCKET_ERROR )  
        {                             
                if( WSAGetLastError() == WSAEWOULDBLOCK )  
                {  
                        printf("the buffer is full  
");  
                        break;  
                }  
        }  
}

用一个大的数组不断调用send发送数据,直到把发送缓冲区填满为止,然后跳出循环。之后当发送缓冲区为空时,系统会触发FD_WRITE消息。

FD_READ

  1. 最初阶段,在数据到达socket后,并且从来没有触发过FD_READ。
  2. 在数据到达socket后,并且前一个recv调用后。
  3. 调用recv后,缓冲区还有未读完的数据。

第3点过程如下:
1.100 bytes 数据到达,winsock2发出FD_READ
2.程序用recv只读入50 bytes,还剩下50 bytes
3.winsock2继续发出FD_READ消息

recv返回WSAEWOULDBLOCK的情况:
1.有数据到达,FD_READ触发,该消息加入程序的消息队列
2.在还没处理该消息前,程序就把数据recv了
3.等到处理该FD_READ消息时,程序调用recv就会返回WSAEWOULDBLOCK(因为数据在这之前就recv了)

注意:
1.winsock2发出一个FD_READ后,如果程序没有用recv,即使还有数据没接收FD_READ也不会再触发另一个FD_READ,要等到recv调用后FD_READ才会发出。
2.对一个FD_READ多次recv的情形:如果程序对一个FD_READ多次recv将会造成触发多个空的FD_READ,所以程序在第2次recv前要关掉FD_READ(可以使用WSAAsynSelect关掉FD_READ),然后再多次recv。
3.recv()返回WSAECONNABORTED,WSAECONNRESET...等消息,可以不做任何处理,可以等到FD_CLOSE事件触发时再处理。

另外,当通信对端多次调用send时,winsock2并不会触发多个FD_READ事件,例如如下代码:

// (1)
socket_stream.send(  buf_send,  buf_size,  0 );
socket_stream.send(  buf_send,  buf_size,  0 );
// (2)
socket_stream.send(  buf_send,  buf_size,  0 );
sleep(100);
socket_stream.send(  buf_send,  buf_size,  0 );

代码(1)会使接收端产生1个FD_READ事件(不考虑recv接收缓冲区不足的情况),而代码(2)会使接收端产生2个FD_READ事件。

FD_CLOSE
根据MSDN上的描述:

The FD_CLOSE network event is recorded when a close indication is received for the virtual circuit corresponding to the socket. In TCP terms, this means that the FD_CLOSE is recorded when the connection goes into the TIME WAIT or CLOSE WAIT states. This results from the remote end performing a shutdown on the send side or a closesocket.

简单来说,当TCP处于TIME_WAIT或CLOSE_WAIT状态时,会触发FD_CLOSE事件,接触TCP状态图:
enter image description here

TIME_WAIT: 表示收到了对方的FIN报文,并发送出了ACK报文,就等2MSL后即可回到CLOSED可用状态了。如果FIN_WAIT_1状态下,收到了对方同时带 FIN标志和ACK标志的报文时,可以直接进入到TIME_WAIT状态,而无须经过FIN_WAIT_2状态。

CLOSE_WAIT: 这种状态的含义其实是表示在等待关闭。当对方close一个SOCKET后发送FIN报文给自己,系统会回应一个ACK报文给对方,此时则进入到CLOSE_WAIT状态。接下来,实际上你真正需要考虑的事情是察看你是否还有数据发送给对方,如果没有的话,那么你也就可以 close这个SOCKET,发送FIN报文给对方,也即关闭连接。所以你在CLOSE_WAIT状态下,需要完成的事情是等待你去关闭连接。

当服务器进行主动关闭时,TCP会进入FIN_WAIT_1(FIN_WAIT_2)阶段,只有当接收到客户端发来的FIN,TCP才会进入TIME_WAIT状态.客户端在执行closesocket或shutdown时会发生FIN给服务器,这时TCP进入TIME_WAIT状态,从而会触发FD_CLOSE消息。
当服务器被动关闭时,即客户端主动执行closesocket或shutdown,TCP直接进入CLOSE_WAIT状态,从而触发FD_CLOSE消息。

综上,只有当连接对端执行了closesocket或shutdown(这个只有关闭写才行),本端会触发FD_CLOSE消息,而在本端调用closesocket或shutdown不会触发,当然对端程序意外终止,也会导致本端触发FD_CLOSE消息。

参考:
1.http://msdn.microsoft.com/query/dev10.query?appId=Dev10IDEF1&l=ZH-CN&k=k(WSAEVENTSELECT);k(DevLang-%22C%2B%2B%22)&rd=true
2.http://www.cppblog.com/tx7do/archive/2009/09/15/96340.html
3.http://www.cnblogs.com/qlee/archive/2011/07/12/2104089.html