本文的copyleft归gfree.wind@gmail.com全部,使用GPL发布,能够自由拷贝,转载。但转载请保持文档的完整性,注明原做者及原连接,严禁用于任何商业用途。
======================================================================================================
今天探讨一个很看似简单的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;
- }
在这个函数中只有一个出口:
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仍然会采起一致的行为——有心人能够去看代码验证一下。