代码是这样的:
void CMy_CAsyncSocket::OnReceive(int nErrorCode)
{
// TODO: 在此添加专用代码和/或调用基类
if(nErrorCode == 0)
{                
//这个是缓存池结构,可以不管
netdata_cls * recvcls;

//这个while循环去掉就没事了,这里设置为 -- [[[[[[解决方法A]]]]]]
while(true)
{
//到缓存池拿出一个结构和缓冲区
recvcls = lbuff_SysNetDataClsSck.Get();
recvcls->data_string = lbuff_cmystring.Get(MIDDLE_BFSZ); //接收
int readed = Receive(recvcls->data_string->data, recvcls->data_string->all_len);

if(readed == 0)
{
lbuff_cmystring.Put(recvcls->data_string);
lbuff_SysNetDataClsSck.Put(recvcls);
OnClose(GetLastError());//出错,远程机器被关闭,需要结束这台客户端的一切
return;
}
else if(readed == SOCKET_ERROR)
{
lbuff_cmystring.Put(recvcls->data_string);
lbuff_SysNetDataClsSck.Put(recvcls); //现在问题是在这里,这里为 --  [[[[[[问题A]]]]]]
nErrorCode = GetLastError();
if(nErrorCode == WSAEWOULDBLOCK)
{
break;
}
else
{
OnClose(nErrorCode);//出错,需要结束这台客户端的一切
return;
}
}
else
{
//正常情况,设置数据长度,放入已经接受的链表中
//这部分也可以不看.
recvcls->data_string->data_len = readed;
recvcls->is_fin_code = false;
recvcls->op_n = 0;
recvdata_list_PutLast_With_CES(recvcls, GO_SET_EVENT); //这里设置为 --  [[[[[[解决方法B]]]]]]
//如果数据缓存装满了,说明还有部分数据在socket栈中
//如果数据缓存没装满,说明socket栈中已经没有数据了.退出while循环
//if(readed != recvcls->data_string->all_len)
//{
// break;
//}
}
}
}
else
{
OnClose(nErrorCode);//发生了错误,需要结束这台客户端的一切
return;
}
CAsyncSocket::OnReceive(nErrorCode);
}
如上面的代码这样,错误发生在"[[[[[[问题A]]]]]]"的地方.错误发送的过程是:我的目的是每次数据过来之后(触发了OnReceive),就收走全部的Socket栈中的数据.所以我用while不停的Receive,按道理说,如果Socket栈中没有数据了,Receive将返回SOCKET_ERROR,然后用GetLastError(),将返回WSAEWOULDBLOCK,告诉我现在没办法收数据.然后我的程序退出本次OnReceive.等待下次触发.可是,事情没有像我预想的那样,如果Socket栈中没数据了,Receive将返回SOCKET_ERROR错误,然后用GetLastError(),返回的却是WSA_INVALID_HANDLE错误..只好关闭这条连接.我试过忽略这个WSA_INVALID_HANDLE错误,直接退出本次OnReceive,没想到发送方(另外一个程序却产生了OnClose错误,nErrorCode错误码也是WSA_INVALID_HANDLE).也就是说.无论如何,只要Socket栈中没有了数据,我再去Receive它,肯定会结束这条socket链接..我不知道为什么会这样,大家有没有碰到这样的情况呢?.....这是问题一.解决办法有两个,不过我经过了这样古怪的错误之后,不知道这样是不是能很好的解决,所以上来问问大家.就像解决办法[[[[[[解决方法A]]]]]]这样,不用while循环了,如果一次OnReceive收不完数据,就等待下次再触发OnReceive,反正Socket栈中只要有数据没有被Recv,就肯定会一直触发OnReceive事件,不知道是不是这样呢?...这是问题二..反正我把While去掉后收发大文件都很正常.[[[[[[解决方法B]]]]]]那样也行,每次增加一个判断,如果存在后续数据,才再一次执行Receive函数,不过我感觉这样的判断方式比较不可靠.虽然我测试中没发现问题,但是不保证实际应用上不会出差错.大家感觉呢?...这是问题三...就这三个问题,拜托大家了.我的意思是,有没有让while存在的情况下,能一次收完全部的Socket栈数据,而不会触发古怪的WSA_INVALID_HANDLE错误.或者是,我的代码其他部分写的有问题,大家都没有发现有WSA_INVALID_HANDLE错误的情况.

