关注「Java视界」公众号,获取更多技术干货

TCP/IP相关知识点(持续更新)

一、OSI分层模型

物理层:

这一层主要和各种硬件挂钩,会用到光缆、电缆、双绞线、无线电波、中继器、集线器等多种设备,就像打电话会用到电话线一样。搞这么设备干什么呢?官方的表达就是:利用传输介质为相互通信的主机间建立、管理、和释放物理链接,保证比特流的透明与正确传输。简单点打个比方来说,现在A和B进行通信,A和B的链接是通过光缆、电缆、双绞线、无线电波等物理介质链接起来的,首先,这种物理链接的建立、管理、和释放都是在物理层完成的;其次,在光缆/电缆里面传输的是具有高压或低压的模拟信号,计算机是不认识的,计算只会识别0或1的二进制数据,所以这种物理信号和数字信号的转换过程也是在物理层完成的,且不管什么介质传来的信号(光信号/电信号/电磁波)在这一层都会统一转换成010101...这样的bit流数据,这样其他层(主要是数据链路层)就不用再去考虑到底是哪种传输介质传来的哪种信号了,到了数据链路层都变成了统一的bit流形式,也就是为啥bit流是“透明”的原因。

所以,物理层主要就是干了两件事:

  1. 建立、管理、和释放物理链接
  2. 完成模拟信号和bit流之间的互相转换,为上层屏蔽传输介质的影响

但有一点需要主要:

      物理层只接收和发送一串比特(bit)流,但却不会考虑信息的意义和信息结构,这个不属于物理层的职责范畴。

数据链路层:

发送方实现物理层数据的可靠传输、在接收方检验所收到的数据的可靠性。

物理层的数据是比特流的形式存在,比特流是连续的01串,毕竟这里面不全都是真正有用的数据,还有各层封装的报文头部信息校验信息等,这么一串01信号从那开始读呢?读到哪就是结束呢?多长是一个分组呢?又以什么样的速度读呢?怎么保证接收到的位数量不会少于、等于或者多于发送的位数,即实现差错控制?数据链路层就是来解决这些疑问的。

数据链路层采用成帧技术把数据封装成“帧”,就是在一段数据的前后分别添加特殊的首部和尾部,这样就构成了一个帧。接收端在收到物理层上交的比特流后,就能根据首部和尾部的标记,从收到的比特流中识别帧的开始和结束。解决了从哪里读又读到哪里的问题。

对于发送方,网络层的IP报文数据在数据链路层会加上首部和尾部构成完整的帧数据(对于接收方,物理层的比特流也会被转换层帧结构)。首部和尾部还包含许多必要的控制信息,在发送帧时,是从帧首部开始发送。各种数据链路层协议都要对帧首部和帧尾部的格式有明确的规定,为了提高帧的传输效率,应当使帧的数据部分长度尽可能大于首部和尾部的长度。但是,每一种链路层协议都规定了帧的数据部分的长度上限——最大传送单元MTU(Maximum Transfer Unit)。

采用帧传输方式的好处是,在发现有数据传送错误时,只需将有差错的帧再次传送,而不需要将全部数据的比特流进行重传,这就在传送效率上将大大提高

此外,MAC寻址是数据链路层另一个主要功能。这里所寻找地址是计算机网卡的MAC地址,也称“物理地址”、“硬件地址”,而不是IP地址。在以太网中,采用媒体访问控制(Media Access Control, MAC)地址进行寻址,MAC地址被烧入每个以太网网卡中。这在多点连接的情况下非常必需,因为在这种多点连接的网络通信中,必须保证每一帧都能准确地送到正确的地址,接收方也应当知道发送方是哪一个站。

网络层:

基于数据包的逻辑地址进行转发,寻找网络中目的的位置,并在去往目的的多条路径中选择一条最佳的路径。

这里也是寻址,但是寻找的是IP地址,即确定数据包从源到目的如何选择路由。最佳路由的选择、如何防止路由死循环、如何保证路由安全等有很多内容要是有兴趣可以去找找资料。这里主要介绍网络层的主要作用。

传输层:

主要提供端到端的连接方式,主要完成:

  1. 可靠的传输方式(TCP)
  2. 不可靠的传输方式(UDP)
  3. 数据包分段

