转: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;
}
?
?
本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/andylin02/archive/2007/06/25/1665060.aspx
轉載于:https://www.cnblogs.com/cumtb3S/archive/2010/06/14/1758392.html
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的转:Socket在阻塞模式下的信息收发和文件接收的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: CentOS上搭建Nginx + Mon
- 下一篇: 讲真话的朋友才是我们需要的!