http://blog.chinaunix.net/uid-23629988-id-3035613.html

今天探讨一个很看似简单的API “read”的返回值问题。read的返回值有哪几个值?每个值又是在什么情况下发生的?
 
先问一下男人吧:man 2 read
RETURN VALUE
       On success, the number of bytes read is returned (zero indicates end of file), and the file position is advanced by this number.  It is not an error if this number is smaller than the number of bytes requested; this may happen for example because fewer  bytes  are  actually available  right  now (maybe because we were close to end-of-file, or because we are reading from a pipe, or from a terminal), or because read() was interrupted by a signal.  On error, -1 is returned, and errno is set appropriately.  In this case it is left unspecified whether the file position (if any) changes.
 
从上面的描述中,read的返回值一共有三种情况:
1. 大于0:成功读取的字节数;
2. 等于0:到达文件尾;
3. -1:发生错误,通过errno确定具体错误值。
Note:本次讨论只限于阻塞的fd,不讨论非阻塞的情况。
 
通过这个man的介绍,看似read的应用很简单,但真的是这样吗?莫忘了Linux中文件是一个很common的概念。它可以是一个真实的文件,也可以是一个socket,一个设备,等等。对于真实的文件,文件尾EOF是一个确定的情况。
 
那么如果是一个socket,它的返回值何时为0呢?还有,在read的过程中,如果被信号中断,究竟是返回-1,还是返回一个正值或者0呢?当对端关闭后,是否socket还可以读取对端关闭socket前发送的数据呢?
 
为了搞清楚socket的行为,必须要研究一下对应的kernel的代码。本次以unix域的TCP连接的socket为例,来探讨一下socket的行为。
 
unix_stream_recvmsg为unix域的TCP连接的socket的读取函数:
  1. static int unix_stream_recvmsg(struct kiocb *iocb, struct socket *sock,
  2. struct msghdr *msg, size_t size,
  3. int flags)
  4. {
  5. struct sock_iocb *siocb = kiocb_to_siocb(iocb);
  6. struct scm_cookie tmp_scm;
  7. struct sock *sk = sock->sk;
  8. struct unix_sock *u = unix_sk(sk);
  9. struct sockaddr_un *sunaddr = msg->msg_name;
  10. int copied = 0;
  11. int check_creds = 0;
  12. int target;
  13. int err = 0;
  14. long timeo;
  15. err = -EINVAL;
  16. if (sk->sk_state != TCP_ESTABLISHED)
  17. goto out;
  18. err = -EOPNOTSUPP;
  19. if (flags&MSG_OOB)
  20. goto out;
  21. target = sock_rcvlowat(sk, flags&MSG_WAITALL, size);
  22. timeo = sock_rcvtimeo(sk, flags&MSG_DONTWAIT);
  23. msg->msg_namelen = 0;
  24. /* Lock the socket to prevent queue disordering
  25. * while sleeps in memcpy_tomsg
  26. */
  27. if (!siocb->scm) {
  28. siocb->scm = &tmp_scm;
  29. memset(&tmp_scm, 0, sizeof(tmp_scm));
  30. }
  31. mutex_lock(&u->readlock);
  32. do {
  33. int chunk;
  34. struct sk_buff *skb;
  35. unix_state_lock(sk);
  36. skb = skb_dequeue(&sk->sk_receive_queue);
  37. if (skb == NULL) {
  38. if (copied >= target)
  39. goto unlock;
  40. /*
  41. *    POSIX 1003.1g mandates this order.
  42. */
  43. err = sock_error(sk);
  44. if (err)
  45. goto unlock;
  46. if (sk->sk_shutdown & RCV_SHUTDOWN)
  47. goto unlock;
  48. unix_state_unlock(sk);
  49. err = -EAGAIN;
  50. if (!timeo)
  51. break;
  52. mutex_unlock(&u->readlock);
  53. timeo = unix_stream_data_wait(sk, timeo);
  54. if (signal_pending(current)) {
  55. err = sock_intr_errno(timeo);
  56. goto out;
  57. }
  58. mutex_lock(&u->readlock);
  59. continue;
  60. unlock:
  61. unix_state_unlock(sk);
  62. break;
  63. }
  64. unix_state_unlock(sk);
  65. if (check_creds) {
  66. /* Never glue messages from different writers */
  67. if ((UNIXCB(skb).pid != siocb->scm->pid) ||
  68. (UNIXCB(skb).cred != siocb->scm->cred)) {
  69. skb_queue_head(&sk->sk_receive_queue, skb);
  70. break;
  71. }
  72. } else {
  73. /* Copy credentials */
  74. scm_set_cred(siocb->scm, UNIXCB(skb).pid, UNIXCB(skb).cred);
  75. check_creds = 1;
  76. }
  77. /* Copy address just once */
  78. if (sunaddr) {
  79. unix_copy_addr(msg, skb->sk);
  80. sunaddr = NULL;
  81. }
  82. chunk = min_t(unsigned int, skb->len, size);
  83. if (memcpy_toiovec(msg->msg_iov, skb->data, chunk)) {
  84. skb_queue_head(&sk->sk_receive_queue, skb);
  85. if (copied == 0)
  86. copied = -EFAULT;
  87. break;
  88. }
  89. copied += chunk;
  90. size -= chunk;
  91. /* Mark read part of skb as used */
  92. if (!(flags & MSG_PEEK)) {
  93. skb_pull(skb, chunk);
  94. if (UNIXCB(skb).fp)
  95. unix_detach_fds(siocb->scm, skb);
  96. /* put the skb back if we didn't use it up.. */
  97. if (skb->len) {
  98. skb_queue_head(&sk->sk_receive_queue, skb);
  99. break;
  100. }
  101. consume_skb(skb);
  102. if (siocb->scm->fp)
  103. break;
  104. } else {
  105. /* It is questionable, see note in unix_dgram_recvmsg.
  106. */
  107. if (UNIXCB(skb).fp)
  108. siocb->scm->fp = scm_fp_dup(UNIXCB(skb).fp);
  109. /* put message back and return */
  110. skb_queue_head(&sk->sk_receive_queue, skb);
  111. break;
  112. }
  113. } while (size);
  114. mutex_unlock(&u->readlock);
  115. scm_recv(sock, msg, siocb->scm, flags);
  116. out:
  117. return copied ? : err;
  118. }
