TCP三次握手四次挥手

来源:互联网 发布:网络推广博客方案 编辑:程序博客网 时间:2024/06/05 15:36
本篇博客主要讲解 TCP 的三次握手和四次挥手的过程
感谢观看,欢迎提出建议和问题
联系方式:blbagony@163.com
[参考资料] : linux高性能服务器编程

此篇文章大多引用自此书。


TCP 服务的特点

  • 传输层协议主要有两个:TCP 协议和 UDP 协议。TCP 协议相对于 UDP 协议的特点是:面向连接、字节流和可靠传输

  • 使用 TCP 协议时通信双方必须先建立连接,然后才能开始数据的读写。双方都必须为链接分配必要的内核资源,以管理连接状态和链接上数据的传输。TCP 链接是全双工的,即双方的数据读写可以通过一个链接进行。完成数据交换之后,通信双方都必须断开连接以释放系统资源。

  • TCP 协议的这种连接是一对一的,所以基于广播和多播 (目标是多个主机地址)的应用程序不能使用 TCP 服务。较之无连接协议 UDP 则非常适合广播和多播。

  • 当发送端应用程序执行多次读写操作时,TCP 模块现将这些数据放入 TCP 发送缓冲区中。当 TCP 模块真正开始发送这些数据时,发送缓冲区的数据可能被封装成一个或多个 TCP 报文段发出。因此,TCP 模块发送出的 TCP 报文段的个数和应用程序执行的写操作次数之间没有固定的数量关系。

  • 当接收到一个或多个 TCP 报文段后,TCP 模块将它们携带的应用程序数据按照 TCP 报文段序号可以一次性将 TCP 接收缓冲区的数据全部读出,也可以分多次读出,这取决于用户指定的程序读缓冲区的大小。因此,应用程序执行的都操作次数和 TCP 模块接收到的 TCP 报文段个数之间也没有固定数量关系。

  • 综上所述,发送端执行写操作数和接收端执行读操作数之间没有任何数量关系,这就是字节流的概念:应用程序对数据的发送和接收没有边界限制。UDP 则不然。发送端应用程序每执行一次写操作(通过 recvfrom 系统调用),否则就会丢包(这经常发生在较慢的服务器上)。并且,如果用户没有指定足够的程序缓冲区来读取 UDP 数据,则 UDP 数据将被截断。

  • 下图是 TCP 字节流服务和 UDP 数据包服务的区别

center


  • TCP 传输是可靠的。

    1. TCP 协议采用应答机制,即发送端发送的每个 TCP 报文段都必须得到接收方的应答,才认为这个 TCP 报文段传输成功。

    2. TCP 协议采用超时重传机制,发送端在发送出一个 TCP 报文段之后启动定时器,如果在定时时间内未收到应答,他将重发该报文。

    3. TCP 协议会保证数据的按需到达以及丢包重传。

    4. TCP 协议是基于滑动窗口的流量控制。

    5. 当遇到大面积丢包时会触发网络拥塞避免算法


TCP 头部结构

