【JavaSE-网络部分04】网络原理-传输层:UDP + TCP 可靠性三大核心机制(确认应答 / 超时重传 / 连接管理)

张开发
2026/4/10 3:25:00 15 分钟阅读

分享文章

【JavaSE-网络部分04】网络原理-传输层:UDP + TCP 可靠性三大核心机制(确认应答 / 超时重传 / 连接管理)
传输层的学习传输层我们说过最核心的协议是TCP和UDP。那么在这里面我们再谈一下端口号。再谈端口号我们说端口号是用整数表示用来区分同一台主机上不同的应用程序。我们前面在网络编程冲每个程序中的socket创建的时候都需要关联端口号那么对于服务器来说端口号是程序员的手动指定的而对于我们的客户端来说端口号是系统自动分配的。端口号是由两个字节表示的无符号整数范围0~65535。虽然它的范围呢比较多但是呢并不是所有的数都能是可以使用的。0~1023 这样的范围通常我们是不使用的他们叫做知名端口号是给一些知名的服务器预留的。虽然现在我们知名的服务器没有太多已经寥寥无几了但是呢有两个知名的端口一定要重点认识。80 这个是给HTTP服务器留的端口号。443 》 这个是给HTTPS服务器留的端口。问题1一个进程是否可以绑定多个端口号答这个是完全可以的但是注意其实不是进程绑定端口号而是我们的socket绑定端口我们一个进程中完全可以创建多个socket所以呢可以同时关联到多个端口号,这个呢是一个非常常见的情况。举个例子我们在开发服务器程序的时候一般会提供至少两个端口业务端口:他的是用户的客户端通过调试端口/管理端口这个是不对外公开的我们程序自己使用生产环境下服务器程序不能通过调试器来调可能会把整个进程阻塞住使用户无法访问。问题2一个端口号是否可以被多个进程绑定答如果是同一种协议则不行如果是不同协议是可以的。UDP我们接下来就看传输层中的UDP协议学习一个协议其实重点学习的是协议的格式那么通过这个协议的格式我们可以带出协议的很多特性。整个UDP报头是8个字节分成四个部分每个部分占两个字节。UDP报头每个属性的含义源端口目的端口表⽰数据是从哪个进程来,到哪个进程去;长度通过2个字节的数据描述了整个udp数据报的长度(包括报头和载荷)【其中报头固定8个字节长度所以整个长度-8个字节的长度就是我们剩下载荷的长度】那么我们2个字节表示的数字最大是65535也就是64KB那么一个UDP数据包呢最大也就是64KB了。校验和它起到的作用是验证传输的数据是否出错了。那么我们考虑一个问题网络传输中为啥会出错比如我们10101比特流本质上它是以电信号或者光信号传输的而我们网络传输过程中它是会受到外界环境的干扰的因此呢我们数据往往会出错如果出错了我们怎么办呢有两种方案1、发现错误UDP的校验和写起到了发现错误的作用【也就是知道数据错误了但是不知道哪个位错误了】我们的UDP使用的是发现错误这种方案本质上是把udp数据报每个字节(除去检验和本身的这两个字节)将它们依次带入到一个数学公式进行一系列的计算得到一个数字然后此时如果输入的这一串字节内容是固定的并且呢计算公式也是固定的话此时得到的结果也是一定的反之如果发现两次计算的结果不同那么此时输入的内容也一定是不同的。举个例子发送方在发送之前先把这个数据进行计算得到校验和1然后呢我发送的时候就把数据和检验和1发过去。然后接收方收到数据之后再计算一遍校验和(计算公式呢是和发送方得是提前确定好的)得到新的检验和2然后他就对比校验和1是否等于校验和2如果不相等那么数据就一定是错的。2、纠正错误我们用的是海密码知道数据错了也能够知道哪个位错了。那么如果我们的数据超过64KB怎么办呢解决这个问题我们方案有两个:1.我们使用手动实现拆包组包的这样一个逻辑但是需要考虑如何拆分还得考虑如何组合,所以呢相当麻烦。2.使用TCP代替UDP。计算校验和的方案有多种。1.最简单粗暴的一种是CRC循环冗余校验他就是简单的相加。他计算起来简单快捷但是缺点相对来说容易出现输入不同数据得到相同结果的情况。2.更常见的方案是md5系列/sha1系列这样的方案它的特点1定长即无论你输入的内容是多少个字节他输出结果就是固定长度的。2分散输入的内容哪怕只改变一点点最终算出来的结果都会差别很大。3不可逆根据原始数据计算出md5或者sha1是非常快的然后呢再根据md5或者sha1的值反推出原始数据的成本会非常非常大几乎不可能。这样的方案也经常会在加密领域中使用。UDP更主要的应用场景对效率要求高丢包概率低的情况。比如我们在同一个机房内部的机器之间通信。TCPTCP的报文格式具体的图属性16位端口号和16位目的端口表⽰数据是从哪个进程来,到哪个进程去;4位首部长度他描述一个TCP报头的长度TCP数据报报头载荷那TCP报头长度是可变的导致需要有一个属性来描述它到底有多长这个属性我们叫做选项option它是可选的可有可无的选项option中有很多个属性可以选择一个也可以选择多个还可以一个都不选【也就是说我们的TCP报头是可变的原因就是他有了选项这个属性】。我们从上面的图中是可以看到的这个4位首部长度是4bit那4bit他是0~15的一个位数。在我们tcp的报头中除了选项这样一个属性之外固定部分长度总共是20字节。那么如果我们再算上这个选项的话肯定用这15个比特是不能表示的呀。其实tcp他有一个独特的技巧他在表示报文长度之时不是使用字节为单位。而是使用“4个字节”为单位比如tcp4比特长度选项中写了长度为15那么实际上真实的长度是60就是4×1560【4比特最大为15而以4字节为单位因此呢报头最大为4×1560个字节】而我们固定长度占了20个字节因此我们说报头中选项属性部分最多占40个字节且由于爆头的长度是4个字节为单位因此呢添加选项属性中值的时候我们是以四的倍数去添加的。保留位他的意思是现在不用但是留给未来做扩展他呢是吸取了udp不够用的一个教训我们前面学udp可以知道udp有一个问题就是长度不够的话是不可扩展的若要扩展会相当麻烦因此tcp的设计者就考虑了这样的问题所以呢我们tcp设计者在报头中预留了一些保留位即我现在先不用我就占个位置指不定后面呢就有用了。6个标志位它是TCP中最重要的6个标志位。16位校验和和前面udp的校验和是一样的也是用来检验数据是否出现错误。发送端填充,CRC校验.接收端校验不通过,则认为数据有问题.此处的检验和不光包含TCP⾸部,也包含TCP数据部分.以上是我们现在能够认识的一些属性那么对于6个标志位分别什么意思32位序号32位确认序号16位窗口大小16位紧急指针选项等等这些属性我们所有后续会慢慢讲因为呢它需要结合tcp的一些机制来解释我们在说tcp的核心机制的时候会带动这些属性学习。可靠性TCP最核心的机制就是可靠性。我们在网络通信中可能会存在丢包的情况我们就是针对于丢包这样的一个情况才去谈可靠不可靠。那网络通信中我们为啥会丢包呢比如堵车这样的一个情况那么我们为什么会堵车呢其实啊是因为一段路线中单位时间内能够通过的车的流量是有上限的。一旦超过了这个上限我们就会堵车。我们的网络世界也是如此我们主机与主机之间并不是一根网线直接相连的而是中间有很多很多的设备构建了我们的网络路径一个数据发送出去到对方接收到中间是经过很多的路由器或交换机的这些路由器或交换机就相当于我们的路口那每一个路由器或者交换机它的转发能力也是有上限的所以此时如果我们在某个时刻数据转发量暴增那么就可能超出某个设备的转发能力上限从而呢会导致数据丢包。而我们的网络世界中数据是有时效性的如果数据出现了堵车那么最终我们到达目的地可能就无意义了还不如直接丢了【因此丢包通常是路由器或交换机的一个主动行为】。丢包问题UDP和TCP的处理方式UDP的处理方式是数据发送出去之后就不管了这种称为不可靠传输。而我们的TCP处理方式是数据发送出去之后呢要关心对方是否已经收到如果没有收到还会有补救的措施这种我们称之为可靠传输。所以丢包仍然是客观存在的并且呢无法预知啥时候产生丢包因此我们的可靠传输是为了对抗丢包的怎么对抗感知到丢包如果丢包了能够做出补救。我们说TCP是可靠传输那么TCP如何实现可靠传输的呢这个就是我们TCP核心机制所要来完成的事情我们首先来看核心机制1确认应答。TCP核心机制1确认应答【实现可靠性基础1】TCP的核心机制一确认应答它是实现可靠传输的最核心机制。这里呢我们补充我们网上会有资料说TCP实现可靠性主要是靠三次握手这样的说法呢其实是不太对的那么具体呢我们会在后续的机制三次握手中解释。那么什么是确认应答呢就是我们怎么能够感知到对方是否收到我们的消息呢我们只需要对方收到消息之后呢要回复一个收到就可以了。那么我们怎样知道对方收到了呢哎就需要对方返回一个应答报文(acknowledge)简称ACK发送方知道应答报文之后呢就可以确定对方已经收到消息了。于是此时我们就说到了我们6个标志位中的第二位ACK举个栗子我想要请女神去吃饭然后呢我就用手机给女神发了一个短信eg1简单模型我女神女神我请你去吃火锅吧。女神好啊好啊【应答报文】上述这样的简单模型其实是有问题的。比如如果我发送多条消息eg2我女神女神我请你去吃火锅吧。我第一条刚发送过去我就立马发第二条了我女神女神你做我女朋友吧。此时在女神这边呢就收到了两条消息于是呢她就回复两条。女神好啊好啊【应答报文】女神滚此时呢我就根据女神回复的消息的顺序明确他的意思。此时我们就会面临两个情况1.如果处理顺利的话我就能正确的收到女神对应回复的消息。2.如果处理不当的话就会出现除了丢包之外的一个情况叫做后发先至如图此时呢我收到的肯定是先收到滚后收到好啊好啊。于是呢就代表着女神同意做我女朋友但不同意去吃火锅这不就让我理解错误了吗所以后发先至会影响我们的理解那么关于后发先至为什么会出现这样的一个情况呢啊咱们就举一个例子咱们结婚了有一个环节叫做接亲新郎在婆家新娘在娘家。我们一大早天没亮新郎这边呢就会组织一大帮车队啊带着他的伴郎团队准备去新娘家去接亲如图所示啊假设我们的新郎是在头车里面我们万事俱备只欠东风ok我们这些车队大家一起出发同时出村但是呀由于好多好多的原因呢比如等红绿灯新郎的头车突然遇到了红绿灯但是在走的途中呢后车伴郎他们的车呢是比较快的他们过了红绿灯于是啊我们走着走着哎这些车队呢会出现走散的一个情况于是最终我们到达的目的地是相同的只不过我们到目的地的顺序就不一样了于是就可能出现后面的候车比头车要先到达新娘家。因此呢在我们网络世界中也是类似这样的一个情况我们在转发数据的时候我们发的多个数据包就是一辆一辆车经过的每个路由器或者交换机就等同于十字路口每个数据包在经过路由器转发的时候走的路径可能是不同的。因此呢出现了典型的后发先至如图所示那么针对这样的一个场景呢我们TCP如何处理对于上述这样的情况首先我们应答当然是需要的只不过我们得考虑应答的顺序。那如何做到给应答保持顺序呢我们给数据编号。此时啊对于我们的应答数据呢就可以根据我们这些数据的编号完成顺序应答了。比如我们上述中女神女神我请你吃火锅吧【我们编成1号数据1.女神女神我请你吃火锅吧】女神女神你做我女朋友吧【我们编成2号数据2.女神女神你做我女朋友吧】于是女神的回复就是针对2滚针对1好啊好啊。最终这两条应答哪怕在我收到的时候顺序呢是不对的但是我能通过编号来得出它是对应哪一条请求的回复。这里我们举的这个例子其实是比较简单的它呢是按照条(一条一条的数据)去编号的。但是我们真实的TCP并非如此我们TCP是使用字节流的TCP实际上是针对每个字节进行编号的用连续递增的整数来对数据进行编号。那么由于我们的编号是连续递增的所以我们只需要知道数据中第一个字节的编号然后后面每个字节的编号我们自然就可以得出来了。那么这样的一个编号是在哪里记录着的呢此时就谈到我们前面会说的一个属性tcp报头中的属性32位序号。注意我们的编号是给TCP的载荷部分编号的而一个TCP的载荷是由多个字节构成的。可是TCP报头中的32位序号只能写一个数字呀那么请问我们写哪个数字呢就是说呀我们的载荷是由多个字节构成的那这多个字节就意味着有多个编号而我们的32位序号这个属性里面只能填一个请问是填哪一个呢答我们填的是第一个字节的编号因为只要知道第一个字节的编号那么后面字节的编号我们自然就可以得到。注意他是给载荷部分编号的不是给报头编号的保存的就是当前数据包载荷中第一个字节的编号。然后我们的应答报文要根据这样的序号来进行应答。假设我们发送方案的序号从1开始是1~44号的数据。那么我的接收方收到这些数据之后就要返回确认序号ACK用于告诉发送方收到了哪些数据。那么我们确认信号是怎么填写的呢这个确认序号在哪里填写的呢1.填写在哪里此时我们就谈到我们TCP报头中没有说到的那个属性32位确认序号我们的确认序号就是写在这个位置。2.怎么填写他的填写规则是把收到的数据载荷的最后一个字节序号加1填写到确认序号中。这样的好处呢我们要后面才能体会到我们到时候会说。也就是说应答报文中的确认序号填写的值是收到的数据最后一个字节的序号加1而不是第一个字节的序号。你可以理解成1.接收方告诉发送方哪个序号前面的数据全部收到了2.接收方也是向发送方索要下一个数据从哪里开始好比我们刚才发送1-44的数据那么我们接收方返回的序号应该是45告诉发送方1~44的数据我已经收到了你下一个要发送序号的位置从45号开始发。再举一个例子发送方发送1~1000的时候此时TCP是一个普通的TCP数据包此时序号这里填写成1ACK标志位为0接收方返回应答报文1001的时候此时TCP是一个应答数据报此时序号这里独立编号【独立编号的意思就是说你发送方和接收方他们各自编各自的编号。就是两个是互不影响的我接收端不可能跟在你那个发送方后面去编号的我接收方编号我自己的】确认序号这里填写的是1001ACK标志位是1同时ACK数据报的载荷是空的【目的只是确认 1~1000 字节收到不需要发任何数据所以载荷为空 —— 这是 TCP 里最常见的 ACK 形式】此时我们怎样理解呢首先我们发送方拿到确认应答是1001那它就代表小于1001的数据都已经确认收到了同时呢还代表接下来你要从1001开始给接收端发送数据。注意1序号和确认序号都是针对载荷的TCP报头不参与编号。注意2独立编号的意思就是说你发送方和接收方他们各自编各自的编号。就是两个是互不影响的我接收端不可能跟在你那个发送方后面去编号的我接收方编号我自己的。画个图解释一下【注意哦】我们的应答是acknowledge响应是response他两个是不一样的东西哦响应是带有业务的数据不要将他们混在一起。也就是说你客户端给服务器去发送一个请求那么我一定会给你一个应答的但是我给不给你响应那你看你的业务代码怎么写因此呢在这样的场景下我们编号是分别编号也就是独立编号就是说但凡是客户端给服务器发送的数据它是一组编号然后呢我服务端给客户端访问的数据它也是一组编号。我们在考虑确认应答的时候我们是没有考虑丢包的情况的但是这样的情况实际是存在的那么对于丢包的情况我们怎么处理呢我们就得考虑到我们的TCP的核心机制二超时重传。TCP核心机制2超时重传【实现可靠性基础2】确认应答呢是实现可靠传输的核心机制而我们的超时重传是针对确认应答进行的重要补充【就是说确认应答是一般情况而我们超时重传是对确认应答出现丢包的一种特殊情况处的理】他也是实现可靠传输的机制。确认应答和超时重传一起就实现了我们TCP的可靠性。我们说丢包是客观存在的它是随机出现的我们无法预测什么时候可能会出现丢包但是我们能够在出现丢包的时候能够做出一些事情做什么事情呢就是如果丢包了那么把丢的数据重新发送一遍。我们重发数据是能够对抗丢包的丢包是概率事件假设某个系统中丢包的概率是10%那么一次传输中数据丢包率是10%如果两次连续传输丢包了那么概率就是10%乘10%等于1%。是的所以呢也就是说你多传输一次过去他成功的概率从90%变成了99%。故重传它是可以提升我们成功的概率。那么我们如何识别当前数据丢没丢呢我们根据等待超时时间来判断的。也就是说如果你的发送方收到了确认应答ack那么就代表接收方收到了你的数据。而如果你的发送方没有收到ack此时对方你能知道他有没有收到吗不好说我们先等一会看一看等待一段时间再判断。我们在这个特定的时间间隔这个时间内都没有收到ack就认为丢包了。我们数据丢包有哪一种情况呢有两种情况[对于我们的发送方来说下述两种情况他都只能一视同仁都去重传因为我们区分不了这两种情况。]1A给B发数据丢了对于此种情况呢我们主机B本来就没有收到1~1000的数据。所以呢你主机A重新发送就好了如下图2B给A返回数据ack丢了那对于此情况呢我们主机B已经有了1~1000的数据了然后你再重新发送一遍导致我们B收到两份一样的数据那么你觉得这个合理吗具体的解释我们写在下面。对于情况2的话我已经收到1 ~ 1000的数据了然后你重传1~1000此时呢他就收到重复数据了我们怎么办呢我们不做任何处理吗它确实是有问题如果TCP不做处理那么可能会是应用层读了两次一样的数据。这样是很危险的比如我们的扣款数据你不可能扣两次吧。因此呢针对这样的情况我们的TCP会在内部进行了一个去重操作我们TCP的接收方会根据收到的序号进行去重从而呢保证同一份序号的数据在应用程序这里只能read一次。如何去除呢我们接收方在操作系统内核中有一个接收缓冲区。这个接收缓冲区啊是在我们内存中的我们每个socket都有自己独立的接收缓冲区这个缓冲区你可以把它想象成一个队列。我们接收方每次收的数据都会把它放到这个队列中然后呢每次收到新的数据操作系统都会判定当前这个序号是否已经在缓冲区中存在的。诶如果存在就直接把这个数据给丢弃了如果不存在我再放到接收缓冲区中。然后后面我们应用程序读数据也是从接收缓冲区中去读取。所以从这里我们可以看出TCP通信本质上也是生产者消费者模型。我们所谓的接收缓冲区就好比是一个阻塞队列。数据在接收缓冲区中有两个作用第一个去重第二个重新排序这样不仅能保证我们接收方收到的数据是不重复的而且呢也能保证应用程序得到的数据就是发送的数据从而解决了后发先至的问题。好比我们的之前举的那个例子去接亲对吧还有我们到达娘家的那个村口啊我们就在那等等着给新郎的头车进来之后我们再排好队再进村。那如果你用UDP去实现这些东西的话我们说UDP他的数据可能会超过上限然后呢你就得拆包所以你就得自己写代码去实现这些等待重传排序等等过程反正成本很高。这里呢我们说通过超时时间来判断是否丢包对吧那么超时时间的设定等多久合适呢在我们的TCP中这样的超时时间是一个动态变化的值因为在我们网络世界中如果你的网络很通畅的话诶它过一会就回来了但是如果你的网络很拥堵的话你就得很久对吧所以呢它是动态变化的如果你把这个时间写固定写死的话那他就无法契合这两种情况了。那他怎么做的呢他的核心思路就是我们每次重重传的时候会扩大超时等待的时间比如我发送一个数据对吧然后我等了一下。诶发现还不行我就进行第一次重传我再等一下把那个等待时间给延长发现还是没来我就进行第二次重传然后我再继续延长等待时间等待过后还是没来我就第三次重传延长等待时间每一次的等待时间都会增加也就是延长时间的阈值t0t1t2t3的如下图最后如果我收了这个ack了那么此时就没有超时重传了我们就收到ack但是注意我们重传的等待时间不是无限长的重传的次数也不是无限多的我们是有一定目标阈值的。我们达到一定程度就会视为网络出现了严重的故障此时我就不传了管你的。直接放弃通信也就是释放掉连接。超时重传和确认应答都是TCP实现可靠传输的核心机制。TCP核心机制3连接管理【辅助可靠性传输】。连接管理是辅助实现可靠传输的我们连接管理就做两件事儿建立连接断开连接这里呢我们再回忆一下什么是连接咱们在网络上的连接是指抽象的逻辑上的问题他呢并不是拿一根绳子两个主机给连起来。而是通信双方各自保存了对端的信息【核心保存的是IP和端口】。那所谓的建立连接呢就是通信双方保存对端信息这样的一个过程。而断开连接的就是通信双方删除对端信息的一个过程。建立连接建立连接的工作过程称为三次握手。那么什么是握手呢他的单词叫做hardshake它是我们计算机中常见的专业术语在我们的现实生活中握手其实本质上就是打招呼那么握手这样的一个动作他本身是没有实质性的业务数据的我们只起到一个打个招呼的作用因此呢我们将它引入计算机之中。那在我们网络中什么叫做握手呢就是说我发送一个不携带业务的数据通过这个数据和对方打个招呼【仅仅是打招呼】换句话说我们握手过程中传递的是“只有报头没有载荷”的TCP数据包。那么三次握手是怎么个握法呢如图所示三次握手首先是由客户端主动发起。注意别忘了客户端之所以叫客户端因为他是主动的一方【谁先发起第一次请求谁就是客户端】。1.我们说握手他呢只有报头数据没有携带载荷数据我们客户端给服务器发的这个请求中他的报头里面包含了一个SYN标记。这个SYN标记呢就是我们TCP报头中6个标记位中的第5位SYN它是synchronized的一个简写译为同步。这个synchronized在我们多线程中也出现过然后我们此处的呢也是synchronized的一个简写。那么虽然他们都表示同步但是呢他们的含义是不同的。也就是说在我们计算机中一个专业术语经常会有多种含义我们得结合上下文来理解。在我们多线程的加锁的同步中呢它本质上是互斥而在我们TCP这里的同步其实是一种通知那意思就是说诶接下来我要和你进行通信了我这个同步就是代表我要把我的信息同步给你【比如你在学校里的辅导员要开会刚好到你的同学小李呢请假了那么会后辅导员会告诉你你把今天会议的内容同步给小李说一下】。2.此处这个SYN数据包呢我们也称之为同步报文段。3.此时服务器会给我们返回一个响应这个响应就是我们前面所谈到的ACK【注意哦我们的确认应答不是在发送消息后才会有的我们在建立连接之初确认应答也是存在的】返回ACK代表当前我这个消息呢被服务器给收到了。也就是说呀服务器对客户端说你要给我建立连接那么我回复你客户端好的。此时其实就完成了这件事客户端说我要跟你建立连接了所以我客户端要保存你服务器的信息了服务器就来了一个好的。4.而建立连接是双方的事情一个巴掌我们是拍不响的我们要两个巴掌类似于我们结婚领证一样双方都要有一个结婚证。我们此时完成的其实就是 TCP 三次握手的核心过程首先客户端主动告诉服务器我要和你建立连接我会保存你的信息服务器同意后客户端就会记下服务器的信息。与此同时服务器在返回确认应答ACK时还会向客户端发送同步报文段SYN意思是我也要和你建立连接我同样需要保存你的信息。最后客户端收到这个同步报文段后会再返回一个 ACK 给服务器表示我知道你要保存我的信息了已确认。到现在为止我们的通信双方呢把对方的信息都保存好了那我们的连接才算完成。举个栗子比如我们的结婚我们的那个婚证人啊他会问新郎你是否愿意娶新娘啊那新郎说我愿意然后呢那个婚证人呢再问我们的新娘你是否愿意嫁给新郎呢那新娘说我愿意只有双方都愿意了我们这个婚礼才能举办下去如果有其中一方说我不愿意那我们肯定是无法进行结婚的。我们的建立连接也是如此。若通信双方有一端不愿意我们的连接都建立不起来。那我们上述的图呢其实是不太标准的你们不是说三次握手吗那这里为什么会有四次呢其实原因如下。中间那两次我们合并在一起了为什么要合并在一起呢首先中间两次的数据传输它们的时机是完全相同的。第二个原因是我们把两个数据合并成一个它能够提高我们的传输效率。那么我们为什么会提高传输效率呢这里边这里就提到了我们前边学过网络通信整个过程中的封装和分用如果我们合并成一次发送的话我们就只需要封装和分用一次而如果我们分两次发送的话就需要封装分用两次。所以此时我们这样的一个数据包里面我们将标志位位SYN和ACK都设为1我们上述解释完三次握手是怎么握的之后那么我们思考一个问题三次握手之意义是什么他能够解决什么问题1.第一个意义它能够起到投石问路的效果初步验证了网络通信路径是畅通的。因为你只要验证出这个网络通信是通畅的那么他就为我们后续进行可靠传输做了一个前提条件(这也就是说连接管理是我们可靠传输的一个辅助功能的原因)。比如啊我们修一个地铁那你修好地铁之后你可以直接去跑吗我们可不敢我们一般会拿一个空车先去跑一遍如果你这个空车呢跑通了就代表我们这个地铁修的就没问题。所以呢对比我们这里边的连接我建立连接我只是握手去探一下路我不携带任何数据我只是去看一下路后面如果他通的话你就将数据携带在载荷中传过去。2.第二个意义他能够验证通信双方的发送能力和接受能力是否正常他这个什么意思呢我们举这样一个例子我们放假回家了和自己的室友呢也就各回各家了那么我们有一天在一起开黑打游戏我们打什么游戏呢我们打王者荣耀于是我们为了方便沟通就把王者荣耀的语音麦克风给打开用于沟通交流所以我们在开游戏之前会检查我们的耳机和麦克风是否正常工作。第一次由我发出语音我说听得见吗听得见吗然后我的室友呢他听见我的语音了此时就可以验证我这边麦克风是正常的然后室友的耳机是正常的。然后我室友那边呢就反过来问我我听得见你听得见吗你听得见吗此时我这边能够听到了是有“你听得见吗”这个语音此时就可以验证了我的耳机是正常的我室友的麦克风是正常的。这里还要注意哦我们是约定过的只有我的室友听见我发的“听得见吗”他才会回复我我听得见你听得见吗你听得见吗所以到此时在我的这边我也就知道了我室友耳机和麦克风都是正常的啦同时自己的耳机和麦克风也是正常的。但是我室友那边他只知道我的麦克风正常的以及他的耳机是正常的他并不知道我的耳机是否正常。因此呢我们就得第三次通信目的是把我掌握的设备正常这些信息也告诉我的室友。于是第三次通信我就发送正常正常一切正常。此时我的室友收到之后呢他就知道了我的室友刚才发送的 “我听得见你听得见吗” 已经被我收到了。所以呢此时我的室友也自然就知道了我室友自己的麦克风是正常的然后我的耳机也是正常的从而呢我的室友就知道我设备正常的信息。也就是说我室友知道了我室友的耳机麦克风都正常以及我的麦克风我的耳机正常。这里边的耳机就代表接受能力麦克风就代表发送能力。3.还有第三个意义三次握手过程中可以完成关键参数的协商。什么是参数协商呢我们用办酒席的例子一讲就懂。办酒席前总得先商量好要摆多少桌才能让所有亲戚朋友都坐得下我先和家里长辈商量长辈说远方来的亲戚大概要准备 20 桌我再回复本地的邻居、朋友大概 17 桌最后长辈两边合计敲定总共准备 40 桌。这个双方互相沟通、一起敲定细节的过程就是 TCP 里的参数协商。三次握手不只是 “确认能连上”还会在这个过程里客户端和服务器互相商量好后续通信要用的关键参数为后面稳定传数据做好准备。那我们做参数协商的这个过程其实是在我们TCP报头的属性选项部分去填的。疑问我们三次握手中协商的一个重要参数就是序号从哪里开始也就是说我们TCP通信时序号一定是从1开始吗这是不一定的。从哪开始我们是可以通过参数协商这样的一个作用去调整的。具体从哪开始呢其实就是在我们三次握手的时候客户端和服务端各自生成自己的一个初始序号再通过三次握手去告诉对方然后对方就知道接下来咱们的通信你的数据会从哪个序号开始了。为什么咱们不直接从1开始而是要搞一种这种随机的数据答我们不从1开始是为了防止一种特殊情况前朝的剑斩本朝的官。如图比如有一次我们的客户端给服务器发送数据但是很遗憾我们这个数据它迷路了也就是说它绕了一个很远的弯路然后还堵车了。导致他一直到不了我们的服务端在他没有到达服务器之前啊可能我们的客户端和服务器就已经断开连接了。那后面我们又重新建立连接了这个迷路的数据兜兜转转它又跑到服务端了但是呀你当前的连接已经不是之前的链接了而你这个数据是之前连接发的数据。那问题来了你当前连接收到的这个数据是之前连接所发的这样的数据你要不要这样的数据我们是不要的我们应该丢弃掉因为我们重新建立连接之后有一种可能就是我们本次通信所建立的连接程序和之前所建立的连接程序并不是同一个程序。那这里边我们如何识别这个数据是上次连接而不是当前连接的我们通过序号来判别也就是说我们每次建立连接的时候协商序号的目的即我们每次建立连接都相当于随机数字作为起始序号此时我们就会出现上次连接和本次连接的序号就会差别很大。通过这个现象如果接收方看到有一个序号呢和大部分序号差别特别大哎就能判别出他是上次连接了就把它丢掉。这就是我们说不能都从1开始防止你看不出这样的一个差距【即防止你那个上次连接的序号和本次大部分数据的序号没太大差距】三次握手为啥要经过三次四次是否可以两次是否可以四次是否可以呢我们认为是可以的但是没必要。我们中间了两次通信最好呢还是合并在一起进行不要拆开如果你拆开的话他效率就会降低。两次是否可以呢我们认为是不可以的我们说三次握手有一个重要的目标就是验证通信双方发送能力和接收能力是否能够正常。如果是两次的话我们没法达成这个目标的。三次握手中的TCP状态变化图我们按照如图所示中的序号来解释状态状态在多线程中我们线程的状态是给我们调试提供了参数依据。因此我们此处tcp中三次握手它的状态也是起到这样的一个作用就是为调试提供参数依据。closed它呢是一个虚拟的状态表示tcp连接是没有的listen他表示服务器进入了专属状态就是说我们服务器已经准备就绪随时可以用客户端来建立连接。syn_send客户端发送出第一个syn处于的状态。syn_rcvd服务器收到第一个syn处于的状态。syn_send和syn_rcvd这两个状态他们存在的时间是特别短的正常情况下我们是看不到的。但是如果你的通信存在问题此时你就可以看到这里的状态了所以呢当你肉眼能够看到这两个状态就说明你三次握手应该是出现了麻烦。established他代表连接已经建立好了接下来就可以进行数据通行。好比就你电话已经打通了接下来就可以说话了。断开连接断开连接我们用的是4次挥手他也是一个不携带载荷没有业务意义表示特定功能的TCP数据包。我们三次握手一定是客户端开始第一个操作的。开始第一个操作的一方我们称之为客户端。而对于四次挥手来说客户端和服务器都有可能是主动发起的一方。我们假设我们客户端呢先主动发出吧那么客户端想给服务器说哎呀我要和你断绝连接了就是说我打算把你的信息给删除了。此时客户端就会向服务发送一个信息叫做FINFIN是我们结束报文段它呢是6个标志位中的第6位。此时我的服务器就会返回一个ack就是说服务器说哎你要把我删了好可以。与此同时呢服务器就说你要删除我的信息你要和我断开连接那么我也要把你删除我要将你的信息给删除掉。最后客户端说哎你服务器要把我的信息删除可以可以的。客户端回复一个可以之后呢双方就分别加对端的关键信息给删除掉此时我们的连接就断开成功。哎看到这里细心的老铁可能会发现我们三次握手中中间那两次是可以合并的。那么对于我们的四次挥手可不可以将中间两次也能合并变成三次挥手呢这个不一定哦有时候是能的有时候是不能滴。先解释为什么不能在我们标准情况下它是不能合并我们不能合并的关键原因是在于服务器返回ack和发送fin的时机是不同的服务器返回ack是在服务器收到fin后由操作系统内自动的立刻就返回了而fin则需要应用程序执行到close方法等进程退出之后才会出发。这两者之间它是有时间差的。为什么能触发特殊机制情况下能【后面会学一个机制捎带应答捎带应答下fin和ack可能会达到同一时机】四次挥手状态变化图按照图中的序号解释状态fin_wait_1和fin_wait_2和Last_ack这几个状态都会很快消失。close_wait它是在等待执行你的应用程序代码来调用close方法。time_wait谁主动断开连接谁就会进入到这个状态。还有一个特点就是我们等待一定时间之后再释放连接进入到这个状态之后呢正常情况下我们收到也不会发送任何的数据了我们就静静的等待一段时间连接就释放了。那么为啥要等一会再释放为什么不直接释放掉呢如图所示我们的关键要点是最后一个ack可能会丢包如果是前面的数据丢包的话那么直接触发超时重传机制就可以重传此时通讯双方的连接都正常的你可以重传。如果客户端在发送最后一个 ACK 报文后直接释放连接一旦这个 ACK 报文丢失那么此时对方服务器因为没有收到ack从而服务器就会重传这个fin而由于你的客户端这边已经把连接给释放掉了所以呢就没有人能够响应这个fin的数据也就没有人能够返回ACK了。因此呢我们这个time_wait这个状态呢就可以说是你等待一段时间再释放连接这样就能解决上述出现的问题也就是说你这个状态平时呢也没啥作用但是真出问题了你是能解决它的。那么我们说time_wait是等待一段时间的那么等待多久合适呢我们说要让他等到一个比较充分的时间要让它存在的时间足够对方进行FIN的重传确保一定是对方不会重传了我们再释放。那他这个状态时间是可以设定的我们操作系统中有一个参数就是做MSL他表示网络通信中从一端到另一端传输经历的最大时间time_wait等待时间是2MSL。好啦UDP 和 TCP 的前三套核心机制就聊到这TCP 一共有 10 个核心机制下一期咱们重点讲第四、第五、第六个 —— 滑动窗口、流量控制、拥塞控制看看 TCP 是怎么在保证可靠传输的前提下把性能也提高的老铁们别忘了点赞、关注、加收藏咱们下期见

更多文章