在这个函数中只有一个出口:
  1. return copied ? : err;
copied在unix_stream_recvmsg为已读取的字节数。那么这行代码的意义就比较明显了,当已读取了一定数据,那么read的返回值即为读取的字节数。当没有读取任何数据时,就返回err。
 
下面看前面提出的两个问题:
1. 何时返回值为0;
2. 被信号中断时,read的返回值是什么?
3. 对端关闭后,是否可以继续读取对端关闭前发送的数据呢?
 
先看第一个问题:何时返回值为0。这需要copied为0,且err为0。
  1. err = sock_error(sk);
  2. if (err)
  3. goto unlock;
  4. if (sk->sk_shutdown & RCV_SHUTDOWN)
  5. goto unlock;

几行代码告诉了我们答案。首先这几行代码是在socket的receive
queue没有数据时,才会运行到达的。当socket没有错误时,会继续坚持socket的RCV_SHUTDOWN标志位,如果设置了该标志位,则
goto 到unlock,直至最后的return返回语句。此时,copied为0时,err也会为0.

sk->sk_shutdown的标志位会在两种情况下被设置,参见unix_shutdown函数。在unix_shutdown函数,由API

close或者shutdown触发,它不仅设置了本端的sk_shutdown标志位,还会设置对端相对应的sk_shutdown标志位。所以无论是
本端还是对端调用shutdown或者close时,都有可能导致本端的read返回为0。这里之所以说可能导致,是因为shutdown时可以指定
shutdown的行为,是关闭发送还是接收。

 
第二个问题,被信号中断时,返回值是什么?
  1. timeo = unix_stream_data_wait(sk, timeo);
  2. if (signal_pending(current)) {
  3. err = sock_intr_errno(timeo);
  4. goto out;
  5. }

几行代码是这个问题的答案。这几行代码同样是处于receive queue为空的判断中。那么,这说明如果receive
queue中已有数据,且大于要读取的字节数,那么在这个函数中,根本就不会去判断是否有pending的信号,返回值为读取的字节数。如果
receive
queue中没有足够的数据,那么就会运行到此处,检查是否有pending的信号。当有信号pending时,这时的返回值就决定于copied的值。
如果已读取了一些字节,那么就返回copied即已读取了的字节数——小于我们要去读取的字节数。如果没有读取任何字节,那么就返回-1,且errno为
EINTR。
 
第三个问题,对端关闭后,是否可以读取对端在关闭之前发送的数据。

前面两个问题以及第一个问题的答案。这个问题的答案也很明显了。在unix_stream_recvmsg中,只要receive
queue中有数据,那么就不会去检查是否sk_shutdown的标志。所以答案是,即使对端关闭socket,本端仍可以读取对端在关闭之前发送的数
据。
 
本文只探讨了unix域的TCP连接的socket的读取情况。至于其它种类的socket的read行为,我猜测Linux仍然会采取一致的行为——有心人可以去看代码验证一下。
 

