当前位置: 首页 > article >正文

网络原理(3)—— 应用层、传输层(TCP)

1. 应用层

日常开发中最常用到的一层,主要涉及到两种情况:

1) 使用现成的应用层协议

2) 自己定义应用层协议

1.1 自定义应用层协议的流程

1. 明确前后端交互过程中需要传递哪些信息

实例:开发一个外卖软件

打开软件,首先需要展示一个“商家列表”,根据需求决定传递哪些信息

a) 请求:用户 id,用户所在的位置(外卖上显示的商家列表都是用户附近的商家)

b) 响应:商家列表,其中包含多个商家,每个商家的信息中又包含:商家名、图片、距离、评分等


2. 明确组织这些信息的格式(使用现有的格式)

针对信息组织的格式有很多种方式,使用哪种方式都可以,但是要保证前后端是同一种方式

例如:使用行文本的方式来组织上述数据

a) 请求:

格式:用户 id,用户位置\n

实际:1001,E45N60\n

b) 响应

格式:商家 id,商家名称,商家图片地址,商家距离,商家评分;商家 id,商家名称,商家图片地址,商家距离,商家评分\n

实例:2001,煎饼果子,http://image1.com,1.5km,4.5;2002,蛋炒饭,http://image2.com,2.0km,4.6\n


上述“行文本”这样简单粗暴的方案在实际开发中很少会用

1.2 常用的信息组织方案如下:

1. xml (传统的方案)

通过 “成对的标签” 表示 “键值对” 信息

<request>
    <userId>1001</userId>
    <position>E45N60<position>
</request>

xml 进行网络传输的时候,会消耗大量的带宽


2. JSON (当前更主流的方案)

JSON 也是 “键值对” 格式,键和值之间使用 : 分割,键值对之间使用 , 分割,所有的键值对都使用 { } 括起来

{
    "userId":1001,
    "position":"E45N60"
}

其相比于 xml 来说,可读性比较好,同时也能节省一定的带宽

但是它的可读性也不是完全那么好,因为其没有明确要求数据的格式,所以存在一些极端情况,如下

{"userId":1001,"position":"E45N60","userId":1001,"position":"E45N60","userId":1001,"position":"E45N60","userId":1001,"position":"E45N60","userId":1001,"position":"E45N60"}

像这样 { } 里若有 20,30 个键值对(日常开发很常见),可读性就不高了


3. yml(yaml)

强制要求了数据组织的格式,如下

request:
    userId:1001

    position:"E45N60"

yml 强制要求了键值对必须独占一行,而且“嵌套”结构必须通过缩进来进行表示


4. google protobuffer

前三个方案都是关注可读性,protobuffer 则关注性能,牺牲了可读性(通过二进制的方式组织数据),直接通过“位置”约定字段的含义,不需要传输 key 的名字,也会针对传输的数值进行二进制的编码,起到一些“压缩”效果

极大的缩减了要传输的数据的体积,从而带宽消耗就越小,效率就越高

但由于二进制数据无法肉眼阅读,调试相关程序的时候就会比较麻烦

2. 传输层

2.1 端口号

端口号是一个整数,用来区分不同的进程,同一时刻,同一个机器上,同一个协议,一个端口只能被一个进程绑定(一个进程是可以绑定多个端口的!)

端口号是通过 2 个字节的无符号整数表示的,取值范围:0~65535,0 端口比较特殊(随机设定空闲端口),一般不会使用

端口号划分

  • 1-1023:知名端口号,像 http(80)、https(443)、ssh(22)、ftp(21)、telnet(23)...他们的端口号都是固定的
  • 1024-65535:操作系统动态分配的端口号,客户端程序的端口号就是由操作系统从整个范围分配的