center

  • 16 为端口号:告知主机该报文段来自哪里(源端口)以及传给哪个上层协议或应用程序(目的端口)的。进行 TCP 通信时,客户端通常使用系统自动选择的临时端口号,而服务器则使用知名端口号。所有知名服务使用的端口号都定义在 /etc/services 文件中。

  • 32 位序号:一次 TCP 通信(从 TCP 链接建立到断开)过程中某一个传输方向上的字节流的编号。假设主机 A 和主机 B 进行 TCP 通信,A 发送给 B的第一个 TCP 报文段中,序号值被系统初始化为某一个随机值 ISN (Initial Sequence Number,初始序号值)。那么在该传输方向上(从 A 到 B ),后续的 TCP 报文段中序号值将被系统设置成 ISN 加上该报文段所携带数据的第一个字节在整个字节流中的偏移。

  • 32 位确认信号:用作对另一方发送来的 TCP 报文段响应。其值是收到的报文段的序号加 1。

  • 4位头部长度:标识该 TCP 头部有多少个 32bit 字(4 字节)。因为 4 位最大能表示 15,所以 TCP 头部长度最长 60 字节。

  • 6 为标志位

    1. URG 标志,表示紧急指针是否有效。
    2. ACK 标志,表示确认信号是否有效。我们称携带 ACK 标志的 TCP 报文段为确认报文段。
    3. PSH 标志,提示接收端应用程序应该立即从 TCP 接收缓冲区都走数据,为接受后续数据腾出空间(如果应用程序不将接收到的数据读走,他们就会一直停留在 TCP 接收缓冲区)。
    4. RST 标志, 表示要求双方重新建立一个链接。我们称携带 RST 标志的 TCP 报文为复位报文段。
    5. SYN 标志,表示亲求建立一个链接。我们称携带 SYN 标志的 TCP 报文段为同步报文段。
    6. FIN 标志,表示通知对方本端要关闭连接了。我们称携带 FIN 的标志段的 TCP 报文段为结束报文段。
  • 16 位窗口大小:是 TCP 流量控制的一个手段。他告诉对方本端的 TCP 接收缓冲区还能容纳多少字节的数据,这样对方就能控制发送数据的速度。

  • 16 位校验和:由发送端填充,接收端对 TCP 报文执行 CRC 算法以检验报文段在传输过程中是否损坏。注意,校验不仅包含 TCP 头部,也要包含数据部分。这时 TCP 可靠传输的一个重要保障。

  • 16 紧急指针:是一个正偏移量。它和序号字段的值相加表示最后一个紧急数据的下一个字节的序号。TCP 紧急指针是发送端向接收端发送紧急数据的方法。


TCP三次握手

center

  1. client 端(ernest-laptop)向 server(Kongming20)发送第一个 TCP 报文段包含 SYN 标志,因此它是一个同步报文段,即 client 向 server 端发起连接请求。同时,该同步报文段包含一个 ISN 值为 535734930 的序号。
  2. server 端在接收到 client 端的请求后,向 client 端发送一个同步报文段,表示 server 端同意与 client 端建立连接。同时发送自己的 ISN 之为 2159701207 的序号,并对第一个同步报文段进行确认。确认值是 535734931 ,即第一个同步报文段序号值加 1。序号值是用来标识 TCP 数据流中每一个字节的。但同步报文没有携带任何应用程序数据,它也要占一个序号值。
  3. 第三个TCP 报文段是 client 端对 server 连接报文的确认报文。至此,TCP 连接就建立起来了。双方为此次连接分配系统资源。

TCP 四次挥手

