概述:

收发数据是网络编程的主题,在套接字上收发数据我们可以使用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在阻塞模式下的信息收发和文件接收相关推荐

  1. Socket 非阻塞模式下connect 返回EINPROGRESS(115)错误

    今天再测试socket的时候,发现一个很奇怪的问题,就是客户端再connect的时候第一次connect总是会返回-1,errno是115,往往第二次连接就可以成功了.但是对于服务端来说,第一次连接已 ...

  2. socket的阻塞模式和非阻塞模式(send和recv函数在阻塞和非阻塞模式下的表现)

    socket的阻塞模式和非阻塞模式 无论是Windows还是Linux,默认创建socket都是阻塞模式的 在Linux中,可以再创建socket是直接将它设置为非阻塞模式 int socket (i ...

  3. Qt:Qt实现Winsock网络编程—非阻塞模式下的简单远程控制的开发(WSAAsyncSelect)

    Qt实现Winsock网络编程-非阻塞模式下的简单远程控制的开发(WSAAsyncSelect) 前言 这边博客应该是 Qt实现Winsock网络编程-TCP服务端和客户端通信(多线程) 的姐妹篇,上 ...

  4. [转]Socket的阻塞模式和非阻塞模式

    http://blog.csdn.net/VCSockets/ 阻塞模式 Windows套接字在阻塞和非阻塞两种模式下执行I/O操作.在阻塞模式下,在I/O操作完成前,执行的操作函数一直等候而不会立即 ...

  5. connect函数在阻塞和非阻塞模式下的行为

    connect函数在阻塞和非阻塞模式下的行为 当socket使用阻塞模式时,connect函数会阻塞到有明确结果才会返回,如果网络环境较差,可能要等一会,影响体验, 为了解决这个问题,我们使用异步co ...

  6. socket的阻塞模式和非阻塞模式

    文章目录 socket的阻塞模式和非阻塞模式 如何将socket设置为非阻塞模式 send和recv函数在阻塞和非阻塞模式下的表现 非阻塞模式下send和recv函数的返回值总结 阻塞与非阻塞sock ...

  7. 非阻塞模式下 SEND 和 RECV 函数的返回值总结

    send 和 recv 函数的各种返回值意义: 返回值 n 返回值含义 大于 0 成功发送 n 个字节 0 对端关闭连接 小于 0( -1) 出错或者被信号中断或者对端 TCP 窗口太小数据发不出去( ...

  8. 如何在dos模式下打开电脑里面的文件

    如何在dos模式下打开电脑里面的文件 首先进入你要打开的盘符,直接输入盘符名字即可,如:d: 然后浏览该盘符下的文件和文件夹,命令:dir 之后打开你想要浏览的目录,命令:cd  目录名 最后运行你要 ...

  9. Java nio Socket非阻塞模式

    NIO 有一个主要的类Selector,这个类似一个观察者,只要我们把需要探知的socketchannel告诉Selector,我们接着做别的事情,当有 事件发生时,他会通知我们,传回一组Select ...

最新文章

  1. 26岁应届博士被聘985博导!入职半年实现学院顶会论文零的突破
  2. 定时从linux获取文件,Linux 使用scp命令定时将文件备份到另一台服务器
  3. 第四周实践项目6 循环双链表应用
  4. web 页面传值乱码问题
  5. 罚款200元的交通违法行为
  6. zookeeper zoo.cfg配置文件
  7. (软件工程复习核心重点)第一章软件工程概论习题
  8. uniCloud免费云存储图床源码
  9. linux centos7磁盘分区扩容,centos7 xfs文件系统的磁盘扩容
  10. 数据结构:二叉搜索树(BST)全部基本操作
  11. Java回调函数实现案例
  12. 台达变频器485通讯接线图_三菱PLC 与台达VFD-L 变频器通讯(RS485)程序
  13. java iplimage 头文件_javaCV图像处理之Frame、Mat和IplImage三者相互转换(使用openCV进行Mat和IplImage转换)......
  14. Ardunio开发实例-MSA301三轴加速计
  15. 计算机组成原理基本概念,《计算机组成原理》基本概念.doc
  16. Anaconda修改国内镜像源
  17. 计算机中丢失swr.dll,win10系统提示模块initpki.dll加载失败如何解决
  18. 为你的WSL 2编译一个最新的Linux内核吧!
  19. 教大家如何在官网下载不同版本的postgresql包含之前历史版本--适合linux系统
  20. 【】宝塔搭建网站教程,新增一个网站

热门文章

  1. 初中英语多词性单词怎么办_高考英语阅读理解生僻单词太多怎么办?十大招数帮到你...
  2. oracle如何自定义类型,Oracle 自定义类型
  3. 新型计算机作文1000,人类:感性的计算机作文1000字
  4. 加密解密_作业-加密解密程序
  5. java简单创建图片面板_图像界面编程简单窗体创建
  6. python批处理代码_【原创源码】【python】python文本文件批处理
  7. vba 指定列后插入列_在不同的列左侧插入指定数量的空白列
  8. 最优化学习笔记(七)——Levenberg-Marquardt修正(牛顿法修正)
  9. 不为人知的心理学效应
  10. “数”说系列洞察报告:30+女性专题——浪姐无价,又A又飒