解决方案 »

  1.   

    另外,由于我是用CAsyncSocket类,所以Receive函数在MFC源代码中就是一个recv函数的封装.这样出错的可能性就是windows的socket实现上了.我上午搜索了一个上午的WSA_INVALID_HANDLE和recv相关的错误信息,发现几乎没人发觉这个问题..我非常郁闷.
      

  2.   

    CString pMsg=""
    char tempMsg[1001]="";
    int ByteCount;
    bool endflages=true;
    do
    {
    strcpy(tempMsg,"");
    ByteCount=YourSocket->Receive(tempMsg,1000);
    tempMsg[ByteCount]=0;
    if(ByteCount<1000&&ByteCount>=0)
    endflages=false;
    CString temp="";
    pMsg+=temp.Format("%s",tempMsg)
    }while(endflages);
    //这个样子所有的内容都在pMsg里面了~~~~
      

  3.   

    楼上的,你的代码未免太简单了点吧.且不说我的问题没解决,就是你这些代码还是有问题呢.1.数据不一定是char的,用Format("%s",tempMsg)有断流的可能.
    2.用CString效率上问题很大的,因为CString在while循环之内,作为局部变量,每一次循环都会构造和析构String类.
    给出编译器汇编例子你看看: for(int i = 0; i < 100; i++)
    0041A28C  mov         dword ptr [i],0 
    0041A293  jmp         CMy_ZxClient_With_MFCDlg::OnInitDialog+7Eh (41A29Eh) 
    0041A295  mov         eax,dword ptr [i] 
    0041A298  add         eax,1 
    0041A29B  mov         dword ptr [i],eax 
    0041A29E  cmp         dword ptr [i],64h 
    0041A2A2  jge         CMy_ZxClient_With_MFCDlg::OnInitDialog+0D7h (41A2F7h) 
    {
    CString str;
    0041A2A4  mov         esi,esp 
    0041A2A6  lea         ecx,[str] 
    0041A2A9  call        dword ptr [__imp_ATL::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char> > >::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char> > > (42CB24h)] 
    //这里可以看到构造函数
    0041A2AF  cmp         esi,esp 
    0041A2B1  call        @ILT+2170(__RTC_CheckEsp) (41187Fh) 
    0041A2B6  mov         dword ptr [ebp-4],0 
    str.Format("%d", i);
    0041A2BD  mov         esi,esp 
    0041A2BF  mov         eax,dword ptr [i] 
    0041A2C2  push        eax  
    0041A2C3  push        offset string "%d" (424A24h) 
    0041A2C8  lea         ecx,[str] 
    0041A2CB  push        ecx  
    0041A2CC  call        dword ptr [__imp_ATL::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char> > >::Format (42CB1Ch)] 
    0041A2D2  add         esp,0Ch 
    0041A2D5  cmp         esi,esp 
    0041A2D7  call        @ILT+2170(__RTC_CheckEsp) (41187Fh) 
    }
    0041A2DC  mov         dword ptr [ebp-4],0FFFFFFFFh 
    0041A2E3  mov         esi,esp 
    0041A2E5  lea         ecx,[str] 
    0041A2E8  call        dword ptr [__imp_ATL::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char> > >::~CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char> > > (42CB20h)] 
    //这里是析构函数
    0041A2EE  cmp         esi,esp 
    0041A2F0  call        @ILT+2170(__RTC_CheckEsp) (41187Fh) 
    0041A2F5  jmp         CMy_ZxClient_With_MFCDlg::OnInitDialog+75h (41A295h) 
    //这里是循环结束判断.否则跳回while.
    3.一旦Receive返回SOCKET_ERROR,也就是-1,而你的这句:
    tempMsg[ByteCount]=0;会发生什么事情你应该清楚吧.4.实际上socket是不可能send出0字节的数据,所以也就不可能recv到0字节的数据,所以如果Receive返回0,就说明是个错误,而你的代码中这里没有处理.5.其实我用的是异步的方式的socket,你这段代码明显是同步阻塞方式的.最后说一句,我不才,不敢说自己的代码具有工业强度,不过你这段代码未免太粗糙了点,可能自己做试验写写还好了.出产品可不敢用的.不过还是感谢你的回帖,希望我的建议对你有用.
      

  4.   


    我就奇怪为什么这么多人使用CsyncSocket等异步的socket接收数据时还用循环去接收数据.(今天回了三个以上贴子都是这样循环接收数据的).CsyncSocket使用的是消息机制,如果你使用这样的接收数据的方法也不出问题的话,那么这种模式还需要FD_READ,FD_WRITE等消息来做啥用?这种异步模型的消息机制接收数据就是要系统通知你的程序FD_READ,然后你的程序就知道有数据可读,才调用WSARecv()或recv()去接收数据.就算系统有很多数据可读,一次接不完,只要还有数据可读,系统还会继续地给你的程序发送FD_READ消息.但如果你使用循环去读,那么这些FD_READ等消息全会被过滤掉.反正有FD_READ消息来时,调用个WSARecv()或recv()接收就是.
      

  5.   

    楼上的你好,实际上我是考虑到效率问题,因为接收缓存区的大小是固定的,如果一次能收完整个接受缓存区大小的数据,那很可能还有一些数据在socket栈中,既然都进来了,就干脆再recv一次收全了,比较好.如果退出,等待下次OnReceive.感觉上就有点效率问题.因为这时可能socket系统要检查一下看看socket堆栈里面,发现还有数据没被取走,就再发了一个消息给操作系统,操作系统就又给这个窗体发了消息,如果这个时候我的程序中socket句柄很多,比如1000多个,哪么就算是log(n)级的搜索(socket句柄对应的OnReceive总不会是n级的搜索吧),也要花销很大.总之感觉再发送一次消息的开销有点大,不如一次性取完了所有数据来的方便.当然这只是我的臆想,没有分析过socket实现.另外就是我主要是问WSA_INVALID_HANDLE错误这个到底怎么来的,因为之前我也是直接用socket函数,也就是WSAAsynSelect写的这个用消息驱动的异步socket,没发现这个问题,用了mfc的这个类之后发现的这个问题的.我感觉是我其他部分代码的错误,才导致这个问题,如果大家都没碰到WSA_INVALID_HANDLE错误的话.因为WSA_INVALID_HANDLE这个错误貌似在MFC文档中对于Receive函数的错误描述中并没有提到.
      

  6.   

    于是我按照上述考虑,设计了个循环来一次OnReceive就接收完全部的socket栈数据.可是"如果一次能收完整个接受缓存区大小的数据,那很可能还有一些数据在socket栈中"这句不一定是对的(所以我用了可能),或者说不好判断,比如缓存区大小是1000,哪么接受了1000字节,有两种可能:1.socket栈存在1000+的数据,你只接受了1000个.
    2.socket栈刚刚好只有1000字节的数据,全部被接收了.对于第一种情况,再次循环过去,调用Receive是不会出错的.
    对于第二种情况,我原先设想的是,就算没数据了,再次调用Receive也只不过触发WSAEWOULDBLOCK罢了(就像send,大量数据,就循环分批发送,如果暂时发送不了,返回WSAEWOULDBLOCK).没想到,对于第二种情况,实际上返回SOCKET_ERROR后,GetLastError获得的却是一个WSA_INVALID_HANDLE错误.而且是个不可忽略的错误,出现了这个错误,就意味着当前这条连接要断开了.对于上述问题,无法解决,所以我目前也是暂时选择不用while的recv.
      

  7.   


    "an application need not read all available data in response to an FD_READ message — a single recv in response to each FD_READ message is appropriate. If an application issues multiple recv calls in response to a single FD_READ, it can receive multiple FD_READ messages. Such an application can require disabling FD_READ messages before starting the recv calls by calling WSAAsyncSelect with the FD_READ event not set."以上是msdn关于FD_READ要注意的情况,很明确地做了说明,一个FD_READ,应该只是对应一个recv的操作.如果你想进行这样循环接收,先将FD_READ事件从socket中去除,然后接收数据,接收完后再将socket加上FD_READ事件.虽然msdn中没有明确说明为什么单个FD_READ事件触发时要执行单个的recv()操作,但我相信这样的操作关系是一个"压栈和弹出",例如触发FD_READ消息是一个"压栈"的信号,那么单个的recv操作就将这个"压栈"信号删除.但如果多个recv操作就足已将这样的配对关系搞到乱套.至于实际是否如我所想,我也不清楚了,微软没给出明确的信息,或者研究内核的会知道一二.