center

  1. 后面 4 个 TCP 报文段是关闭连接的过程。第 4 个 TCP 报文段包含 FIN 标志,因此它是一个结束报文段,主动断开连接一方(client)向 server 发送包含 FIN 标志的报文段, 表示 client 端向要与 server 端断开连接。
  2. server 端收到含有 FIN 标志的报文段向 client 段发送确认报文,表示已经知道此事件。
  3. 发送完确认报文后 server 端也要向 client 发送一个含有 FIN 标志的同步报文段,表示我也要关闭连接。
  4. 当 client 端收到 server 端含有 FIN 标志的同步报文段时,client 端会向 server 端发送确认报文,表示我知道你要断开连接了。此后 主动断开连接的一方会进入 TIME_WAIT 状态。至此 TCP 四次挥手完成,连接关闭。

    • 为什么是 3 次挥手
      如果是两次挥手,当 server 端收到 client端发送的含有 SYN 标志的同步报文时,server 端向 client 端发送含有 ACK 标志的确认报文,至此 server 端会认为连接建立成功。同时,为链接分配资源。假设 server 端发送的确认报文丢失,没有被 client 端收到,client 重发请求连接报文,此时 server 端已经为 client 的连接分配了资源。再一次接收到 client 端的请求时有会重复上一过程。并且,server 为 client 分配的是知名端口,即对应的 client 有对应的 port 号,client 在绑定时会报出文件已存在的错误,并且 sever 段之前为 client 建立的连接资源会长时间闲置,造成了 server 端的资源浪费,我们知道 server 端本身资源有限,所以不能让 server 端冒险。

    • 3 次握手的优点,server 端接收到 client 的连接请求报文后,并不会认为链接完成,他会向 client 端发送一个同步和确认报文,表示我知道你想要建立连接,但你必须让我知道你收到我的确认信息了,所以只有当 server 端收到 client 端的对上一报文的确认报文后才认为链接建立成功,并为连接分配资源。如果 server 端没有收到 client 端的确认报文,不会认为连接建立,会向 client 端重新发送确认同步报文。这样做可以让 client 端承担风险,client 端的 port 由系统随机分配,所以不会出现上述 server 端的问题。

    • 为什么是四次挥手
      连接在关闭后,双方都需要将连接关闭,并且将连接资源释放,主动断开连接的一方(client)向对方(server)首先发送包含 FIN 标志的结束报文,此时 server 知道 client 端向要关闭连接会向 client发送确认表示知道你想要断开连接了。同时,他也会向 client 端发送同步报文,表示我也要关闭连接了, client 端收到 server 端发送的结束报文后,向 server 回应确认报文,并进入 TIME_WAIT 状态。此时双方都知道对方要关闭连接了,双方为连接建立的资源也会释放,注意的是,主动断开连接的一方会先进入 TIME_WAIT 状态,之后才会将绑定的端口解除该状态。下面会讲到为什么主动链接的一方会进入 TIME_WAIT 状态。


TCP 状态转移

center

  • 首先我们讨论服务器的典型状态转移过程

    1. 服务器通过 listen 系统调用进入 LISTEN 状态,被动等待客户连接,执行被动连接。服务器一旦监听到某个连接请求(收到同步报文段),就将该连接放入内核的等待队列中,并向客户端发送带 SYN 的确认报文。此时链接处于 SYN_RCVD 状态。如果服务器成果接受到客户端发送回的确认报文,则该链接转移到 ESTABILSHED 状态。ESTABILSHED 状态表示连接双方能够进行双向数据传输。

    2. 当客户端主动关闭连接时,服务器通过返回确认报文使连接进入 CLOSE_WAIT 状态。这个状态表示等待服务器应用程序关闭连接。通常,服务器检测到客户端关闭连接后,也会立即给客户端发送给结束报文来关闭连接。这会使连接转移到 LAST_ACK 状态,以等待客户对结束报文段的最后一次确认。一旦确认完毕,连接就彻底关闭了。

  • 下面讨论客户典型状态转移过程

    1. 客户端通过 connect 系统调用主动与服务器建立连接。connect 系统调用首先向服务器发送同步报文段,使连接转移至 SYN_SENT 状态。此后,connect 系统调用可能有两个原因失败返回:
    2. 如果 connect 连接的目标端口不存在(未被任何进程监听),或者改端口仍被处于 TIME_WAIT 状态的连接所占用,此时服务器会给客户端发送一个复位报文段,connect 调用失败。

    3. 如果目标端口存在,但 connect 在超时时间内未收到服务器的确认报文段,则 connect 调用失败。

connect 调用失败会使连接立即返回 CLOSED 状态。如果客户端成功收到服务器的同步和确认,则 connect 调用成功返回,链接转移至 ESTABLISHED 状态。

  • 当客户端主动关闭连接时,他会向服务器发送结束报文段,同时进入 FIN_WAIT_1 状态。若此时客户端成功收到服务器专门用于确认关闭连接的报文段,则连接状态转移至 FIN_WAIT_2 状态。此时服务器处于 CLOSE_WAIT 状态,这一状态可能会使双方进入半关闭状态。此时若服务器也关闭连接(发送结束报文段),则客户端将给予确认并进入 TIME_WAIT 状态。

  • 处于 FIN_WAIT_2 状态的客户端需要等待服务器发送结束报文,才能转移至 TIME_WAIT 状态,否则他会一直停留在 FIN_WAIT_2。如果不是为了在半关闭状态下继续接收数据,连接长时间停留在该状态并无益处。

  • 停留在FIN_WAIT_2 状态可能发生的情况:
    客户端执行版关闭后,未等待服务器关闭连接就强行退出。此时客户端连接由内核来管理,可以称当前连接为孤儿连接(和孤儿进程类似)。Linux 为防止孤儿链接长时间停留在内核中,定义了两个变量: tcp_max_orphans 和 tcp_fin_timeout。前者指定内核接管孤儿连接数目,后者指定孤儿连接在内核中生存时间。


