常见的网络事件中,FD_ACCEPT和FD_READ都比较好理解。一开始我唯一困惑的就是FD_WRITE,搞不清楚到底什么时候才会触发这个网络事件,后来仔细查了MSDN又看了一些文章并测试了下,终于搞懂了FD_WRITE的触发机制。
下面是MSDN中对FD_WRITE触发机制的解释:
The FD_WRITE network event is handled slightly differently. An FD_WRITE network event is recorded when a socket is first connected with connect/WSAConnect or accepted with accept/WSAAccept, and then after a send fails with WSAEWOULDBLOCK and buffer space becomes available. Therefore, an application can assume that sends are possible starting from the first FD_WRITE network event setting and lasting until a send returns WSAEWOULDBLOCK. After such a failure the application will find out that sends are again possible when an FD_WRITE network event is recorded and the associated event object is set
FD_WRITE事件只有在以下三种情况下才会触发
①client 通过connect(WSAConnect)首次和server建立连接时,在client端会触发FD_WRITE事件
②server通过accept(WSAAccept)接受client连接请求时,在server端会触发FD_WRITE事件
③send(WSASend)/sendto(WSASendTo)发送失败返回WSAEWOULDBLOCK,并且当缓冲区有可用空间时,则会触发FD_WRITE事件
①②其实是同一种情况,在第一次建立连接时,C/S端都会触发一个FD_WRITE事件。
主要是③这种情况:send出去的数据其实都先存在winsock的发送缓冲区中,然后才发送出去,如果缓冲区满了,那么再调用send(WSASend,sendto,WSASendTo)的话,就会返回一个 WSAEWOULDBLOCK的错误码,接下来随着发送缓冲区中的数据被发送出去,缓冲区中出现可用空间时,一个 FD_WRITE 事件才会被触发,这里比较容易混淆的是 FD_WRITE 触发的前提是 缓冲区要先被充满然后随着数据的发送又出现可用空间,而不是缓冲区中有可用空间,也就是说像如下的调用方式可能出现问题
else if(event.lNetworkEvents & FD_WRITE)
{
if(event.iErrorCode[FD_WRITE_BIT] == 0)
{
send(g_sockArray[nIndex], buffer, buffersize);
....
}
else
{
}
}
问题在于建立连接后 FD_WRITE 第一次被触发, 如果send发送的数据不足以充满缓冲区,虽然缓冲区中仍有空闲空间,但是 FD_WRITE 不会再被触发,程序永远也等不到可以发送的网络事件。
基于以上原因,在收到FD_WRITE事件时,程序就用循环或线程不停的send数据,直至send返回WSAEWOULDBLOCK,表明缓冲区已满,再退出循环或线程。当缓冲区中又有新的空闲空间时,FD_WRITE 事件又被触发,程序被通知后又可发送数据了。
上面代码片段中省略的对 FD_WRITE 事件处理
{
if(event.iErrorCode[FD_WRITE_BIT] == 0)
{
while(TRUE)
{
// 得到要发送的buffer,可以是用户的输入,从文件中读取等
GetBuffer....
if(send(g_sockArray[nIndex], buffer, buffersize, 0) == SOCKET_ERROR)
{
// 发送缓冲区已满
if(WSAGetLastError() == WSAEWOULDBLOCK)
break;
else
ErrorHandle...
}
}
}
else
{
ErrorHandle..
break;
}
}