面试反客为主 TCP

Posted 程序员大咖

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了面试反客为主 TCP相关的知识,希望对你有一定的参考价值。

👇👇关注后回复 “进群” ,拉你进程序员交流群👇👇

作者丨SoWhat1412 

来源丨sowhat1412

3 传输层 TCP/UDP

承接上文 HTTP,数据经过应用层就到传输层,但数据到传输层之前需要先获得服务端的 IP 地址,这就涉及到 DNS 域名解析。

3.1 DNS

3.1.1 DNS 讲解

主机的真正地址是 IP ,问题是 IP 地址不方便人们记忆,就像你拿手机给张三打电话,难道你能瞬间说出张三电话号码么,手机里做一个名字跟电话的映射即可,想通话时直接从通讯录找到张三就可以找到对应的手机号,在网络请求时候也是需要映射的,而域名服务器 Domain Name System 就是干这个事的,深入讲 DNS 前先了解下域名。

我们在浏览器地址栏中输入的每一个地址都是一个域名,比如 www.baidu.com。域名是由.和不同级别域的域名组成。通常我们在书写时会省略根域名,即域名结尾的.,如 www.baidu.com.。由于域名是老外发明的所以从左到右范围逐步变大且以.分割。

DNS分层

由上到下域名之间相互包含跟嵌套,根域名服务器是关键,必须是众所周知的,找到了它,下面的各级域名服务器才能找到,否则域名解析就无从谈起了。我们看下请求 www.baidu.com 的 DNS 解析流程:
  1. 首先访问根域名服务器,根域名是不做域名解析,只是给你指路用的,现在你获取 com 顶级域名服务器的地址。

  2. 请求 com 顶级域名服务器,返回 baidu.com 域名服务器的地址。

  3. 然后请求 baidu.com 域名服务器,返回 www.baidu.com 的地址。

这样进行 DNS 的流程是OK的,但问题是全球数十亿的PC电脑,如果每个电脑请求上网都按照上面流程走一波,那上面的 DNS 核心解析系统瞬间爆炸!解决办法就是用缓存,很多大公司跟运营商都会搭建自己的 DNS 服务器来代替用户请求核心 DNS 系统,如果查到的话可以缓存查询记录,再次收到请求的号如果有缓存结果或者缓存未过期,则直接返回原来的缓存结果,知名的 Google 8.8.8.8 DNS 解析服务器,就是 Google 自建的 非权威域名服务器 。除了非权威域名服务器,我们经常看到的有浏览器缓存,操作系统缓存,比如 /etc/hosts文件等。

3.1.2 DNS 样例
DNS域名解析

  1. 用户输入网址先看下浏览器的DNS缓存是否过期,没过期直接拿来用。过期了看看本地操作系统缓存 /etc/hosts 文件等。

  2. 请求本地配置的 非权威域名服务器 DNS resolver。

  3. DNS resolver 将网址转发到根域名请求,返回 com 域名地址。

  4. DNS resolver 将网址转发到 com 域名的服务器请求,返回跟 baidu.com 相关的 权威DNS解析器。

  5. DNS resolver 将网址转发权威 DNS 解析器继续请求,然后返回真正的目标域名IP。

  6. DNS resolver 最终将目标 IP 返回给用户继续接下来的访问请求。

3.2 TCP

3.2.1 TCP 头部讲解

TCP 是一个是面向连接的、可靠的、基于字节流的、工作在传输层的数据传输服务。用 TCP 传输数据能确保接收端接收的网络包是无损坏无间隔非冗余有序。这里需注意 TCP 是一对一连接的。

