原作者:DREWSIKORA
我本想把发送和接收分开作为两部分,但是最后我决定只略微解释一下FD_READ,留下更多的时间来说 明更复杂的FD_WRITE,FD_READ事件非常容易掌握.当有数据发送过来时,WinSock会以FD_READ事件通知你,对于每一个 FD_READ事件,你需要像下面这样调用recv():
intbytes_recv=recv(wParam,&data,sizeof(data),0);
基 本上就是这样,别忘了修改上面的wParam.还有,不一定每一次调用recv()都会接收到一个完整的数据包,因为数据可能不会一次性全部发送过来.所 以在开始处理接收到的数据之前,最好对接收到的字节数(即recv()的返回值)进行判断,看看是否收到的是一个完整的数据包.
FD_WRITE相对来说就麻烦一些.首先,当你建立了一个连接时,会产生一个FD_WRITE事件.但是如果你认为在收到FD_WRITE时调用send()就万事大吉,那就错了.FD_WRITE事件只在发送缓冲区有多出的空位,可以容纳需要发送的数据时才会触发.
上 面所谓的发送缓冲区,是指系统底层提供的缓冲区.send()先将数据写入到发送缓冲区中,然后通过网络发送到接收端.你或许会想,只要不把发送缓冲区填 满,让发送缓冲区保持足够多的空位容纳需要发送的数据,那么你就会源源不断地收到FD_WRITE事件了.嘿嘿,错了.上面只是说FD_WRITE事件在 发送缓冲区有多出的空位时会触发,但不是在有足够的空位时触发,就是说你得先把发送缓冲区填满.
通常的办法是在一个无限循环中不断的发送数 据,直到把发送缓冲区填满.当发送缓冲区被填满后,send()将会返回SOCKET_ERROR,WSAGetLastError()会返回 WSAWOULDBLOCK.如果当前这个SOCKET处于阻塞(同步)模式,程序会一直等待直到发送缓冲区空出位置然后发送数据;如果SOCKET是非 阻塞(异步)的,那么你就会得到WSAWOULDBLOCK错误.于是只要我们首先循环调用send()直到发送缓冲区被填满,然后当缓冲区空出位置来的 时候,系统就会发出FD_WRITE事件.有没有想过我能指出这一点来是多么不容易,你可真走运.下面是一个处理FD_WRITE事件的例子.
caseFD_WRITE: //可以发送数据了
{
//进入无限循环
while(TRUE)
{
//从文件中读取数据,保存到packet.data里面.
in.read((char*)&packet.data,MAX_PACKET_SIZE);
//发送数据
if(send(wparam,(char*)(&packet),sizeof(PACKET),0)==SOCKET_ERROR)
{
if(WSAGetLastError()==WSAEWOULDBLOCK)
{
//发送缓冲区已经满了,退出循环.
break;
}
else//其他错误
{
//显示出错信息然后退出.
CleanUp();
return(0);
}
}
}
}break;
看到了吧,实现其实一点也不困难.你只是弄混了一些概念而已.使用这样的发送方式,在发送缓冲区变满的时候就可以退出循环.然后,当缓冲区空出位置来的时候,系统会触发另外一个FD_WRITE事件,于是你就可以继续发送数据了.
在 你开始使用新学到的知识之前,我还想说明一下FD_WRITE事件的使用时机.如果你不是一次性发送大批量的数据的话,就别想着使用FD_WRITE事件 了,原因很简单-如果你寄期望于在收到FD_WRITE事件时发送数据,但是却又不能发送足够的数据填满发送缓冲区,那么你就只能收到连接刚刚建立时触发 的那一次FD_WRITE-系统不会触发更多的FD_WRITE了.所以当你只是发送尽可能少的数据的时候,就忘掉FD_WRITE机制吧,在任何你想发 送数据的时候直接调用send().
结论
这是我在GOOGLE上搜到的一篇文章中的一部分.虽然原作者的 部分观点似乎并不正确,但是文章写得很易懂.其实,如果你想收到FD_WRITE 事件而你又无法先填满发送缓冲区,可以调用 WSAAsyncSelect(...,FD_WRITE).如果当前发送缓冲区有空位,系统会马上给你发FD_WRITE事件.
FD_WRITE消息,MFC的CAsyncSocket类将其映射为OnSend()函数.FD_READ消息,被映射为OnReceive()函数.