文章目录
- 一、 TCP报文的结构
- 二、TCP建立连接-三次握手
- 2.1 三次握手建立连接的过程
- 2.2 三次握手的思考
- 2.3 针对连接过程的DDOS攻击-SYN flood
- 三、 TCP断开链接-四次挥手
- 3.1 客户端主动断开链接的过程
- 3.2 四次挥手的思考
- 四、 TCP状态机
- 六、 TCP的流量控制-滑动窗口协议
- 七、 TCP拥塞控制-拥塞窗口cwnd(congestion window)
- 八、 TCP的性能分析和优化
TCP 传输控制协议(TCP,Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议。
吐血整理的TCP相关的知识,包括TCP的连接建立、连接断开,三次握手、四次挥手、还有TCP的包确认、延迟确认、慢启动、滑动窗口、拥塞控制、累计应答等和TCP相关的概念做了一个梳理。有些地方没有深入去写,因为我们毕竟不用自己去实现TCP协议,但是理解TCP的各个机制和算法可以很好的学习到设计一个靠谱的协议需要考虑到的东西是多么细致复杂。 老铁们一次看不完可以加个收藏先。
一、 TCP报文的结构
字段 | 作用 |
---|---|
源端口和目标端口 | 用来标识这个包从哪个程序发出的,和发到目标机器上由哪个程序接收。 |
序号 seq | 用来保证数据包的有序性,不然无法确定发出去的数据包的先后也没办法重新组装。 |
确认序号 ack | 用来确认收到的包的序号,可以以此告诉对方包已收到,否则就需要重发。 |
状态位 | SYN 发起连接、ACK回复、RST重新连接、FIN端开连接、UGR紧急标志位,标识紧急指针有效。、PSH标志位让接收方收到该数据后立即从TCP缓冲区中拿走该数据。 |
紧急指针 | 配和URG标志位才有效,指向数据中某个字节的结尾。此时数据从开始只紧急指针位置,为紧急数据。 |
二、TCP建立连接-三次握手
TCP连接
一个TCP连接通过四个值来识别的,也叫TCP四元组,这四个值一起唯一定义了一条连接。< 源 IP 地址、 源端口号、 目的 IP 地址、 目的端口号 >
。TCP连接的建立,可以简化为 请求->应答->应答之应答
虽然只有3个回合但里面有很多细节。
2.1 三次握手建立连接的过程
- 初始时,客户端和服务端连接未建立都是处于Close状态,服务端主动监听某个端口,处于LISTEN监听状态,然后客户端主动打开并发起TCP连接。
- 第一次握手:客户端发送连接请求,
标志位SYN=1 并且附带连接的起始序号seq=x
,seq是从一个32位计数器中获取,这个计数器4微秒+1。问:这里序号为啥不从1开始? - 第二次握手:服务端收到连接请求后,回复消息
SYN=1 ACK=1 seq=y ack=x+1
这里的seq=y是服务端的连接序号。 - 第三次握手:客户端发送
ACK=1 seq=x+1 ack=y+1
- 握手完成后客户端和服务端就可以开始互相通信了。
2.2 三次握手的思考
2.3 针对连接过程的DDOS攻击-SYN flood
DDOS又称为分布式拒绝服务,全称是Distributed Denial of Service。DDOS本是利用合理的请求造成服务器资源过载,导致服务不可用。常见的DDOS攻击有SYN flood(SYN flood)、UDP flood、ICMP、flood等,其中SYN flood是一种最为经典的DDOS攻击。SYN flood如此猖獗是因为它利用了TCP协议设计中的缺陷,而TCP/IP协议是整个互联网的基础,牵一发而动全身,如今想要修复这样的缺陷几乎成为不可能的事情。
SYN flood攻击原理
- SYN flood在攻击时,首先伪造大量的源IP地址,分别向服务器端发送大量的SYN包。
- 服务器端返回SYN/ACK包,因为源地址是伪造的,所以伪造的IP并不会应答。
- 服务器端没有收到伪造IP的回应,会重试3~5次并且等待一个SYN Time(—般为30秒至2分钟),如果超时则丢弃这个连接。
- 攻击者大量发送这种伪造源地址的SYN请求,服务器端将会消耗非常多的资源来处理这种半连接,同时还要不断地对这些IP进行SYN+ACK重试。
- 最后的结果是服务器无暇理睬正常的连接请求,导致拒绝服务。
三、 TCP断开链接-四次挥手
TCP是一个全双工协议,所以任一方都可发送数据,发送断开。
数据传输完毕后,双方都可释放连接。最开始的时候,客户端和服务器都是处于established(表示连接已经建立)状态,然后客户端主动关闭,服务器被动关闭。
3.1 客户端主动断开链接的过程
- 第一次握手:客户端发送断开连接报文
FIN=1,seq=u
,并停止发送数据。客户端进入FIN-WAIT-1 状态。TCP规定,FIN报文即使不携带数据也要消耗1个序号。 - 第二次握手:服务端收到连接释放报文,回复确认报文
ACK=1 ack=u+1 seq=v
,服务端此时进入了 CLOSE-WAIT 关闭等待状态。此时TCP服务通知上层应用进程,客户端数据发送完毕,即将释放连接。此时服务器端如果仍有数据要发送,客户端将继续接收,这个状态持续的时间,就是等待关闭的时间。 - 客户端收到服务端发送的ACK后,进入FIN-WAIT-2状态,这个状态时仍然接收服务器发送的数据。等待服务器发送释放报文。
- 第三次握手:服务端数据发送完毕,可已断开时。发送释放连接报文,
FIN=1 ACK=1 seq=w ack=u+1
这里的W是假设中间服务端又发送了一堆数据,但是为什么ack还是u+1我也没找到,是为了标识回复的哪条消息? - 第四次握手:客户端收到服务器的释放连接报文后,回复确认
ACK=1 seq=u+1 ack=w+1
,此时连接还没完全关闭,客户端进入了TIME-WAIT 时间等待状态,TCP连接并未关闭,必须等待 2*MSL(最长报文段寿命)后,才进入CLOSED状态 - 服务端收到客户端回复的确认关闭,就进入了CLOSED状态,结束了本次连接。比客户端关闭时间更早。
3.2 四次挥手的思考
四、 TCP状态机
# 五、TCP如何实现靠谱的传输
- 数据包编号 : TCP为了保证顺序性,会给每个包分配一个序号,建立连接时会确定双方包序号的起始值。
- 包确认: 发送确认TCP对于发送的包都需要对方进行应答。不过为了提高效率并不需要对每个包分别应答,而是应答最后一个包,就可以表示这个包之前的包都已接收。这个机制称为:累计确认/累计应答
- 丢包重发: tcp对于丢失包进行重发,有几种处理机制 **1.超时重传 2.快速重传 3.SACK选择性确认 4.Duplicate - SACK **
六、 TCP的流量控制-滑动窗口协议
顺序问题、丢包问题、流量控制都是通过滑动窗口来解决的,这其实就相当于你领导和你的工作备忘录,布置过的工作要有编号,干完了有反馈,活不能派太多,也不能太少;
使用滑动窗口的TCP缓存结构
- TCP为了记录所有发送的包和接收的包,发送端和接收端,都有自己的缓存。
- 发送端的缓存里按照包的id顺序排列,根据处理情况分为四个部分:1发送并已确认接受 2已发送但未确认接收 3没有发送但是已经准备发送的 4没有发送,并且暂时不会发送的。
- 接收端缓存分为:1接收并返回了确认 ;2没收到但是还能接收处理的 ;3没接收收到也处理不了的。
- 接收端会给发送端一个窗口大小,**Advertised window(公告窗口) **这个值会根据网络情况进行调整。这个就是滑动窗口,动态的调整这个值,就能起到流量控制的功能。这个值对于发送端来说,就等于发送了未确认的部分+未发送但可发送的部分。
发送端的缓存结构
接收端的缓存结构
- MaxRcvBuffer 表示这个TCP连接最大可用于接收的缓存,超出这个部分的接收到了也无法处理。
- LastByteRead 在接收已确认部分中,被应用层读取的与未被读取的分界线,一般被读取出去了就不在缓存了。
- NextByteExpected 等待接收数据的部分的起始值
- Advertised window 窗口的大小,就相当于最大缓存大小MaxRcvBuffer 减去 接收且确认中未被读出缓存的部分。AdvertisedWindow=MaxRcvBuffer-((NextByteExpected-1)-LastByteRead)
利用窗口进行流量控制
tcp接收端在对收到的数据包返回ACK确认时,会同时返回窗口的大小,如果接收端处理不过来,接收端就能动态的调整窗口大小来进行流量控制。
窗口大小不变的情况
- 发送端收到一个确认,那么窗口就会对应的右移一个包长度,这时未发送但可发送部分长度+1
- 如果将未发送可发送部分全都发送出去了,那么由于窗口限制就会暂停发送,直到收到新的确认包。
接收方处理能力弱,修改窗口让发送端发慢点
- 假设一种极端情况,接收数据的应用一直不读取tcp接收端缓存的数据,那么每次返回一个确认包,窗口的大小就会-1
- 直到最后lastByteRead会变到MaxRcvBuffer同一个位置,缓存被已接受确认但未被读取的数据包占满。这时候窗口的大小就是0了
- 这个过程中随着窗口逐渐减小,发送端能发送的数据也会一直减小,到最后窗口为0就停止发送。
- 窗口大小为0之后,发送端会定时发送探测数据包,看看窗口恢复了没。这时候还有一个要注意的,就是接收端不会说一空出1个数据包就立即修改窗口大小,会直到窗口恢复到一定的大小,才会更新窗口。不然就会出现低能窗口综合征。
七、 TCP拥塞控制-拥塞窗口cwnd(congestion window)
滑动窗口主要是解决接收方处理能力不足主的问题。拥塞控制也是通过窗口的大小来控制的,TCP的cwnd 就是拥塞窗口。
为什么要拥塞控制
- TCP拥塞控制的目的,就是在最小的消耗(不丢包,不重传)的情况下,最大程度的利用网络的带宽
- 如果没有拥塞控制,那么发送的太快了就会大量丢包重发,重发包又会继续堵塞网络通道
- 如果发送的太慢了,那么传输的速度又太低了,传输的时延无法保证。
- TCP的发送速度,其实取决于两个窗口值中最小的一个值。rwnd就是接收方滑动窗口(receiver window) 公式:
lastByteSent - LastByteAcked <= min {cwnd,rwnd}
cwnd的工作流程
cwnd和ssthresh的单位都是字节,ssthresh=cwndmss,所以比较时可以假设是包个数。
下面说的达到ssthresh意义是cwndmss大于或等于ssthresh
MSS含义是Maximum Segment Size 最大报文长度,和链路层的MTU不同,MTU能包住MSS
- tcp连接刚刚建立时,网络的情况对于TCP连接来说是完全不可知的,只有开始传输数据了才能知道网络的速度,这时候cwnd 被设置为1 ,这一机制被称为TCP的慢启动。cwnd的大小则表示发送端一次能发送多少个数据包。
- 慢开始算法 指数增长阶段(cwnd<ssthresh):如果传输很顺畅(没超时没重发,能正常收到ACK),那么cwnd就会开始以指数级开始增长,收到第一个ACK cwnd+1; 收到第二个ACK cwnd+2;cwnd的变化序列,
1、2、4、8、16 . . .
- 指数增长阶段会在什么时候结束呢?会在cwndmss达到了ssthresh的值 65535字节,cwnd改为线性增长阶段。或者是在出现了超时重传的情况时(网络阻塞)开始转为线性增长。默认的ssthresh设置的如此之大,其实就是为了能最大的速度经历一次丢包,丢包后ssthresh的值会被重新设定为cwnd/2mss,而cwnd也会重新被设置为1 重新开始慢启动 。
- 拥塞避免算法 线性增长阶段(cwnd>ssthresh):cwnd达到了ssthresh的值后,假如此时的cwnd是6 那么一次发送6个数据包,每次收到1个ACK cwnd就增加1/6 ,这时cwnd的增长就是线性增长阶段。
- 出现拥塞 超时重传或者快速重传, 出现丢包导致的超时重传,ssthresh的值会被重新设定为cwnd/2*mss ,然后cwnd重新设置为1 重新开始慢启动。快速重传的丢包TCP则不会认为是网络出现严重拥堵因为还能正常收到ACK,所以cwnd 减半为 cwnd/2 ,sshthresh = cwnd,然后每次收到确认之后cwnd+1 这是快恢复算法。
TCP BBR 拥塞算法
通过上面的cwnd拥塞窗口,TCP实现了基础的拥塞控制,但是有两个问题并未得到妥善的解决
- 丢包不一定是通道被完全占满,而是有一定的随机性,一出现丢包就主动降速可能不合适
- 一旦大量丢包,则说明网络中传输设备的缓存都已经被占满了,这个时候才开始降速已经有点晚了,这种处理方式会使得网络的延时增大并且变得不稳定。
最理想的场景tcp传输过程中,应该尽可能的占满通道,而不填满通道的缓存,这样延时和速率都是一个比较均衡的状态。而**TCP的BBR拥塞算法,**就是企图找到一个平衡点的功能。
八、 TCP的性能分析和优化
应用层建立在传输层之上,所以HTTP这种应用层协议的速度受到传输层速度影响很大。