YNZH's Blog

TCP三次握手、四次挥手

TCP Header

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 0                            15                              31
-----------------------------------------------------------------
| source port | destination port |
-----------------------------------------------------------------
| sequence number |
-----------------------------------------------------------------
| acknowledgment number |
-----------------------------------------------------------------
| HL | rsvd |C|E|U|A|P|R|S|F| window size |
-----------------------------------------------------------------
| TCP checksum | urgent pointer |
-----------------------------------------------------------------

//A TCP header usually holds 20 octets of data

1.三次握手

1
2
3
4
5
sequenceDiagram

Client ->> Server:SYN = 1,seq = x,
Server ->> Client:SYN = 1, ACK = 1, seq = y, ack = x+1
Client ->> Server:seq = x+1, ACK = 1,ack = y+1

1.1. 三次握手详解

  1. 首先刚开始Client和Server都是Closed状态。Client主动打开,构建一个TCB(Transmission Control Block),内容(为SYN=1,seq=x)发送到Server。然后将自己的状态设置为SYN_SEND(已发送同步块)。

  2. Server收到客户端的同步请求后,首先将状态设置为SYN_RECV(同步已接收),然后构建好TCB(SYN=1, ACK=1,seq=y,ack=x+1)。

  3. Client收到Server的同步响应请求之后,设置自己状态为ESTABLISHED(已建立连接可以发送报文),然后向服务端发送报文附带(ACK=1,ack=y+1,seq=x+1)。

  4. Server收到报文并设置自己为ESTABLISHED(已建立间接开始接受报文)。三次握手结束。

1.2. 为什么第一二次握手不传输报文?

因为三次握手是为了建立可靠的连接,刚开始还不知到服务器的状态,是未知的,而经过两次握手之后,可以确认服务器可以接收数据。如果贸然的传输报文可能第一次连接就会失败,继续重连还是失败,也许根本无法建立连接,浪费资源得不偿失。

1.3. 为什么要三次握手?两次不行吗?

不可以,假如是两次握手建立间接的话,假如客户端第一次请求建立连接被阻塞,然后客户端没及时收到服务端的确认。重新发送建立连接请求,经过两次握手建立了连接,完成数据传输之后各自关闭,加入这时候之前被阻塞的消息到达服务器,服务器就会误以为客户端还处于连接状态,等待数据的到来,而此时客户端已经关闭。服务器却处于等待接受状态。造成很多不必要的资源浪费。

2. 四次挥手

1
2
3
4
5
6
sequenceDiagram
Client ->> Server:FIN = 1, seq = x
Server ->> Client:ACK = 1, seq = y, ack = x+1
Server -->> Client:FIN = 1, seq = z, ack = x+1
Note over Client,Server: Client Wait 2MSL(最长报文传输时间)
Client ->> Server:ACK = 1, seq=x+1, ack = z+1

2.1. 四次挥手详解

  1. 首先双方都是ESTABLISHED的状态,假如Client想结束连接,Client会发送一个(FIN=1,seq=c)的报文段,将自己的状态设置为FIN-WAIT-1,告诉Server我不会再给你发数据了。你也准备关闭吧。

  2. Server接受到FIN报文后,会立即返回一个确认报文(ACK=1,seq=y,ack=c+1),告知Client我准备一下,马上关闭。同时将自己的状态设置为CLOSE-WAIT。

  3. Server准备完成之后(把剩余的一些数据发完),向Client发送(FIN=1,ACK=1,ack=c+1,seq=s)将自己的状态你设置由CLOSE-WAIT –> LAST-ACK。

  4. Client收到Server发来的带有FIN=1的确认报文之后,会立即 发送(ACK=1,ack=s+1,seq=c+1)回去,然后将自己的状态设置为FIN-WAIT-2,然后等待2个MSL(最长等待间隔)进入CLOSED状态关闭。

  5. Server收到Client发来的确认信息之后,将状态由LAST-ACK变为CLOSED,关闭服务器。

2.2. 为什么Client要等待2MSL之后才关闭?

确保自己的ACK发送到服务器,假如Server没有收到Client发来的ACK消息,超时了或者丢失了,Server肯定会再次发送FIN报文而不是傻傻的等待,因为Client收不到服务器的FIN就会以为还有数据要接收就不会Closed。而客户端正好等待两个MSL时间(发送到Server+Server重发FIN),则默认Server收到了自己的ACK消息。然后各自关闭。

2.3. 可不可以三次挥手

不可以。若没有第四次挥手,服务器第三次发送完FIN就直接关闭了,之后Server就不知道Client是否收到自己关闭的信号,若Client正好没收到则会一直傻傻的等待,而服务器已经关闭。造成资源浪费。

那可不可以将第二三次挥手合并呢?不可以!因为Client停止发送数据之后,Server的上层应用程序可能还有好多数据要发送,必须发送完才可以结束。

