今天探讨一个很看似简单的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的读取函数:

static int unix_stream_recvmsg(struct kiocb *iocb, struct socket *sock,struct msghdr *msg, size_t size,int flags)
{struct sock_iocb *siocb = kiocb_to_siocb(iocb);struct scm_cookie tmp_scm;struct sock *sk = sock->sk;struct unix_sock *u = unix_sk(sk);struct sockaddr_un *sunaddr = msg->msg_name;int copied = 0;int check_creds = 0;int target;int err = 0;long timeo;err = -EINVAL;if (sk->sk_state != TCP_ESTABLISHED)goto out;err = -EOPNOTSUPP;if (flags&MSG_OOB)goto out;target = sock_rcvlowat(sk, flags&MSG_WAITALL, size);timeo = sock_rcvtimeo(sk, flags&MSG_DONTWAIT);msg->msg_namelen = 0;/* Lock the socket to prevent queue disordering* while sleeps in memcpy_tomsg*/if (!siocb->scm) {siocb->scm = &tmp_scm;memset(&tmp_scm, 0, sizeof(tmp_scm));}mutex_lock(&u->readlock);do {int chunk;struct sk_buff *skb;unix_state_lock(sk);skb = skb_dequeue(&sk->sk_receive_queue);if (skb == NULL) {if (copied >= target)goto unlock;/**    POSIX 1003.1g mandates this order.*/err = sock_error(sk);if (err)goto unlock;if (sk->sk_shutdown & RCV_SHUTDOWN)goto unlock;unix_state_unlock(sk);err = -EAGAIN;if (!timeo)break;mutex_unlock(&u->readlock);timeo = unix_stream_data_wait(sk, timeo);if (signal_pending(current)) {err = sock_intr_errno(timeo);goto out;}mutex_lock(&u->readlock);continue;unlock:unix_state_unlock(sk);break;}unix_state_unlock(sk);if (check_creds) {/* Never glue messages from different writers */if ((UNIXCB(skb).pid != siocb->scm->pid) ||(UNIXCB(skb).cred != siocb->scm->cred)) {skb_queue_head(&sk->sk_receive_queue, skb);break;}} else {/* Copy credentials */scm_set_cred(siocb->scm, UNIXCB(skb).pid, UNIXCB(skb).cred);check_creds = 1;}/* Copy address just once */if (sunaddr) {unix_copy_addr(msg, skb->sk);sunaddr = NULL;}chunk = min_t(unsigned int, skb->len, size);if (memcpy_toiovec(msg->msg_iov, skb->data, chunk)) {skb_queue_head(&sk->sk_receive_queue, skb);if (copied == 0)copied = -EFAULT;break;}copied += chunk;size -= chunk;/* Mark read part of skb as used */if (!(flags & MSG_PEEK)) {skb_pull(skb, chunk);if (UNIXCB(skb).fp)unix_detach_fds(siocb->scm, skb);/* put the skb back if we didn't use it up.. */if (skb->len) {skb_queue_head(&sk->sk_receive_queue, skb);break;}consume_skb(skb);if (siocb->scm->fp)break;} else {/* It is questionable, see note in unix_dgram_recvmsg.*/if (UNIXCB(skb).fp)siocb->scm->fp = scm_fp_dup(UNIXCB(skb).fp);/* put message back and return */skb_queue_head(&sk->sk_receive_queue, skb);break;}} while (size);mutex_unlock(&u->readlock);scm_recv(sock, msg, siocb->scm, flags);
out:return copied ? : err;
}

在这个函数中只有一个出口:

return copied ? : err;

copied在unix_stream_recvmsg为已读取的字节数。那么这行代码的意义就比较明显了,当已读取了一定数据,那么read的返回值即为读取的字节数。当没有读取任何数据时,就返回err。

下面看前面提出的两个问题:
1. 何时返回值为0;
2. 被信号中断时,read的返回值是什么?
3. 对端关闭后,是否可以继续读取对端关闭前发送的数据呢?

先看第一个问题:何时返回值为0。这需要copied为0,且err为0。

 err = sock_error(sk);if (err)goto unlock;
if (sk->sk_shutdown & RCV_SHUTDOWN)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的行为,是关闭发送还是接收。

第二个问题,被信号中断时,返回值是什么?

timeo = unix_stream_data_wait(sk, timeo);
if (signal_pending(current)) {err = sock_intr_errno(timeo);goto out;
}