TIME_WAIT

客户端在收到服务器的结束报文段之后,并没有直接进入 CLOSED 状态,而是转移到 TIME_WAIT状态。在该状态,客户端连接要等待一段长为 2MSL(Maximum Segment Life, 报文最大生存时间)的时间,才能完全关闭。MSL 是 TCP 报文段在网络中最大生存时间,标准文档 RFC 1122 的建议是 2 min。

TIEM_WAIT 状态存在的原因有两个:

  • 可靠的终止 TCP 连接。
  • 保证让迟来的 TCP 报文段有足够时间被识别并丢弃。

    1. 用于确认服务器结束报文段 6 的报文段 7 丢失,那么服务器会重发结束报文段。因此客户需要停留在某个状态已处理重复收到的结束报文段(即向服务器发送确认报文段)。否则,客户端会以复位报文段来回应服务器,尔服务器会认为这是一个错误,因为它期望的是一个像 TCP 报文段 7 那样的确认报文段。

    2. 在 Linux 系统上,一个 TCP 端口不能同时被打开多次(两次及以上)。当一个 TCP 连接处于 TIME_WAIT 状态时,我们无法立即使用该端口建立新的连接。如果不存在 TIME_WAIT 状态,应用程序可以立即建立一个和刚关闭连接一样的连接(相似指的是具有同样的 IP 地址和端口号)。这个连接称之为原来连接的化身。该化身很可能手袋原来连接的、携带应用程序数据的 TCP 报文段(迟到的报文段),这显然是不应该发生的。这就是为什么 TIME_WAIT 状态存在的第二个原因。

  • 为什么是 2MSL

TCP 报文最大生存时间是 MSL ,所以坚持 2MSL 时间的 TIME_WAIT 状态能够确保网络上两个传输方向上尚未被接受到的、迟到的 TCP 报文段都已经消失(被中转路由器丢弃)。因此,一个连接的新化身在等待 TIME_WAIT 时间后绝对不会收到属于原来连接的数据,这就是 TIME_WAIT 持续 2MSL 的原因。

  • 如何立即解除 TIME_WAIT 状态:

考虑一种情况,当某台服务器因为客户端连接过多,导致程序崩溃,我们需要立马重启服务器,可之前连接的知名端口处于 TIME_WAIT 状态(这里服务器是主动断开连接的一方),导致服务器不能立即重启。不过我们可以通过 socket 选项 SO_RESUEADDR 来强制进程立即使用处于 TIEM_WAIT 状态的连接占用的端口。

下面我们来模拟这一过程
center
其中 1 、2 是服务器处于监听状态,等待客户端连接,在第三次我们用 telnet 连接上服务器,双方处于 ESTABLISHED 状态,此状态可以相互传输数据。阶段 4、5 强制让服务器退出,处于 TIME_WAIT 状态的是客户端端口,而服务器端口则在阶段 6 马上重启,并没有等待 TIME_WAIT 状态结束。

center
server端代码
使用 setsockopt 函数,将SO_REUSEADDR用于对 TCP 套接字处于 TIME_WAIT 状态下的 socket,才可以重复绑定使用。server 程序总是应该在调用 bind() 之前设置 SO_REUSEADDR 套接字选项。TCP,先调用 close() 的一方会进入 TIME_WAIT 状态。

原创粉丝点击