这里我们来探讨一下在网络编程过程中,有关read/write 或者send/recv的使用细节。这里有关常用的阻塞/非阻塞的解释在网上有很多很好的例子,这里就不说了,还有errno ==EAGAIN 异常等等。首先我们拿一个简单的实例代码看一下。

read/write面临的是什么问题:

字节流套接字上调用read或write的返回值可能比请求的数量少,这并不是出错的状态,这种情况发生在内核中的用于套接字缓冲区的空间已经达到了极限,需要再次的调用read/write函数才能将剩余数据读出或写入。那么这里可以看到是内核缓冲区到达极限,那么一般情况下是多大呢?

 CLIENT]$ cat /proc/sys/net/ipv4/tcp_wmem
4096    16384    4194304
CLIENT]$ cat /proc/sys/net/ipv4/tcp_rmem
4096    87380    6291456

第一个数据表示最小,第二个表示默认情况下,第三个表示最大,单位是字节。如果read的缓冲区已经到达极限,那么一次read并不能读出自己想要的数据大小。那么更多的情况我们并不知道对方发送的数据量是多大,我们只有一个最大阀值。那么这时该怎样去控制read/write呢?

阻塞的read和write的问题:

我们来看<unix网络编程> 中的read代码如下

ssize_t                     /* Read "n" bytes from a descriptor. */
readn(int fd, void *vptr, size_t n)  //这里的n 是接收数据buffer的空间   真实情况下我们确实不太清楚客户端到底它会发多少数据 一般是个阀值。
{  size_t  nleft;  ssize_t nread;  char    *ptr;  ptr = vptr;  nleft = n;  while (nleft > 0) {  if ( (nread = read(fd, ptr, nleft)) < 0) {  if (errno == EINTR)  nread = 0;      /* and call read() again */  else  return(-1);  } else if (nread == 0)  break;              /* EOF */  nleft -= nread;  ptr   += nread;  }  return(n - nleft);      /* return >= 0 */
}

我们先看这个参数size_t n,因为多数情况下,我们并不严格规定客户端到底一次要发多少数据,对于常规的服务器来说都要有一个最大阀值,超过这个阀值就表示是异常数据。想像一下如果没有最大阀值,一个恶意的客户端向一个服务器发送一个超大的文件,那么这个服务器很快就会崩溃! 这里的n的大小其实跟我们的业务相关了。文件服务器就除外了我们不谈这种情况。我们继续看 若此时文件描述符为阻塞模式时,那么当一个连接到达并开始发送一段数据后暂停发送数据(还没有断开),因为客户端并没有断开,同时它发送的数据还没有到达阀值 那么势必在read处一直阻塞,那么如果是一个单线程服务器的话就不能处理其他请求了。write的话这种情况我们一般都知道要发送数据的真实大小一般不发生这种情况。

ssize_t                     /* Write "n" bytes to a descriptor. */
writen(int fd, const void *vptr, size_t n)  //这里传入的n一般就是数据的实际大小   while循环会正常返回。
{  size_t      nleft;  ssize_t     nwritten;  const char  *ptr;  ptr = vptr;  nleft = n;  while (nleft > 0) {  if ( (nwritten = write(fd, ptr, nleft)) <= 0) {  if (nwritten < 0 && errno == EINTR)  nwritten = 0;       /* and call write() again */  else  return(-1);         /* error */  }  nleft -= nwritten;  ptr   += nwritten;  }  return(n);
}

非阻塞的read/write:

由上面阻塞模式的情况我们再分析一下非组塞:

还是以上的代码:readn来说,如果是非阻塞,我们还是假定这里客户端发送了一点数据并没有断开。

