Socket在阻塞模式下的資訊收發和檔案接收

來源:互聯網
上載者:User

 Socket在阻塞模式下的資訊收發和檔案接收

概述:

收發資料是網路編程的主題,在通訊端上收發資料我們可以使用send和recv,當然還有Winsock2的WSASend和WSARecv。我們這裡只討論send和recv。

通訊端可以工作在阻塞態和非阻塞態,,阻塞態就是函數調用會停住,非阻塞態就是函數調用會立刻返回,待到後面的某個時間點在去取得結果。我們這裡先討論阻塞態。

收發資訊就是在Socket上收發二進位流。而收發檔案實際上也就是收發資訊,只是多了開啟檔案,把檔案資料讀成二進位流傳到SOCKET上去和在接收端把二進位流寫進一個檔案裡去這兩步。

為什麼要寫這篇文章:

為什麼要討論這個主題呢?因為我在一個項目中使用資訊收發的時候,發現會出現一些莫名其妙的現象,就是有時候某台機器可以順利運行,但另外的機器又不能好好執行,而某台機器有時候正常運行,有時候在運行時又會出現運行時的錯誤。

我知道這肯定是一些記憶體的操作引起的,這涉及到具體的項目內容就不談了,這裡重點說一下如何正確的進行SOCKET上資訊的收發和檔案的收發。如果沒耐心的人可以直接到下面去看代碼,把這些函數運用到MFC程式裡去會省去很多事。(代碼用了Cstring,所以只適合MFC程式,如果要其他用途可以根據函數的演算法來重寫)。

關鍵問題(Key Point):

關鍵問題在於send和recv這兩個函數。

send函數原型:

int send(
  SOCKET s,             
  const char FAR *buf, 
  int len,              
  int flags             
);

這裡S是通訊端控制代碼,buf是存放準備發出去資料的緩衝區,長度是緩衝區中資料的長度。Flag給0吧。暫時不管(可以查看MSDN)。傳回值是實際發出去的位元組長度。(在非阻塞態實際發出的位元組數可能會比指定的len的數量少,這是另一種情況)。

這就很好理解了,我給出一個本地的緩衝區,在len那裡指定資料的長度,然後通過send 就發出去。這有什麼難呢?別急,我們再來看看接收函數recv。

recv函數原型:

int recv(
  SOCKET s,      
  char FAR *buf, 
  int len,       
  int flags      
);

這裡S是通訊端控制代碼,buf是存放準備接收資料的緩衝區,長度是緩衝區的長度。Flag給0吧。暫時不管(可以查看MSDN)。傳回值是實際接收到的位元組長度。

問題就是這個recv收到的位元組數不一定和我們給出的buf的位元組數一樣。會根據網路狀態隨機變化。那我們如何在發送端和接收端協調好這種位元組的收發呢?(要知道,阻塞態的通訊端講究的就是位元組的協調,就是這個階段我給你多少位元組,你就只能接收多少位元組,多一個或者少一個都會把後面的位元組流打亂了)。

所以我們要想個辦法來定義好位元組數量的收發。定個協議,檢測頭尾?也可以,這可以自己去發揮。為了說明問題,我們這裡用的是最土的方法,就是每次都發送300個位元組,接收方也接收300位元組。

在接收方能不能這樣?

recv(hSocket, szBuf,  300, 0)

這樣就能把300個位元組接收過來了嗎?事實證明這樣是不行的,你可能收到200、220、150等等沒規律的數字,如果這樣,那SOCKET上的位元組流豈不是被搞亂了,對,就是被搞亂了,隨之而來的就是程式的崩潰,莫名其妙的錯誤。

收發資訊--解決方案

我們要如何解決這個問題呢?

首先第一個想法就是,如果收的位元組沒達到我的要求數目時,要繼續接收,並且填在緩衝區中。好這個想法完全正確,那如何在代碼中表現出來呢?首先我需要一個變數表示接收了多少,或者還有多少沒接收,然後迴圈的接收,直到滿足了我要接收的位元組數,這才算一次成功的接收。收發的代碼如下:

/*****************************************************************************/
 * hSocket:   通訊端控制代碼
 * nRecvSize: 需要接收的位元組數
 * strOut:    用字串返回接收到的位元組流
/*****************************************************************************/
BOOL CDGSocket::Recv(SOCKET hSocket, int nRecvSize, CString &strOut)
{
 BOOL bRet = TRUE;
 char *pszBuf = new char[nRecvSize];
 int nLeftToRecv = nRecvSize;

 do
 {
  // 取得要開始寫入的地址
  char *pLeftBuf = (char *) ( pszBuf + (nRecvSize - nLeftToRecv) );

  // 接收位元組流
  int nRecvBytes = recv(hSocket, pLeftBuf, nLeftToRecv, 0);
  
  // 判斷一下是否出錯了
  if(nRecvBytes == SOCKET_ERROR)
  {
   MyOutput("My Recv Error!");
   bRet = FALSE;
   goto FINAL;   
  }
  
  nLeftToRecv -= nRecvBytes;
  
 } while(nLeftToRecv > 0);

    strOut = pszBuf;

FINAL:
 delete []pszBuf;
 return bRet;
}