tip:

  • 编写服务器一定需要先绑定至少一个端口(业务端口),和客户端交互
  • 服务器运行过程中,希望能够对这个服务器的行为进行一些“控制”,比如让服务器重新加载某个数据/某个配置/修改服务器的某个功能,就可以通过网络通信完成(让服务器绑定另一个端口(管理端口),通过这个端口编写一个客户端,给服务器发送一些“控制类”的请求)
  • 当需要针对服务器运行状态进行检测和调试或需要查看服务器运行中某个关键变量的数值时,不能用调试器来调试,若使用调试器很可能会使服务器的一些线程被阻塞,从而无法给客户端正确提供服务了(可以让服务器绑定另一个端口(调试端口),实现一些相关的打印关键变量的逻辑,由客户端发送对应的调试请求)

2.2  UDP 协议

2.2.1 UDP 的“数据格式”/“报文格式”

tip:

实际上的 UDP 的数据并没有“换行”动作,如下图:

UDP 报头中的四个字段没有指定分隔符,而是通过固定长度来进行区分的,总共 8 字节

UDP 报文长度 = 报头长度 + 载荷长度

报文长度的单位是 “字节”,如:报文长度 1024 => 整个 UDP 数据报就是 1024 字节

由于是使用 2 字节来表示报文长度,所以最大值就是 65535 => 64KB


2.2.2 关于校验和

前提:网络传输过程中是非常容易出现错误的,电信号/光信号/电磁波 受到环境的干扰会使里面传输的信号发生改变(比特翻转)

校验和存在的目的就是为了能够 “发现” 或者 “纠正” 这里出现的错误,给传输的数据中引入额外信息(checksum),用来 发现/纠正 传输数据的错误(只是 “发现错误” 携带的 额外信息 可以少一点,若是想“纠正错误”,就需要 携带更多额外信息,消耗更多的带宽)

1) 校验和纠正原理

1. UDP 中使用 2 个字节作为校验和,使用方案为 CRC校验和(循环冗余校验)

把 UDP 数据报整个数据都进行遍历,分别取出每个字节往 一个 两个 的变量上进行累加,由于整个数据可能很多,加着加着可能结果就溢出了,溢出也没关系,重点不关心最终加和是多少,而是关心校验和结果是否会在传输中发生改变,具体流程如下:

当接收方收到数据后,就可以根据数据的内容,按照同样的算法再算一遍校验和得到 checksum2,如果传输的数据在网络通信过程中没有发生任何改变,那么 checksum1 == checksum2,反之如果 checksum1 != checksum2,就可以认为数据在网络传输中出错了


2) MD5 和 SHA1

除了 CRC 之外,另外两个典型的算法,其中的 MD5 算法本质上是一个 “字符串 hash 算法”。背后的实现过程是一个“数学过程”(可简单理解为套公式)

MD5 的特点(SHA1也是这三个特点):

1) 定长:无论输入的字符串长度多长,算出的 md5 的结果都是固定长度(适合做 校验和 算法)

2) 分散:输入的内容,哪怕只有丁点改变,得到的 md5 值都会相差非常大(适合做 hash 算法)

3) 不可逆:根据输入内容计算 md5 非常简单,但若是已知 md5 值,想要还原出原始的内容,理论上是不可行的(适合作为加密算法)

2.3 TCP 协议

TCP 全称为“传输控制协议(Transmission Control Protocol)”

2.3.1 TCP 的“数据格式”/“报文格式”

4 位首部长度:TCP 的报头长度,表示该 TCP 头部有多少个 32 位 bit(此处的长度单位是 “4 字节”,不是 “字节”)

保留(6位):现在虽然不用,但是先申请下来,如果未来某天 TCP 需要新增属性/或者某个属性的长度不够用,就可以把保留位拿出来使用(用于考虑未来的可扩展性)

选项:optional => 更准确的翻译 “可选的”,TCP 报头变长的主要原因就是 选项 可以有,也可以没有

2.3.2 TCP 相关机制

TCP 最核心的机制就是 “可靠传输”(代码层面感知不到,系统内核完成了这里的工作),但是也不能做到 100% 送达,只能尽可能使数据能到达对方,

1) TCP 核心机制(一)——确认应答

感知对方是否收到,所谓的感知对方收到,就是靠对方告诉一声(应答报文),如下图:

上述应答报文的机制还存在问题,如果发送方发送多个消息的时候,就可能存在歧义,如下:

后发先至是客观的情况,无法改变,可以给传输的数据添加“编号”,通过编号可以区分出数据的先后顺序:

这样即使收到的应答报文顺序出现错乱,也能识别出来

由于 TCP 是面向字节流的,实际上编号并非是按照“第一条、第二条”这样的方式来编排的,而是按照“字节”(第 1 个字节...第 1000 个字节)

每个字节都有一个独立的编号,字节和字节之间,编号是连续的,递增的,按照字节编号这样的机制,就称为“TCP 的序号”

在应答报文中,针对之前收到的数据进行对应的编号,称为 “TCP 的确认序号”


32 位序号

由于编号是连续的,对于一个 TCP 数据报来说,知道了数据部分的第一个字节,就知道了后续所有字节的序号(序号只是针对 TCP 数据报携带的载荷来进行编号的,TCP 报头不参与)

这里 32 位序号(4字节)表示的范围是 0~42亿9千万(0~4GB),是否意味着一个 TCP 一次传输最大就只能传输 4GB呢?

答案是否定的,因为 TCP 是面向字节流的,一个 TCP 数据报(一个 TCP 报头 + 载荷)和下一个 TCP 数据报携带的数据天然就是 “可拼装的”

比如要传输一个特别大的数据,传输过程中本身就会通过多个 TCP 数据报进行携带,这些 TCP 数据报彼此之间携带的载荷都是可以在接收方自动被拼起来的

这样就不像 UDP 存在传输的上限,使用 UDP 传输大数据,就需要考虑调用这一次 send 操作,参数是否超过 64KB,超过 64KB 就不行


32 位确认序号的设定方式

如果是普通报文,序号是有效的,确认序号是无效的;如果是 ack 应答报文,序号和确认序号都是有效的(这是另一套编号的体系,和传输的数据的序号不是一套)


再谈 “后发先至”

TCP 就可以针对接收方收到的数据进行重新排序,确保应用程序 read 到的数据一定是和发送方发的数据顺序是一致的


2) TCP 核心机制(二)——超时重传

实际上网络传输中,并不是一帆风顺的,而是可能会出现 “丢包” 的情况

丢包的原因:

1) 数据传输过程中发生了 bit 翻转,收到这个数据的接收方/中间路由器计算校验和发现对不上了

出现这种情况,就会把这个数据报丢弃掉,不继续往后转发/不交给应用层使用了

2) 数据传输到某个节点(路由器/交换机),这个节点负载太高了

某个路由器单位时间内只能转发 N 个包,现在网络高峰期,这个路由器单位时间需要转发的包超过了 N,后续传输过来的数据就可能被这个路由器直接丢弃掉

发生丢包,是完全随机的,不可预测的!


TCP 对丢包的应对方式

TCP 中通过应答报文来区分是否丢包,收到应答报文说明数据没丢包,没收到说明数据丢包了,发送方发送数据之后,会给出一个 “时间限制”(超时时间),如果在这个时间限制之内没有收到 ack,就视为数据丢包了

如果丢包,就重新发一次,假设网络丢包率 10%(数据报到达对方是 90%),此时进行一次重传,两次传输至少一次到达对方的概率:1 - 10% * 10% = 99%,传输次数越多,数据到达对方的概率就越大


TCP 的去重操作

发送方无法区分上面两种情况,当出现第二种情况后,B 就收到了两份一样的数据(若是传输的数据是 “扣款” 这样的请求就会造成扣两次款的问题)

TCP 针对上述情况,在接收方会有一个 “接收缓冲区”,收到的数据先进入到缓冲区里,后续再收到数据,就会根据序号在缓冲区中找对应的位置(排序),如果发现当前序号 1-1000 这个数据已经在缓冲区中存在了,就直接把新收到的数据丢弃了(去重)

确保应用程序调用 read 读取出来的数据是唯一的,不重复的


超时重传时间的设定

