关于使用WSAAsyncSelect注册FD_READ后,不能一次完全recv()全部字节的问题

dos5gw 2010-07-10 11:11:14
要求描述: 现在要完成一个客户端程序,接收服务端的消息,(服务端每隔1s就向客户端发送一次4107字节的消息)
客户端要接收这些消息并处理

出线问题描述: 客户端目前使用WSAAsyncSelect(m_sockClient,m_hWnd,NETWORK_EVENT, FD_READ)注册了读事件,如果产生FD_READ事件的话,就执行recv(),原以为4107字节的消息很短,一次recv能接收完,但是int result=recv(),发现recv()返回值不是4107, 而是1608,2499,5036,,,这些数,
我觉得可能是recv()一次没有接收完4107个字节,而触发了多次FD_READ去执行recv(),

请问:
(1)如何才能在非阻塞recv()中一次读完4017个字节?
(2)如果非阻塞recv()确实不能一次读完4017个字节,要分几次才能读完的话,我如何把收到的零散消息"拼接" , 由于服务端是间隔1s发一次消息,我如何才能判断那些消息片段是同一条消息的呢?

相关代码如下:

if(WSAAsyncSelect(m_sockClient,m_hWnd,NETWORK_EVENT,FD_CLOSE | FD_READ | FD_CONNECT) == SOCKET_ERROR)
{
closesocket(m_sockClient);
//MessageBox("CComWnd注册网络异步事件失败!");
//WSACleanup();
return 0;
}
//...省略
void CInstrumentStatusView::OnNetEvent(WPARAM wParam, LPARAM lParam) {
int iEvent = WSAGETSELECTEVENT(lParam);
int error = WSAGETSELECTERROR(lParam); //if error!=0, it means failure
SOCKET CurSock= (SOCKET)wParam;
switch(iEvent){
case FD_READ:
OnSockReceive();
//...省略
}
}
void CInstrumentStatusView::OnSockReceive(){
int ReadResults=0;
ReadResults=recv(m_sockClient,(char*)m_ReadBuffer,5036,0);
if(ReadResults==m_pCurCommand->m_nExpectedBytes){ //接收字节数ReadResults=4017的情况
m_pCurCommand->m_bCommandExcuted=TRUE; //Will remove CurrCommand at next OnTimer()
int i=0;
while(!Ready) {
if(m_pCurCommand->AddToReceiveBuffer(*(m_ReadBuffer+i))) {
Ready=TRUE;
}
i++;
}
}
else{ //接收字节数ReadResults !=4017的情况
//not recv completely,
m_pCurCommand->m_bCommandExcuted=TRUE;
m_pCurCommand->m_status=OverTime;
m_comInstrument.m_bComError=TRUE;
}
}

...全文
589 18 打赏 收藏 转发到动态 举报
写回复
用AI写文章
18 条回复
切换为时间正序
请发表友善的回复…
发表回复
nowordwind 2011-02-19
  • 打赏
  • 举报
回复
看看,是啥。。。。
dos5gw 2010-07-12
  • 打赏
  • 举报
回复
代码贴在:http://blog.csdn.net/dos5gw/archive/2010/07/12/5729103.aspx
仅供参考
周药师 2010-07-11
  • 打赏
  • 举报
回复
昨晚看世界杯了 才起床
所以前面的问题没有及时回复
我给你的代码只是 一个逻辑的方法 你需要按照你实际的需求修改
问题解决了 祝贺你!
周药师 2010-07-11
  • 打赏
  • 举报
回复
既然问题解决了
你把你现在的代码整理好贴出来,让大家一起看看吧
对于大量的数据传输,选择用何种模型,主要看你的实际需求
如果客户端少的话,可以用简单的select阻塞模型即可,
如果数量在200-1000以内的话 可以用WSAAsyncSelect非阻塞模型
如果更大量的客户端的话 就用完成端口了
传输的数据量太大的话 可以考虑用FTP方式传输
dos5gw 2010-07-11
  • 打赏
  • 举报
回复
谢谢LS各位,问题解决了,特别感谢9楼周药师的代码
最后再问个问题,
(1)以实际开发经验来看,"WSAAsyncSelect注册FD_READ事件" 这种做法是否适合大量数据传输? 在MFC上还有哪些适合winsock大数据传输的方案?

(2)在WSAAsyncSelect模型下,收到FD_READ事件后如果必须多次执行recv(),必须在执行recv前关掉FD_READ,请问如下的关闭方式是安全的吗? 当执行完所有的recv,如下的恢复READ事件也是安全的吗?

