2.1 TCP 报文结构
- 源/目的端口号:表示数据是从哪个进程来,到哪个进程去;
- 32位序号/32位确认号:与确认应答机制有关(这里讲~)
- 4位TCP报头长度:表示该TCP头部有多少个32位bit(有多少个4字节),首部长度有4个bit位,表示最大长度是15,因此,TCP头部最大长度是15 * 4 = 60 字节
-
6位标志位:
URG:紧急指针是否有效
ACK:确认号是否有效
PSH:提示接收端应用程序立刻从TCP缓冲区把数据读走
RST:对方要求重新建立连接,将携带RST标识的称为复位报文段
SYN:请求建立连接;把携带SYN标识的称为同步报文段
FIN:通知对方,本端要关闭了,称携带FIN标识的为结束报文段 - 16位窗口大小:与滑动窗口有关(这里讲~)
- 16位校验和:发送端填充,CRC校验,接收端校验不通过,则认为数据有问题。此处的检验和不仅包含TCP首部,也包含TCP数据部分
- 16位紧急指针:标识哪部分数据是紧急数据;
- 40字节头部选项:是一个可变长的可选信息部分,允许TCP报文携带额外的配置或控制信息,TCP包括20字节固定部分,以及最多 40 字节可选部分,因此,TCP 报头的总长度可以在 20-60字节变化~
2.2 TCP 特性
2.2.1 确认应答
实现可靠机制的最核心机制!!!
接收方就可以通过 ack 的确认序号,告诉发送方哪些数据已经收到了(TCP 传输引入了序号和确认序号)
TCP 将每个字节的数据都进行了编号,即为序列号
每个 ACK 都带有对应的确认序列号,就是告诉发送方,我已经收到这些数据了,下次从哪里开始发~
TCP 是针对每个字节都去编号,TCP 并没有一条消息两条消息这样的说法,是从前往后,把每个字节分别分配一个编号~
【注意】确认序号的规则
并不是说,发送发的序号是什么,确认序号就是什么
而是取得是发送发过来的所有数据,最后一个字节的下一个字节的序号
确认序号 1001 的含义:
1) < 1001 的数据,接受方已经收到
2)接下来,想向发送方索要从 1001 开始的数据
Q1:为什么要有序号?
举一个例子,更好理解~ 假设这样一个场景,小丁准备把小万追到手,小丁想通过发短信来表达自己的心意
小万是想应答小丁,可以一起去吃饭,但是不能做小丁的女朋友~ 结果这两个短信回应,因为一些原因,比如网络呀等,导致了先发后至的效果~ 造成小丁理解错误,不吃饭就不吃饭呗,反正可以做我的女朋友!
因此,需要引入序号,确保数据传输的可靠性,避免造成语义错误~
对于TCP来说,自身也承担着整队的任务,TCP 会有一个接收缓冲区,一块内核中的内存空间,每个 socket 都有一份自己的缓冲区,TCP 就可以按照序号针对收到的消息进行整队,排好序,这样应用程序读数据,读到的一定是有序的,和发送顺序一致了~
A1:因此,答案也就显而易见了,有序号是为了解决网络中的数据包可能经过不同的路径到达接收端,因此到达的顺序可能与发送的顺序不一致的问题,TCP为每个字节分配了一个唯一的序号,这样接收端就可以根据序号将接收到的字节按照正确的顺序重新组装起来
2.2.2 超时重传
如果上述过程一切顺利,就可以直接确认应答,可靠性自然得到了支持!
但是,丢包也是网络非常典型的情况~
如果中间任何一个节点,出现了问题,都会导致丢包,每个设备,都是在承担了很多的转发任务的,每个设备转发能力都是有上限的,某一时刻,某个设备,上面的流量达到了峰值,就可能引起部分数据被丢包~
这种情况下,非常容易出现概率性丢包~
对于看视频,看直播,对于丢包是不敏感的,看视频提前有预缓冲
打游戏,尤其是对抗性的游戏,对于丢包是非常敏感的!
超时重传:
如果包丢了,接收方就收不到了,自然不会返回ack,发送方就迟迟拿不到应答的报文
等待了一段时间之后,还是没有收到应答报文,发送发就视为刚才发送的数据报丢失了,就会重新发一遍
(网络丢包是概率性事件,上个包丢了,重传还是有很大机会传过去的)
对于丢包的判定:
发送方对于丢包的判定,是一定时间内,没有收到 ACK,有如下两种情况
1)传输的数据直接丢了,接收方没收到,自然就不会发ACK
2)接收方收到数据了,返回的ACK丢了
发送发无法区分这两种情况的,只能都重传!
在第 2 种情况下,返回ACK丢失了,发送方又传了一遍数据,这样接收方不是会收到重复的数据嘛~
TCP 非常贴心地帮助我们处理好这个问题了,会在接收缓冲区中根据收到的数据的序号,自动去重,保证应用程序读到的数据仍然只有1份
(哪有什么岁月静好,全是 TCP 帮助我们负重前行呀)
有小伙伴就问了:重传的数据有没有可能又丢失了
也是有可能的!重传不是百分之百可以传过去的~一旦出现连续丢包,这种情况多半你的网络出现了非常严重的问题!!!
对于多个包丢失:
TCP 针对多个包丢失,处理思路是,继续超时重传~
但每丢一次包,超时等待的时间,都会变长,即重传的频率降低了,TCP 可能觉得,重传也怕是没啥希望的,干脆就摆烂摸鱼了,反正重传过去也啥用,网络已经出现严重的问题了~
对于连续多次重传,都无法得到ACK:
连续多次重传,都无法得到 ACK,此时 TCP 就会尝试重置连接,相当于尝试重连,如果重置连接也失效,TCP 就会关闭连接,放弃网络通信!
总结一句话:能重传就重传,实在传不了,就关闭连接,尽可能完成传输~
- 如果数据传输一切顺利,使用确认应答保证可靠性
- 出现丢包,使用超时重传作为补充
确认应答和超时重传这两个机制,是 TCP 可靠性的基石!!!
2.2.3 连接管理
TCP 建立连接:三次挥手
TCP 断开连接:四次挥手
上述这两个过程,和可靠性多少有一点关系,但也仅此而已~
比如被问到:TCP 是如何实现可靠性的?
答:确认应答+超时重传
(可能有些小伙伴们会回答,是因为 TCP 有三次握手和四次挥手,这是不正确滴)
2.2.3.1 TCP 三次握手
握手,指的是通信双方,进行一次网络交互,相当于客户端和服务器之间,通过三次交互,建立了连接关系,双方各自记录对方的信息,**确认客户端和服务器各自的发送能力和接收能力都正常!**这就是后续可靠传输的基础~
syn 称为同步报文段,意思就是一方要向另一方,申请建立连接~
就比如,小丁对小万说,你愿意做我的唯一嘛~(小丁的内心独白:小万你只能爱我一个,我还可以爱别人!)
小万就返回一个应答 ack,可以捏!当然,小万也不蠢,听出了里面的话里的潜在含义,也发一个 syn,你也愿意做我的唯一嘛~ 意思就是小丁只能爱小万一个!!!
小丁就返回一个应答 ack,俺也一样,只爱你一个!
双方都确认了自己是对方的唯一,此时关系就建立起来了,连接建立完成
上述过程内核自动完成,应用程序干预不了~ 等到连接完成后,服务器 accept 把建立好的连接从内核拿到应用程序~
三次握手需要达成什么样的效果?
三次握手本质上是投石问路,确认一下是可以发送数据的,验证了客户端和服务器,各自的发送能力和接收能力是否正常!
(从这个角度看,三次握手和可靠性也是有关系的,但是肯定没有确认应答超时重传更重要!)
再举一个形象的栗子,帮助我们更好的理解~
假如小丁和小万连麦,甜蜜双排~ 每次连麦的时候需要进行三次握手的测试,测试双方的麦克风和耳机的正确性!
确认了客户端和服务器各自的发送能力和接收能力都正常,这就是后续可靠传输的基础!!!
Q1:两次握手可以吗?
不可以,为了防止服务器一直等(服务器不知道客户端接收的能力),同时也为了防止客户端将已经失效的连接请求突然又传送到了服务器
Q2:四次握手可以吗?
完全可以但是没有必要,中间的 syn 和 ack 可以分两次分别发送,同样可以达成目的,但是完全没必要! 分两次发的效率不如合并成一次! 原因是多一个封装和分用!不用多此一举啦~
2.2.3.2 TCP 四次挥手
TCP 四次挥手,即断开连接(和三次握手非常像)
通信双方,各自给对方发送一个 FIN 结束报文,再各自给对方返回 ACK
ACK 和 FIN 有一定概率合并成一个的,但是通常情况下,不能合并!
Q:有小伙伴就会问,FIN 和 ACK不能合并一次发过去嘛~ 就跟三次握手,SYN 和 ACK 一起发过去一样,为什么三次握手能100%合并,四次挥手就不能合并呢?
A:因为 FIN 和 ACK 触发时机不同!
三次握手,ACK 和 SYN 都是同一时机触发的,都是内核来完成的
四次挥手,ACK 和 FIN,则是不同时机触发的,ACK 是内核完成的,会收到 FIN 的时候第一时间返回!但是,FIN 则是应用程序代码控制的,在调用 socket 的 close 方法的时候才会触发 FIN
服务器发现客户端断开连接后,服务器自己也进行 close 操作,正是这个 close 触发了第二个 FIN
但是这个 close 的执行时机,可能是立即,也可能是隔很久,取决于代码如何写,如果是立即 close,就可以趁着刚才的 ACK 还没有发,这里就可以合并了,如果是隔很久再 close,此时 fin 就只能单独发送了~
还有一些情况,比如 TCP 客户端没有显示调用 close,fin 如何发呢~
其实,进程结束了,自然就会自动进行 close,此时就触发了 fin!
此时客户端虽然结束进程了,但是 TCP 连接还在,内核维护的,进程是结束了,但是内核还是会把 TCP 连接继续维护,直到四次挥手完成,服务器这边也是一样~
(小小总结:因此,进程的结束对四次挥手没有太大的影响~)
【注意】
建立连接,一定是客户端主动发起的!
断开连接,客户端和服务器都有可能先发起
2.2 4 滑动窗口
TCP 要保证的不仅仅是可靠性,还有效率~ 提升可靠性,往往意味着损失效率(总不能啥都想要吧~)
滑动窗口:批量传输数据
(上述图片来自一本书《图解 TCP/ IP》,非常好的讲解网络原理的书~)
这样,我们就可以清楚的知道,滑动窗口的目的是提高数据传输的效率~
但是!!! 整体效率的提升是和没有进行批量发送进行对比的,而不是和可靠性对比的,再怎么快,都比不上 UDP 的快,TCP 的效率机制,本质上是让性能损少一点~
同时,并不是所有时候都需要批量发送,如果数据量很少,只有一两条等,少量的,低频的操作,就完全不需要批量发送呀,就仍然按照前面朴素的确认应答和超时重传~
注意!! 批量不是无限发送,是发送到一定程度,就等待 ACK,不等待直接发送数据量是有上限的,并且是回来一个 ACK,就立即发送下一条,就相当于总的要批量等待的数据是一致的,把批量等待数据的数量,就称为 "窗口大小"
下面模拟一下滑动窗口:
- 批量发送了四条数据,就等待着四个ACK(红色框起来的区域,就相当于是等待的窗口)
- 收到一个 ACK,就会立即发送下一条,当收到 2001 这个 ACK,意味着 1001 - 2000 这个数据得到了确认,此时就会立即发送 5001-6000这个数据
- 此时,就可以看到窗口移动了一格,如果收到的 ACK 非常快,此时这个窗口就在快速的往后滑动~
Q1:批量发送的过程中,如果出现丢包怎么办呢~
A1:分为两种情况:
1)ACK 丢了的情况
分析:
这种情况,什么事都没有,即使丢了这么多 ACK,对可靠性没有任何影响!!!
我们可以回顾一下确认序号的含义,确认序号表示该序号之前的数据都已经收到了,后一个 ACK 能够涵盖前一个 ACK 的意思,当收到 2002 这个 ACK 的时候,此时,发送方就知道 2001 之前的数据都收到了,即 1- 1000 这个数据也就收到了,1001 这个 ACK 丢了就丢了,无所谓的~
因此,确认序号的目的就在于这里,可以表示该序号之前的数据都已经收到,后一个 ACK 能够涵盖前一个 ACK 的作用
当然,如果是最后一个 ACK 丢了,那就照常超时重传~
2)数据丢了的情况
【注意】
当A把1001-2000这个数据重传后,B收到之后,返回的ACK确认序号是7001而不是2001!!!因为2001 - 7000 这些数据,B都已经收到过了~ 通过下面这个图进行解释:
上述重传的过程,没有任何冗余的操作,丢了数据才会重传,不丢的数据不必重传,整体速度是比较快的,这个重传过程也叫快速重传,是基于滑动窗口的~
B这边接收的数据,是先放到接收缓冲区,接下来应用程序就可以通过 socket 里的 InputStream 来读,代码中读出来的数据,就从接收缓冲区删除了
(有没有想起是生产者消费者模型,生产者就是对端,发送过来数据,消费者,就是应用程序,通过socket 里的 InputStream 来读,取出数据,消费数据)
2.2.5 流量控制
也是保证可靠性的机制~
滑动窗口是批量发送数据,窗口越大,相当于批量的数据越多,整体的速度就越快~ 但是,越快就越好吗?要记得,我们 TCP 可是可靠传输呀,如果发额度太快,瞬间让接受让缓冲区,接下来继续发送,此时数据就会丢包,这种情况就得不偿失, 那还不如发慢一点呢~
因此,通过流量控制,本质上就是让接收方来限制一下发送方的速度,让发送的慢一点,甚至阻塞一下!
具体控制的方式是:让 ACK 报文,携带一个"窗口大小"的字段
当 ACK 为 1 的时候,此时为ACK 报文,窗口大小字段就会生效,这里的值就是建立发送方发送的窗口大小
接收方计算窗口大小的方式,简单粗暴,直接使用接受缓冲区剩余空间,作为窗口大小
用一张图生动解释一下:
既然实际的发送窗口大小由流量控制和拥塞控制共同决定,下面介绍拥塞控制!
2.2.6 拥塞控制
滑动窗口的大小取决于流量控制(衡量接收方的处理能力)和拥塞控制(衡量了传输路径的处理能力)
互联网中的两台主机通信,可不是拿一个网线直连的~ 在这中间包括很多路由器和交换机的!如下图:
从上面的图,可以很明显看到,传输路径上,任何一个设备,如果处理能力遇到了问题,都会对整体的传输速率产生明显的影响~
拥塞控制,做的事情就是衡量中间节点传输的能力
拥塞控制,是要衡量中间路径,如何衡量呢?
如果通过中间路径有多少节点,每个节点的当前情况,是不好衡量的,因为可能每次传输走的路径都不一样!!
拥塞控制的衡量方式:这里采用的是实验的方式,找到一个合适的发送速率,开始的时候,按照一个小的速率发送,如果不丢包,就可以提高一下速率,扩大窗口的大小,如果出现丢包,则立即把速率再调小,重复上述过程。(同时,网络拥堵的情况,也不是一成不变的,在时刻变化中,此时,拥塞控制这样的策略就是能很嗨的适应变化的网络环境)
实际的发送方的窗口的大小 = min(拥塞窗口,流量控制窗口)
通过一张图,理解拥塞窗口变化的策略趋势,如下:
- 第 1 阶段,刚开始传输,会给一个非常小的窗口,比较小的初始速度,称为慢开始
- 第 2 阶段,以指数增长,速度非常快,可以让窗口大小,在短时间内就达到一个比较大的值,快速地接近当前网络传输路径的能力瓶颈
- 第 3 阶段,指数增长到一定阈值,就变成线性增长,避免一下突然超过上限很多,这样线性增长可以使传输速度逐渐接近传输上限,增长到一定程度,出现丢包,认为当前窗口大小,已经达到当前路径上的传输上限了
- 第 4 阶段,此时又立即把窗口大小回归到一个比较小的初始值,重复上述过程
- 第 5阶段,以指数增长,阈值变小,很快就达到新的阈值,变成线性增长,与上述过程一致
2.2.7 延时应答
TCP 可靠性的核心是确认应答,ACK 要发,但不是立即发送,而是稍稍等一会发送,就是延时应答
通过上述 TCP 核心特点介绍,TCP 中决定传输效率的关键元素就是窗口大小,而窗口大小是通过流量控制和拥塞控制一起决定的,其中流量控制的窗口大小是接收方接收缓冲区剩余空间的大小
- 如果立即返回一个 ACK ,此时 ACK 里带有一个窗口大小,设为 n;
- 如果稍等片刻再返回 ACK,此时 ACK 的窗口大小,大概率比 n 大,因为等的这一小会,应用程序从接收缓冲区里,消费了一批数据了,所以窗口变大了
由此可见,延时应答的目的就是,通过这个延时,让接收方应用程序,趁着这个时间多消费一些数据,此时反馈的窗口大小,就会更大一些,这样发送方的发送率也就能快一些了,同时满足接收方能够处理得过来
不是所有的包都可以延迟应答,这里有两种情况:
- 数量限制:每隔 n 个包就应答一次
- 时间限制:超过最大延迟时间就应答一次
2.2.8 捎带应答
捎带应答是基于延时应答的,字面意思,就是带着一起发过去~
客户端服务器之间的通信模型,通常是"一问一答"的这种模式,通过下图进行解释:
显而易见,合成一个,比分两次发,效率要更高!
因此,相信小伙伴们已经知道为啥是四次挥手,有可能是三次挥完,同上述,是捎带应答起的效果,fin 和 ack 触发时机不同,但如果在应用层调用 socket 的 close 方法触发 fin 和 内核负责 ack 是同一时机的时候,ack 就可以捎带 fin,捎带应答过去了~
2.2.9 面向字节流
我们知道 TCP 是面向字节流的,这里暗藏一个问题:粘包问题!
举一个栗子吧,如下:小丁和小万的对话
小万的视角,很好区分从哪到哪是一句完整的话,因为小丁的说话习惯,都是以小万开头,容易区分
但是小丁的视角,到底从哪到哪是一句完整的话呢!难以区分(这里的"一句话",就相当于一个"应用层数据报")
即当 A 给 B 连续发了多个应用层数据报之后,这些数据就都积累到 B 的接收缓冲区中,紧紧挨在一起,此时 B 的应用程序在读数据的时候,就难以区分从哪到哪是一个完整的应用层数据报,很容易读出半个包或者一个半包等等
粘包问题的有效解决方式:
- 定义分隔符
- 约定长度
以上两种方式都是自定义应用层协议的注意事项,HTTP 协议既会使用分隔符,也会使用长度的策略~
Q:不是定义了序号吗?怎么会区分不了呢!
A:注意,接收缓冲区已经是分用过的了,把 TCP 报头都拆掉了,只剩下应用层数据 TCP 载荷~
2.2.10 异常处理
这里列举 4 个 异常情况,TCP 的处理:
- 进程关闭/进程崩溃
进程没有了, socket 是文件,随之被关闭,虽然进程没有了,但是连接还在,仍然可以四次挥手(进程关闭与四次挥手无关)
- 主机关机(正常流程关机)
先杀死所有的用户进程,这样进程没有了,同理,socket 随之被关闭,也会触发四次挥手,如果在关机这段时间,挥完了,更好,如果没有挥完,比如对方发过来的 FIN,这边还没有来得及发 ACK,就关机了,此时对端就会重传 FIN,重传几次后,发现都没有 ACK,尝试重置连接,如果还不行,就直接释放连接
- 主机掉电(不考虑笔记本,拔电源,一下很快的关机)
瞬间就关机了,来不及进行任何挥手操作,分两种情况:
1)对端是发送方
对端就会收不到 ACK,就会超时重传,几次超时重传后,没有反应,就重置连接,释放连接
(收不到ACK ——> 超时重传 ——> 重置连接 ——> 释放连接)
2)对端是接收方
对端是没法立即知道,这边是还没来得及发新的数据,还是直接没有了,TCP 内置了心跳包保活机制,虽然对端是接收方,对端会定时给这边发一个心跳包(ping),这边返回 pong,如果每个 ping 都有及时的 pong,说明当前对端的状态良好,如果 ping 过去了之后,没有 pong,说明心跳没了,这边挂了(不是说一发 ping 没 pong 就证明心跳没了,因为 ping 和 pong 也有可能丢失,比如连续几次都没 pong,说明心跳没了)
心跳包的特点:
a)周期性
b)如果心跳没了,就挂了
-
网线断开
同 3 主机掉电的处理方式
(这里只是介绍了 TCP 的十大核心特性,TCP 还有很多特性~小伙伴们还可以继续深入了解)