/*****************************************************************************/
 * hSocket:   通訊端控制代碼
 * nRecvSize: 需要發送的位元組數
 * pStr:      要發送出去的位元組流
/*****************************************************************************/
BOOL CDGSocket::Send(SOCKET hSocket, int nSendSize, LPCTSTR pStr)
{
 BOOL bRet = TRUE;

 if(send(hSocket, pStr, nSendSize, 0) == SOCKET_ERROR)
 {
         MyOutput("My Send Error!");
  bRet = FALSE;
 }

 return bRet;
}

 

收發檔案--解決方案

上面已經說過收發檔案實際也是收發資訊,只是多了把檔案資訊變為位元組流這一步。下面給出了配對的範例程式碼:

/*****************************************************************************/
 * hSocket:   通訊端控制代碼
 * fName:    發送的本地檔案路徑
/*****************************************************************************/
BOOL CDGSocket::SendFile(SOCKET hSocket, CString fName)
{
 BOOL bRet = TRUE;
 int fileLength, cbLeftToSend;

 // pointer to buffer for sending data (memory is allocated after sending file size)
 BYTE* sendData = NULL;
 CFile sourceFile;
 CFileException fe;
 BOOL bFileIsOpen = FALSE;

 if( !( bFileIsOpen = sourceFile.Open( fName, CFile::modeRead | CFile::typeBinary, &fe ) ) )
 {
  TCHAR strCause[256];
  fe.GetErrorMessage( strCause, 255 );
  TRACE( "SendFileToRemoteRecipient encountered an error while opening the local file/n"
   "/tFile name = %s/n/tCause = %s/n/tm_cause = %d/n/tm_IOsError = %d/n",
   fe.m_strFileName, strCause, fe.m_cause, fe.m_lOsError );
  
  /* you should handle the error here */
  
  bRet = FALSE;
  goto PreReturnCleanup;
 }
 
 // first send length of file
 fileLength = sourceFile.GetLength();
 fileLength = htonl( fileLength );
 cbLeftToSend = sizeof( fileLength );
 
 do
 {
  int cbBytesSent;
  const char* bp = (const char*)(&fileLength) + sizeof(fileLength) - cbLeftToSend;
  cbBytesSent = send(hSocket, bp, cbLeftToSend, 0);
  
  // test for errors and get out if they occurred
  if ( cbBytesSent == SOCKET_ERROR )
  {
   int iErr = ::GetLastError();
   TRACE( "SendFileToRemoteRecipient returned a socket error while sending file length/n"
    "/tNumber of Bytes sent = %d/n"
    "/tGetLastError = %d/n", cbBytesSent, iErr );
   
   /* you should handle the error here */

   bRet = FALSE;
   goto PreReturnCleanup;
  }
  
  // data was successfully sent, so account for it with already-sent data
  cbLeftToSend -= cbBytesSent;
 }
 while ( cbLeftToSend > 0 );
 
 // now send the file's data
 
 sendData = new BYTE[SEND_BUFFER_SIZE];
 
 cbLeftToSend = sourceFile.GetLength();
 
 do
 {
  // read next chunk of SEND_BUFFER_SIZE bytes from file
  
  int sendThisTime, doneSoFar, buffOffset;
  
  sendThisTime = sourceFile.Read( sendData, SEND_BUFFER_SIZE );
  buffOffset = 0;
  
  do
  {
   doneSoFar = send(hSocket, (const char*)(sendData + buffOffset), sendThisTime, 0);
   
   // test for errors and get out if they occurred
   if ( doneSoFar == SOCKET_ERROR )
   {
    int iErr = ::GetLastError();
    TRACE( "SendFileToRemoteRecipient returned a socket error while sending chunked file data/n"
     "/tNumber of Bytes sent = %d/n"
     "/tGetLastError = %d/n", doneSoFar, iErr );
    
    /* you should handle the error here */
    
    bRet = FALSE;
    goto PreReturnCleanup;
   }
   
/***************************
  un-comment this code and put a breakpoint here to prove to yourself that sockets can send fewer bytes than requested
    
   if ( doneSoFar != sendThisTime )
   {
    int ii = 0;
   }
****************************/
   
   // data was successfully sent, so account for it with already-sent data
   
   buffOffset += doneSoFar;
   sendThisTime -= doneSoFar;
   cbLeftToSend -= doneSoFar;
  }
  while ( sendThisTime > 0 );
  
 }
 while ( cbLeftToSend > 0 );
 
 
PreReturnCleanup:  // labelled goto destination
 
 // free allocated memory
 // if we got here from a goto that skipped allocation, delete of NULL pointer
 // is permissible under C++ standard and is harmless
 delete[] sendData;
 
 if ( bFileIsOpen )
  sourceFile.Close();  // only close file if it's open (open might have failed above)
 
 return bRet;
}