TCP头部 + HTTP
  1. 发送端口:是一个大于 1023 的 16 位数字,由基于TCP应用程序的用户进程随机选择。

  2. 目的端口:指明接收者所用的端口号,一般由应用程序来指定。

  3. 序列号:建立连接时客户端生成随机数作为初始值,通过 SYN 包传给接收端主机,每发送一次数据,就累加一次。序列号达到最大值会出现序列号回绕,再次从0 开始。核心作用就是 接收方去重数据 + 按序接收

  4. 确认号:用来解决不丢包的问题,指定下一次希望收到的数据的序列号,发送端收到这个确认应答以后可以认为在这个序号以前的数据都已经被正常接收。

  5. 数据偏移:表示 TCP 报文段的首部长度,4 位二进制最大表示15,由于TCP首部包含个可变长度选项,需要指定这个 TCP 报文段到底有多长。它指出 TCP 报文段的数据起始处距离 TCP 报文段的起始处有多远。该字段的单位是4字节,所以TCP首部最大15*4 = 60字节

  6. 保留:保留6位,未使用,应置零。

下面的7~12是控制位,用来表示说明报文段的性质

  1. URG:表示本报文段中发送的数据是否包含紧急数据。只有当 URG=1 时后面的紧急指针字段urgent pointer才有效。

  2. ACK:表示是否前面确认号字段是否有效。只有当 ACK=1 时,前面的确认号字段才有效。TCP 规定连接建立后 ACK=1,带 ACK 标志的TCP报文段称为确认报文段

  3. PSH:提示接收端需立即从 TCP 接收缓冲区中读走数据,为接收后续数据腾出空间。为1表示对方应当立即把数据提交给上层应用,如果应用程序不将接收到的数据读走,就会一直停留在 TCP 接收缓冲区中。

  4. RST:收到一个 RST=1 的报文说明与主机的连接出现了严重错误,必须释放连接,然后再重新建立连接。或者说明上次发送给主机的数据有问题,主机拒绝响应,带 RST 标志的 TCP 报文段称为复位报文段

  5. SYN:建立连接时用来同步序号。SYN=1 说明这是一个请求建立连接或同意建立连接的报文。只有在前两次握手中 SYN 才置为1,带 SYN 标志的 TCP 报文段称为同步报文段

  1. 当 SYN=1 且 ACK=0 时表示这是一个请求建立连接的报文段。

  2. 当 SYN=1 且 ACK=1 时表示对方同意建立连接。

  1. FIN:通知对方本端要关闭连接了,标记数据是否发送完毕。如果 FIN=1 告诉对方释放连接,带FIN标志的TCP报文段称为结束报文段

  2. 窗口大小:表示现在允许对方发送的数据量,告诉对方从本报文段的确认号开始允许对方发送的数据量,达到此值,需要ACK确认后才能再继续传送后面数据。

  3. 校验和:提供额外的可靠性。

  4. 紧急指针:标记紧急数据在数据字段中的位。

  5. 选项部分:选项部分的最大长度可根据TCP首部长度进行推算。TCP首部长度用4位表示,选项部分最长为:(2^4-1)*4-20=40 字节。

TCP 只规定了一种选项,即TCP报文段最大长度 MSS,通常是1460字节,整个TCP报文段的长度 = 数据字段的长度 + TCP 首部的长度 。

  1. 填充:这里需注意,为了网络设备硬件设计和处理方便, 数据传输过程中首部长度必须是 4 字节的整数倍。

3.2.2 TCP 三次握手
TCP三次握手

  1. 一开始客户端跟服务器都处于 closed 状态,然后服务端主动监听某个客户端端口,此时服务端处于listen 状态。

  2. 客户端随机初始化序列号 seq = client_isn,同时将 SYN = 1 表示这是 SYN 报文,接着把该 SYN 报文发给服务器,注意此时报文不包含引用层数据,客户端处于 syn-sent状态。

  3. 服务端收到客户端的 SYN报文后也随机初始化个序号 seq = server_isn,并且将确认序号 ack = client_isn + 1,接着把  SYN = 1ACK = 1,然后该报文发送给客户端,服务器处于 syn-rcvd状态。

  4. 客户端收到服务器的报文后,将 ACK = 1,确认应答号 ack = server_isn + 1,然后把报文发送给服务器,本次报文可发送数据,同时客户端处于 established 状态。

  5. 服务器收到客户端的应答报文后,也进入 established 状态。

  6. 客户端和服务端建立好了连接,可以相互发送数据。