首先要说一下“端到端”,end-to-end,指的就是整个报文的进程到进程传递。每台计算机上的运行的进程都会对应一个端口,因此传输层信息的头部就必须包含服务点地址(也叫端口地址)。网络层将每个分组传送到指定的计算机上,而传输层则将整个报文传送给该计算机上的指定进程(IP+Port)。

数据包分段:将报文分解成可传输的片段,并且给这些片段编上序号。这些序号不仅使传输层可以在接收端将报文正确地组装,而且可以用来标识和替换传输中丢失的分组。

会话层:

会话层不参与具体的传输,它提供包括访问验证和会话管理在内的建立和维护应用之间通信的机制,如服务器验证用户登录便是由会话层完成的。

  1. 建立会话:A、B两台网络设备之间要通信,要建立一条会话供他们使用,在建立会话的过程中也会有身份验证,权限鉴定等环节;
  2. 保持会话:通信会话建立后,通信双方开始传递数据,当数据传递完成后,OSI会话层不一定会立刻将两者这条通信会话断开,它会根据应用程序和应用层的设置对该会话进行维护,在会话维持期间两者可以随时使用这条会话传输局;
  3. 断开会话:当应用程序或应用层规定的超时时间到期后,OSI会话层才会释放这条会话。或者A、B重启、关机、手动执行断开连接的操作时,OSI会话层也会将A、B之间的会话断开。

表示层:

应用层提供各种各样的应用层协议,这些协议嵌入在各种我们使用的应用程序中,为用户与网络之间提供一个打交道的接口。

当我们第一次学习网络,对网络的概念不会很直观,我们使用网络时肉眼看到的不是网络,而是一堆电脑、网络设备,然后用一堆线连在一起,这只设备,而网络是这些设备互相连接后经过网络工程师进行规划配置之后才有的。网络并不是以物理形式存在的,它看不见也摸不着。

我们在使用各种网络应用程序时,都是内置有应用层协议的。比如,IE浏览器,使用的是应用层的HTTP协议;QQ使用的应用层协议是OICQ;Outlook使用收发邮件的SMTP、POP3协议……

这里要注意一点,我们所使用的软件是应用程序,这些软件只是软件开发者编程开发出来的,这些应用软件只是一个壳子,而这些软件里嵌套的协议才是应用层的内容,使用网络的程序需要集成协议才可以正常使用。

举几个OSI应用层与我们打交道的常见例子:
我们要看网页,打开IE浏览器,输入一个网址,就进入了相应的网站,那么这个IE浏览器就是我浏览网页的应用工具,也是工作在应用层的。IE浏览器是基于HTTP协议开发的,HTTP是一个应用层的协议。
我们要使用FTP上传下载一个文件,会首先打开FTP客户端,然后去连接FTP服务器,这个客户端,就是应用层的工具。FTP客户端是基于FTP协议开发的,FTP协议也是一个应用层的协议。
我要发一封E-mail,我会在电脑装一个Foxmail、Outlook等邮件客户端软件,然后编辑邮件,发送给相应的人。Outlook、Foxmail是基于SMTP和POP3协议开发的,SMTP、POP3也是应用层的协议。

应用层的协议非常的多,现在所有的网络工具,大多是基于这些程序所开发。
 

二、TCP 三次握手

TCP是全双工的(A在给B发的同时,B也可以给A发),且是面向连接的传输层协议,所谓面向连接就是正式数据传输前通信的两端必须是建立好连接的,TCP通过三次握手来完成连接,具体的过程如下:

(1)首先客户端向服务器端发送一段TCP报文

SYN=1表示“请求建立新连接”,seq表示发送端的序列号,x不一定从0开始而是随机的,发送完后客户端进入SYN-SENT状态

(2)服务器端接收到来自客户端的TCP报文之后,结束LISTEN阶段;并返回一段TCP报文

标志位为SYN和ACK,表示“确认客户端的报文Seq序号有效,服务器能正常接收客户端发送的数据,并同意创建新连接”(即告诉客户端,服务器收到了你的数据);Seq=y表示服务端的序列号是y开始;确认号为ack=x+1,表示收到客户端的序号Seq并将其值加1作为自己确认号Ack的值;随后服务器端进入SYN-RCVD阶段。

(3)客户端接收到来自服务器端的确认收到数据的TCP报文之后,明确了从客户端到服务器的数据传输是正常的,结束SYN-SENT阶段。并返回最后一段TCP报文。