3. 拥塞控制(Reno) ? 流量控制 ?

流量控制:流量?啥是流量,啥是流?流是有方向的呀,所以一搬值的是端到端的传输才叫流量,啥是量?就是a到b一次传送多少嘛,所以TCP中流量控制指的是端到端的一TCP连接之间传输报文多少的控制呀,因为a和b的发送接收能力谁也不知道是不是匹配的,对吧,所以a,b各自都有一个叫做发送窗口和接收窗口的东西,在通信的 时候附带着传输过去,告诉你我就这接收,传输能力,你看着办,被给我搞多了,弄不过来可别怪我。哈哈所以a和b在建立连接的时候就会沟通,a要给b发送,就会选择 b的接收窗口和自己发送窗口,其中最小值来作为实际的发送窗口。所以就产生了滑动窗口这种策略。总结一下就是流量控制是端到端的的。

拥塞控制:英文单词是Congestion control啥意思 就像是堵车了,交警需要控制不堵车这个意思,他是站在全局的角度开看问题的,说到拥塞控制就不得不提 拥塞窗口(Congestion window,cwind)这个东东,乍一听好像和滑动窗口这个发送窗口有点像,不会是同一个东西吧,当然不是!!为啥????,虽然我们通过三次握手知道了接受方可以接收的滑动窗口大小,那我们发送方是不是可以一次性的把自己发送窗口内的报文全部发完呢?答案是不可以,因为传送过程中其实是要经过很多网络节点的,我们不知道当前路上是不是堵车啊,要是堵车我靠,发送失败那不得重传吗,得不偿失呀,所以TCP协议采用了一种比较保守的策略,除了发送方的发送窗口,还维护了一个拥塞窗口,最开始的时候拥塞窗口大小为1,意思就是尽管你的发送窗口很大,但是你一次只能发送拥塞窗口大小(1)个报文,(其实TCP这里是在小心翼翼的试探当前路上堵不堵车),如果成功接受ACK了,那么就将拥塞窗口+1,然后一次发送两个,结果都成功了,看来路况很好嘛,继续这次发三个,这样依次。。直到当前发送发的发送窗口全部发送完了(称为一轮),发现拥塞窗口 = 发送方发送窗口 大小了,仔细想想确实啊每次收到ACK都会加1,然后下一轮的拥塞窗口就会是上一轮的2倍,这就是慢启动机制,除此之外还提供了拥塞避免、快重传、快启动等机制。

慢启动会通过指数级别的增长,因此会越来越快很容易引起网络阻塞,所以对于慢启动有一个ssh(slow start threshold)阈值,达到这个阈值以后,就会进入拥塞避免阶段,这个阶段不在每轮发送完成拥塞窗口只增加一。这样就可以缓慢的增长较小网络组阻塞的几率,通过这种方式可以试探出当前网络的最大拥塞窗口,当发送方接收到连续三个重复的确认号的时候会触发快重传机制,举个例子加入发送出 4,5,6,7三个报文,那么报文4丢失也就是没有收到确认号5,那么在当接收方收到5的时候,并不会返回确认号6,因为5前面的4并没有接收到,而是会返回确认号5(表面4没收到),6和7也是同样的返回ACK(5),当发送方连续接收到3个重复的ACK(5)的时候,就会乐观的认为5,6,7已经送到了,就会快速重传报文4,同时会有ssh = max(2,cwind/ 2) 阻塞窗口cwnd = ssh + 3 * SMSS,这里我i什么要加三呢,因为收到连续三个重复ACK暗示了后面三个保温已经抵达,所以会提前透支一下窗口大小,不然可能就会导致发送变得很慢,然后当发送方接收到新发送数据的ACK时候表面之前的4,5,6,7已经全部被确认了就会将cwind重新设置为ssh(归还提前透支的窗口大小,因为发生快重传就表明网络有阻塞的风险,cwind应该减小为一半,之所以前面还加了三是因为提前透支嘛,这时候要还回来)。cwind = ssh 也就是变味了原来的一半大小,这就是所谓的快启动(不是从cwind=1开始)。加入发生了更加恶劣的阻塞情况如超时了(比连续三个重复ACK更加阻塞),TCP就会直接进入慢启动状态(cwind=1)重复之前的过程。

注意:

上述所涉及到的是Reno拥塞控制算法,此外还有Cubic是 Linux 内核 2.6 之后的默认 TCP 拥塞控制算法,

还有BBR算法是谷歌在 2016 年提出的一种新的拥塞控制算法,目前 BBR 已经集成到 Linux 4.9 以上版本的内核中。不同算法各自都有不同的使用场景。具体问题还需具体分析。

参考:拥塞控制


 评论


博客内容遵循 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 协议

本站使用 Material X 作为主题 , 总访问量为 次 。
载入天数...载入时分秒...