这里你可能发现了客户端跟服务器的初始化序列号是各自随机的,原因是网络中的报文会重发、会延迟、也有可能丢失,为避免相互影响干脆各用各的为好。同时通过流程发现前两次握手是不带数据的,第三次可携带数据

3.2.3 TCP 数据传输大致流程

前面在HTTP时候就说过了,数据到TCP层跟IP层都会拆分发送,有人可能会问:既然IP会分帧,那为什么TCP层还分层呢?原因是如果TCP不分层,只用IP层分帧数据发送,如果有一帧出现丢失则会导致整个IP报文分帧全部重传。本质在于IP层没有重传机制而TCP层可以实现数据的超时重传丢失重传

信息传输大致流程
3.2.4 TCP 状态查询

服务器一般用  netstat 查看 tcp,udp 的端口和进程等相关情况。netstat -tunlp | grep 端口号

-t (tcp) 仅显示tcp相关选项
 -u (udp) 仅显示udp相关选项
 -n 拒绝显示别名,能显示数字的全部转化为数字
 -l 仅列出在Listen(监听)的服务状态
 -p 显示建立相关链接的程序名
netstat样例
3.2.5  TCP 为啥三次握手

TCP是不区分客户端和服务端,连接的建立是双向的过程。所以客户端要给服务器通讯的话两次握手是必须的。

  1. 第一次握手客户端发个连接请求给服务端,服务端收到后知道自己可以跟客户端连接了,

  2. 但是此时客户端不知道啊,所以必须的执行第二次握手,反馈下信息给客户端。

  3. 第一次握手请求连接如果因为网络导致延迟,直到连接释放后信息才到达服务端,那此时服务端也会给客户端进行第二次握手回复,关键是客户端已经不要这个连接了,此时服务端会一直在等待接收客户端信息,造成资源浪费

  4. 如果用了三次握手则客户端会发送 RST 报文告知服务端请终止本次旧连接。

如果还不太理解,我们用个生活常识说明下。晚上你在小区里散步,不远处看见一位漂亮妹子迎面而来,因为路灯有点暗不能100%确认,所以要通过招手的方式来确定对方是否认识自己。

1. 你首先向妹子 招手 syn

2. 妹子看到你向自己招手后,向你点头 微笑 ack

3. 她也需要确认一下你有没有可能你是在看别人呢,妹子也向你 招手 syn

4. 你看到妹子 微笑ack 后确认了妹子成功辨认出了自己,进入 established 状态。

5. 妹子给你 招手 syn 了,你也 微笑 ack 回复,妹子收到后也进入 established 状态。

因为妹子连续进行了两个动作,先是点头微笑,然后再次招手,所以可以将这两个动作合成一个动作,招手的同时点头和微笑。于是这四个动作就简化成了三个动作。

你与妹子的相识
3.2.6 TCP 三次握手的意义
  1. 避免历史连接

客户端建立连接时发送多次 SYN 报文,由于网络拥堵可能旧的 SYN 报文比新的 SYN 报文先到服务器,服务器不管新旧,收到就回复 SYN + ACK 给客户端,三次握手情况下客户端可以根据序列号或超时时间判断回复的连接是否是历史连接,如果是历史连接直接发送 RST 报文给服务端来终止连接。

  1. 同步双方初始序列号

TCP 协议的通信双方都在维护各自的序列号,且必须要让对方知道。只有通过三次握手才可以实现。

  1. 避免服务端资源浪费

二次握手情况下,如果客户端的 SYN 阻塞导致重复发送多次 SYN 报文,那么服务器在收到请求后就会建立多个冗余的无效链接,造成不必要的资源浪费。而三次握手发现无效链接可在第三次给服务器端发送终止指令。