标志位为ACK,表示“确认收到服务器端同意连接的信号”(即告诉服务器,我知道你收到我发的数据了);序号为Seq=x+1,表示收到服务器端的确认号Ack,并将其值作为自己的序号值;确认号为Ack=y+1,表示收到服务器端序号Seq,并将其值加1作为自己的确认号Ack的值;随后客户端进入ESTABLISHED阶段。

  

三、为什么是三次握手,而非两次?

前两次握手好像已经把连接建立好了,接着开始传输数据就好了,又搞第三次握手干啥呢?

可以这样想象一下:

我们把发送端想象成猪八戒,服务器想象成嫦娥。有一天猪八戒想邀约嫦娥仙子赏花赏月赏秋香,于是给嫦娥捎封信说“想和你出去溜达”,但是送信的人路上贪杯喝多了把信弄丢了,嫦娥没有收到,八戒看嫦娥很久都没有回应,于是又厚着脸皮给嫦娥写了一封,差了另一个送信的送信,这次嫦娥正常收到了,嫦娥收到来信觉得天蓬元帅也不错(没变猪前比较帅),准备应邀,于是给天蓬写了回信说“我也想溜达呢”,天蓬收到嫦娥的回信很开心,于是又给嫦娥回信说“那好,今晚吴刚的大树下见”。于是嫦娥和天蓬顺利奔现,度过了愉快的一晚,最后各回各家。到了第二天第一个喝醉的信使清醒了,于是找到猪八戒第一封信给嫦娥送过去了,嫦娥收到信以为天蓬又约自己,于是和上次一样写了回信答应应邀,打扮好在那等待天蓬的回信确定时间地点,但是天蓬这时正和别的女娃玩耍根本没有时间去看来信,结果嫦娥等了一天天蓬也没来,心情有些失落。

说了这个例子不知各位是否有些明白了,实际通信中如果有消息在一次tcp连接完成后才到达服务端,如果是两次握手,那么服务端以为是新的连接,就会发确认报文到客户端确认并建立连接,但客户端可能已经关闭,即使没关闭,在没有到达SYN-SENT状态时,也不会响应服务端的确认信息,服务端可能就这样等待,造成网络中资源的浪费。

所以第三次握手就十分必要了,第三次握手是客户端给服务端发送确认报文,服务端接收到后就知道客户端接收了自己的确认信息,如果在此发生上面的问题,服务端再次接收到了滞留在网络中的信息,发送确认信息给客户端,假如客户端也已经关闭,没有回复的时候服务端的时候,服务端不会像两次握手那样继续等待,服务端就知道没有收到第3次回应,这是错误信息,就不会等待。

另一个角度,三次握手也是为了完成通信双方的序列号的互相确认:

为了实现可靠数据传输, TCP 协议的通信双方, 都必须维护一个序列号, 以标识发送出去的数据包中, 哪些是已经被对方收到的。因此通信双方都需要随机产生一个初始的序列号, 并且把这个起始值告诉对方。 三次握手的过程即是通信双方相互告知序列号起始值, 并确认对方已经收到了序列号起始值的必经步骤,如果只是两次握手, 至多只有连接发起方的起始序列号能被确认, 另一方选择的序列号则得不到确认。

四、为什么不是四次握手?

实际上四次握手和两次握手是一样的效果,五次握手和三次握手效果一样,也就是奇数效果一样,偶数次也一样,只不过握手次数多会占用更多资源。

五、四次挥手

(1)首先客户端想要释放连接,向服务器端发送一段TCP报文

标记位为FIN,表示“请求释放连接“;序号为Seq=U;随后客户端进入FIN-WAIT-1阶段,即半关闭阶段。并且停止在客户端到服务器端方向上发送数据,但是客户端仍然能接收从服务器端传输过来的数据。这里不发送的是正常连接时传输的数据(非确认报文),而不是一切数据,所以客户端仍然能发送ACK确认报文。

(2)服务器端接收到从客户端发出的TCP报文之后,确认了客户端想要释放连接,随后服务器端结束ESTABLISHED阶段,进入CLOSE-WAIT阶段(半关闭状态)并返回一段TCP报文

标记位为ACK,表示“接收到客户端发送的释放连接的请求”;序号为Seq=V;确认号为Ack=U+1,表示是在收到客户端报文的基础上,将其序号Seq值加1作为本段报文确认号Ack的值;随后服务器端开始准备释放服务器端到客户端方向上的连接。