/*****************************************************************************/
 * hSocket:   通訊端控制代碼
 * fName:    要接收到本地的檔案路徑
/*****************************************************************************/
BOOL CDGSocket::RecvFile(SOCKET hSocket, CString fName)
{
 BOOL bRet = TRUE;        // return value
 
 int dataLength, cbBytesRet, cbLeftToReceive; // used to monitor the progress of a receive operation
 
 BYTE* recdData = NULL; // pointer to buffer for receiving data (memory is allocated after obtaining file size)
 
 CFile destFile;
 CFileException fe;
 BOOL bFileIsOpen = FALSE;
 
 // open/create target file that receives the transferred data
 
 if( !( bFileIsOpen = destFile.Open( fName, CFile::modeCreate | CFile::modeWrite | CFile::typeBinary, &fe ) ) )
 {
  TCHAR strCause[256];
  fe.GetErrorMessage( strCause, 255 );
  
  MyOutput(fName);

  CString strErrMsg;
  strErrMsg.Format("GetFileFromRemoteSender encountered an error while opening the local file/n"
   "/tFile name = %s/n/tCause = %s/n/tm_cause = %d/n/tm_IOsError = %d/n",
   fe.m_strFileName, strCause, fe.m_cause, fe.m_lOsError);

  MyOutput( strErrMsg );
  
  /* you should handle the error here */
  
  bRet = FALSE;
  goto PreReturnCleanup;
 }
 
 
 // get the file's size first
 
 cbLeftToReceive = sizeof( dataLength );
 
 do
 {
  char* bp = (char*)(&dataLength) + sizeof(dataLength) - cbLeftToReceive;
  cbBytesRet = recv(hSocket, bp, cbLeftToReceive, 0);
  
  // test for errors and get out if they occurred
  if ( cbBytesRet == SOCKET_ERROR || cbBytesRet == 0 )
  {
   int iErr = ::GetLastError();
   CString strErr;
   strErr.Format("GetFileFromRemoteSite returned a socket error while getting file length/n"
    "/tNumber of Bytes received (zero means connection was closed) = %d/n"
    "/tGetLastError = %d/n", cbBytesRet, iErr );
  
   /* you should handle the error here */
   
   MyOutput(strErr);

   bRet = FALSE;
   goto PreReturnCleanup;
  }
  
  // good data was retrieved, so accumulate it with already-received data
  cbLeftToReceive -= cbBytesRet;
  
 }
 while ( cbLeftToReceive > 0 );
 
 dataLength = ntohl( dataLength );
 
 
 // now get the file in RECV_BUFFER_SIZE chunks at a time
 
 recdData = new byte[RECV_BUFFER_SIZE];
 cbLeftToReceive = dataLength;
 
 do
 { 
  int iiGet, iiRecd;
  
  iiGet = (cbLeftToReceive<RECV_BUFFER_SIZE) ? cbLeftToReceive : RECV_BUFFER_SIZE ;
  iiRecd = recv(hSocket, (char *)recdData, iiGet, 0);
  
  // test for errors and get out if they occurred
  if ( iiRecd == SOCKET_ERROR || iiRecd == 0 )
  {
   int iErr = ::GetLastError();
   TRACE( "GetFileFromRemoteSite returned a socket error while getting chunked file data/n"
    "/tNumber of Bytes received (zero means connection was closed) = %d/n"
    "/tGetLastError = %d/n", iiRecd, iErr );
   
   /* you should handle the error here */
   
   bRet = FALSE;
   goto PreReturnCleanup;
  }

/************************* 
  un-comment this code and put a breakpoint here to prove to yourself that sockets can return fewer bytes than requested
   
   if ( iiGet != iiRecd )
   {
   int ii=0;
   }   
***************************/
  
  // good data was retrieved, so accumulate it with already-received data
  
  destFile.Write( recdData, iiRecd); // Write it
  cbLeftToReceive -= iiRecd;
  
 }
 while ( cbLeftToReceive > 0 );
 
 
PreReturnCleanup:  // labelled "goto" destination
 
 // free allocated memory
 // if we got here from a goto that skipped allocation, delete of NULL pointer
 // is permissible under C++ standard and is harmless
 delete[] recdData;  

 if ( bFileIsOpen )
  destFile.Close(); // only close file if it's open (open might have failed above)

 return bRet;
}

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.