3.2.7 TCP 连接中客户端忽然挂掉咋办?

TCP还设有一个保活计时器,服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75秒发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。

3.2.8 TCP 如何避免 SYN 攻击

TCP 连接时会经过三次握手,在第一次握手后,服务端收到 SYN 报文 就会发出 ACK + SYN 报文 同时进入 SYN_RCVD 状态,如果有黑客伪造 n 个不同 IP 发出请求,会导致服务器的 SYN_RCVD 队列 爆满,最终无法对外提供服务。

解决方法

  1. 设置 SYN_RCVD 最大值:服务端超过处理能力时,直接将新的 SYN 请求 RST 丢弃。

  2. 可缩短 SYN Timeout 时间:通过缩短从接收到 SYN 报文到确定这个报文无效并丢弃该连接的时间,可以降低服务器负荷。

  3. 设置SYN Cookie:给每个请求连接的 IP 地址分配一个 Cookie,如果短时间内收到同一个 IP 的重复 SYN 报文,则以后从这个 IP 地址来的包会被丢弃。

3.2.9 TCP 四次挥手

客户端跟服务端都可以发出端口请求,TCP 断开连接是通过四次挥手方式。

TCP四次挥手
  1. 客户端停止发送数据并且发出释放连接的报文,报文中FIN = 1,FIN报文段即使不携带数据,也要消耗一个序号,此时序列号seq = u ,u = 前面已经传输过来数据最后一个字节序号加1,客户端进入FIN-WAIT-1状态。

  2. 服务器收到连接释放报文,发出确认报文,ACK=1,应答确认好 ack=u+1,并且带上自己的序列号seq=v,此时服务端就进入了CLOSE-WAIT 状态。TCP 服务器通知高层的应用进程进入半闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个 CLOSE-WAIT 状态持续的时间。

  3. 客户端收到服务器的确认请求后,此时客户端就进入FIN-WAIT-2 状态,等待服务器发送连接释放报文,在这之前还需要接受服务器发送的最后的数据。

  4. 服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,FIN=1ack=u+1,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为 seq=w,此时服务器就进入了LAST-ACK 状态,等待客户端的确认。

  5. 客户端收到服务器的连接释放报文后必须发出确认,ACK=1ack=w+1seq=u+1,此时客户端就进入了TIME-WAIT状态。注意此时 TCP 连接还没有释放,必须经过最长报文段寿命  2MSL 的时间后,当客户端撤销相应的 TCB 后,才进入 CLOSED 状态。

  6. 服务器只要收到了客户端发出的确认,立即进入 CLOSED 状态。同样撤销 TCB 后,就结束了这次的 TCP 连接,可以看到服务器结束TCP连接的时间要比客户端早一些。

我们还以你跟妹子碰面交流为例,你俩彼此确认后交流几分钟后,你打算结束这个谈话,毕竟交流太久没老婆发现就凉了。

你跟妹子挥手离别
3.2.10  TCP 为什么四次挥手

其实分析下整个关闭的流程就知道为什么必须是四次挥手而不是三次挥手了。

  1. 关闭连接时,客户端向服务端发送 FIN 时,仅仅表示客户端不再发送数据出去了但是还是能接收数据。

  2. 服务器收到客户端的 FIN 报文时,先回一个 ACK应答报文,意思是不再接受数据了,但服务端可能还有数据需往外发送,等服务端不再发送数据时才发送 FIN 报文给客户端来表示同意现在关闭连接。这里注意服务端的 ACKFIN分开发的。

  3. 客户端收到服务端的 ACK 后,再给服务端发送 ACK,最终客户端跟服务器都进入 close 状态。

3.2.11  TCP 挥手为什么需要 TIME_WAIT 状态

MSL 定义

Maximum Segment Lifetime 报文最大生存时间,意思是网络传输的报文在网络上存在的最长时间,超过这个时间报文将被丢弃。而数据之所以可以被抛弃是因为TCP层的下面的IP层有个TTL来记录报文传输过程中经过的最大路由次数。

