基于MFC的消息处理,微软提供一个函数WSAAsyncSelect, 把winsock的事件通知抛到的UI线程。
其实我们在MFC中编程,代码处理大多在这个主UI线程中,类定义变量也好处理,最重要的一点是不需要考虑多线程的机制了。我见过之前的工作同事,他们就是用单独的读写线程来处理socket,然后通过sendmessage发送消息到主UI线程来进行事务处理,他们觉得还挺好用。其实这就是WSAAsyncSelect实现的机制了。
但是WSAAsyncSelect的使用需要我们注意几点,在微软的MSDN文档中有详细的描述了(https://docs.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-wsaasyncselect)。但真正理解的人却不多。总结一下:
1. WSAAsyncSelect自动就把socket置为非阻塞模式,所以在后续的读写当中会很容易碰到读写失败,但是我想要的数据(以读为例),还未读够。这时候你得通过WSAGetLastError判断是否由于WSAEWOULDBLOCK的原因,如果是的话,你得循环继续读,直到满足你的要求了;
2.WSAAsyncSelect上报FD_READ消息,但是你读的时候有可能失败,返回WSAEWOULDBLOCK的错误,是WSAAsyncSelect误报了吗? 不是,过程可能是这样的:
*WSAAsyncSelect 发现有100个字节收到了,上报FD_READ消息;
*在你的消息处理中,你第一次通过recv收了50个字节,winsock在你调用recv的时候判断是否含有数据可读,它继续发一个FD_READ到你的UI线程(现在还没出来呢)
*还是在当前的消息处理中,你又recv读了25个字节,winsock还会再发一个FD_READ
*还是在当前的消息处理中,你最后又recv读了剩下25个字节,winsock还会再发一个FD_READ
3.当你处理完这个事务之后,你还会收到3个FD_READ,但是你去读的时候,却返回WSAEWOULDBLOCK的错误了---没有数据可读啊!
所以第3点的读和第1点的读应该要分别设计的,第3点发现WSAEWOULDBLOCK时要放弃返回,第1点则要循环死读下去。
Read3()
{
int rd;
rd = recv(m_Sock, (char *)&cmd, sizeof(int), 0);
if(rd == 0 || rd == SOCKET_ERROR)
{
return false;
}
return true;
}
Read1()
{
int rd, j = 0;
while(j < bytes)
{
rd = recv(sock, pbuf+j, bytes-j, 0);
if(rd == 0 || rd == SOCKET_ERROR)
{
int errnon = WSAGetLastError();
if(errnon == WSAEWOULDBLOCK)
continue;
else
return false;
}
j += rd;
}
}
4.还有比较重要的一点是FD_READ的重入问题:例如对方定期在发送查询udp包,我们通过WSAAsyncSelect收到消息之后,在处理,而如果你在处理过程中进入了UI消息处理(例如,MessageBox),那么又会被后续收到的udp包,再次重入你的处理函数了,这会把你搞惨的!