TCP协议------三次握手&&四次挥手

更多linux和网络知识点:linux目录索引


前言

  TCP的他点有面向连接和可靠性等特点,其中面向连接体现在三次握手和四次挥手,今天来看一下三次握手和四次挥手
  

一、三次握手

1. 过程分析

这里写图片描述

分析:

  1. 主机 A 运行TCP客户端程序,主机 B 运行TCP服务器程序。最初两端的TCP进程都处于 CLOSED(关闭)状态。

  2. B 的 TCP 服务器进程先创建传输控制块 TCB,准备接受客户进程的连接请求。然后服务器进程就处于 LISTEN(收听) 状态,等待客户的请求。如有,即做出响应。

  3. A 的 TCP 客户进程也是首先创建传输控制块 TCB,然后向 B 发出连接请求报文,报文首部中的同步位 SYN=1,同时选择一个初始序号 seq=x。TCP 规定,SYN 报文段(即 SYN=1 的报文段)不能携带数据,但要消耗掉一个序号。此时,TCP 客户进程进入 SYN-SENT(同步已发送)状态。

  4. B 收到连接请求报文段后,如同意建立连接,则向 A 发送确认。在确认报文段中应把 SYN 位和 ACK 位都置为 1,确认号是 ack=x+1,同时也要为自己选择一个初始序号 seq=y。这个报文段也不能携带数据,也同样消耗一个序号。此时 TCP 服务器进程进入 SYN-RCVD(同步收到) 状态。

  5. TCP 客户进程收到 B 的确认后,还要向 B给出确认确认报文段的 ACK 置 1,确认号 ack=y+1,而自己的序号 seq=x+1。TCP规定:ACK 报文段可以携带数据。但如果不携带则不消耗序号,在这种情况下,下一个数据报文段的序号仍是 seq=x+1。这时 TCP 连接已建立, A进入 ESTABLISHED(已建立连接) 状态。

  6. 当 B 收到 A 的确认后,也进入ESTABLISHED(已建立连接) 状态。

注意:在客户端最后一次进行确认时,只须发送ACK确认即可,不在带SYN,因为带了SYN,服务器程序就认为是一条新的连接

2. 三次握手的目的

问题:
为什么需要三次握手,而不是两次?(即为什么A最后还要发送一次确认)

  正常情况,A 发送连接请求,但因请求连接报文丢失而未收到确认。于是 A 再重传一次连接请求。后来收到确认,建立连接。数据传输完毕后,就释放连接。A 共发了两个连接请求报文段,第一个丢失,第二个到达了 B。没有“已失效的连接请求报文段”
  
  假设是两次握手,客户端发送连接请求,服务器向客户端发送确认应答,当服务器发送的确认报文丢包;此时服务器认为连接已经建立好了,就会分配资源维护连接,但是客户端没有收到,经过超时重传,又会发送 一条请求报文,此时服务器又要分配资源维护连接,此时就造成了资源的浪费;假设现在有10000个客户端建立连接,但确认应答报文全都丢了,此时服务器端就认为我有10000个连接,就会分配大量资源来维护这些连接,造成资源浪费


二、四次挥手

1. 过程分析

这里写图片描述

  1. 数据传输结束后,通信双方都可释放连接。现在 A 和 B 都处于 ESTABLISHED 状态。

  2. A 的应用程序先向其 TCP 发出连接释放报文段,并停止发送数据。主动关闭 TCP 连接。A 把连接释放报文段首部的终止控制位 FIN 置 1,其序号 seq=u,他等于前面已传送过的数据的最后一个字节的序号加 1。这时 A 进入 ==FIN-WAIT-1(终止等待1)== 状态。注意,即使 FIN 报文段不携带数据也消耗一个序号。

  3. B收到连接释放报文段后即发出确认,确认号是 ack=u+1,而这个报文段自己的序号是 v,等于 B 前面已传送的数据的最后一个字节的序号加 1.然后 B 就进入 CLOSE-WAIT(关闭等待) 状态。TCP 服务器进程这时应通知高层应用进程,因为从 A 到 B 这个方向的连接就释放了,此时 TCP 处于半关闭(half-close)状态,即 A 已经没有数据要发送了,但 B 若发送数据,A 仍要接受。

  4. A 收到 来自 B 的确认后,就进入 FIN-WAIT-2(终止等待2),等待 B 发出的连接释放报文段。

  5. 若 B 已没有要向 A 发送的数据了,其应用进程就要通知 TCP 释放连接。这时候 B 发出的连接释放报文必须使 FIN=1。现假定 B 的序号为 w(在半关闭状态 B 可能又发送了一些数据)。B 还必须重复发送上次已发送过的确认号 seq=u+1。此时 B 进入 LAST-ACK(最后确认) 状态,等待 A 确认。

  6. A 在收到 B 的连接释放报文段后,必须对此发出确认。在确认报文段中把 ACK 置 1,确认号 ack=w+1,自己的序号是 seq=u+1。然后进入 TIME-WAIT(时间等待) 状态。此时,TCP 连接还没释放掉,必须经过时间等待计时器设置的时间 2MSL 后,A 才进入到 CLOSED 状态。(时间 MSL 叫最长报文寿命)

2. TIME_WAIT 状态 

  先发送断开连接的一方处于TIME_WAIT状态,等待两个MSL的时间才能回到CLOSED状态

理解为什么要等待2MSL时间?

 第一,为了保证 A 发送的最后一个 ACK 报文段能够到达 B。这个 ACK 报文段有可能丢失,因而使处于 LAST-ACK 状态的 B 收不到对已发送的 FIN+ACK 报文段的确认。B 就会超时重传这个 FIN+ACK 报文段,而 A 就能在 2MSL 时间内收到这个重传的 FIN+ACK报文段。接着 A 重传一次确认,重新启动 2MSL 计时器。最后,A 和 B 都正常进入到 CLOSED 状态。

  第二,防止出现上面提到的“已失效的连接请求报文段”出现在本连接中。A 在发送完最后一个 ACK 报文段,在经过 2MSL,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失。
  

3. 解决 TIME_WAIT 状态引起的 bind失败的方法

  
  如果在断开连接过程中,如果是服务器主动断开连接,服务器就会进入 TIME_WAIT 状态,此时服务器还不认为我已经断开连接了,还会继续为连接分配着端口号,如果此时连接着大量的客户,服务器主动关闭连接,就会导致服务器的端口号不够用,重启服务器就会失败,这种情况是很可怕的,此时就要避免发生这种情况。
  

#include <sys/socket.h>

int setsockopt(int socket, int level, int option_name,
                           const void *option_value, 
                           size_t option_len);

使用 setsockopt()函数设置 socket 描述符的选项 SO_REUSEADDR 为 1,表示允许创建端口号相同的多个 socket 描述符。

代码如下:

int opt = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

猜你喜欢

转载自blog.csdn.net/zhangye3017/article/details/80938858