TIME_WAIT 定义

  1. TIME_WAIT  = 2* MSL,原因是 发送方数据到接受方后,接收方会返回响应,这样一来一回正好2 倍的MSL。

  2. Time_wait 是从客户端接收到 FIN 后发送 ACK 开始计时的,如果在 TIME-WAIT 时间内,客户端的 ACK 没有传输到服务端,客户端又接收到了服务端重发的 FIN 报文,那 2MSL 将重新计时。

TIME_WAIT 存在意义

  1. 防止旧连接的数据包被重新消费:上一次连接时候如果有网络震荡导致服务端数据在网络游荡,如果因为 time_wait 时间太短,新的连接可能会重新接受到游荡的消息。有了延迟时间可以避免消耗游荡的数据。

  2. 确保连接正确关闭:TIME-WAIT 作用是等待足够的时间以确保最后的 ACK 能让被动关闭方接收,从而帮助其正常关闭。

TIME_WAIT 发生场景

  1. 在高并发短连接的 TCP 服务器上,当服务器处理完请求后立刻主动正常关闭连接。这个场景下会出现大量 socket 处于 TIME_WAIT 状态。如果客户端的并发量持续很高,因为端口有限,内存有限,会导致此时部分客户端显示连接不上。

  2. 在Linux内核中 TIME_WAIT = 60秒。

避免 TIME_WAIT 过多

  1. 取消短连接,改用长连接方式,

  2. 设定阈值,一旦超过阈值系统会将所有time_wait 连接重置。

  3. 修改客户端程序代码。

3.2.12 TCP 如何保证数据传输可靠
  1. 校验和:发送跟接受数据都会进行检验的,如果不一致,那么传输有误。

  2. 确认应答序列号:TCP进行传输时数据都进行了编号,每次接收方返回ACK都有确认序列号。

  3. 超时重传:如果发送方发送数据一段时间后没有收到ACK,那么就重发数据。并且自带去重功能。

  4. 连接管理:三次握手和四次挥手的过程。

  5. 流量控制:TCP协议报头包含16位的窗口大小,接收方会在返回ACK时同时把自己的即时窗口填入,发送方就根据报文中窗口的大小控制发送速度。

  6. 拥塞控制:刚开始发送数据的时候,拥塞窗口是1,以后每次收到ACK,则拥塞窗口+1,然后将拥塞窗口和收到的窗口取较小值作为实际发送的窗口,如果发生超时重传,拥塞窗口重置为1。这样做的目的就是为了保证传输过程的高效性和可靠性。

3.3  UDP

UDP 为应用程序提供了一种无需建立连接就可以发送封装的 IP 数据包的方法,它的协议很简单,头部只有八个字节:

UDP头部
  1. 两个十六位的端口号:分别为源端口和目标端口。

  2. 包长度:该字段 =  UDP首部长度 + 数据长度。

  3. 校验和:整个数据报文的检验和,该字段用于发现头部信息和数据中的错误。

3.3.1 UDP 特点

UDP有不提供数据包分组、组装和不能对数据包进行排序的缺点,当报文发送之后,是无法得知其是否安全完整到达的。

  1. 面向无连接

  1. UDP 不会进行三次握手建立连接,想建立连接就建立连接,并且也只是数据报文的搬运工,不会对数据报文进行任何拆分和拼接操作。

  2. 在发送端,应用层将数据传递给传输层的 UDP 协议,UDP 只会给数据增加一个 UDP 头标识下是 UDP 协议,然后就传递给网络层了。

  3. 在接收端,网络层将数据传递给传输层,UDP 只去除 IP 报文头就传递给应用层,不会任何拼接操作

  1. 有单播、多播、广播的功能

UDP 不止支持一对一的传输方式,同样支持一对多,多对多,多对一的方式,也就是说 UDP 提供了单播,多播,广播的功能。

  1. UDP面向报文