这几行代码是这个问题的答案。这几行代码同样是处于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. No.002 关于Python函数返回值的三种情况

    关于Python函数返回值的三种情况,无返回值.有1个返回值,以及有多个返回值 # 本批次所有文章都是跟着马士兵的杨淑娟老师的视频教学所得,视频地址在下面: # https://www.bilibil ...

  2. IDEA自动补全返回值的三种方式

    一. 前言 平常在编码的过程中,可能需要调用第三方Api接口,这个过程中可能涉及到不太熟悉第三方Api接口的返回值类型,有可能是String.int.Object.void 等等这些,为了方便快速编码 ...

  3. Elasticsearch 设置默认值的三种方式

    1.实战问题 在使用 Elasticsearch 过程中,不免还会有 Mysql 等关系型数据库的使用痕迹,以下两个都是实战开发问到的问题: Elasticsearch 新增字段,能在 Mapping ...

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

    第一种情况 os.system('ps aux') 执行系统命令,没有返回值 第二种情况 result = os.popen('ps aux') res = result.read() for lin ...

  5. 【详细】传参的四种方式以及返回传值的三种方式

    [转载]本文主要参考链接下博主文章,再行总结的 传参的四种方式 按值传参 在调用函数时,将实参的值拷贝给形参,形参在函数中被使用.对形参的操作不会影响到实参的值.但是因为发生了拷贝会增加系统开销. p ...

  6. vue中处理后台返回的 html 特殊标签(‘\lt; p style=“xxx“ \gt;‘)或(\<p>)的三种情况及传给后端数据的解决方案

    问题一:vue中处理后台返回的 html 特殊标签('&lt: p style="xxx" &gt:')或(\<p>)的三种情况 返回数据 // 返回数 ...

  7. 交换a,b值的三种方法

     给定两个数a=10,b=20交换a与b的值的三种方法 1.首先第一种是我们最容易想到的,定义一个空tmp,用其交换,在三个数按从小到大(或从大到小)排序输出时曾用到过 2.第二种方法,用加减法来 ...

  8. matlab求pi值的三种方法

    https://www.icourse163.org/learn/CSU-1002475002?tid=1450231442#/learn/content?type=detail&id=121 ...

  9. JAVA 取出Map元素值 的三种方法

    JAVA 取出Map元素值 的三种方法: (1)方法一: 通过Map的keySet()方法获取key 的set ,迭代set元素(每个元素调再用Map的get()方法); (2)方法二 : 通过Map ...

最新文章

  1. 0UL能做C语言常量吗,c语言中的0UL或1UL是什么意思
  2. sqlite3里类似top的用法
  3. Python代码运行不够流畅?看大神如何多角度优化!
  4. 使用ssh-keygen生成私钥和公钥
  5. Basic Example of JMX Technology--转载
  6. 讲解虚拟服务器的书_程序员不得不看的书
  7. 2021阿里巴巴研发效能峰会来啦!
  8. 用“夜间模式”模式(javascript书签)浏览网页
  9. java集合基础_java常用集合基础知识
  10. NanShan开源即时通讯团队讨论程序员空闲期可以做的事
  11. C/C++ OpenCV之Scharr边缘检测
  12. javascript在数组的开头推送元素[重复]
  13. pmp学习资料_201912期PMP学习内容汇总表
  14. 通用技术和信息技术合格考知识点_通用技术学业水平考试必背知识点
  15. SONY α系列(A6000A7)数码微单相机APP破解免付费安装教程
  16. OFDM学习笔记(四)(信道估计简介)
  17. Linux下文件夹下子文件全部复制到多个文件夹中
  18. ES集群单机(单节点/多节点)搭建
  19. HTML5七夕情人节表白网页制作【浪漫森林落叶钢琴紫色3D相册】HTML+CSS+JavaScript
  20. 洞见科技解决方案总监薛婧:联邦学习助力数据要素安全流通

热门文章

  1. 单词 ---- part3
  2. 链表和动态申请内存空间
  3. 如何在自己的网站加入qq在线交谈功能
  4. A Fixed-Point Model for Pancreas Segmentation in Abdominal CT Scans
  5. 秋天SWF生成PDF免费版V1.4
  6. 【MFC_消息】WM_DRAWITEM与DrawItem()的讨论
  7. 巨噬细胞膜囊泡化纳米粒|干细胞膜囊泡包裹在PLGA纳米颗粒表面
  8. 透过汽车之家二手车业务,看二手车市场的模式终局
  9. ipad协议720版本
  10. Java使用JAVE获取MP4播放时长