ssize_t                     /* Read "n" bytes from a descriptor. */
readn(int fd, void *vptr, size_t n)  //这里的n 是接收数据buffer的空间   这种情况我们确实不太清楚客户端到底它会发多少数据 一般是个阀值。
{  size_t  nleft;  ssize_t nread;  char    *ptr;  ptr = vptr;  nleft = n;  while (nleft > 0) {  if ( (nread = read(fd, ptr, nleft)) < 0) {  if (errno == EINTR)  nread = 0;      /* and call read() again */       if (errno == EAGAIN)    //发生了这种异常我将它返回了,这里表示文件描述符还不可读,没有准备好,我就直接将其返回,最后IO复用select/poll/epoll就会再读取准备好的数据。          return n - nleft;  else  return(-1);  } else if (nread == 0){        //close(fd);        break;              /* EOF */  }nleft -= nread;  ptr   += nread;  }  return(n - nleft);      /* return >= 0 */
}

1.  如果客户端发送了一点数据然后没有断开处于暂停状态的话。

那么在调用read时就会出现EAGAIN的异常,这里当发生这种异常时表示文件描述符还没有准备好,那么我这里直接将其返回已经读到的size。这样就不会造成一直阻塞在这里其他连接无法处理的现象。

2. 如果客户端发送了一点数据然后立刻断开连接了

比如我们第一次read的时候读到了最后发来的数据,当再次读取时读到了EOF客户端断开了连接那我们这个程序还是有问题阿! 我们这里看到跳出来while并返回了正确读到的数据  这时readn的返回是正确的,但是我们有这次返回还是不知道客户端断开了,虽说我们可以向上述代码加入close 但是我们并不能有readn函数知道客户端主动断开连接。

对于2这种情况就是我们在开发过程中常常遇到的情况,这时我们可以在readn中再加入时当的参数就可以解决,比如我们传入的是一个包含文件描述符号的结构体,结构体中含有标志状态的字段,再read == 0时将字段赋予一个值。再readn之后再有这个结构体的某个标志知道已将连接断开后续再断开连接和删除其事件即可。下面找到了Nginx关于recv的使用代码:

ssize_t
ngx_unix_recv(ngx_connection_t *c, u_char *buf, size_t size)
{ssize_t       n;ngx_err_t     err;ngx_event_t  *rev;rev = c->read;#if (NGX_HAVE_KQUEUE)if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) {ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,"recv: eof:%d, avail:%d, err:%d",rev->pending_eof, rev->available, rev->kq_errno);if (rev->available == 0) {if (rev->pending_eof) {rev->ready = 0;rev->eof = 1;if (rev->kq_errno) {rev->error = 1;ngx_set_socket_errno(rev->kq_errno);return ngx_connection_error(c, rev->kq_errno,"kevent() reported about an closed connection");}return 0;} else {rev->ready = 0;return NGX_AGAIN;}}}#endif#if (NGX_HAVE_EPOLLRDHUP)if (ngx_event_flags & NGX_USE_EPOLL_EVENT) {ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,"recv: eof:%d, avail:%d",rev->pending_eof, rev->available);if (!rev->available && !rev->pending_eof) {rev->ready = 0;return NGX_AGAIN;}}#endifdo {n = recv(c->fd, buf, size, 0);ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,"recv: fd:%d %z of %uz", c->fd, n, size);if (n == 0) {rev->ready = 0;rev->eof = 1;#if (NGX_HAVE_KQUEUE)/** on FreeBSD recv() may return 0 on closed socket* even if kqueue reported about available data*/if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) {rev->available = 0;}#endifreturn 0;}if (n > 0) {#if (NGX_HAVE_KQUEUE)if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) {rev->available -= n;/** rev->available may be negative here because some additional* bytes may be received between kevent() and recv()*/if (rev->available <= 0) {if (!rev->pending_eof) {rev->ready = 0;}rev->available = 0;}return n;}#endif#if (NGX_HAVE_EPOLLRDHUP)if ((ngx_event_flags & NGX_USE_EPOLL_EVENT)&& ngx_use_epoll_rdhup){if ((size_t) n < size) {if (!rev->pending_eof) {rev->ready = 0;}rev->available = 0;}return n;}#endifif ((size_t) n < size&& !(ngx_event_flags & NGX_USE_GREEDY_EVENT)){rev->ready = 0;}return n;}err = ngx_socket_errno;if (err == NGX_EAGAIN || err == NGX_EINTR) {ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err,"recv() not ready");n = NGX_AGAIN;} else {n = ngx_connection_error(c, err, "recv() failed");break;}} while (err == NGX_EINTR);rev->ready = 0;if (n == NGX_ERROR) {rev->error = 1;}return n;
}

View Code

其中我们还要注意在writen的非阻塞中,如果第一次写入返回,当第二次写入时对方断了,再次写入时就会发生EPIPE异常

while (nleft > 0) {  if ( (nwritten = write(fd, ptr, nleft)) <= 0) {  if (nwritten < 0 && errno == EINTR)  nwritten = 0;       /* and call write() again */  else if(errno == EPIPE){return 0;// 这里我返回了0 因为最后一次发送数据并不保证对面已经收到了数据,这个数据到底有没有被正确接收在这里我们无法获得。如果对方关闭了就直接造成异常
            }else if(errno == EAGAIN){ return n-left;}else  return(-1);         /* error */  }  nleft -= nwritten;  ptr   += nwritten;  }                      

其实write数据并不代表数据被对方成功接收了,只是往内核缓冲区写,如果写入成功write就返回了,所以就无法知道数据是否被收到,在一些严格要求的数据交互中常常使用应用层的确认机制。至于详细的消息接收和发送的内容推荐下列博客: http://blog.csdn.net/yusiguyuan/article/details/24111289   和 http://blog.csdn.net/yusiguyuan/article/details/24671351

转载于:https://www.cnblogs.com/MaAce/p/8058698.html

TCP程序中发送和接收数据相关推荐

  1. socket简介和udp网络程序-发送、接收数据

    socket简介 不同电脑上的进程之间如何通信 首要解决的问题是如何唯一标识一个进程,否则通信无从谈起! 在1台电脑上可以通过进程号(PID)来唯一标识一个进程,但是在网络中这是行不通的. 其实TCP ...

  2. TCP发送和接收数据

    学习笔记--TCP发送和接收数据 TCP协议 三次握手 四次挥手 UDP协议介绍 TCP通信 TCP客户端构建流程 TCP服务端 TCP与UDP区别 socket之send和recv原理剖析 send ...

  3. TCP协议发送和接收数据

    TCP协议发送与接收数据 一.发送数据 1.使用Socket类的构造方法创建Socket对象 Socket(InetAddress address, int port) 创建一个流套接字并将其连接到指 ...

  4. 25. Python语言 Web 开发 之 Socket 编程 · 第一章 UDP发送与接收数据

    UDP发送与接收数据 本章主题 关键词 前导: 计算机网络的发展及基础网络概念 两台电脑的通信 IP地址介绍及分类 IP地址与IP协议 Windows 和 Linux 查看网卡信息 IP地址分类 以太 ...

  5. 使用DatagramSocket发送、接收数据(Socket之UDP套接字)

    2019独角兽企业重金招聘Python工程师标准>>> 创建一个DatagramSocket实例,并将该对象绑定到指定IP地址.指定端口. 通过上面三个构造器中的任意一个构造器即可创 ...

  6. 网络——在网络上发送,接收数据

    问题 创建并加入一个网络会话是一回事,但如果不能发送或接收任何数据那么网络会话有什么用呢? 解决方案 当玩家连接到会话时,你可以在一个PacketWriter流中存储所有想要发送的数据.完成这个操作后 ...

  7. 在c语言中如何屏蔽一段程序,如何在用C语言关闭TCP程序中的Nagle算法

    TCP为了防止在网络中过多的小分组会导致阻塞,因此提供了Nagle算法:要求一个TCP连接上最多只能有一个未被确认的未完成的小分组,在该分组的确认到达之前不能发送其他的小分组.相反,TCP收集这些少量 ...

  8. STM32L152RE实现串口发送及接收数据

    本文主要讲解用keil软件实现USART串口发送及接收数据,默认读者keil环境已经配好,且头文件已正确引入,如出现编译错误以及st-link下载问题,请自行百度解决. 串口发送和接收数据是一件看起来 ...

  9. UHD C/C++ 编程实例 USRP发送、接收数据

    UHD C/C++ 编程实例 USRP发送.接收数据 如有相关问题,欢迎随时讨论交流 jxwxg@foxmail.com 1. UHD库函数简介 1.1 发送函数 新建一个usrp设备 std::st ...

最新文章

  1. 爬虫之xpath语法-节点修饰语法
  2. 图书管理系统python代码课程设计报告_数据结构图书管理系统课程设计报告
  3. python模拟太阳系_用 Python 动态模拟太阳系运转
  4. 浅谈 EF CORE 迁移和实例化的几种方式
  5. axture动画原型制作_Axure制作原型-基础操作
  6. OpenCV:SURF算法浅析
  7. Linux下的MySQL安装及卸载
  8. 微信开发h5支付功能,配置单价和商品信息无法更新问题解决方法!
  9. Python动态创建变量的方法
  10. 用jquery替换dojo中的ajax
  11. iOS:Tagged Pointer
  12. 分享12个黑科技网站,每个都是十分良心
  13. mysql odbc 卸载_Linux卸载MySQL
  14. 什么是java全栈工程师
  15. 公民住宅权不可侵犯!为阻强拆致人重伤,属正当防卫
  16. 工具推荐:用VS code 导出、导入和运行Excel中的VBA代码
  17. 如何运用计算机辅助英语教学,谈英语计算机辅助教学 (中学英语教学论文)
  18. 兼职项目分享,在家就可以做的八个副业项目,利用业余时间增加收入吧
  19. 怎么样在Excel单元格里批量加小数点和单位?
  20. android 仿360浮动,Android仿360悬浮小球自定义view实现示例

热门文章

  1. DE26 Continuation: Repeated Real Eigenvalues
  2. linux wget安装mysql_linux安装mysql
  3. leetcode最大矩形_柱状图中的最大矩形
  4. python计算数组元素个数_python简单获取数组元素个数的方法
  5. keras版Mask-RCNN来训练自己的目标检测数据集
  6. c语言printf打印字符串,puts()vs printf()用于以C语言打印字符串
  7. java jquery怎么取值_jquery 取值
  8. 使用计算机解决问题的本质,(新教材)教科版高中信息技术必修一 2.1 计算机解决问题的过程 课件(共25张PPT)...
  9. Oracle拜年段子,oracle标题
  10. 2019日历全年一张_蒙太奇手帐丨2019品牌日历合集,手帐素材最佳选择