发送方的UDP对应用程序交下来的报文,在添加首部后就向下交付IP层。UDP对应用层交下来的报文,既不合并,也不拆分,而是保留这些报文的边界。因此应用程序必须选择合适大小的报文

  1. 不可靠性

  1. 不可靠性体现在无连接上,通信都不需要建立连接,想发就发,这样的情况肯定不可靠。

  2. 收到什么数据就传递什么数据,并且也不会备份数据,发送数据也不会关心对方是否已经正确接收到数据了。

  3. 没有拥塞控制,一直会以恒定的速度发送数据。网络不好可能导致丢包,在某些实时性要求高的场景,比如视频电话就需要使用 UDP。

  1. 头部开销小

UDP 的头部开销小,只有八字节,相比 TCP 的至少二十字节要少得多,在传输数据报文时是很高效的

3.3.2 TCP 跟  UDP 对比

3.3.3 TCP UDP 共有端口

你可能经常被问到,TCPUDP 为何可以共用同一端口?这是因为从网络层的角度来看,它是不知道端口这个概念的,TCP/UDP 都是包裹在 IP 协议内的,IP 协议只需要知道 IP 对应的硬件地址就可以把远端的网络包发送到目的主机上。

端口这个概念是由操作系统划分的。因为内核不可能把所有网络数据都发送给所有的进程,所以为了区分哪些数据该划分给哪些进程,便在传输层的协议中定义了端口。而TCP和UDP协议中的端口号占位都是16位,所以操作系统能绑定的端口也就只有65535个。

如果你查看 C 语言有关 Socket 编程中的 socketbind 函数你会发现,系统是以 协议 + ip + 端口来绑定端口的,所以不同协议相同的ip和端口也是可以绑定成功的。

4 TCP 进阶

4.1  TCP 重传机制

为保证数据安全到达接受端,TCP引入了超时重传、快速重传、SACK、D-SACK

4.1.1 超时重传

以时间为基准,在发送数据时设置个定时器,如果期限内没收到接受者的ACK就会重新发送数据,一般数据包丢失或确认应答丢失会导致超时重传,这里先普及两个跟时间相关的参数跟一些规则。

  1. RTT:Round-Trip Time 往返时间,指的是数据从发送到接受的耗时时间。

  2. RTO :Retransmission Timeout 超时重传时间。

  3. 动态:RTT 收到网络波动是动态变化的,同理RTO也是动态变化的。

  4. RTO翻倍:每遇到一次超时重传,系统都会将下一次RTO翻倍。

RTO跟RTT

RTT跟RTO之间的关系十分微妙。
  1. RTO 较小时可能导致数据本来就没丢失只是还没被响应, 又重发会增加网络拥塞,导致更多的超时重发。

  2. RTO较大时候可能导致数据已经丢了好久才重发数据。

所以离线情况下 RTO 稍微大于 RTT是最好的。具体规则有兴趣的可自行百度。

4.1.2 快速重传

TCP有累计确认机制,当接收端收到比期望序号大的报文段时,便会重复发送最近一次确认的报文段的确认信号,我们称之为冗余ACK(duplicate ACK)。
如图所示,报文段1成功接收并被确认ACK 2,接收端的期待序号为2,当报文段2丢失,报文段3失序到来,与接收端的期望不匹配,接收端重复发送冗余ACK 2。

快速重传机制

发送端如果在超时重传定时器溢出之前,接收到连续的三个重复冗余ACK(其实是收到4个同样的ACK,第一个是正常的,后三个才是冗余的),发送端便知晓哪个报文段在传输过程中丢失了,于是重发该报文段,不需要等待超时重传定时器溢出,最后客户端收到 2,因为345已经回复过了,返回ACK6。