(3)服务器端自从发出ACK确认报文之后,经过CLOSED-WAIT阶段,做好了释放服务器端到客户端方向上的连接准备,再次向客户端发出一段TCP报文

标记位为FIN,ACK,表示“已经准备好释放连接了”。注意:这里的ACK并不是确认收到服务器端报文的确认报文。序号为Seq=W;确认号为Ack=U+1;表示是在收到客户端报文的基础上,将其序号Seq值加1作为本段报文确认号Ack的值。

随后服务器端结束CLOSE-WAIT阶段,进入LAST-ACK阶段。并且停止在服务器端到客户端的方向上发送数据,但是服务器端仍然能够接收从客户端传输过来的数据。

(4)客户端收到从服务器端发出的TCP报文,确认了服务器端已做好释放连接的准备,结束FIN-WAIT-2阶段,进入TIME-WAIT阶段,并向服务器端发送一段报文

标记位为ACK,表示“接收到服务器准备好释放连接的信号;序号为Seq=U+1;表示是在收到了服务器端报文的基础上,将其确认号Ack值作为本段报文序号的值;确认号为Ack=W+1;表示是在收到了服务器端报文的基础上,将其序号Seq值作为本段报文确认号的值;随后客户端开始在TIME-WAIT阶段等待2MSL;服务器端收到从客户端发出的TCP报文之后结束LAST-ACK阶段,进入CLOSED阶段。由此正式确认关闭服务器端到客户端方向上的连接。客户端等待完2MSL之后,结束TIME-WAIT阶段,进入CLOSED阶段,由此完成“四次挥手”。

通俗理解:

A:我没有数据传给你了,请求断开连接

B:同意断开,但是我不能直接断开,等我处理完数据;

B:我处理完了,我也没有要传输的数据可,可以断开了;

A:好的,我将在2MSL后断开,你可以直接断开了

B:(收到回复后)直接断开

2MSL后A断开

六、为什么2MSL后才断开?

当客户端发出最后的ACK确认报文时,并不能确定服务器端能够收到该段报文。

  1. 如果客户端在2MSL内,再次收到了来自服务器端的FIN报文,说明服务器端由于各种原因没有接收到客户端发出的ACK确认报文。客户端再次向服务器端发出ACK确认报文,计时器重置,重新开始2MSL的计时;
  2. 如果客户端在2MSL内没有再次收到来自服务器端的FIN报文,说明服务器端正常接收了ACK确认报文,客户端可以进入CLOSED阶段,完成“四次挥手”。

MSL是TCP报文的最大生命周期,2MSL完全能够保证两个传输方向上尚未接收到的或者迟到的报文已经失效或消失。等待最大的2msl可以让本次连接的所有的网络包在链路上消失,以防造成不必要的干扰。

七、为什么“握手”是三次,“挥手”却要四次?

TCP建立连接时之所以只需要"三次握手",是因为在第二次"握手"过程中,服务器端发送给客户端的TCP报文是以SYN与ACK作为标志位的。SYN是请求连接标志,表示服务器端同意建立连接;ACK是确认报文,表示告诉客户端,服务器端收到了它的请求报文。建立连接时,被动方服务器端结束CLOSED阶段进入“握手”阶段并不需要任何准备,可以直接返回SYN和ACK报文,开始建立连接。即SYN建立连接报文与ACK确认接收报文是在同一次"握手"当中传输的,所以"三次握手"不多也不少,正好让双方明确彼此信息互通。

释放连接时,被动方服务器,突然收到主动方客户端释放连接的请求时并不能立即释放连接,因为还有必要的数据需要处理,所以服务器先返回ACK确认收到报文,经过CLOSE-WAIT阶段准备好释放连接之后,才能返回FIN释放连接报文。

所以是“三次握手”,“四次挥手”。

