TCP协议--TCP连接的状态转移

《Linxu高性能服务器编程》阅读笔记:

1. TCP服务端连接的状态转移

  (1) 服务器调用listen()系统调用进入LISTEN(监听)状态,被动等待客户端连接。

  (2) 服务端一旦监听到某个连接请求(收到同步报文段),就将该连接放入内核等待队列中,并向客户端发送带有SYN标志的确认报文段,此时服务端进入SYN_RCVD状态。

  (3) 如果服务端成功接收到客户端发送回的确认报文段,就转移至ESTABLISHED状态。ESTABLISHED状态表示连接双方能够进行双向数据传输。

  (4) 当客户端主动关闭连接时(通过close()/shutdown()系统调用向服务端发送结束报文),服务端将进入CLOSE_WAIT状态,表示在等待服务端应用程序关闭该连接,同时发送确认报文。当应用程序关闭该连接时,会给客户端发送一个结束报文段来关闭连接,服务器转移到LAST_ACK状态,以等待客户端对结束报文的最后一次确认,一旦确认完毕,连接彻底关闭,即CLOSED状态。

2. TCP客户端连接的状态转移

  (1) 客户端调用connect()系统调用主动与服务端建立连接: connect()系统调用首先给服务端发送一个同步报文段(SYN),接着转移到SYN_SENT状态。connect()系统调用会因为如下两个原因返回失败:
  a. 连接的目标端口不存在(服务器没有任何进程在监听该端口),或者该端口仍被其他处于TIME_WAIT状态的连接所占用,则服务端将给客户端发送一个复位报文段,connect()调用失败。
  b. 连接的目标端口存在,但connect()在超时时间内没收到服务端发来的确认报文段,则connect()调用失败。

  connect()调用失败将使连接立即返回最开始的CLOSED状态,若客户端成功收到服务端的同步报文段和确认,则connect()调用成功,客户端转移至ESTABLISHED状态。

  (2) 当客户端执行主动关闭时,它将向服务端发送一个结束报文段(FIN),同时转移至FIN_WAIT_1状态。在接收到服务端专门用于确认目的确认报文段后,客户端转移至FIN_WAIT_2状态。像这样的状态(服务端处于CLOSE_WAIT,客户端处于FIN_WAIT_2),即该TCP连接处于半关闭状态: 客户端可以接收数据但不能(向服务端)发送数据。此时如果服务端也关闭连接(发送结束报文段),则客户端将给予确认并键入TIME_WAIT状态。

  在TCP协议–TCP连接的建立和关闭一文中,通过tcpdump抓取的数据包可以发现,服务端对于客户端的FIN报文段没有单独发回确认报文段,而是跟接下来的发回客户端的FIN报文段一并发送,在这种情况下,客户端从FIN_WAIT_1状态直接进入TIME_WAIT状态,中间不经过FIN_WAIT_2状态

  处于FIN_WAIT_2状态的客户端需要等待服务端发来结束报文才能转移至TIME_WAIT状态,否则一直停留在这个状态,也就是处于半关闭状态。如果客户端应用程序不是为了在半关闭接收数据,连接长时间处于FIN_WAIT_2状态下并无益处。另外,如果客户端执行半关闭之后还没等服务端关闭就强行退出也会出现一直处于FIN_WAIT_2状态的客户端连接,此时客户端连接交付系统内核管理,称之为孤儿连接(跟孤儿进程类似)。Linux操作系统为了防止孤儿连接长时间存留与内核中,定义了两个内核变量:

/proc/sys/net/ipv4/tcp_max_orphans
/proc/sys/net/ipv4/tcp_fin_timeout

TCP协议--TCP连接的状态转移_第1张图片

  综上,TCP连接的建立和关闭过程中,客户端和服务端的状态变化可概括如下图:
TCP协议--TCP连接的状态转移_第2张图片

  客户端在收到服务端的结束报文段(报文段6)之后,并没有直接进入CLOSE状态,而转移到TIME_WAIT状态。在TIME_WAIT状态中,客户端连接需要等待2MSL(Maximum Segment Life,报文最大生存时间)的时间才完全关闭。TIME_WAIT状态存在的原因为:

  a. 可靠的终止TCP连接
  假设图中的报文7丢失,那么服务端将会重发结束报文段,因此客户端需要停留在某个状态以处理重复收到的结束报文段,即向服务端发送确认报文段。若客户端被异常终止(没有转移至TIME_WAIT状态),将会以复位报文段端回复服务端,服务端会认为这是一个错误。

  b. 保证让迟来的TCP报文段有足够的时间被识别并丢弃
  在Linux系统中,一个TCP端口不能被同时打开2次及以上,而一个TCP连接处于TIME_WAIT状态时,将无法立即使用该连接占用着的的端口来建立一个新连接。假设不存在TIME_WAIT状态,则应用程序可以建立与刚关闭的连接相同IP地址、端口号的新连接,新连接与被关闭的连接就可能收到属于原来连接的、迟来的报文段。这显然不应该发生,所以这也是TIME_WAIT存在的原因。

  TCP报文段的最大生存时间是MSL,TIME_WAIT坚持2MSL的时间能够确保网络上的两个传输方向上的尚未被接收到的、迟到的TCP报文段都已经消失(被中转路由丢弃)。

  对客户端来说,一般是使用系统自动分配的临时端口号来建立连接,由于随机性,临时端口号一般和程序上一次使用的还处于TIME_WAIT状态的连接使用的端口号不同,所以客户端一般可以立即重启。但如果服务端主动关闭连接后被异常终止,由于它使用的是同一个知名的端口号,所以连接的TIME_WAIT状态将导致它不能立即重启。在编程中,程序员可以通过设置socket选项SO_REUSEADDR来强制进程立即使用处于TIME_WAIT状态的连接占用的端口号。

你可能感兴趣的:(Linux系统/网络编程,Linux编程)