为啥是3次呢? 你要明白发送端即使按序发送,接收端也是会出现乱序的。乱序也会造成冗余ACK发送,那冗余ACK是乱序导致还是丢包导致呢?经过权衡把3次冗余ACK作为判定丢失的准则其本身就是估计值。

数据接收情况

A为发送端,B为接收端,A的待发报文段序号为 【N-1,N,N+1,N+2】,假设报文段N-1成功到达。
  1. 在没丢失的情况下,有40%的可能出现3次冗余ACK,在乱序的情况下必定是2次冗余ACK。

  2. 在丢失的情况下,必定出现3次冗余ACK。

基于这样的概率,选定3次冗余ACK作为阈值也算是合理的。实际抓包时大多数的快速重传都会在大于3次冗余ACK后发生。

快速重传解决了超时问题,可是重传时是重传之前的一个,还是重传所有它是定不了的。

4.1.3 SACK

既然快速重传搞不定,就用 Selective Acknowledgment 选择性确认,原理也很简单,服务端给客户端回复的时候多加个字段SACK,SACK的内容就是告知发送端服务端收到了哪些。这样服务端可以根据收到的信息选择性发送丢失的包。

4.1.4 D-SACK

DSACK是在SACK的基础上做了一些扩展,主要用于对收到的重复报文进行了处理。DSACK同样使用了与SACK一样的报文格式。核心关注点是发送的时候出问题了还是回复的时候出问题了

  1. 如果发送端发送数据A延时而触发了快速重传机制,快速重传机制发送过来的信息新A,然后老A又到了,接收端会回复SACK 意思是网络震荡导致的。

  2. 如果服务端的ACK 客户端没收到,客户端重发的时候,服务端会回复SACK,意思就是你的数据发送重复了。

4.2 TCP 滑动窗口

如果没有滑动窗口的机制:传输N份文件,就需要等待N次应答时间。

总的传输时间 = N份传输时间 + N份应答传输时间。

保证可靠性的前提下TCP 引入了窗口概念,滑动窗口可以让我们进一步提高传输效率。在窗口内的数据无需等待确认应答就可以继续发送数据。窗口的本质是OS开辟的一个缓存空间,然后进行批量传输,只要接收方没确认应答那么缓存中会一直存在。

总的传输时间 = N分数据传输时间叠加成一份时间,N份应答传输时间,重叠成一份时间

窗口大小为4000字节

窗口大小一般是接收方来决定的,接收方会告知发送方自己有多少缓存可接受数据,如果超过这个数据量接收方就无法接收了。
4.2.1 发送滑动窗口
滑动窗口

在一的状态下发送方收到一个请求序列号2001的确认应答ACK,则2001前数据被标记为传输完毕,系统会进行窗口滑动变为二的样子。

  1. 窗口左边是已经发送并且受到服务器的ACK的数据,这些数据可以从缓存删除。

  2. 窗口内的数据其实也分为两类,一类是发送还没接收到ACK的,一类是还未发送的。在收到整个窗口的确认应答ACK之前,如果数据有丢失,发送端仍然需要重传。所以发送端需要有缓存保留可能被重传的数据,直到收到服务端ACK。

  3. 收到服务端ACK后,发送端会将窗口滑动到确认应答中的序列号的位置。这样可以顺序地将多个段同时发送提高通信性能。这种机制也别称为滑动窗口控制

  4. 窗口模式下发送方也会根据接收方的能力来进行发送数据来进行流量控制

4.2.2 窗口数据丢失

这里的数据丢失其实跟前面说到的重传机制类似,主要分为两种

  1. 接收端收到信息但是返回ACK失败了:如果丢失ACK,不需要做任何处理,如3001这个ACK丢了,但是4001ACK却已经发送给主机A,说明2001~3000这个数据也顺利到达,3001ACK丢了无所谓,只要当前序号开始,就说明之前的数据已经正确传输到达主机B

    收到数据但ACK丢失
  2. 发送端发送数据中途数据丢失了:如下图1001-2000数据包丢了,而2001-3000,3001-4000都顺利到达,此时接收方反馈的ACK确认序号始终是1001,发送方如果发现接收方连续发送ACK都是1001,接收方就明白1001-2000这个数据丢包,就会重新传送,当接收方重新收到丢失的1001-2000数据后,直接返回ACK4001,因为2001-4000已经接受过放到缓存区了,接下来ACK直接从4001开始。

