这是一篇查漏补缺的文章,探究一下linux网络编程中read函数的几种返回值,以及分别在什么情况下发生的,当然也会顺带提及 TCP 的一点点知识(毕竟谈到网络是离不开TCP的)。

为了验证,写了一个 client 和 server 来测试相关的东西。

TCP的三次握手和三次挥手

是的,你没看错,是三次挥手,而不是四次。一般情况下,TCP在建立连接是需要三次握手,在断开连接时需要四次挥手,但是有时只需要三次挥手就够了,下图是我用tcpdump的抓包情况:

tcp连接过程
tcp连接过程

上图可以分为三个部分:

  • 建立连接,三次握手,红色部分;
  • 数据传输,蓝色部分;
  • 关闭连接,三次挥手,紫色部分。

对应TCP的传输过程如下:

tcp传输过程
tcp传输过程

出现三次挥手的原因是因为,被动关闭连接的一端(本图中的 server 端)缓冲区内没有需要发送的数据,所以将ACK 和 FIN 合并发送给了 Client 端。如果 server 端收到 FIN 时缓冲区内还有未发送的数据,那么 server 端会先回 ACK,等到数据发送完成,再发送 FIN,这样就是通常我们看到的四次挥手了。

阻塞模式下对 read/write 的测试

测试1

流程如下:

测试1
测试1

结果:

当缓冲区还有未读取的数据时,调用 close 函数关闭 socket,会触发 TCP 发送 RST,此时对方等到数据接收,调用 read 函数,会得到 -1 的返回值,errno 被设置为104 (Connection reset by peer),如果在收到 RST 之后仍然继续调用 write 函数,会触发系统的 SIGPIPE 信号,导致程序退出(如果未处理该信号的话)。

sigpipe信号
sigpipe信号

测试2

流程如下:

测试2
测试2

结果:

当对方已经关闭连接时(即对方发送了 FIN),此时再调用 write 写数据,会触发对方发送一个 RST,如果忽略 SIGPIPE 信号,继续 write 数据,得到的返回值是 -1errno被设置为32(Broken pipe)。

测试3

流程如下:

测试3
测试3

结果:

调用 setsockopt 给read设置一个超时时间(setsockopt(sockfd, SOL_SOCKET,SO_RCVTIMEO, &ti, sizeof(ti)); ),超时之后,read函数返回-1errno被设置为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。