1.TCP基本认识
TCP是面向连接的、可靠的、基于字节流的传输层通信协议。
- ⾯向连接:⼀定是「⼀对⼀」才能连接,不能像 UDP 协议可以⼀个主机同时向多个主机发送消息,也就是⼀ 对多是⽆法做到的;
- 可靠的:⽆论的⽹络链路中出现了怎样的链路变化,TCP 都可以保证⼀个报⽂⼀定能够到达接收端
- 字节流:消息是「没有边界」的,所以⽆论我们消息有多⼤都可以进⾏传输。并且消息是「有序的」,当 「前⼀个」消息没有收到的时候,即使它先收到了后⾯的字节,那么也不能扔给应⽤层去处理,同时对「重复」的报⽂会⾃动丢弃
1.1 TCP头格式
-
序列号:在建立连接的时候,计算机会生成一个随机数作为初始值,并通过SYN包发送给服务器,每次发送一次数据,就累加一次该数据字节数的大小。这样服务器就知道接受到的包是否是顺序的。用来解决网络包乱序问题
-
确认应答号:指下一次想要收到的数据的序列号从当前这个应答号开始,发送端收到这个确认后可以认为在这个包之前的数据都被正常接受。用来解决不丢包的问题
-
控制位:
- ACK:建立连接时,将此位置为1.
- RST:该位为1时,说明TCP连接出现异常,强制断开连接
- SYN:同步的意思,该位为1时,表示我想和你建立连接。并在其「序列号」的字段进⾏序列号初始值的设定。
- FIN:说拜拜,当此位为1时,表示我这边没有数据发送了,希望可以断开连接。当通信结束希望断开连接时,通信双方的 主机之间就可以相互交换 FIN 位为 1 的 TCP 段
-
窗口大小:用来做流量控制
1.2 TCP工作层次
计算机网络模型
由于IP层是不可靠的,他不保证网络包的交付、不保证网络包的按序交付、也不保证网络包中的数据完整型。如果需要保障网络数据包的可靠性,那么就需要由上层(传输层)的TCP协议来负责。
因为 TCP 是⼀个⼯作在传输层的可靠数据传输的服务,它能确保接收端接收的⽹络包是⽆损坏、⽆间隔、⾮冗余 和按序的。
1.3 UDP和TCP区别
UDP不提供复杂的控制机制,利⽤ IP 提供⾯向「⽆连接」的通信服务。
- ⽬标和源端⼝:主要是告诉 UDP 协议应该把报⽂发给哪个进程。
- ⽬标和源端⼝:主要是告诉 UDP 协议应该把报⽂发给哪个进程。
- 校验和:校验和是为了提供可靠的 UDP ⾸部和数据⽽设计。
TCP和UDP的区别:
- 连接
- TCP是面向连接的传输层协议,传输数据前要先建立连接
- UDP不需要连接,即可传输数据
- 服务对象
- TCP是1对1的两点服务,即一条连接只有两个端点
- UDP支持一对一、一对多、多对多的交互通信
- 可靠性
- TCP 是可靠交付数据的,数据可以⽆差错、不丢失、不重复、按需到达。
- UDP 是尽最⼤努⼒交付,不保证可靠交付数据。
- 拥塞控制、流量控制
- TCP 有拥塞控制和流量控制机制,保证数据传输的安全性。
- UDP 则没有,即使⽹络⾮常拥堵了,也不会影响 UDP 的发送速率
- 首部开销
- TCP ⾸部⻓度较⻓,会有⼀定的开销,⾸部在没有使⽤「选项」字段时是 20 个字节,如果使⽤了「选项」 字段则会变⻓的。
- UDP ⾸部只有 8 个字节,并且是固定不变的,开销较⼩
- 传输方式
- TCP 是流式传输,没有边界,但保证顺序和可靠
- UDP 是⼀个包⼀个包的发送,是有边界的,但可能会丢包和乱序
- 分⽚不同
- TCP 的数据⼤⼩如果⼤于 MSS (最大报文段长度)⼤⼩,则会在传输层进⾏分⽚,⽬标主机收到后,也同样在传输层组装 TCP 数据包,如果中途丢失了⼀个分⽚,只需要传输丢失的这个分⽚
- UDP 的数据⼤⼩如果⼤于 MTU (最大传输单元)⼤⼩,则会在 IP 层进⾏分⽚,⽬标主机收到后,在 IP 层组装完数据,接着 再传给传输层,但是如果中途丢了⼀个分⽚,在实现可靠传输的 UDP 时则就需要重传所有的数据包,这样 传输效率⾮常差,所以通常 UDP 的报⽂应该⼩于 MTU
- 应用场景
- 由于 TCP 是⾯向连接,能保证数据的可靠性交付,因此经常⽤于:FTP文件传输、HTTP/HTTPS
- 由于 UDP ⾯向⽆连接,它可以随时发送数据,再加上UDP本身的处理既简单⼜⾼效,因此经常⽤于:包总量较少的通信,如 DNS 、 SNMP。视频、音频等
1.4 三次握手
TCP 是⾯向连接的协议,所以使⽤ TCP 前必须先建⽴连接,⽽建⽴连接是通过三次握⼿来进⾏的。
- 一开始,客户端和服务都处于CLOSED状态,服务器先开启监听某个端口,处于LISTEN状态。
- 客户端随机初始化序号(Clint_isn),将此需要赋值给TCP首部的序列号字段,同时把SYN位置为1,然后发送这个报文,该报文不包含应用层数据,之后客户端处于SYN-SENT状态。SYN就是英语synchronize,表示同步,这个报文就是说:兄弟,我的序列号是x,并且我的syn是1,我现在要和你建立连接了。
- 服务器收到客户端发过来的报文后,发现SYN = 1,就说:哦,兄弟你要和我建立连接呀,那我也初始化我的序列号(server—isn)。为了让你知道我收到了,我的确认应答号就等于你的序列号+1吧。我也要和你同步,那我的SYN就要变成1,我要和你建立连接,ACK也变成1吧。我准备好了,我把信息就发给你了哦。该报⽂也不包含应⽤层数据,之后服务器处于SYN-RCVD状态。
-
客户端收到服务端报⽂后,说:我收到了,bro。我也记一下你的序列号,把他放到我的确认应答号处,值就位你的序列号+1。咋俩都建立连接了,那我的ACK就为1啦。这次报⽂可以携带客 户到服务器的数据,之后客户端处于 ESTABLISHED 状态。
-
服务器收到客户端的应答报⽂后,也进⼊ **ESTABLISHED **状态
从上⾯的过程可以发现第三次握⼿是可以携带数据的,前两次握⼿是不可以携带数据的
⼀旦完成三次握⼿,双⽅都处于ESTABLISHED 状态,此时连接就已建⽴完成,客户端和服务端就可以相互发送 数据了。
1.5 为什么需要三次握手
从三个方面来看:
- 三次握⼿才可以阻止重复历史连接的初始化(主要原因)
- 三次握⼿才可以同步双⽅的初始序列号
- 三次握⼿才可以避免资源浪费
1.5.1 原因1:避免历史连接
⽹络环境是错综复杂的,往往并不是如我们期望的⼀样,先发送的数据包,就先到达⽬标主机,反⽽它很骚,可能 会由于⽹络拥堵等乱七⼋糟的原因,会使得旧的数据包,先到达⽬标主机,那么这种情况下 TCP 三次握⼿是如何 避免的呢?
客户端连续发送多次 SYN 建⽴连接的报⽂,在⽹络拥堵情况下:
- ⼀个「旧 SYN 报⽂」⽐「最新的 SYN 」 报⽂早到达了服务端;
- 那么此时服务端就会回⼀个 SYN + ACK 报⽂给客户端;
- 客户端收到后可以根据⾃身的上下⽂,判断这是⼀个历史连接(序列号过期或超时),那么客户端就会发送 RST 报⽂给服务端,表示中⽌这⼀次连接。
如果是两次握⼿连接,就不能判断当前连接是否是历史连接,三次握⼿则可以在客户端(发送⽅)准备发送第三次 报⽂时,客户端因有⾜够的上下⽂来判断当前连接是否是历史连接。
- 如果是历史连接(序列号过期或超时),则第三次握⼿发送的报⽂是 RST 报⽂,以此中⽌历史连接;
- 如果不是历史连接,则第三次发送的报⽂是 ACK 报⽂,通信双⽅就会成功建⽴连接;
1.5.2 原因2:同步双方初始化序列号
TCP 协议的通信双⽅, 都必须维护⼀个「序列号」, 序列号是可靠传输的⼀个关键因素,它的作⽤:
- 接收⽅可以去除重复的数据
- 接收⽅可以根据数据包的序列号按序接收;
- 可以标识发送出去的数据包中, 哪些是已经被对⽅收到的;
可⻅,序列号在 TCP 连接中占据着⾮常重要的作⽤,所以当客户端发送携带「初始序列号」的 SYN 报⽂的时 候,需要服务端回⼀个 ACK 应答报⽂,表示客户端的 SYN 报⽂已被服务端成功接收,那当服务端发送「初始序 列号」给客户端的时候,依然也要得到客户端的应答回应,这样⼀来⼀回,才能确保双⽅的初始序列号能被可靠的 同步。
四次握手其实也可以做的,但2、3步完全可以合并,所有就成了三次握手。
⽽两次握⼿只保证了⼀⽅的初始序列号能被对⽅成功接收,没办法保证双⽅的初始序列号都能被确认接收
1.5.3 原因3:避免资源浪费
如果只有「两次握⼿」,当客户端的 SYN 请求连接在⽹络中阻塞,客户端没有接收到 ACK 报⽂,就会重新发 送 SYN ,由于没有第三次握⼿,服务器不清楚客户端是否收到了⾃⼰发送的建⽴连接的 ACK 确认信号,所以 每收到⼀个 SYN 就只能先主动建⽴⼀个连接,这会造成什么情况呢?
服务器重复接受⽆⽤的连接请求 SYN 报⽂,⽽造成重复复分配资源。
1.5 四次握手
TCP断开连接的方式就是四次握手。双方都可以主动断开连接,断开连接后主机中的资源被释放。
- 客户端打算关闭连接,发送一个TCP首部FIN标志位被置为1的报文,也即FIN报文,之后客户端进入FIN-WAIT_1状态
- 服务端收到该报⽂后,就向客户端发送 ACK 应答报⽂,接着服务端进⼊ CLOSED_WAIT 状态
- 客户端收到服务端的 ACK 应答报⽂后,之后进⼊ FIN_WAIT_2 状态。
- 等待服务端处理完数据后,也向客户端发送 FIN 报⽂,之后服务端进⼊ LAST_ACK 状态。
- 客户端收到服务端的 FIN 报⽂后,回⼀个 ACK 应答报⽂,之后进⼊ TIME_WAIT 状态
- 服务器收到了 ACK 应答报⽂后,就进⼊了 CLOSED 状态,⾄此服务端已经完成连接的关闭。
- 客户端在经过 2MSL ⼀段时间后,⾃动进⼊ CLOSED 状态,⾄此客户端也完成连接的关闭
这里需要注意,谁主动关闭,谁有TIME_WATE状态。
1.6 为什么需要四次挥手
- 关闭服务是,客户端向服务发送FIN时,仅表示我不不发数据了,但我还可以接受数据
- 服务器收到客户端的 FIN 报⽂时,先回⼀个 ACK 报⽂,表示我知道了,⽽服务端可能还有数据需要处理和发送,等 服务端不再发送数据时,才发送 FIN 报⽂给客户端来表示同意现在关闭连接。
从上⾯过程可知,服务端通常需要等待完成数据的发送和处理,所以服务端的ACK 和 FIN ⼀般都会分开发 送,从⽽⽐三次握⼿导致多了⼀次。
1.7 为什么TIME_WATE等待时间是2MSL
MSL 是 Maximum Segment Lifetime,报⽂最⼤⽣存时间,它是任何报⽂在⽹络上存在的最⻓时间,超过这个时 间报⽂将被丢弃。因为 TCP 报⽂基于是 IP 协议的,⽽ IP 头中有⼀个 TTL 字段,是 IP 数据报可以经过的最⼤路 由数,每经过⼀个处理他的路由器此值就减 1,当此值为 0 则数据报将被丢弃,同时发送 ICMP 报⽂通知源主机。
2MSL刚好就是发出报文到接收到来回的时间。
⽐如如果被动关闭⽅没有收到断开连接的最后的 ACK 报⽂,就会触发超时触发 Fin 报⽂,另⼀⽅接收到 FIN 后, 会重发ACK 给被动关闭⽅, ⼀来⼀去正好 2 个 MSL。
2MSL 的时间是从客户端接收到 FIN 后发送 ACK 开始计时的。如果在 TIME-WAIT 时间内,因为客户端的 ACK 没有传输到服务端,客户端⼜接收到了服务端重发的 FIN 报⽂,那么 2MSL 时间将重新计时。
1.8 为什么需要TIME_WATE状态
主动发起关闭连接的⼀⽅,才会有 TIME-WAIT 状态。
需要 TIME-WAIT 状态,主要是两个原因:
-
防⽌具有相同「四元组」(己方ip+port + 对方ip+port)的「旧」数据包被收到;
-
保证「被动关闭连接」的⼀⽅能被正确的关闭,即保证最后的 ACK 能让被动关闭⽅接收,从⽽帮助其正常关 闭;
1.8.1 原因1:防止旧连接的数据包
假如一个来自服务器在关闭之前发送的报文被网络延迟了。这时候有个相同端口的TCP连接被复用了,被延迟的包抵达了客户端,那么客户端是有可能正常接收 这个过期的报⽂,这就会产⽣数据错乱等严重的问题。
所以,TCP 就设计出了这么⼀个机制,经过 2MSL 这个时间,⾜以让两个⽅向上的数据包都被丢弃,使得原来 连接的数据包在⽹络中都⾃然消失,再出现的数据包⼀定都是新建⽴连接所产⽣的。
原因2:保证连接正确关闭
TIME-WAIT 作⽤是等待⾜够的时间以确保最后的 ACK 能让被动关闭⽅接收,从⽽帮助其正常关闭。(不能让老哥一直等呀)
TIME_WATE过多的危害?
- 对内存资源的占用
- 对端口资源的占用,一个TCP连接至少消耗一个本地端口
客户端受端口资源限制:客户端TIME_WAIT过多,就会导致端⼝资源被占⽤,因为端⼝就65536个,被占满就会导致⽆法创建新的连 接。
服务端受系统资源限制:由于⼀个四元组表示 TCP 连接,理论上服务端可以建⽴很多连接,服务端确实只监听⼀个端⼝ 但是会把连接 扔给处理线程,所以理论上监听的端⼝可以继续监听。但是线程池处理不了那么多⼀直不断的连接了。所以 当服务端出现⼤量TIME_WAIT 时,系统资源被占满时,会导致处理不过来新的连接。
2. TCP重传、滑动窗口、流量控制、拥塞控制
2.1 TCP重传
TCP实现可靠性连接的方式之一,就是通过序列号与确认应答。
在 TCP 中,当发送端的数据到达接收主机时,接收端主机会返回⼀个确认应答消息,表示已收到消息。
但是在复杂的网络当中,数据并不会如你所愿的传输,可能会发生丢失。这里就引出了重传机制
常见的重传机制有:
- 超时重传
- 快速重传
- ASCK
- D-ASCK
2.1.1 超时重传
重传机制的其中⼀个⽅式,就是在发送数据时,设定⼀个定时器,当超过指定的时间后,没有收到对⽅的 ACK 确认应答报⽂,就会重发该数据,也就是我们常说的超时重传。
TCP发生重传的时机:
- 数据包丢失
- 确认应答丢失
超时时间应该设为多少?
RTT(Round-Trip Time 往返时延) 就是数据从⽹络⼀端传送到另⼀端所需的时间,也就是包的往返时间。
超时重传时间是以 RTO (Retransmission Timeout 超时重传时间)表示。
由此可见,超时重传时间应该略大于往返时延。
如果超时时间设置过短,会导致可能并没有丢就重发,于是重发的就快,会增加⽹络拥塞,导致更多的超 时,更多的超时导致更多的重发。
超时时间设置过长,重发就慢,丢了⽼半天才重发,没有效率,性能差。导致网络的空隙时间增大,降低了网络传输时延。
2.1.2 快速重传
TCP 还有另外⼀种快速重传(Fast Retransmit)机制,它不以时间为驱动,⽽是以数据驱动重传。
由图可知,三次接受方返回的ACK报文相同,这时候就触发了快速重传。
所以,快速重传的⼯作⽅式是当收到三个相同的 ACK 报⽂时,会在定时器过期之前,重传丢失的报⽂段。
但是,快速重传机制只解决了⼀个问题,就是超时时间的问题,但是它依然⾯临着另外⼀个问题。就是重传的时候,是重 传之前的⼀个,还是重传所有的问题。
2.1.3 ASCK方法
快速重传的一个优化就是SACK ( Selective Acknowledgment 选择性确认)。
这种方式需要在TCP头部[选项]放一个SACK的东西,他可以将缓存的地图发送给发送方,这样发送方就知道哪些包没有传过去了,就只发没有传过去的数据。
2.1.4 D-ASCK
Duplicate SACK ⼜称 D-SACK ,其主要使⽤了 SACK 来告诉「发送⽅」有哪些数据被重复接收了。
- ACK丢包
- 网络延时
从上面两个图来看,D-SACK有这个几个好处:
- 可以让「发送⽅」知道,是发出去的包丢了,还是接收⽅回应的 ACK 包丢了
- 可以知道是不是「发送⽅」的数据包被⽹络延迟了;
- 可以知道⽹络中是不是把「发送⽅」的数据包给复制了
2.2 滑动窗口
TCP是每发送一个消息,都要进行一次应答。当上一个数据包收到后,才发下一个。这个效率是不是太低了?
为解决这个问题,TCP 引⼊了窗⼝这个概念。即使在往返时间较⻓的情况下,它也不会降低⽹络通信的效率。那么有了窗⼝,就可以指定窗⼝⼤⼩,窗⼝⼤⼩就是指⽆需等待确认应答,⽽可以继续发送数据的最⼤值,窗⼝的实现实际上是操作系统开辟的⼀个缓存空间,发送⽅主机在等到确认应答返回之前,必须在缓冲区中保留已 发送的数据。如果按期收到确认应答,此时数据就可以从缓存区清除。
窗口大小由哪一方决定?
这里就要看之前的TCP头部结构了,TCP首部存在一个window字段,也就是窗口大小。
这个字段是接收端告诉发送端⾃⼰还有多少缓冲区可以接收数据。于是发送端就可以根据这个接收端的处理能⼒来 发送数据,⽽不会导致接收端处理不过来。
为滑动窗⼝并不是⼀成不变的。⽐如,当接收⽅的应⽤进程读取数据的速度⾮常快的话,这样的话接收窗⼝可以 很快的就空缺出来。那么新的接收窗⼝⼤⼩,是通过 TCP 报⽂中的 Windows 字段来告诉发送⽅。那么这个传输过 程是存在时延的,所以接收窗⼝和发送窗⼝是约等于的关系。
2.3 流量控制
发送⽅不能⽆脑的发数据给接收⽅,要考虑接收⽅处理能⼒
如果⼀直⽆脑的发数据给对⽅,但对⽅处理不过来,那么就会导致触发重发机制,从⽽导致⽹络流量的⽆端的浪 费。
为了解决这种现象发⽣,TCP 提供⼀种机制可以让「发送⽅」根据「接收⽅」的实际接收能⼒控制发送的数据量, 这就是所谓的流量控制。
2.4 拥塞控制
前⾯的流量控制是避免「发送⽅」的数据填满「接收⽅」的缓存,但是并不知道⽹络的中发⽣了什么。
在⽹络出现拥堵时,如果继续发送⼤量数据包,可能会导致数据包时延、丢失等,这时 TCP 就会重传数据,但是 ⼀重传就会导致⽹络的负担更重,于是会导致更⼤的延迟以及更多的丢包,这个情况就会进⼊恶性循环被不断地放 ⼤…
所以,TCP 不能忽略⽹络上发⽣的事,它被设计成⼀个⽆私的协议,当⽹络发送拥塞时,TCP 会⾃我牺牲,降低 发送的数据量。
于是,就有了拥塞控制,控制的⽬的就是避免「发送⽅」的数据填满整个⽹络。
这里就需要定义一个拥塞窗口。拥塞窗⼝ cwnd是发送⽅维护的⼀个的状态变量,它会根据⽹络的拥塞程度动态变化的。
我们在前⾯提到过发送窗⼝ swnd 和接收窗⼝ rwnd 是约等于的关系,那么由于加⼊了拥塞窗⼝的概念后,此 时发送窗⼝的值是swnd = min(cwnd, rwnd),也就是拥塞窗⼝和接收窗⼝中的最⼩值。
拥塞窗⼝ cwnd 变化的规则:
- 只要⽹络中没有出现拥塞, cwnd 就会增⼤;
- 但⽹络中出现了拥塞, cwnd 就减少
拥塞控制算法:慢启动,拥塞避免,拥塞发生,快速恢复。
2.4.1 慢启动
TCP 在刚建⽴连接完成后,⾸先是有个慢启动的过程,这个慢启动的意思就是⼀点⼀点的提⾼发送数据包的数量, 如果⼀上来就发⼤量的数据,这不是给⽹络添堵吗?(慢慢试探)
慢启动的算法记住⼀个规则就⾏:当发送⽅每收到⼀个 ACK,拥塞窗⼝ cwnd 的⼤⼩就会加 1。慢启动算法,发包的个数是指数性的增⻓。
那么慢启动涨到什么时候呢?
有⼀个叫慢启动⻔限 **ssthresh (slow start threshold)**状态变量。
- 当 cwnd < ssthresh 时,使⽤慢启动算法
- cwnd >= ssthresh 时,就会使⽤「拥塞避免算法」
2.4.2 拥塞避免
当拥塞窗⼝ cwnd 「超过」慢启动⻔限 ssthresh 就会进⼊拥塞避免算法。⼀般来说 ssthresh 的⼤⼩是 65535 字节。
那么进⼊拥塞避免算法后,它的规则是:每当收到⼀个 ACK 时,cwnd 增加 1/cwnd。
拥塞避免算法就是将原本慢启动算法的指数增⻓变成了线性增⻓,还是增⻓阶段,但是增⻓ 速度缓慢了⼀些
就这么⼀直增⻓着后,⽹络就会慢慢进⼊了拥塞的状况了,于是就会出现丢包现象,这时就需要对丢失的数据包进行重传。 当触发了重传机制,也就进⼊了「拥塞发⽣算法」。
2.4.3 拥塞发生
当⽹络出现拥塞,也就是会发⽣数据包重传,重传机制主要有两种:超时重传、快速重传。
-
发⽣超时重传的拥塞发⽣算法
-
ssthresh 设为 cwnd/2
-
cwnd 重置为 1
-
进入慢启动
-
-
发⽣快速重传的拥塞发⽣算法
- cwnd = cwnd/2 ,也就是设置为原来的⼀半;
- ssthresh = cwnd ;
- 进入快速恢复算法
2.4.4 快速恢复
快速重传和快速恢复算法⼀般同时使⽤,快速恢复算法是认为,你还能收到 3 个重复 ACK 说明⽹络也不那么糟 糕,所以没有必要像 RTO 超时那么强烈
进⼊快速恢复算法如下:
- 拥塞窗⼝ cwnd = ssthresh + 3 ( 3 的意思是确认有 3 个数据包被收到了);
- 重传丢失的数据包;
- 如果再收到᯿复的 ACK,那么 cwnd 增加 1;
- 如果收到新数据的 ACK 后,把 cwnd 设置为第⼀步中的 ssthresh 的值,原因是该 ACK 确认了新的数据,说 明从 duplicated ACK 时的数据都已收到,该恢复过程已经结束,可以回到恢复之前的状态了,也即再次进 ⼊拥塞避免状态
整个过程如图所示。