探讨read的返回值的三种情况的更多相关文章

  1. net异步线程获取返回值的三种方式

    方式一:endInvoke using System; using System.Collections.Generic; using System.Text; using System.Thread ...

  2. 自己遇到的ajax调用ashx文件无法获取返回值的一种情况

    无法获取返回值的ashx文件大致如下: public void ProcessRequest (HttpContext context) { context.Response.ContentType ...

  3. 服务器文档下载zip格式 SQL Server SQL分页查询 C#过滤html标签 EF 延时加载与死锁 在JS方法中返回多个值的三种方法(转载) IEnumerable,ICollection,IList接口问题 不吹不擂,你想要的Python面试都在这里了【315+道题】 基于mvc三层架构和ajax技术实现最简单的文件上传 事件管理

    服务器文档下载zip格式   刚好这次项目中遇到了这个东西,就来弄一下,挺简单的,但是前台调用的时候弄错了,浪费了大半天的时间,本人也是菜鸟一枚.开始吧.(MVC的) @using Rattan.Co ...

  4. python执行系统命令后获取返回值的几种方式集合

    python执行系统命令后获取返回值的几种方式集合 今天小编就为大家分享一篇python执行系统命令后获取返回值的几种方式集合,具有很好的参考价值,希望对大家有所帮助.一起跟随小编过来看看吧 第一种情 ...

  5. Activity的跳转及返回值 的四种方法

    Activity生命周期 从创建到销毁的生命周期: onCreate()→onStart()→onResume()→onPouse()→onStop()→onDestroy() 从起动到后台再到前台: ...

  6. Tomcat内存溢出的三种情况及解决办法分析

    Tomcat内存溢出的原因 在生产环境中tomcat内存设置不好很容易出现内存溢出.造成内存溢出是不一样的,当然处理方式也不一样. 这里根据平时遇到的情况和相关资料进行一个总结.常见的一般会有下面三种 ...

  7. SSO单点登录三种情况的实现方式详解

    单点登录(SSO——Single Sign On)对于我们来说已经不陌生了.对于大型系统来说使用单点登录可以减少用户很多的麻烦.就拿百度来说吧,百度下面有很多的子系统——百度经验.百度知道.百度文库等 ...

  8. Spring如何使用JdbcTemplate调用存储过程的三种情况

    注:原文 <Spring如何使用JdbcTemplate调用存储过程的三种情况 > Spring的SimpleJdbcTemplate将存储过程的调用进行了良好的封装,下面列出使用Jdbc ...

  9. SSO单点登录三种情况的实现方式详解(转载)

    单点登录(SSO——Single Sign On)对于我们来说已经不陌生了.对于大型系统来说使用单点登录可以减少用户很多的麻烦.就拿百度来说吧,百度下面有很多的子系统——百度经验.百度知道.百度文库等 ...

随机推荐

  1. CSS常见的浏览器前缀

    为了让浏览器识别某些专属属性,有时候需要在CSS属性前增加浏览器前缀 -ms-:Microsoft IE -moz-:Mozilla Firefox -o-:Opera Opera -webkit-: ...

  2. 使用C#通过调用minitab的COM库自动化生成报表

    本文介绍通过C#调用minitab com组建自动化生成报表的方法. 首先需要在minitab中通过手动配置的方式生成报表来得到该报表的命令行,过程如下 选择菜单“编辑器”->“启用命令”启用命 ...

  3. 关键字 explicit

    C++中, 一个参数的构造函数(或者除了第一个参数外其余参数都有默认值的多参构造函数), 承担了两个角色. 1 是个构造器 ,2 是个默认且隐含的类型转换操作符. 所以, 有时候在我们写下如 AAA ...

  4. fopen vs fsocketopen vs curl

    http://stackoverflow.com/questions/2647170/curl-vs-fopen-vs-fsocketopen http://stackoverflow.com/que ...

  5. offset() position() scrollTop() scrollLeft()

    (1)offset:获取当前元素相对于文档的高度.只对可见元素有效. 不管该元素如何定位,也不管其父元素如何定位,都是获取的该元素相对于当前视口的偏移 (2) position:获取元素相对于最近的一 ...

  6. CocoaPods安装和使用及问题:Setting up CocoaPods master repo

    CocoaPods是什么? 当你开发iOS应用时,会经常使用到很多第三方开源类库,比如JSONKit,AFNetWorking等等.可能某个类库又用到其他类库,所以要使用它,必须得另外下载其他类库,而 ...

  7. C#让TopMost窗体弹出并置顶层但不获取当前输入焦点的终极办法

    为了使程序在弹出窗口时置顶层且不获取系统输入焦点,避免影响用户当前的操作,来电通来电弹屏软件尝试过N多种办法,例如:弹出前保存当前焦点窗口句柄,弹出时因为使用TopMost系统默认将焦点交给了弹出窗口 ...

  8. NoSQL专家王涛访谈:为什么我们还要做一个NoSQL?

    ChinaUnix:各位网友大家好,今天有幸请到王涛先生到CU做客,与大家交流一些工作经验.首先请王涛先介绍一下自己. 王涛:大家好,我是王涛.过去八年里我一直在IBM多伦多实验室从事DB2引擎研发的 ...

  9. Oracle中NULL值与索引

    NULL值是关系数据库系统布尔型(true,false,unknown)中比较特殊类型的一种值,通常称为UNKNOWN或空值,即是未知的,不确定的.由于NULL存在着无数的可能,因此NULL值也不等于 ...

  10. 在虚拟中开启Windows 8.1的Hyper-V平台

    VM安装windows8开启Hype-V 今天老魏用VM安装了Windows8.1系统,想用此系统来开发一下Windows Phone8,但是要求确实要开启Hyper-V平台技术,本来是没有任何的问题 ...