这里的时间不是固定值,而是动态变化的,如:发送方第一次重传,超时时间是 t1,如果重传之后,仍然没有 ack,还会继续重传,第二次重传超时时间是 t2,其中 t2 > t1,每多重传一次,超时时间的间隔会变大/重传的频次会降低

这是因为,经过一次重传之后,就能让数据到达对方的概率提升很多,再重传一次,又会提升很多,若是重传几次都没有到达,说明网络的丢包率已经达到了一个非常高的程度(网络发生了严重故障,大概率没法继续使用了

重传不会无休止的进行,当重传达到一定次数之后,TCP 就不会继续重传了,会先尝试进行 “重置/复位 连接” ,发送一个特殊的数据报 “复位报文”,如果网络这时恢复了,复位报文就会重置连接,使通信可以继续进行,如果网络还是有严重问题,复位报文也没有得到回应,此时 TCP 就会单方面放弃连接


tip:

1) 连接就是通信双方各自保存对方的信息,发送方释放掉之前保存的接收方的相关信息,这个连接就无了

2) 确认应答 和 超时重传 相互补充,共同构建了 TCP “可靠传输机制”(TCP 的可靠传输不是通过“三次握手和四次挥手”保证的!!)


3) TCP 核心机制(三)——连接管理

在正常情况下,TCP 都要经过三次握手建立连接,四次挥手断开连接

三次握手

三次握手的意义

1) 投石问路,初步的验证通信的链路是否畅通(进行可靠传输的 “前提条件”)

类似于日常中,地铁在开始运营之前都会空跑一趟,先进行“链路畅通的验证”

2) 确认通信双方各自的发送能力和接收能力是否正常

类似于连麦时:

tip:

a) 四次也行就是将中间一次拆成两次,但没必要

b) 两次不可行,无法完成通信双方针对各自接发能力的验证

c) 针对 TCP 来说必须是三次握手,但是其他协议握手过程不一定是三次

3) 让通信双方在进行通信之前,对通信过程中需要用到的一些关键参数进行协商

类似于开会时准备茶杯:

TCP 通信时,起始数据的序号就是通过三次握手协商确定的(换而言之,TCP 序号并不是从 1 开始的),每次建立连接 TCP 的起始序号都不同(而且故意差别很大)

这样做的意义就是避免出现 “前朝的剑斩本朝的官”,如下:

给每个连接都协商不同的起始序号,如果发现收到的数据和起始序号以及最近收到的数据序号,有较大差距,就把这个数据视为“前朝”数据

tip:

三次握手对于可靠性是有一定的支持的,但是 “可靠性就是三次握手体现的”,这个说法非常武断,三次握手只是建立连接的时候进行的,一旦连接建立好,数据就开始传输了,和三次握手没关系了

确认应答 + 超时重传 才是负责传输数据中的 “可靠性”


四次挥手

通信双方各自给对方发送“FIN”,各自给对方返回“ACK”,通过这个过程双方各自把对端的信息删除掉

三次握手,是因为中间两次的交互合并在一起了,而对于四次挥手来说,中间两次,不一定能合并(大概率不能)

因为对于三次握手来说,中间的两次 ACK+SYN 都是在内核中,由操作系统负责进行的,因为其执行的时机都是在收到 SYN 之后,所以可以合并

对于四次挥手来说,ACK 是由内核控制的,但是 FIN 的触发则是通过应用程序调用 close/进程退出,来触发的

TCP 回显服务器中例:


三次握手四次挥手中的状态

1) LISTEN(服务器进入的状态)

服务器把端口绑定好,相当于进入了 listen 状态了,此时服务器就已经初始化完毕,准备好随时迎接客户端了

2) ESTABLISHED (客户端和服务器都会进入的状态)

TCP 连接建立完成(保存了对方的信息了),接下来就可以开始进行业务数据的通信了

3) CLOSE_WAIT(被断开连接一方会进入这个状态)

先收到 FIN 的一方(等待代码执行 close 方法)

