这是一篇查漏补缺的文章,探究一下linux网络编程中read函数的几种返回值,以及分别在什么情况下发生的,当然也会顺带提及 TCP 的一点点知识(毕竟谈到网络是离不开TCP的)。
为了验证,写了一个 client 和 server 来测试相关的东西。
TCP的三次握手和三次挥手
是的,你没看错,是三次挥手,而不是四次。一般情况下,TCP在建立连接是需要三次握手,在断开连接时需要四次挥手,但是有时只需要三次挥手就够了,下图是我用tcpdump的抓包情况:
上图可以分为三个部分:
- 建立连接,三次握手,红色部分;
- 数据传输,蓝色部分;
- 关闭连接,三次挥手,紫色部分。
对应TCP的传输过程如下:
出现三次挥手的原因是因为,被动关闭连接的一端(本图中的 server 端)缓冲区内没有需要发送的数据,所以将ACK 和 FIN 合并发送给了 Client 端。如果 server 端收到 FIN 时缓冲区内还有未发送的数据,那么 server 端会先回 ACK,等到数据发送完成,再发送 FIN,这样就是通常我们看到的四次挥手了。
阻塞模式下对 read/write 的测试
测试1
流程如下:
结果:
当缓冲区还有未读取的数据时,调用 close 函数关闭 socket,会触发 TCP 发送 RST,此时对方等到数据接收,调用 read 函数,会得到 -1 的返回值,errno 被设置为104 (Connection reset by peer),如果在收到 RST 之后仍然继续调用 write 函数,会触发系统的 SIGPIPE 信号,导致程序退出(如果未处理该信号的话)。
测试2
流程如下:
结果:
当对方已经关闭连接时(即对方发送了 FIN),此时再调用 write 写数据,会触发对方发送一个 RST,如果忽略 SIGPIPE 信号,继续 write 数据,得到的返回值是 -1,errno被设置为32(Broken pipe)。
测试3
流程如下:
结果:
调用 setsockopt 给read设置一个超时时间(setsockopt(sockfd, SOL_SOCKET,SO_RCVTIMEO, &ti, sizeof(ti));
),超时之后,read函数返回-1,errno被设置为11(Resource temporarily unavailable)。
非阻塞模式下对read的测试
使用 fcntl 设置socket为非阻塞模式,调用 read 的结果是会立刻返回 -1,然后errno被设置为了11。
结论
read 函数返回值:
- 大于0:成功读取的数据长度(Byte);
- 等于0:该 socket 已经关闭;
- 等于-1:异常发生,包括但不限于以下几种:
- 超时,errno=11;
- 连接异常关闭(RST),errno=104;
- 主动关闭socket后再去 read,errno=9;
- 非阻塞模式下的没有数据时,errno=11。
write 函数返回值:
- 大于0:成功写入的数据长度(Byte);
- 等于0:写入长度为0;
- 小于0:异常发生,包括但不限于以下几种:
- 主动关闭再写数据,errno=9;
- 连接异常关闭(RST)之后再写数据,errno=32。