最近用Winows socket基于TCP协议写一个接收socket字符串的程序,偶尔会出现数据掉失的问题。我也不清楚是否我的程序有问题,特把代码贴出来向大家请教。下面是函数说明:
                             //有关接收socket消息字符串的函数的说明
 // GlocalReceiveMsg功能为接收整个socket字符串,pClientSocket为windows socket类的派生类的指针,// pResultBuf为返回的数据缓冲区指针,iResultLen为实际接收的字节数BOOL GlocalReceiveMsg(CMIClientSocket* pClientSocket,BYTE** pResultBuf,int& 
iResultLen){ int nsize=0; // 接收包的剩余长度       DWORD dwSize = 0; // 包体长度     BYTE *pReceiveInfo = NULL;       BYTE* pRstbuffer = NULL;       int result = 0;        BYTE PackLenBuf[STDATABAND_SIZE]; // 用于存放包头的缓冲区       // STDATABAND_SIZE为预定义的包头长度,具体数值为12       int nRecPackLen = STDATABAND_SIZE; // 接收包头的剩余长度// 初始化实际接收的长度为0    iResultLen = 0; //  接收包头        do        {// Receive函数第一个参数为缓冲区指针,第二个参数为缓冲区长度,返回值为// 实际接收的字节数。注意这里有缓冲区指针的移动和缓冲区长度的变化,变化规律就是// 首先设定缓冲区长度为STDATABAND_SIZE(即12),若实际接收字节数为4,那么第二// 次设定缓冲区指针移动4个字节,长度为12-4=8,照此方法循环接收         result = pClientSocket->Receive(PackLenBuf+STDATABAND_SIZE-nRecPackLen,nRecPackLen);     if(result<STDATABAND_SIZE)        {           if(result == SOCKET_ERROR)              {                     AfxMessageBox("接收数据失败");                     return false;              }               else if(0 == result)              {                     pClientSocket->m_hSocket = INVALID_SOCKET;                     return FALSE;              }              else              {             nRecPackLen = nRecPackLen - result;              }        }     else         {               break;         }       }while(nRecPackLen!=0); // 解析数据包头,获取数据包长度。数据包体长度(数据包除开包头的长度)放在包头的// 第DATA_LENGTH_INDEX字节。       memcpy(&dwSize,PackLenBuf+DATA_LENGTH_INDEX,sizeof(DWORD));// 开辟相应内存。pRstbuffer返回的外部使用的缓冲区,pReceiveInfo为临时接收缓冲区       pRstbuffer = new BYTE[dwSize+STDATABAND_SIZE];       pReceiveInfo = new BYTE[dwSize+STDATABAND_SIZE];       // 拷贝包头数据到外部缓冲区       memcpy(pRstbuffer,PackLenBuf,STDATABAND_SIZE);// 设置剩余接收的数据长度为包体长度       nsize  =  dwSize;// 若包体长度为0,就退出        if(nsize==0)              return TRUE;// 实际接受数据的长度加上包头长度,因为已经成功接收包头       iResultLen = iResultLen + STDATABAND_SIZE;        do        {// 循环接收包体数据,其原理和接收包头数据类似              result = pClientSocket->Receive(pReceiveInfo,dwSize+STDATABAND_SIZE);        iResultLen = iResultLen + result;              if(result == SOCKET_ERROR)              {                     AfxMessageBox("接收数据失败");                     return false;              }                            else if(0 == result)              {                     pClientSocket->m_hSocket = INVALID_SOCKET;                     return FALSE;              }              else              {   // 将实际接受的包体数据从接收缓冲区拷贝到外部输出缓冲区。注意   // 这里的pRstbuffer是向前偏移的           memcpy(pRstbuffer+iResultLen-result,pReceiveInfo,result);   // 现在剩余接收的长度为上次剩余接收的长度减去本次实际接受的长度            nsize-=result;  // 假如剩余接收的长度为0,表示完全接收整个包,就退出循环            if(nsize==0)                   break;              }       } while(true); // 设定外部缓冲区指针              *pResultBuf = pRstbuffer;// 释放临时缓冲区的内存       delete []pReceiveInfo;       return TRUE;}
  

解决方案 »

  1.   

    兄弟,接收包体数据有问题啊
     result = pClientSocket->Receive(pReceiveInfo,dwSize+STDATABAND_SIZE);
    应该这样:
     result = pClientSocket->Receive(pReceiveInfo+STDATABAND_SIZE,dwSize);
    因为你不是已经读完包头了吗?怎么读包体时的长度是包头+包体的长度.而且pReceiveInfo已有包头的数据,会被覆盖的
      

  2.   

    哦,我把pReceiveInfo和pRstbuffer变量混淆了
    接收包体应该这样:
    result = pClientSocket->Receive(pReceiveInfo, nsize); --------
    实际上不用临时变量pReceiveInfo是可以的,就在pRstbuffer内移动就可以了
    比如:
    result  = 0;
    result = pClientSocket->Receive(pReceiveInfo+STDATABAND_SIZE+ result , nsize); 注释掉//memcpy(pRstbuffer+iResultLen-result,pReceiveInfo,result);大概这样.你调试一下
      

  3.   

    result  = 0;是在进入包体循环前设置为0的
      

  4.   

    见凉,又看花眼了,不用临时变量pReceiveInfo:
    result = pClientSocket->Receive(pReceiveInfo+ iResultLen , nsize); 
      

  5.   

      根据cnzdgs大侠和gzlyb大侠的意见,特将代码作了一些修改和优化. 1.接收函数返回0时应该关闭SOCKET,而不是pClientSocket->m_hSocket = INVALID_SOCKET; 
    因此pClientSocket->m_hSocket = INVALID_SOCKET;应该替换为pClientSocket->Close(); 2.临时接收缓冲区是不必要的,完全可以在接收函数里搞定。 
    3.变量命名还不够标准,应该改为更有意义的变量名。     综上所述,接收函数代码改为: BOOL GlocalReceiveMsg(CMIClientSocket* pClientSocket,BYTE** pResultBuf,int& iResultLen)
    {
      // 待接收的数据包的长度
      int  nLeftDataLen = 0; 
    // 数据包体长度
    int  nPackBodyLen = 0;
     // 外部接收缓冲区
    BYTE* pRstbuffer = NULL;
    // 接收函数一次实际收到的数据长度
    int nRevOnceLen = 0;
    // 包头缓冲区
    BYTE PackLenBuf[STDATABAND_SIZE];
    // STDATABAND_SIZE为网络数据包头,具体数值为12
    int nRecPackLen = STDATABAND_SIZE;
    // 初始化实际接收的长度为0
    iResultLen = 0;// 接收包头数据
    do 
    {
    // Receive函数第一个参数为缓冲区指针,第二个参数为缓冲区长度,返回值为// 实际接收的字节数。注意这里有缓冲区指针的移动和缓冲区长度的变化,变化规律就是// 首先设定缓冲区长度为STDATABAND_SIZE(即12),若实际接收字节数为4,那么第二// 次设定缓冲区指针移动4个字节,长度为12-4=8,照此方法循环接收
      nRevOnceLen = pClientSocket->Receive(PackLenBuf+STDATABAND_SIZE-nRecPackLen,nRecPackLen);
         if(result<STDATABAND_SIZE)
     {
        if(nRevOnceLen == SOCKET_ERROR)
    {
    AfxMessageBox("接收数据失败");
    return FALSE;
    }

    else if(0 == nRevOnceLen)
    {
    pClientSocket->Close();
    return FALSE;
    }
    else
    {
                 nRecPackLen = nRecPackLen - nRevOnceLen;
    }
     }
         else
      {
     break;
      }
    }while(nRecPackLen!=0);
    // 解析数据包头,获取数据包长度。数据包体长度(数据包除开包头的长度)放在包头的// 第DATA_LENGTH_INDEX字节。 memcpy(&nPackBodyLen,PackLenBuf+DATA_LENGTH_INDEX,sizeof(DWORD));
    pRstbuffer = new BYTE[nPackBodyLen +STDATABAND_SIZE];
    nLeftDataLen = nPackBodyLen +STDATABAND_SIZE;
      // 拷贝包头数据到外部缓冲区
    memcpy(pRstbuffer,PackLenBuf,STDATABAND_SIZE);
    // 设置剩余接收的数据长度为包体长度
    nLeftDataLen -=STDATABAND_SIZE;
    // 若包体长度为0,就退出 
    if(nLeftDataLen ==0)
    return TRUE; // 实际接受数据的长度加上包头长度,因为已经成功接收包头
    iResultLen = iResultLen + STDATABAND_SIZE; do 
    {
    // 循环接收包体数据,其原理和接收包头数据类似,
    //注意这里的缓冲区指针pRstbuffer是向前偏移的
    nRevOnceLen = pClientSocket->Receive(pRstbuffer + iResultLen, nPackBodyLen);
            iResultLen = iResultLen + nRevOnceLen; if(nRevOnceLen == SOCKET_ERROR)
    {
    AfxMessageBox("接收数据失败");
    return FALSE;
    }

    else if(0 == nRevOnceLen)
    {
    pClientSocket->Close();
    return FALSE;
    }
    else
    {
    // 现在剩余接收的长度为上次剩余接收的长度减去本次实际接受的长度
                nLeftDataLen -= nRevOnceLen;
    // 假如剩余接收的长度为0,表示完全接收整个包,就退出循环
                if(nLeftDataLen ==0)
                       break;
    }
    } while(true);
    // 赋值给外部缓冲区指针  
    *pResultBuf = pRstbuffer; return TRUE;
    }
      

  6.   

          有一个地方有问题,请大家注意:nRevOnceLen = pClientSocket->Receive(pRstbuffer + iResultLen, nPackBodyLen); 应该为
    nRevOnceLen = pClientSocket->Receive(pRstbuffer + iResultLen, nLeftDataLen);      同时感谢cnzdgs的指出.
      

  7.   

    如果是在服务器上接收大量的数据,不断new和 delete也是应该尽量避免的