八、TCP报文首部有哪些字段,说说其作用

  • 16位端口号:源端口号,主机该报文段是来自哪里;目标端口号,要传给哪个上层协议或应用程序

  • 32位序号:一次TCP通信(从TCP连接建立到断开)过程中某一个传输方向上的字节流的每个字节的编号。

  • 32位确认号:用作对另一方发送的tcp报文段的响应。其值是收到的TCP报文段的序号值加1。

  • 4位头部长度:表示tcp头部有多少个32bit字(4字节)。因为4位最大能标识15,所以TCP头部最长是60字节。

  • 6位标志位:URG(紧急指针是否有效),ACk(表示确认号是否有效),PSH(缓冲区尚未填满),RST(表示要求对方重新建立连接),SYN(建立连接消息标志接),FIN(表示告知对方本端要关闭连接了)

  • 16位窗口大小:是TCP流量控制的一个手段。这里说的窗口,指的是接收通告窗口。它告诉对方本端的TCP接收缓冲区还能容纳多少字节的数据,这样对方就可以控制发送数据的速度。

  • 16位校验和:由发送端填充,接收端对TCP报文段执行CRC算法以检验TCP报文段在传输过程中是否损坏。注意,这个校验不仅包括TCP头部,也包括数据部分。这也是TCP可靠传输的一个重要保障。

  • 16位紧急指针:一个正的偏移量。它和序号字段的值相加表示最后一个紧急数据的下一字节的序号。因此,确切地说,这个字段是紧急指针相对当前序号的偏移,不妨称之为紧急偏移。TCP的紧急指针是发送端向接收端发送紧急数据的方法。

九、TCP 是如何保证可靠性的

  • 首先,TCP的连接是基于三次握手,而断开则是四次挥手。确保连接和断开的可靠性。

  • 其次,TCP的可靠性,还体现在有状态;TCP会记录哪些数据发送了,哪些数据被接受了,哪些没有被接受,并且保证数据包按序到达,保证数据传输不出差错。

  • 再次,TCP的可靠性,还体现在可控制。它有报文校验、ACK应答、超时重传(发送方)、失序数据重传(接收方)、丢弃重复数据、流量控制(滑动窗口)和拥塞控制等机制。

  • TCP 重传机制

十、TCP 重传机制

超时重传

TCP 为了实现可靠传输,实现了重传机制。最基本的重传机制,就是超时重传,即在发送数据报文时,设定一个定时器,每间隔一段时间,没有收到对方的ACK确认应答报文,就会重发该报文。

这个间隔时间,一般设置为多少呢?我们先来看下什么叫RTT(Round-Trip Time,往返时间)

RTT就是,指发送端从发送TCP包开始到接收它的立即响应所消耗的时间,即数据包的一次往返时间。超时重传时间,就是Retransmission Timeout ,简称RTO

RTO的 值被设置过大过小都会对协议造成不利影响。

(1)RTO设长了,重发就慢,没有效率,性能差。

(2)RTO设短了,重发就快,会增加网络拥塞,导致更多的超时,更多超时导致更多的重发。

快速重传

快速重传机制,它不以时间驱动,而是以数据驱动。它基于接收端的反馈信息来引发重传。

 

发送端发送了 1,2,3,4,5,6 份数据:

  • 第一份 Seq=1 先送到了,于是就 Ack 回 2;

  • 第二份 Seq=2 也送到了,假设也正常,于是ACK 回 3;

  • 第三份 Seq=3 由于网络等其他原因,没送到;

  • 第四份 Seq=4 也送到了,但是因为Seq3没收到。所以ACK回3;

  • 后面的 Seq=4,5的也送到了,但是ACK还是回复3,因为Seq=3没收到。

  • 发送端连着收到三个重复冗余ACK=3的确认(实际上是4个,但是前面一个是正常的ACK,后面三个才是重复冗余的),便知道哪个报文段在传输过程中丢失了,于是在定时器过期之前,重传该报文段。

  • 最后,接收到收到了 Seq3,此时因为 Seq=4,5,6都收到了,于是ACK回7.

快速重传还可能会有个问题:ACK只向发送端告知最大的有序报文段,到底是哪个报文丢失了呢?并不确定!那到底该重传多少个包呢?

带选择确认的重传(SACK)

为了解决快速重传的问题:应该重传多少个包? TCP提供了SACK方法(带选择确认的重传,Selective Acknowledgment)。

SACK机制就是,在快速重传的基础上,接收端返回最近收到的报文段的序列号范围,这样发送端就知道接收端哪些数据包没收到,酱紫就很清楚该重传哪些数据包啦。SACK标记是加在TCP头部选项字段里面的。

如上图中,发送端收到了三次同样的ACK=30的确认报文,于是就会触发快速重发机制,通过SACK信息发现只有30~39这段数据丢失,于是重发时就只选择了这个30~39的TCP报文段进行重发。

 D-SACK