发送时丢失

4.3 拥塞控制

前面说到的流量控制只是单纯的对于发送方跟接受方而已,但是我们要知道网络一般都是公用的,别的服务器也可以能将网络搞阻塞,因为阻塞导致重发,然后重发导致更阻塞,最后陷入恶性循环。

为了控制发送方的数据量避免数据阻塞整个网络,发送方维护着一个叫拥塞窗口的东西,前面说到过发送窗口接受窗口,现在由于有了拥塞窗口,此时 发送窗口swnd  = min(拥塞窗口cwnd,接受窗口rwnd)。拥塞窗口的大小是动态变化的,当网络没阻塞就会变大,网络中有阻塞就会变小。判断阻塞的依据就是如果发送方在指定时间内没收到数据那就是阻塞了。

拥塞控制主要通过慢开始快重传快恢复避免拥塞来实现的。

4.3.1 慢开始

TCP建立连接后系统有个慢启动的过程,意思就是一点一点的提高发送数据包的数量,慢启动的原则就是当发送方每收到一个 ACK,拥塞窗口 cwnd 的大小就会加 1。有一个叫慢启动门限  slow start threshold 状态变量来充当最大值。

  1. cwnd < ssthresh 时,使用慢启动算法。

  2. cwnd >= ssthresh 时,使用拥塞避免算法。

4.3.2 拥塞避免算法

一般情况下 slow start threshold  = 65535字节,系统进入拥塞避免算法后,每当收到一个 ACK 时,拥塞窗口就增加 1/拥塞窗口。拥塞避免算法存在的意义就是将慢开始的那种指数增长变化为线程增长

4.3.3 快重传

进入拥塞避免算法后的数据随着不断增长最终会导致网络阻塞,最终引发丢包。然后会采用前面说到的超时重传快速重传

  1. 超时重传:ssthresh  =  cwnd/2 同时 cwnd 重置为 1,然后重新开始慢启动,回到了起点。

  2. 快速重传:cwnd = cwnd/2 同时 ssthresh = cwnd 然后进入快速恢复算法。

4.3.4 快恢复

快恢复与快重传配合使用,当发送方接收到连续三个重复确认请求,为了避免网络拥塞,执行快速重传(cwnd = cwnd/2 同时 ssthresh = cwnd ),执行快速恢复算法。

  1. cwnd = ssthresh + 3

  2. 重传丢失的数据包

  3. 收到重复ACK则 cwnd 累加 1。

  4. 收到新ACK后设置 cwnd  =  ssthresh,进入拥塞避免算法。

5 参考

  1. 科技哥网络:https://t.1yb.co/gJRx

  2. 小林网络:https://t.1yb.co/fQG3

  3. TCP/IP讲解:

    https://developer.51cto.com/art/201906/597961.htm

  4. 快速重传:

    https://blog.csdn.net/whgtheone/article/details/80983882

-End-

最近有一些小伙伴,让我帮忙找一些 面试题 资料,于是我翻遍了收藏的 5T 资料后,汇总整理出来,可以说是程序员面试必备!所有资料都整理到网盘了,欢迎下载!

点击👆卡片,关注后回复【面试题】即可获取

在看点这里好文分享给更多人↓↓

以上是关于面试反客为主 TCP的主要内容,如果未能解决你的问题,请参考以下文章

TCP/IP高频考点之一个数据包的流浪日记

面试常用的代码片段

面向面试编程代码片段之GC

异常和TCP通讯

前端面试题之手写promise

QQ 为什么以 UDP 协议为主,以 TCP 协议为辅?