WSAAsyncSelect(m_sockClient,m_hWnd,NETWORK_EVENT,0); //关闭READ事件,这样关闭是否妥当?
//多次执行recv()
WSAAsyncSelect(m_sockClient,m_hWnd,NETWORK_EVENT,FD_READ);//重新注册READ事件,这样恢复是否妥当?


arong1234 2010-07-11
  • 打赏
  • 举报
回复
WSAEWOULDBLOCK一般是异步套接子才有的,这不是问题,遇到这个错误信息其实是正常的,如果你遇到,你应该立刻进行select,等待下次可读事件发生再继续读
dos5gw 2010-07-11
  • 打赏
  • 举报
回复
还有个问题,
如果WSAEWOULDBLOCK == WSAGetLastError()的话,代表阻塞,执行continue
除了WSAEWOULDBLOCK 之外的错误, 是不是就意味着sock连接的严重错误? 就应该break此次while循环
比如套接字被意外的关闭了,nReadLen =-1, 并且WSAGetLastError()返回10057,那么就不能跳出此次while了

while ( nTotalLen != nDataLen )
{
char cRecv;
nReadLen = recv(m_s, &cRecv, 1,0); //每次接收1个字符
if (SOCKET_ERROR == nReadLen) //网络错误
{

if (WSAEWOULDBLOCK == WSAGetLastError())
{
continue;
}
else break; //出现SOCKET_ERROR,但不是WSAEWOULDBLOCK ,那么break掉此次循环

reVal = FALSE;
}else if (0 == nReadLen)
{
AfxMessageBox(_T("客户端关闭了连接!"));
reVal = FALSE;
}else if (nReadLen > 0)
{
if ('<' == cRecv) //开始字符
{
strUserInfo.Empty();

}else if ('>' == cRecv) //结束字符
{
break;
}else
{
strUserInfo += cRecv; //添加字符
}
nTotalLen += nReadLen;
}
}
dos5gw 2010-07-11
  • 打赏
  • 举报
回复
[Quote=引用 9 楼 zhouzhangkui 的回复:]

C/C++ code

