1、 正常的TCP连接
数据包从发送方发出,结果一层层的网络传递,到达网卡,而后经过DMA传递到RingBuffer,经过内核的硬中断、软中断处理、协议栈处理,最终放入Sokcet 的接收队列中,而后唤醒用户线程(详情看图解linux发包全过程)。
2、 TCP连接的三次握手
存在两个队列:全连接队列、半连接队列
2.1 具体过程
- 客户端发出SYNC包:客户端一般是通过connect系统调用来发出SYN的,这里牵涉到本机的系统调用和软中断的CPU耗时开销
- SYN传到服务器:SYN从客户端网卡被发出,开始“跨过山和大海,也穿过人山人海......”,这是一次长途远距离的网络传输
- 服务器处理SYN包:内核通过软中断来收包,然后放到半连接队列中,然后再发出SYN/ACK响应。又是CPU耗时开销
- SYC/ACK传到客户端:SYC/ACK从服务器端被发出后,同样跨过很多山、可能很多大海来到客户端。又一次长途网络跋涉
- 客户端处理SYN/ACK:客户端内核收包并处理SYN后,经过几us的CPU处理,接着发出ACK。同样是软中断处理开销
- ACK传到服务器:和SYN包,一样,再经过几乎同样远的路,传输一遍。 又一次长途网络跋涉
- 服务端收到ACK:服务器端内核收到并处理ACK,然后把对应的连接从半连接队列中取出来,然后放到全连接队列中。一次软中断CPU开销
- 服务器端用户进程唤醒:正在被accpet系统调用阻塞的用户进程被唤醒,然后从全连接队列中取出来已经建立好的连接。一次上下文切换的CPU开销
2.2 TCP 连接建立时候的异常情况
1、仅仅两次握手可以吗
不行,如果仅仅两次握手,当服务器接收到了客户端去SYN报文时候,就进入到了Establish阶段的全连接队列中,这时候调用accept()返回一个socket,存在资源浪费(过期的SYN报文也会开启一个socket)和安全隐患(DOS攻击)
2、半连接队列满
如果连接建立的过程中,任意一个队列满了,那么客户端发送过来的syn或者ack就会被丢弃。客户端等待很长一段时间无果后,然后会发出TCP Retransmission重传。拿半连接队列举例:
解决方案
1. 半连接队列满了
(1)去调整系统中半连接队列的大小,
(2)采用syncookies的方式 et.ipv4.tcp_syncookies:将连接信息编码在ISN中返回给客户端,这时server不需要将半连接保存在队列中,而是利用客户端随后发来的ACK带回的ISN还原连接信息,以完成连接的建立
2. 全连接队列满了 调整tcp_abort_on_overflow 关闭时:
客户端会重传,超过了次数,才会发送RST表明了无法建立连接。
开启的时候,当全连接队列满了,会自动发送RST
3、三次握手中的丢包处理
注意下第三个包丢失的情况
如果A 发送了数据,会自动带上ACK 同时接受A的DATA 如果B 想要发送数据,但是发送不了(因为没有到ES阶段),所以会重传第二个报文,直到A确认接受 如果双方都不发送数据, B 会周期性重传
疑惑?这里第二个包丢失情况下,为什么A不会重传,我感觉A也会重传,因为一直没有收到B的确认,希望大佬解答
3、四次挥手
记住2msl 和 发送FIN 代表的是自己已经没有需要发送的数据,处在等待关闭的阶段
4、重传机制
5.1 超时重传
存在两种情况,一种是 数据包丢失,一种是确认包丢失
5.1 快速重传
连续收到三个相同的ACK,立刻重传所需要的 seq
5.2 SACK
这种方式需要在 TCP 头部「选项」字段里加一个 SACK
的东西,它可以将缓存的数据发送给发送方,这样发送方就可以知道哪些数据收到了,哪些数据没收到,知道了这些信息,就可以只重传丢失的数据。
5、 滑动窗口机制
滑动窗口同时存在于接受方和发送发,TCP 头里有一个字段叫 Window
,也就是窗口大小
这种情况下,说明 600已经接收到了,累计确认叫做累计应答。
如果连续收到三个ACK600 就会启动快速重传
6、流量控制
TCP 提供一种机制可以让「发送方」根据「接收方」的实际接收能力控制发送的数据量,这就是所谓的流量控制。 下面举个栗子,为了简单起见,假设以下场景:
客户端是接收方,服务端是发送方
假设接收窗口和发送窗口相同,都为 200
假设两个设备在整个传输过程中都保持相同的窗口大小,不受外界影响
流量控制
根据上图的流量控制,说明下每个过程:
客户端向服务端发送请求数据报文。这里要说明下,本次例子是把服务端作为发送方,所以没有画出服务端的接收窗口。
服务端收到请求报文后,发送确认报文和 80 字节的数据,于是可用窗口 Usable 减少为 120 字节,同时 SND.NXT 指针也向右偏移 80 字节后,指向 321,这意味着下次发送数据的时候,序列号是 321。
客户端收到 80 字节数据后,于是接收窗口往右移动 80 字节,RCV.NXT 也就指向 321,这意味着客户端期望的下一个报文的序列号是 321,接着发送确认报文给服务端。
服务端再次发送了 120 字节数据,于是可用窗口耗尽为 0,服务端无法在继续发送数据。
客户端收到 120 字节的数据后,于是接收窗口往右移动 120 字节,RCV.NXT 也就指向 441,接着发送确认报文给服务端。
服务端收到对 80 字节数据的确认报文后,SND.UNA 指针往右偏移后指向 321,于是可用窗口 Usable 增大到 80。
服务端收到对 120 字节数据的确认报文后,SND.UNA 指针往右偏移后指向 441,于是可用窗口 Usable 增大到 200。
服务端可以继续发送了,于是发送了 160 字节的数据后,SND.NXT 指向 601,于是可用窗口 Usable 减少到 40。
客户端收到 160 字节后,接收窗口往右移动了 160 字节,RCV.NXT 也就是指向了 601,接着发送确认报文给服务端。
服务端收到对 160 字节数据的确认报文后,发送窗口往右移动了 160 字节,于是 SND.UNA 指针偏移了 160 后指向 601,可用窗口 Usable 也就增大至了 200。
流量控制主要是服务端控制发送方的发送速度,告诉发送方你还能发送多少数据给我,其中窗口的大小和服务器的接收缓冲区大小有关系,具体可以参考2,讲的比较细
7、拥塞控制
流量控制主要是控制发送发的发送速度,拥塞控制主要是发送发根据当前的网络情况自动控制发送速率
拥塞窗口 cwnd是发送方维护的一个的状态变量,它会根据网络的拥塞程度动态变化的。
我们在前面提到过发送窗口 swnd
和接收窗口 rwnd
是约等于的关系,那么由于加入了拥塞窗口的概念后,此时发送窗口的值是swnd = min(cwnd, rwnd),也就是拥塞窗口和接收窗口中的最小值。
注意下,在拥塞避免里面可能发生超时重传和快速重传两种情况
慢开始
拥塞窗口指数增加,直到慢启动的门限
指数增长
拥塞避免
那么进入拥塞避免算法后,它的规则是:每当收到一个 ACK 时,cwnd 增加 1/cwnd。
当 8 个 ACK 应答确认到来时,每个确认增加 1/8,8 个 ACK 确认 cwnd 一共增加 1,于是这一次能够发送 9 个 MSS 大小的数据,变成了线性增长。
线性增长
注意,在拥塞避免的时候,可能会发生 快速重传和超时重传两种情况,两种情况都是门限值减半,其中快速重传--快速恢复 的拥塞窗口等于门限大小,超时重传--拥塞恢复的拥塞窗口等于1
快重传
采用累计ACK时候,连续收到三个相同的ack报文,启动快速重传,重传缺失的报文
门限值减半 拥塞窗口从
快回复
拥塞窗口直接从门限值开始进入拥塞避免
超时重传
如果发生了超时重传,说明当前的网络环境比较差(快速重传起码能接收到,超时重传是接收不到的),
拥塞恢复
1.设置sshthresh = cwnd /2
2.设置cwnd 重置为 1
3.进入慢启动过程
参考
www.cnblogs.com/zh1164/p/10… juejin.cn/post/685457… www.cnblogs.com/myworld7/p/… www.pianshen.com/article/526…