目录
1、简单了解应用层协议
2、传输层UDP协议
3、传输层TCP协议
3.1、TCP报文介绍
3.2、TCP实现可靠传输的核心机制
3.2.1、确认应答
3.2.2、超时重传
3.3、连接管理 (三次挥手,四次握手)
3.3.1、建立连接(三次握手)
3.3.2、断开连接(四次挥手)
3.4、滑动窗口
3.5、流量控制
3.6、拥塞控制
3.7、延时应答
3.8、捎带应答
3.9、面向字节流(粘包问题)
3.10、 TCP异常情况
4、TCP和UDP的应用场景的差别
网络通信中,应用层是和代码直接相关的一层,决定了数据要传输的内容和拿到数据之后的使用。和程序员最常打交道的就是应用层了,根据不同的业务场景,很多时候需要程序员自己自定制协议。
❓自定制协议是如何约定的?
- 根据业务场景的需求,分析客户端和服务器之间(请求/响应)要传递的消息。
- 确定数据是以什么样的格式来组织,可以是随意约定的(如:直接基于分隔符,将属性之间通过逗号分割,完整请求以分号结尾)也可以是一些现成的格式(如:xml和json)。
应用层也有知名并广泛使用的成品协议,如:HTTP协议,这里只是了解一下应用层的协议组成类型(可以通过自己约定,也可以使用成品协议)。应用层典型的协议小编会在后面的博客中展示。
✨UDP协议格式:
上面UDP协议格式的这种画法,实际上是不太准确的,这种教科书上为了排版方便,实际上UDP协议格式画成下面这种是比较合理一些的。
✨操作系统对端口号的一些划分
传输层得到数据之后要进行封装,TCP和UDP协议的报头种都会包含源端口号和目的端口号,并且都是用2个字节(16bit来表示端口号,范围为0~65535);我们日常写程序使用端口号一般都是从1024开始的,因为0~1023这个范围的端口号被操作系统已经分配给了一些知名的服务器预留使用。这些端口号也被称为"知名端口号/具名端口号"。
对UDP报头中的属性进行了解。
- 这里源端口和目的端口,就不必多说了,前面的博客中总结的协议的五元组中有说到。
- UDP数据报的长度:数据报的长度包括报头+载荷,这个属性表示了一个UDP数据报的长度,这个属性自己占用两个字节的长度,因为报头中每个属性都占2个字节的长度,但是载荷没有规定长度想要知道载荷的长度,就需要将这个属性中存储的数据拿出来,减去UDP报头的长度,就是载荷的长度。
- 校验和:用来判断传输的数据是否出错。网络传输的本质就是在传输光信号/电信号,在传输的过程中,可能会受到一些物理环境的干扰,就可能出现"比特翻转"的情况,将0变成1,将1变成0。这个时候传输的数据就会出错,这些客观现象是不可避免的,所以就引入了校验和来对传输的数据进行检测。
❗❗❗注意:
1️⃣上述我们了解了UDP报头的属性,UDP数据报的长度这个属性占两个字节的长度,也就是它可以记录0~65535之间的数据报长度,也就是说一个UDP数据报的大小不能超过64kb,但是如果要传输的数据很多,数据报所占的大小超过了64kb,这个时候该怎样处理呢?
- 一种方法是:将要发送的数据拆分成多个部分,使用多个UDP数据报来传输,虽然可行,但是复杂。
- 另一种方法是:不使用UDP协议,改用TCP协议,TCP协议没有这样的限制。
2️⃣ 上述说到校验和是用来判断传输的数据是否正确。下面来看一下校验的过程。
校验和是针对数据的内容或者数据的一部分内容作为参数进行计算。发送方把载荷数据,带入到校验和算法中计算,得到校验和结果,设为sum1。发送方将这个数据报发送给接收方,接收方收到这个数据报,这个数据报中存在载荷数据,也有校验和数据sum1,接收方把载荷中的内容按照校验和算法进行计算,得到的校验和结果为sum2,将sum2和sum1进行比对,如果不相同,那就会存在两种情况,第一种,在数据传输的时候,载荷数据发生了变化,第二种,就是在传输过程中sum1发生了变化,载荷内容并没有发生变化。不管是那种情况,只要校验和结果不同,数据都会重新发送。
TCP,即Transmission Control Protocol,传输控制协议
了解一下TCP报头的字段(固定首部长度为20个字节,可变部分0~40字节)。
- 源端口和目的端口:占2个字节,16个bit位,范围0~65535.
- 32位序号:占4个字节,TCP连接中传输的字节流中的每个字节都是按照顺序编号。例如一个报文要传输的数据为1000个字节,因此就将一条数据中的第一个字节作为了TCP报头里记录的序号也就是1,代表要发送的这一条数据。
- 32位确认序号:占4个字节,是接收方返回给发送方的应答报文。例如,发送发给接收方发送的数据有1000个字节,接收方返回给发送方的应答报文为1001,表示接收方收到了发送方的数据,期望下次发送1001开始的数据。
- URG:该位为1时,表示包中有需要紧急处理的数据。对与需要紧急处理的数据,会在后面的紧急指针中再进行解释。
- PSH:该位为1时,表示需要将收到的数据立刻传给上层应用协议,PSH为0时,则不需要立传而是先进行缓冲。
- RST:该位为1时表示TCP连接中出现异常必须强制断开连接。
- SYN:用于建立连接,SYN为1表示希望建立连接,并在器序列号的字段进行序列号初始值的设定。
- FIN:该位为1表示断开连接。
- ACK:该位为1时,确认序号字段有效,当ACK为0时,确认序号字段无效。
确认应答是实现TCP可靠传输最核心的机制,TCP特性中的可靠传输并不是指发送方能够百分之百将数据发送到接收方,而是尽可能的将数据传输过去,如果传不过去,发送方至少能知道自己没有将数据传输过去。
这里发送方如何知道接收方有没有接收到数据。核心机制在于接收方,收到或者没有收到数据,会有一个应答。(当接收方收到数据,就会返回一个应答报文,没有收到数据,就不会返回应答报文)。
举个例子:你和女神的关系非常要好,你发的消息女神一定会立即回复,当你给女神发消息说要请她吃麻辣烫,当女神(接收方)回复了你之后,你作为发送方,就知道了这条消息女神收到了。
这里的女神发的好呀好呀就称为"应答报文",也叫做ACK(acknowledge)报文。
✨使用序号和确认序号来解决消息传输中的后发先至的问题
上述是发送一条消息的情况,但是我们很多时候,我们是连续发送多条数据的,这个时候可能出现"后发先至"的情况。就像下面的例子。
在网路通信中,两个主机之间,传输路径存在多条,两条数据报可能选择的传输路径不同,设备之间的转发效率也存在差异,受到这样的网络环境的影响,这种后发先至的情况是时常会出现的。就像上述的情况就会产生歧义,本身女神传输的意思就是,我可以和你吃麻辣烫,但是我不能和你处对象。但是由于后发先至的问题,我们得到的消息传输的意思就是,今天不和你吃麻辣烫了,但是我们可以处对象。
解决上述的后发先至的问题,我们就需要针对消息进行编号。这里就需要对发送的消息分配一个"序号",同时应答报文,给出"确认序号"。
这样就解决了后发先至的问题,不会出现歧义。
✨真实的TCP数据传输也是引入了序号和确认序号的概念
实际上发送方在发送数据的时候,TCP序号并不是像上述那样简单的使用1,2这种方式编号,TCP是面向字节流的。在实际TCP报文传输中,是针对要传输的数据的所有字节都进行编号,假设一条数据的长度为1000个字节,传输的数据第一个字节的序号是1,由于这1000个字节都是属于同一个TCP报文,因此就将一条数据中的第一个字节作为了TCP报头里记录的序号也就是1,代表要发送的这一条数据。
确认序号(ACK报文)的取值是收到的数据的最后一个字节的序号+1,接收方收到数据后会返回一个1001作为确认序号,这里的确认序号表示着两个含义:
- 小于1001的数据都已经确认收到
- 发送方接下来应该从1001这个序号开始发送数据(接收方向发送方索要100)
要注意任何一条数据(包括应答报文)都是有序号的,但确认序号只有应答报文有,是否为应答报文取决于ACK这个标志位是否为1,如果为1就表示是应答报文,如果是0就表示不是应答报文。
TCP存在接收缓冲区(每个socket都有一份缓冲区),在数据报到达之后,会先停留在缓冲区,TCP就可以按照序号针对收到的消息进行整队了。这样就解决了后发先至的问题。
上述说到的都是数据都可以收到的情况,如果是数据在传输的时候出现问题,比如说消息发出之后,对方收不到消息(丢包)这种问题改怎样处理。
为什么会出现丢包的情况?
我们知道两台主机在发送数据的时候,中间会经过很多结点,如果中间一个结点出现了问题,都有可能出现丢包。每个设备都承担了很多的转发任务并且每台设备的转发能力是有限的。如果在某一时刻,某个设备上面的流量达到峰值,就有可能会引起部分数据被丢包。换句话说,如果一个设备单位时间内能够转发的数据量是1000,这个时候又来了500个数据需要通过这个设备转发,那么就会有500个数据被丢掉。如果包丢了,接收方就收不到这个数据,接收方自然就不能返回ACK,发送方就拿不到应答报文,这样等待一段时间之后,发送发还是没有收到应答报文,这个时候发送方就会认为刚才发送的数据丢包了,就会重新再发一遍。这个就叫做超时重传机制。
✨丢包的两种情况(发送方对于丢包的判定,是一定时间内,没有收到ACK)
1️⃣发送方发送的数据丢了,接收方没有收到,自然不会返回ACK.
2️⃣接收方收到了数据,但是返回的ACK丢了。
这种情况发送方是不知道,接收方收没收到消息的,没有收到ACK,那么他就会将刚刚的数据再重新发送一遍,这就导致接收方收到重复的数据,这个问题还是很严重的,我们可以想象一下我们在使用手机支付的时候,买了一个东西,多次扣费。这样的问题肯定是允许发生的,所以TCP针对这种重复收到同一消息的情况,TCP实现了去重机制,TCP中存在接收缓冲区和发送缓冲区,接收方拿到数据之后,先将数据放在了接收缓冲区中,缓冲区会根据数据的序号进行排序,根据序号就可以将重复的数据丢掉。再重新返回一个与之前相同的ACK。
数据在重传的时候也是有可能出现丢包的情况的,出现多次重传都对包的情况,这种情况多半是你的网络出现了非常严重的问题。TCP针对多个包丢失的情况的处理思路是,继续超时重传,但是在重传的过程中每丢包一次,超时等待的时间都会变长(重传的频率降低),连续多次重传,都无法得到ACK,此时TCP就会尝试重置连接,如果重置连接也失败,TCP就会关闭连接,放弃本次的网络通信。
TCP的可靠传输就是通过确认应答和超时重传 体现出来的,其中确认应答是传输一切顺利的情况下保证了TCP传输的可靠性,超时重传是传输过程中出现丢包的情况下尽量保证TCP传输的可靠性。这两个机制相互配合,是TCP可靠性的基石。
TCP要完成通信是先建立连接的,所以就有了连接管理的机制,连接管理一定程度上也可以体现TCP的可靠型,但是保证可靠传输最核心的机制还是上面介绍的确认应答和超时重传。
一个连接建立完成就表示通信双发都知晓对方的IP和端口号,就是通信双发各自维护者连接这样的一个数据结构,双发吧对方的地址信息都保存下来就是完成了连接,而断开连接就是把各自存储的连接删除掉。
对于TCP的连接管理就是建立连接(三次握手),断开连接(四次挥手)了。
客户端与服务器之间进行三次交互建立连接的过程被形象的称为"三次握手",在这三次交互中,通信双方要完成彼此信息的记录。这里的"握手"指的是通信双方,进行一次网络交互。三次握手这个过程,应用程序干预不了,是在内核中自动完成的,ACK和SYN是同一时刻触发。所以服务器发送的ACK和SYN两次通信可以合并成一次通信
这里的syn称为同步报文段,表达的意思就是一方要想对另一方申请建立连接。syn是TCP首部控制位当中的一位,这个标志位为1就表示请求建立连接的报文。ack为1表示的是应答报文。
❓上述我们了解了三次握手,还得了解一下,三次握手起到了什么样的作用,达成了什么目标??
三次握手这个过程,本质上就像投石问路,验证客户端和服务器,各自的发送能力和接收能力是否正常。
举个例子:就像你和女神带耳机连麦打游戏,这个时候也需要三次握手测试,测试双方的麦克风和耳机是否正常。
当女神收到你的"hello,能听到吗?"这个时候女神就知道了你的麦克风是正常的,她的耳机是正常的,当你收到女神的回复"可以,你呢?"这个时候你就知道了你的耳机正常,她的麦克风正常,这个时候你再一想既然她回复了你,那么你也就知道了你的麦克风正常,它的耳机正常。这个时候你就知道了双发的设备都是正常的。当你回复了女神之后,女神也就知道了她的麦克风正常,你的耳机正常,这个时候女神就知道了你们双方的设备都是正常的。
与三次握手类似,客户端与服务器通过四次交互断开连接的过程称为"四次挥手",通信双方向对方发起一个断开连接的请求FIN,再各自给对方一个回应ACK,这个时候通信双方就断开连接了。
断开连接的请求也被称为FIN,在TCP报文中也是一个控制位。
❗❗❗注意:断开连接和建立连接是存在区别的。
- 三次挥手先发起连接请求的一定是客户端,但是四次挥手先发起断开连接请求的有可能是客户端也有可能是服务器。
- 建立连接的时候,服务器的ack和syn通信合并成了一次通信 但是断开连接的时候服务器的ack和fin这两次通信不能合并成一次通信。因为三次挥手中服务器发送的ack和syn是同一时机触发的,都是由内核完成的,可以合并成一次通信。但是四次挥手中服务器发送的ack和fin则不是同一时机发送的,ack是由内核完成的,会在收到客户端发来的fin后第一时间返回。fin这是应用程序代码控制的,在调用socket的close方法时才会触发fin.
- 在网络编程的博客中写道了TCP编程,当时TCP客户端并没有写close方法,那么客户端是如何发送断开连接的请求的?客户端虽然进程结束了,但是TCP连接还在,这个连接是在内核中维护的,客户端进程虽然结束了,但是内核还是会把tcp连接维护,知道四次挥手完成。服务器也是一样。
上面介绍的TCP机制都是再给TCP的可靠传输提供支持,但是保证了传输的可靠性,那么就会牺牲一定的效率,滑动窗口做的事情就是在保证可靠传输的基础上,尽量的区提高传输效率。
滑动窗口的由来
在进行IO操作的时候,时间成本主要是两部分,一是像之前A给B发送了一条消息,A等到B返回一个ack之后,才会发送下一条数据。这个期间等待是消耗时间的。二是发送数据的时候消耗时间。这样数据逐条之间往返的时间较长。
既然这样一发一收的方式性能较低,那么我们就使用一次发送多条数据,就可以大大的提高性能(这里的批量发送的数据不是无限的,而是发送到一定的程度,就等待ack).
第一次发送多条数据,然后发送方得到一个接收方返回的ack,就发送一条数据,这就实现了使用等待一份ack的时间发送多条数据的效果,这样的机制就像一个滑动的窗口,窗口大小固定,接收方得到一个ack就向前滑动一格。
上述的窗口大小为4000个字节,主机A发送4条数据之后,主机B接收到数据返回ack,这个时候发送方收到了第一条数据的ack(2001),他就会将(1001~2001)这条数据标灰,窗口向前移动一格,发送方就会发送(5001~6001)这条数据,这个时候发送发等待接收的ack范围就是2001~6001这个窗口。上述描述的过程就形象的称为"滑动窗口"。
上述的滑动窗口是对TCP传输速率的优化,但是我们不论怎样优化TCP的传输速率,TCP传输的可靠性永远是我们第一个要考虑的问题。
❓❓❓批量发送的过程中,如果出现丢包咋办?
我们还是像之前一样分为两种情况,发送方的数据丢了,接收方返回的ack丢了。
1️⃣数据报抵达,ack丢了。这个图中,丢包率已经非常高了,已经相当于一半的ack都丢了,这种情况的丢包什么事都没有,丢了这么多ack,但是对于TCP的可靠性没有任何影响。因为确认序号的含义表示该序号之前的数据都已经收到了,后一个ack,能够涵盖前一个ack的意思。就比如发送方收到了2001这个ack的时候,此时发送方就知道了,2001之前的数据都收到了,当然1~1000这个数据也就收到了,1001这个ack丢了就丢了。
当然滑动窗口中间的ack丢了,没有关系,只要后面有ack发送成功就行,但是滑动窗口的最后一个ack丢了,那就超时重传。
2️⃣发送发数据丢了 :这种情况接收方会通过反复索要同一个数据的方法,告知发送方某条数据丢失了。发送方将丢失的数据进行重传。就像下边的图,发送方将1~1000的数据发送成功了,接收方返回一个ack(1001),这个时候表示接收方收到了1001之前的数据,现在索要1001开始的数据,但是由于1001~2000的数据丢失了,之后就算接收方收到了发送方发送的2001~3000,3001~4000,4001~5000,5001~6000和6001~7000这些数据,接收方返回的还是1001这个ack,这时候发送方就知道1001~2000之间的数据丢失了,他就会将这条数据进行重传。但是接收方在拿到1001~2000这个条数据之后,返回的是7001这个ack。这是因为接收方已经将2001~7000这里的数据接收方已经收到过了,被放到了接收方操作系统内核的接收缓冲区中。
上述重传过程,额米有任何冗余的操作,丢了的数据才会重传,不丢的数据就不必重传,整体的速度是比较快的,这个重传过程也称为快速重传。
我们刚刚说到的滑动窗口机制就是提高TCP数据传输效率,窗口越大,就相当于批量发送的数据越多,这样一份时间等待的ack就越多,数据传输的效率就越快。但是也不是数据传输的速率越快越好,如果发送数据的速度太快了,接收方处理这些数据的速度比较慢,这个时候就会瞬间把接收方的接收缓冲区填满了,接下来继续发送,数据就会丢包。数据丢了之后还得重传,这样就得不偿失了。
因此TCP支持根据接收端的处理能力,来决定发送端的发送速度。这个机制就叫做流量控制。
- 接收端将自己剩余的缓冲区大小放入TCP首部的"窗口大小"字段中,当ack为1的时候,就会告知发送端接收缓冲区剩余空间的大小。
- 这个时候发送方就会根据接收方的剩余缓冲区大小,作为窗口大小,批量发送数据。
- 当接收端一旦发现自己的缓冲区快满了,就会将窗口大小设置成一个更小的值通知给发送端。
- 发送端接收到这个窗口大小之后,就会根据这个窗口大小,批量发送数据,这个时候批量发送的数据就会变少,也就会减慢数据的发送速率。
- 如果接收端缓冲区满了,就会将窗口置为0;这时发送方就不在发送数据了,但是需要定期发送一个窗口探测数据段,使接收端把窗口大小(接收端缓冲区剩余空间大小)告诉发送端。
这个过程只是一个简易的不考虑拥塞控制的控制发送发窗口大小的流程。实际上发送方的窗口大小是由流量控制和拥塞控制一起决定的。
上述说到窗口大小是由流量控制和拥塞控制两个一起决定的,流量控制衡量了接收方处理数据的能力,而拥塞控制衡量了传输路径的处理数据的能力。实际发送方的窗口大小 = min(拥塞窗口,流量窗口)。
由于互联网上两台主机进行数据交互,不可能是通过一条网线来经行数据交互,一定是通过中间的很多结点(交换机、路由器等等)来完成数据的传输。传输数据的时候不仅要考虑接收方主机的数据处理能力,也要考虑这些结点在接收数据时的处理能力。在传输路径上,任何一个设备,处理能力如果遇到瓶颈都会对整体的传输速率产生明显影响。
说到拥塞控制是衡量中间结点处理数据的能力的,也就是说衡量中间结点传输数据的能力,由于中间结点存在很多,每次数据的传输路径也有可能不同,所以通过接收方的接收缓冲区剩余空间大小来控制发送方数据发送速率的这种做法在控制中间结点传输数据速率的问题上不能使用。控制中间结点传输速率的方法,我们使用的通过实验的方式,找到一个合适的发送速率。
开始的时候,按照一个小的速率发送数据,如果不丢包,就可以提高一小数据传输速率(扩大窗口大小);如果出现丢包,则立即把速率再调小,然后重复上述的过程,这里的中间结点的传输速率一直是处于动态平衡的状态。
延时应答也是提升TCP效率的机制,我们之前说过TCP可靠性的核心是确认应答。TCP中决定传输速率的关键因素就是窗口大小,窗口大小由流量控制与拥塞控制共同制约,流量控制控制窗口大小是根据接收方接收缓冲区大小决定的。当接收数据的主机在收到数据之后,立即返回一个ACK,这个时候窗口可能比较小,但是如果接收方消费数据的速度很快,这样就导致接收方消费数据的能力还没有到达极限,这样我们就可以让接收方在接收到数据之后,等待一段时间再应答,在等待的时间中,对缓冲区中的数据再消费一波,然后返回一个ACK,ACK中存在接收方接收缓冲区剩余空间大小,这个时候传给发送方的接收缓冲区的大小变大,发送方也就会根据缓冲区大小将自己数据发送的速率变大(窗口大小变大)。
同样捎带应答也是提升TCP效率的机制,他是在延迟应答的基础上实现的。很多情况下,客户端服务器在应用层也是"一发一收"的,就比如客户端给服务器发送了"How are you",服务器会给客户端返回一个"Fine,thank you"。由于延时应答的原因接收方在收到请求数据之后,系统内核中并不会立即返回一个ACK,而是延时等待一段时间,在推迟的这段时间里,服务器将响应计算好了,服务器将应用层的请求响应数据返回的时候就会捎带上ACK一起发送。此时应用层的响应的时机刚好与ACK响应时机重合,就可以将这两个数据合二为一进行发送。
我们还是通过你和女神的日常来了解一下捎带应答这个过程。
图中女神给你发送消息,当你的电脑收到这个消息数据报的时候,内核首先会返回一个ACK,让女神的电脑知道,它发送的数据接收方收到了,你和你电脑的应用程序进行交互,返回一个消息给女神,由于延时应答的原因,系统内核延时发送ACK,你发送的这条数据的时机与ACK返回的时机重合,这个时候应用层数据返回的时候,就会捎带上ACK一起发送。
本来ACK和应用层数据的应答实际是不同的,但是由于延时应答的机制,让两个不同时机发送的数据报出现了发送时机重合的可能,又由于捎带应答,这个时候就会把时机重合的这两个数据报合并成一个数据报。这就提高了TCP的传输速率,因为一个数据报的分装分用也是要耗费一些时间的,这样就减少了封装分用的次数。这也就会使四次挥手出现三次挥手的可能,因为ACK的延时等待就会让FIN和ACK在同一时机发送,合并成一个数据报。
✨补充: 客户端和服务器的通信模型
- 一问一答:绝大部分的服务器都是这样的
- 多问一答:就比如上传一个大文件的时候,发送方会将一个大的文件差分成一些小的文件进行发送,在所有小文件传输完成之后,会显示一个上传完成。
- 一问多答:就像下载一个大文件的时候,点击下载,会出现多次小文件下载成功的消息。
- 多问多答:远程控制。
TCP是面向字节流的,发送方给接受方连续发了多个应用层数据报之后,接收缓冲区中存放了多条应用层数据,这就导致应用层程序在读取缓冲区中的应用层数据时,会出现分不清从哪里到哪里是一条完整的应用层数据,由于TCP是面向字节流的,那么一次读1个字节或者读N个字节,都是有可能的,这就导致应用层读取的数据可能是完整的一条,也有可能是半条,也有可能是多条。
这也就是说TCP的socket API中没有规定每个数据报之间应该按照什么规则读取。所以具体的一个完整的数据报该怎样读取,完全是由程序员字节约定。
解决粘包问题,主要就是能够明确两个应用层数据报之间的边界。由程序员约定一个应用层协议来明确两个应用层数据报之间的边界。比如下面的几种解决方式
- 对于长度一定的应用层数据报,保证每次读取数据都是按照固定的大小读取。
- 对于长度不定的应用层数据报,可以用一个数据报的前几个字节表示整个数据报的长度,这样在读取每个数据报的时候,就按照这几个字节中存储的长度来读取;也可以在每个数据报结尾约定一个分隔符,这样在读取的时候,遇到这个分隔符就会结束。
1️⃣进程关闭/进程崩溃
客户端和服务器建立连接是通过客户端在创建socket文件对象的时候建立的,这个时候socket对象在系统内核中对应了一个PCB(进程控制块),这个PCB 中存在了一个文件描述符表(记录了这个进程在运行的过程中打开了那些文件)。当进程关闭,对应的PCB 也就没了,同时文件描述符表中的记录也就全都释放了,这就相当于文件正常的关闭,这个过程和手动调用socket.close()方法没有区别,系统内核还是会完成四次挥手的过程。
2️⃣主机关机
主机关机首先终止所有的进程,也会触发四次挥手,这里的关机可能发生的很快,有可能四次挥手刚好执行完成,也有可能四次挥手没有执行完成,这种情况也不会出现问题,比如,对方的FIN发过来了,由于这里已经关机,ACK没有发送过去,此时对端就会重传FIN,重传几次之后,发现都没有返回ACK,就会尝试重置连接,如果还是不行,就会直接释放连接了。
3️⃣主机掉电(考虑台式机或者服务器)
主机断电之后,瞬间机器就关了,没有留给操作系统任何的反应时间,所以根本来不及完成四次挥手,这个时候有两种情况。
- 当对端是发送方:对端将数据发送之后,收不到返回的ACK,就会超时重传,多次重传之后还是不能收到返回的ACK,发送方就会重置连接,重置连接之后还是不行,发送方就会释放连接。
- 当对端是接收方:这个时候对端是没法立即知道发送方这边是新的数据没有发送还是直接挂了。面对这种长时间没有发送方没有发送数据的情况,接收方会周期性的给发送方发送一个探测报文,触发发送方的ACK,如果接收方将探测报文发送之后,能够立即收到ACK,这个时候说明发送方正常,但是没有反应,就说明是发送方挂了。
这样的探测报文被形象的称为"心跳包",这是TCP内置的保活机制。 用来确认通信双方是否处在正常的工作状态,由于心跳是周期性的,如果心跳没了,就说明了挂了。心跳包这样的机制是非常常见的,在后台开发中是非常核心的机制。
4️⃣网线断开
这种情况和主机掉电的情况是很相似的,断网的这端有可能是发送方也有可能是接收方,这个时候就按照断电时的两种情况去思考。
TCP是可靠传输,效率并不高,在绝大部分场景中都能使用;UDP的优势在于高效率,对于效率要求较高对于可靠性要求不高的场景下可以使用UDP,例如同一机房内部的内网之间的数据传输,例如分布式系统(多台主机相互配合),这个时候这些主机大概率是在同一个机房中的,它一方面对效率要求是比较高的,另一方面由于是在机房内部,网络结构简单、带宽充裕,出现丢包的可能比较小,所以他对可靠性的要求没有那么高,这样的情况下更适合使用TCP这样的协议。
像玩对抗性非常高的游戏要求既要有可靠性,也需要比较高的效率,这个时候UDP和TCP都不能使用了,这个时候就需要我们老姐一下KCP等的这样的协议。