D-SACK,即Duplicate SACK(重复SACK),在SACK的基础上做了一些扩展,,主要用来告诉发送方,有哪些数据包自己重复接受了。DSACK的目的是帮助发送方判断,是否发生了包失序、ACK丢失、包重复或伪重传。让TCP可以更好的做网络流控。来看个图吧:

十一、TCP的滑动窗口

TCP的滑动窗口的可靠性也是建立在“确认重传”基础上的。

滑动窗口协议是传输层进行流控的一种措施,接收方通过通告发送方自己的窗口大小,从而控制发送方的发送速度,从而达到防止发送方发送速度过快而导致自己被淹没的目的。拥塞窗口是发送方使用的流量控制,而滑动窗口则是接收方使用的流量控制。

TCP 发送一个数据,需要收到确认应答,才会发送下一个数据。这样有个缺点,就是效率会比较低。

为了解决这个问题,TCP引入了窗口,它是操作系统开辟的一个缓存空间。窗口大小值表示无需等待确认应答,而可以继续发送数据的最大值。

TCP头部有个字段叫win,也即那个16位的窗口大小,它告诉对方本端的TCP接收缓冲区还能容纳多少字节的数据,这样对方就可以控制发送数据的速度,从而达到流量控制的目的。

通俗点讲,就是接受方每次收到数据包,在发送确认报文的时候,同时告诉发送方,自己的缓存区还有多少空余空间,缓冲区的空余空间,我们就称之为接受窗口大小。这就是win。

TCP 滑动窗口分为两种: 发送窗口和接收窗口。发送端的滑动窗口包含四大部分,如下:

  • 已发送且已收到ACK确认

  • 已发送但未收到ACK确认

  • 未发送但可以发送

  • 未发送也不可以发送

  • 虚线矩形框,就是发送窗口。

  • SND.WND: 表示发送窗口的大小,上图虚线框的格子数就是14个。

  • SND.UNA: 一个绝对指针,它指向的是已发送但未确认的第一个字节的序列号。

  • SND.NXT:下一个发送的位置,它指向未发送但可以发送的第一个字节的序列号。

 接收方的滑动窗口包含三大部分,如下:

  • 已成功接收并确认

  • 未收到数据但可以接收

  • 未收到数据并不可以接收的数据

  • 虚线矩形框,就是接收窗口。

  • REV.WND: 表示接收窗口的大小,上图虚线框的格子就是9个。

  • REV.NXT:下一个接收的位置,它指向未收到但可以接收的第一个字节的序列号。

十二、TCP的流量控制

TCP三次握手,发送端和接收端进入到ESTABLISHED状态,它们即可以愉快地传输数据啦。

但是发送端不能疯狂地向接收端发送数据,因为接收端接收不过来的话,接收方只能把处理不过来的数据存在缓存区里。如果缓存区都满了,发送方还在疯狂发送数据的话,接收方只能把收到的数据包丢掉,这就浪费了网络资源啦。

TCP 提供一种机制可以让发送端根据接收端的实际接收能力控制发送的数据量,这就是流量控制。TCP通过滑动窗口来控制流量,我们看下流量控制的简要流程吧:

首先双方三次握手,初始化各自的窗口大小,均为 400 个字节:

  1. 假如当前发送方给接收方发送了200个字节,那么,发送方的SND.NXT会右移200个字节,也就是说当前的可用窗口减少了200 个字节。

  2. 接受方收到后,放到缓冲队列里面,REV.WND =400-200=200字节,所以win=200字节返回给发送方。接收方会在 ACK 的报文首部带上缩小后的滑动窗口200字节

  3. 发送方又发送200字节过来,200字节到达,继续放到缓冲队列。不过这时候,由于大量负载的原因,接受方处理不了这么多字节,只能处理100字节,剩余的100字节继续放到缓冲队列。这时候,REV.WND = 400-200-100=100字节,即win=100返回发送方。

  4. 发送方继续干活,发送100字节过来,这时候,接受窗口win变为0。

  5. 发送方停止发送,开启一个定时任务,每隔一段时间,就去询问接受方,直到win大于0,才继续开始发送。

posted @ 2022-06-25 14:02  沙滩de流沙  阅读(107)  评论(0编辑  收藏  举报

关注「Java视界」公众号,获取更多技术干货