tip:如果发现服务器这端存在大量的 CLOSE_WAIT 状态的 TCP 连接,说明此时服务器代码可能有 bug,需排查 close 是否写了,是否及时执行到了

4) TIME_WAIT(主动断开一方会进入这个状态)

此处的 TIME_WAIT 按照时间来等待,达到一定时间之后,连接就释放了,这是为了防止发送给对端的 ACK 丢包

当 A 保留一段时间后,没有收到 FIN,说明刚才的 ACK 没丢包,就可以释放这里的连接了

TIME_WAIT 存在的时间称为 2MSL(MSL => 数据报在网络传输中消耗的最大时间)


4) TCP 核心机制(四)——滑动窗口

TCP 的可靠传输代价是降低了传输的效率,为了在可靠传输的基础上,也能有一个不错的效率,引入了滑动窗口(这里的提高效率,只是“亡羊补牢”,使传输效率的损失尽可能降低,并不能使传输效率比 UDP 还高)

没有滑动窗口时的传输如下:

A 每次需要收到 ACK 之后再发下一个数据

有滑动窗口后:

把 “发送一个等待一个” 改为 “发送一批等待一批”,把多次等待 ACK 的时间合并成一份时间,批量发送的数据越多,可以认为效率就越高


窗口控制与重发控制(在滑动窗口传输中出现丢包情况)

情况一:数据包已抵达,ACK 丢了

情况二:数据包丢了

tip:

滑动窗口/快速重传、确认应答/超时重传,彼此之间并不冲突,他们是分情况的

如果通信双方单位时间发送的数据量比较少,就按照 确认应答/超时重传

如果单位时间发送的数据比较多,就会按照 滑动窗口/快速重传


5) TCP 核心机制(五)——流量控制

滑动窗口的窗口大小对于传输数据的性能是直接相关的,窗口越大传输越快,但是也不能无限大,因为通信是双方的事,发送方发的快的同时也要保证接收方能处理的过来(滑动窗口提高速度,相当于踩油门;流量控制制约速度,相当于踩刹车)

解决方案:通过 “定量” 的方式来实现制约(看接收缓冲区剩余空间大小)

TCP 中接收方收到数据的时候,就会把接收缓冲区剩余空间大小通过 ACK 数据报反馈给发送方

下一步,发送方就可以根据这个数据来设置发送的窗口大小了

tip:流量控制不是 TCP 独有的机制,其他的协议,也可能会涉及到流量控制(比如数据链路层中有的协议也支持流量控制


6) TCP 核心机制(六)——拥塞控制

拥塞控制与流量控制是有关联的(也是踩刹车)

流量控制,站在接收方的视角来限制发送方的速度

拥塞控制,站在传输链路的视角来限制发送方的速度

流量控制可以精准的使用接收方 接收缓冲区 剩余空间来进行衡量

但站在传输链路的角度,考量中间节点的情况就很麻烦了,因此可以通过 “做实验” 的方式来找到一个合适的发送速度(面多加水,水多加面)

1. 先按照一个比较小的速度发送数据

2. 数据非常流畅没有丢包,说明网络上传输数据整体比较流畅,就可以加快传输数据的速度

3. 增大到一定的速度之后,发现丢包了,说明网络上可能存在拥堵了,就减慢传输数据的速度

4. 减速之后,发现不丢包了,再加速

5. 加速之后丢包了,再减速

tip:发送速度是持续动态变化的,因为网络环境也是一直变化的

流量控制和拥塞控制都会限制发送窗口,这两个机制会同时起作用,最终实际的发送窗口大小取决于两个机制得到的发送窗口较小值

拥塞控制中,窗口大小具体的变化过程

1) 刚开始传输数据,拥塞窗口会非常小,用一个很小的速度来发送数据(因为当前网络是否拥堵是未知的,刚启动时发数据很慢)

2) 不丢包,增大窗口大小,指数增长(增长速度特别快,短时间内达到很大的窗口大小)

3) 增长到一定程度,达到某个指定的阈值,此时即使没有丢包,也会停止指数增长,变为线性增长(避免太快进入到丢包的节奏)