while ( nTotalLen != nDataLen )
{
char cRecv; //接收字符
nReadLen = recv(m_s, &cRecv, Number,0); ……
[/Quote]

to 周药师

char cRecv; //接收字符
nReadLen = recv(m_s, &cRecv, Number,0); //每次接收Number个字符
这里你是单个字符接收的么? 那Number直接写1不就得了,,,,,,,,,,,,,,,,,,,,,,
这样有些类似串口的逐字节传输了,
不过我的数据每次都是4k字节, 这样单字节recv()的话,循环的时间次数过多,耗时太长,会不会影响下次数据接收?
谢谢回复
dos5gw 2010-07-10
  • 打赏
  • 举报
回复
[Quote=引用 2 楼 fishion 的回复:]

大点的数据一般都要循环接收
[/Quote]

请问改成这样可以吗?

void CInstrumentStatusView::OnSockReceive(){
int ReadResults;
int total=0;
while( total != 4107 ){
ReadResults=recv(m_sockClient,(char*)m_ReadBuffer,5036,0);
totol+=intReadResults;
//拼接处理
}
A879455773 2010-07-10
  • 打赏
  • 举报
回复
hao hao
fishion 2010-07-10
  • 打赏
  • 举报
回复
大点的数据一般都要循环接收
DotCpp 2010-07-10
  • 打赏
  • 举报
回复
请问:
(1)如何才能在非阻塞recv()中一次读完4017个字节?

不能,因为tcp/ip数据包有最大长度(应该是1500吧),所以不可能一次读完


(2)如果非阻塞recv()确实不能一次读完4017个字节,要分几次才能读完的话,我如何把收到的零散消息"拼接" , 由于服务端是间隔1s发一次消息,我如何才能判断那些消息片段是同一条消息的呢?

这个问题也就是组包,拆包了。做网络编程你必须面对这个问题。 一般就是自己在你的数据加头部(发送端组包) 如可以发送: 自定义头标识+包长+最否是最后一帧标识+包内容
当你recv后,就根据你自己的协议来把数据组成完整的一条消息。

xgPaul 2010-07-10
  • 打赏
  • 举报
回复
(1) 我在某博客上看到如下:如果程序对一个FD_READ多次recv()将会造成触发多个空的FD_READ,所以程序在第2次recv()前要关掉FD_READ(可以使用WSAAsynSelect关掉FD_READ),然后再多次recv()。 这个结论对吗?
====================================
有情这种况出现,当你用recv接受数据的时候,recv一次没有把接受缓冲区中的数据接受完,则会触发FD_READ,这时就需要暂时关掉FD_READ,把你要接收的数据接收完成后再打开。。。
周药师 2010-07-10
  • 打赏
  • 举报
回复

while ( nTotalLen != nDataLen )
{
char cRecv; //接收字符
nReadLen = recv(m_s, &cRecv, Number,0); //每次接收Number个字符
if (SOCKET_ERROR == nReadLen) //网络错误
{
//表示接收没有结束 还有数据 要你继续去接收
if (WSAEWOULDBLOCK == WSAGetLastError())
{
continue;
}

reVal = FALSE;
}else if (0 == nReadLen)
{
AfxMessageBox(_T("客户端关闭了连接!"));
reVal = FALSE;
}else if (nReadLen > 0)
{
if ('<' == cRecv) //开始字符
{
strUserInfo.Empty();

}else if ('>' == cRecv) //结束字符
{
break;
}else
{
strUserInfo += cRecv; //添加字符
}
nTotalLen += nReadLen;
}
}
dos5gw 2010-07-10
  • 打赏
  • 举报
回复
to 阿荣, to周药师,

我这样修改了代码,下面的代码是出线FD_READ事件后执行的,但却出现了其他问题:


WSAAsyncSelect(m_sockClient,m_hWnd,NETWORK_EVENT,0); //关闭FD_READ事件

while(total<4107){
ReadResults=recv(m_sockClient,((char*)m_ReadBuffer)+total,5036-total,0);
total+=ReadResults;
char dbg[30];

int error=WSAGetLastError();// 打印错误信息
sprintf(dbg,"error=%d",error);
OutputDebugString(dbg);
}
WSAAsyncSelect(m_sockClient,m_hWnd,NETWORK_EVENT,FD_READ);//读完了,恢复FD_READ


经过跟踪日志,发现如下情况:
前几次程序正常,4017的数据分两步都接收到了,比如while循环第一次recv返回2000,第二次recv返回2107,并且WSAGetLastError返回0,没错误,
运行了一段时间,发现recv返回-1,并且WSAGetLastError返回10035(WSAEWOULDBLOCK), 表明阻塞了此次recv.....实在是弄的无奈了,上面的代码分明是收到FD_READ之后才执行的,为什么还出线10035错误呢?
arong1234 2010-07-10
  • 打赏
  • 举报
回复
你这样不行得,你这样第二次接受就覆盖了第一次接受得结果,最终肯定不对正确得做法是

while( total != 4107 ){
ReadResults=recv(m_sockClient,((char*)m_ReadBuffer)+total,5036-total,0);
totol+=intReadResults;
//拼接处理
}

[Quote=引用 4 楼 dos5gw 的回复:]
引用 2 楼 fishion 的回复:

大点的数据一般都要循环接收


请问改成这样可以吗?

C/C++ code

void CInstrumentStatusView::OnSockReceive(){
int ReadResults;
int total=0;
while( total != 4107 ){
ReadRe……
[/Quote]
dos5gw 2010-07-10
  • 打赏
  • 举报
回复
[Quote=引用 5 楼 zhouzhangkui 的回复:]

recv的时候 进行循环
到找到包尾标志的时候就跳出 循环
其中虽然你用的非阻塞WSAAsyncSelect模型,

但接收recv的时候是阻塞的,
所以进行循环的时候一定要适时的跳出,不然会阻塞在recv这里的
[/Quote]
大哥,请教个问题,
(1) 我在某博客上看到如下:如果程序对一个FD_READ多次recv()将会造成触发多个空的FD_READ,所以程序在第2次recv()前要关掉FD_READ(可以使用WSAAsynSelect关掉FD_READ),然后再多次recv()。 这个结论对吗?
我尝试过上面的做法,收到FD_READ的时候应该先WSAAsynSelect(m_sockClient,m_hWnd,NETWORK_EVENT,0),然后循环执行recv(),却发现每次recv返回值=1,
请问这时神马情况?
周药师 2010-07-10
  • 打赏
  • 举报
回复
recv的时候 进行循环
到找到包尾标志的时候就跳出 循环
其中虽然你用的非阻塞WSAAsyncSelect模型,

但接收recv的时候是阻塞的,
所以进行循环的时候一定要适时的跳出,不然会阻塞在recv这里的

18,356

社区成员

发帖
与我相关
我的任务
社区描述
VC/MFC 网络编程
c++c语言开发语言 技术论坛(原bbs)
社区管理员
  • 网络编程
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