4) 线性增长也会使发送速度越来越快,达到某个情况下,就会出现丢包

一旦出现丢包,接下来就要减少发送的速度,减小窗口大小,两种处理方式:

a) 经典的方案,回归到开始非常小的初始值,然后指数增长,再线性增长(已废弃不用)

b) 现在的方案,回归到新的阈值上,线性增长(以后都不会指数增长了)


7) TCP 核心机制(七)——延时应答

延时应答是提升效率的机制,尽可能降低可靠传输带来的性能影响

其实在收到数据的时候,应用程序也会源源不断的消费接收缓冲区中的数据

如果我们返回 ACK 的时间稍晚一点,应用程序就有可能读取缓冲区中更多的数据,假设让 ACK 不是立即返回,而是 100ms 之后再返回,可能在这 100ms 内应用程序又消费掉了 2KB 的数据,此时再返回 ACK 携带的窗口大小就是 6KB了

此时返回的 ACK 窗口大小,大概率要比立即返回 ACK 的窗口大小更大,之所以不是一定:

a) 应用程序代码的实现逻辑是不是一刻不停的读取数据

b) 延时时间内是否发送方会发来新的数据


8) TCP 核心机制(八)——捎带应答

在延时应答的基础上,引入的提升效率的机制,把返回的业务数据和 ACK 两者合二为一了

实际网络通信中,大部分的情况都是 “一问一答” 的形式


9) TCP 核心机制(九)——面向字节流

通过面向字节流的方式传输数据,都会涉及到 “粘包问题”(粘的是 TCP 携带的载荷【应用层数据包】)

上面这样应用层数据包在 TCP 的接收缓冲区中连成一片,粘在一起,就称为 “粘包问题”

要想解决粘包问题,关键就是要明确 “包之间的边界”:

方案一:指定分隔符(适用于文本类的数据,xml、yml、json 用此方案)

在前面 TCP echo server 的例子中,我们约定的做法是,请求响应都以 \n 结尾

发送请求响应的时候,专门使用 println 进行写数据;读取请求响应的时候,专门使用  scanner.next 按照 \n 进行解析

tip:需要确认数据内容的正文中,不能包含分隔符,如果传输的数据是纯文本数据,此时使用 \n 或者 ; 之类的可能都不合适,但是可以使用 ascii 中靠前的 “控制字符”(这些字符早就不再使用了),如下图:


方案二:指定数据的长度(适用于传输二进制,protobuf 用此方案)

例如:约定每个应用层数据包开头的 2 / 4 个字节表示数据包的长度


tip:

1) 如果希望在文件中存储结构化数据,也是存在这样的问题,所以存文件也会经常使用 xml/json 这样的格式来存储(解决粘包问题)

2) UDP 这种面向数据报的传输方式就不涉及 粘包问题 ,因为 send/receive 得到的就是一个完整的 DatagramPacket,这里携带的二进制的字节数组就是一个完整的应用层数据包了


10) TCP 核心机制(十)——异常情况处理
1) 进程崩溃

Java 中的体现就是抛出异常,如果没有 catch 的话,最终会到 JVM,进程就直接无了

进程崩溃看起来挺严重,实际上操作系统会进行善后,当进程崩溃时,进程中的 PCB 就要被回收,PCB 中的文件描述符表里对应的所有文件也都会被系统自动关闭

其中针对 socket 文件,就会触发正常的关闭流程(TCP 四次挥手)


2) 主机关机(正常流程的关机)

正常流程点击关机按钮,此时操作系统会先干掉所有的进程,此过程中也会触发四次挥手,但会分为两种情况:

        a) 四次挥手非常快,四次挥手已经完成了,关机动作才真正完成

        b) 四次挥手没来的及挥完,关机就完成了,如下图:


3) 主机掉电(拔电源)

        a) 接收方掉电

A 给 B 发送的数据不会再有 ACK 了

A 触发超时重传,重传的数据依旧没有响应,反复多次之后,A 尝试重置连接(rst),重置操作也没有 ACK,A 就会单方面释放连接(A 把保存的 B 的信息删除掉)

        b) 发送方掉电

A 发着发着数据,突然停了,B 的视角并不知道 A 是无了,还是休息一下,晚点再发

此时 B 就会给 A 发送一个数据包(不携带业务数据,只是为了触发 ACK),查询 A 的状态

如果发了这个 “探测报文” 之后,A 返回了 ACK,说明 A 只是休息一下;若发了探测报文,甚至发了多个探测报文,A 都没有 ACK,就可以视为 A 无了

这种探测报文是周期性的,同时这个报文时用来探测对方的 “生死” 的,于是把这样的报文称为 “心跳包”

计算机中非常广泛的使用“心跳包”的思想,TCP 内置了心跳包,由于 TCP 内置的心跳包周期比较长(秒级~分钟级),应用程序这一层通常也会自行实现一些心跳包,达到更快速的 “保活机制”


4) 网线断开

和主机掉电一样

TCP 中的其他标志位

1. URG 是和紧急指针配合使用的

当 URG 设置为 1 时,紧急指针能够生效,紧急指针里面保存的是一个偏移量

TCP 正常情况下,都是按照顺序来传输数据的,紧急指针就是让后谜案的数据排队,根据紧急指针的偏移量,把指定位置的数据优先发送出去(这是特殊场景的特殊方案,不通用,日常开发很少直接涉及)

2. PSH 催促标志位

带有这个标志位的数据就相当于提醒接收方,要尽快的来处理这个数据(也是特殊场景下的特殊方案)

3. 选项

选项中包含 “窗口扩展因子”,结合窗口大小使用

实际窗口大小 = 报头窗口大小 << 窗口扩展因子(相当于指数增长)


TCP/UDP 对比

TCP 对于数据需要可靠传输的场景是首选

UDP 对于可靠性要求不高,对于性能要求很高的场景是首选

经典面试题:用 UDP 实现可靠传输

参考 TCP 的可靠性机制,在应用层实现类似的逻辑,例如:

  • 引入序列号,保证数据顺序
  • 引入确认应答,确保对端收到了数据
  • 引入超时重传,如果隔一段时间没有应答,就重发数据
  • 等等。。。

http://www.kler.cn/a/305855.html

相关文章:

  • 如何让GCC生成编译过程的中间文件(.i/.s/.o)?gcc 1.c和g++ 1.c有什么区别?gcc 1.c和g++ 1.c预处理有什么区别?
  • 纯前端实现在线预览excel文件(插件: LuckyExcel、Luckysheet)
  • 08、Java学习-面向对象中级:
  • 解决编译 fast-lio-lc 算法时遇到的error方法
  • SpringBoot技术栈:构建高效共享汽车系统
  • 数据库_SQLite3
  • ArcGIS Pro SDK (十三)地图创作 4 设备
  • Qt 学习第十天:标准对话框 页面布局
  • Windows11 WSL2的ubuntu 22.04中拉取镜像报错
  • 分贝转换 1 mVpp = 9.03dBmV
  • 【软考】设计模式之抽象工厂模式
  • Linux通配符*、man 、cp、mv、echo、cat、more、less、head、tail、等指令、管道 | 、指令的本质 等的介绍
  • 重修设计模式-创建型-建造者模式
  • 基于YOLOv8的遥感光伏板检测系统
  • Vite + Electron 时,Electron 渲染空白,静态资源加载错误等问题解决
  • mysql的监控指标采集
  • 机器学习-------数据标准化
  • 一键生成中秋国风插画!FLUX中秋专属Lora的使用教程
  • 随着Batch size增加,最佳learning rate如何选择?
  • 一个关于Excel的段子
  • 2860. 让所有学生保持开心的分组方法数
  • 模板替换引擎(支持富文本动态表格)
  • 物体识别之微特征识别任务综述
  • Linux文件系统(下)
  • 红黑树前语
  • 存储课程学习笔记5_iouring的练习(io_uring,rust_echo_bench,fio)