

实际上的UDP数据并没有换行的动作

总长度为8字节,采用固定长度格式。报头中的四个字段通过固定长度区分,无需额外分隔符。 UDP报文长度等于报头长度与载荷长度之和,以字节为单位。该长度使用2字节表示,最大值为65535(即64KB)。端口号同样使用2字节表示,因此像10万这样的端口号会在系统底层被截断处理
关于校验和: 前提:在网络传输过程中,数据极易受到干扰。无论是电信号、光信号还是电磁波,都可能因环境因素导致传输信号发生改变(如1变0或0变1)。 目的:校验和旨在检测或纠正传输过程中出现的错误。其原理是通过在数据中添加额外信息来实现:
工作原理:根据传输内容生成对应的校验码,用于后续验证数据的完整性。 UDP采用了一种高效的CRC校验机制(循环冗余校验)。 具体实现方式是:遍历整个UDP数据报的每个字节,并将其累加到一个16位的校验和变量中。由于数据量可能较大,累加过程中允许溢出,我们重点关注的是最终校验结果在传输过程中是否发生变化。

除了CRC之外还可能会用到其它一些算法来实现校验和,另外两个典型算法,MD5和SHA1 MD5算法本质上是一种字符串哈希算法,类似哈希表HashMap中通过hash函数将String转换为数组下标的方式。JDK中已经内置了String的hash算法实现。 相比于MD5的具体实现细节,我们更关注其核心特性:
SHA1和md5非常类似,也是这三个特点~~
应用层传递给UDP的数据报文,UDP会原封不动地发送,既不会拆分也不会合并。例如用UDP传输100字节数据时:
UDP协议首部包含一个16位的长度字段,这意味着单个UDP数据报的传输上限为64KB(包含首部)。然而,在当前网络环境下,64KB的容量明显不足。 超过上限的处理方式:当传输数据超过64KB时,必须在应用层进行手动分包传输,并在接收端重新组装。 UDP 报文长度由报头长度和载荷长度组成。其报头固定为 8 字节,由于 UDP 报文中的长度字段为 2 字节(16 位),所以整个 UDP 报文的最大理论长度为 2¹⁶字节,即 65536 字节(64KB)
TCP的全称是"传输控制协议"(Transmission Control Protocol)。


URG: 紧急指针是否有效; ACK: 确认号是否有效; PSH: 提示接收端应用程序立刻从TCP缓冲区把数据读走; RST: 对方要求重新建立连接,我们把携带RST标识的称为复位报文段; SYN: 请求建立连接;我们把携带SYN标识的称为同步报文段; FIN: 通知对方,本端要关闭了,我们称携带FIN标识的为结束报文段
TCP可靠传输的体现: 不能做到100%的送达,只能尽可能的使数据能到达对方
确认应答:需要对方明确告知是否收到信息,接收方收到数据之后,就要给发送方,返回一个"应答报文"(ack/acknowledge) TCP将每个字节的数据都进行了编号.即为序列号.

TCP引入序号和确认序号,使应答报文和传输的数据对应上 TCP协议通过ACK应答报文中的确认序列号机制,确保数据传输的可靠性。接收方通过确认序列号明确告知发送方:1)已成功接收的数据范围;2)后续应从哪个位置继续发送。这种机制有效避免了网络传输中可能出现的乱序问题,避免出现后发先至的情况,即使数据包到达顺序错乱,也能通过序列号正确重组

TCP的序号:由于TCP是面向字节流的,实际上编号并非是按照"第一条,第二条"这样的方式来编排的而是按照"字节",每个字节都有一个独立的编号,字节和字节之间编号是连续,递增的,按照字节编号这样的机制就称为"TCP的序号" TCP的确认序号:在应答报文中,针对之前收到的数据进行对应的编号称为"TCP的确认序号"


对于应答报文来说,确认序号就会按照收到的数据的最后一个字节序号+1的方式来填写,另外六个标志位中第二位会设为1,对于普通报文ack是0,应答报文就是1 如果是普通报文序号是有效的,确认序号是无效的,如果是ack应答报文序号和确认序号都是有效的 因为TCP是字节流的,32位序号描述了载荷部分第一个字节序号为多少,一个TCP数据报(一个TCP报头+载荷)和下一个TCP数据报携带的数据天然就是"可拼装的",TCP报头长度为4字节/32位,表示的范围是0~42亿9千万,即0~4GB,那是否就意味着最大只能传输4GB呢,TCP不像UDP存在传输的上限需要考虑数据的大小,而TCP可以调用多次write,超过4GB也没关系,数据序号可以从0重新设置
网络传输中可能会出现"丢包"的情况 产生丢包的原因有很多种,是完全随机不可预测的,比如
TCP感知到数据发生丢包就会重新再发一次,

数据丢了 主机A发送数据给B之后,可能因为网络拥堵等原因,数据无法到达主机B; 如果主机A在一个特定时间间隔内没有收到B发来的确认应答,就会进行重发; 但是,主机A未收到B发来的确认应答,也可能是因为ACK丢失了;

ACK丢了 因此主机B会收到很多重复数据,接收方有一个接收缓冲区,收到的数据先进入缓冲区里,后续再收到数据先根据序号进入到缓冲区里找对应的位置,如果发现当前序号1-1000这个数据已经在缓冲区中存在了,就直接把新收到的数据丢弃, 确保应用程序,调用read读出来的数据是唯一的,不重复的,TCP会根据序号进行去重 超时重传的时间是如何设定的? 超时重传的时间不是固定值,而是动态变化的,发送方第一次重传,超时时间是t1.如果重传之后,仍然没有ack,还会继续重传,第二次重传,超时时间是t2 t2 > t1每多重传一次,超时时间的间隔,会变大/重传的频次,会降低 经过一次重传之后,就能让数据到达对方的概率,提升很多,再重传一次,又会提升很多 反之,如果重传几次,都没有顺利到达,说明网络的丢包率,已经达到了一个非常高的程度=>网络发生了严重故障,大概率没法继续使用了. 重传也不会无休止的进行,当重传达到一定次数之后,TCP不会尝试重传,就认为这个连接已经G了 先尝试进行"重置/复位连接”,发送一个特殊的数据包"复位报文”.如果网络这会恢复了,复位报文就会重置连接,使通信可以继续进行. 如果网络还是有严重问题,复位报文也没有得到回应,此时TCP就会单方面放弃连接 (连接就是通信双方各自保存对方的信息,发送方释放掉,之前保存的接收方的相关信息,这个连接也就无了) 超时重传导致的单方面释放连接是指:当发送方发送的数据因丢包等原因未被接收方确认,且多次重传失败后,发送方主动断开连接的行为 丢包 → 超时重传 → 重传失败次数超限 → 发送方单方面释放连接。
确认应答和超时建传,相互补充,共同构建了TCP“可靠传输机制”
TCP经过三次握手建立连接,四次挥手断开连接。次数指的就是网络通讯的次数 建立连接是一个双向操作的过程,A需要给B说,我想和你建立连接(A想保存B的信息) B也需要给A说,我也想和你建立连接(B想保存A的信息),通信双方要各自保存对方的信息

对于三次握手来说,中间的两次,ACK+SYN,都是在内核中,由操作系统负责进行的 时机都是在收到SYN之后.此时同一时机,就可以合并了. 建立连接的三个意义,进行三次握手的原因
TCP通信时,起始数据的序号,就是通过三次握手,协商确定的(换而言之,TCP序号,并不是从1开始的) 每次建立连接,TCP的起始序号都不同(而且故意差别很大),这么约定的意义,在于避免出现"前朝的剑,斩本朝的官”

过了一会,A和B又重新建立连接虽然还是AB两个主机的连接,但是可能是不同的应用程序 对于B来说,就需要区分,当前收到的数据是"本朝”还是"前朝”的 给每个连接,都协商不同的起始的序号 如果发现收到的数据,和起始序号以及和最近收到的数据序号,都差别很大的话,就视为这个数据就是前朝”的数据 注:三次握手对于可靠性是有一定的支持的,但可靠性就是三次握手体现的这句话就非常武断,三次握手是在建立连接时进行的,建好连接后数据就开始传输了,此时和三次握手就没关系了 确认应答+超时重传才是负责传输数据过程中的"可靠性"
四次挥手:(优雅的)断开连接,双方各自把对端的信息删除掉,断开连接不一定是客户端主动,服务器也可以主动

对于四次挥手来说,ACK是内核控制的,但FIN的触发则是通过应用程序调用close/进程退出来触发的,当调用socket.close()时,系统内部会通过发送FIN(Finish)包来终止 TCP 连接

TCP状态转换汇总:

较粗的虚线表示服务端的状态变化情况; 较粗的实线表示客户端的状态变化情况; CLOSED是一个假想的起始点,不是真实状态;

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

滑动窗口是批量传输数据的一种实现方式

窗口大小指的是无需等待确认应答而可以继续发送数据的最大值,上图的窗口大小就是4000个字节(四个段).
滑动窗口中出现丢包如何处理?

丢失ACK只是丢失一部分,不可能全丢 理解,确认序号的含义 表示的是收到的数据最后一个字节的,下一个序号 进一步理解成,确认序号之前的数据,都已经收到了 接下来你要发的数据就从确认序号这里往后发 虽然1001ack丢了.但是2001到达,发送方收到2001之后,意味着2001之前的数据都已经收到了. 后一个ACK能够涵盖前一个ACK的意义

当1001-2000重传过来了之后,由于之前2001-7000数据都是已经发过的 1001-2000相当于补全了之前的空缺,此时就意味着1-7000的数据都齐了 于是接下来索要7001开头的数据即可 快速的识别出是哪个数据丢包,并且针对性的重传,其他顺利到达的数据都无需重传, 这个过程称为“快速重传” 如果通信双方,单位时间发送的数据量比较少,就是按照之前的确认应答/超时重传 如果单位时间发送的数据比较多,就会按照滑动窗口/快速重传
窗口大小 TCP窗口的大小是16位的, 16 位字段的最大值为 2^16 - 1 = 65,535 字节(约 64KB)。这意味着在标准 TCP 中,接收方最多只能通知发送方 “我能接收 65,535 字节数据”,但是否意味着最大就是64KB呢? 为突破 16 位窗口的限制,TCP 引入了窗口缩放选项, 选项中可以设置一个特殊的选项,"窗口扩展因子",发送方的窗口大小=窗口大小<<窗口扩展因子 缩放因子范围为 0~14,最大可将窗口扩展至 65,535 × 2^14 ≈ 1GB。
滑动窗口的窗口大小对于传输数据的性能是直接相关的但窗口大小能无限大吗?很显然不能, 通信是双方的事情因此当发送方传输数据快了也得确保接收方能处理过来 滑动窗口,提高速度(踩油门) 流量控制,制约速度(踩刹车)

可以通过"定量"的方式,来实现制约,看接收缓冲区剩余空间的大小

TCP中接收方收到数据的时候,就会把接收缓冲区剩余空间的大小通过ACK数据报反馈给发送方,下一步发送方就可以根据根据这个数据来设置发送窗口的大小了

流量控制,也不是TCP独有的机制.其他的协议,也可能会涉及到流量控制(比如,数据链路层中有的协议,也支持流量控制)
流量控制,站在接收方的视角来限制发送方的速度的 拥塞控制,站在传输链路的视角来限制发送方的速度的

拥塞控制的具体流程
持续的动态变化,类似于"水多加面,面多加水"的过程 流量控制,会限制发送窗口 拥塞控制,也会限制发送窗口 这两个机制,会同时起作用,最终实际的发送窗口大小,取决于上述两个机制得到的发送窗口较小值 拥塞窗口中窗口大小的具体变化过程 单位理解成"份"

延时应答:接收方收到数据后,不立即发送 ACK,而是延迟一段时间(通常 200ms 左右)再响应。 提升效率机制,尽可能降低可靠传输带来的性能的影响 提升性能=>让窗口变大 目的:
窗口的大小就为接收缓冲区的剩余空间

假设让ack100ms之后再返回,这就意味着此时在100ms内应用程序可能又消费掉2KB的数据了,此时返回的ack携带的窗口大小就是6KB 延时返回的ack的窗口大小,大概率就要比立即返回ack的窗口大小更大,在这个时间里,会有一个消费数据的过程(大概率,取决于应用程序的代码怎么写,是否是不停的读取数据;延时时间内是否发送方会发新的数据过来)
在延时应答基础上,引入的提升效率的机制,把返回的业务数据和ack两者合二为一了 ack是内核返回的.是收到请求之后,立即就返回ack. 响应,则是应用程序返回的.代码中,根据请求计算得到响应,再把响应写回到客户端~~

正常情况下,ack和响应是不同的时机,无法合并,但ack涉及到"延时应答",延时应答就会使ack返回的时间被往后拖,这样一延时就可能赶上接下来发送响应数据的操作了,于是就可以在发送响应的时候把刚才的ack信息也带上,响应数据主要是设置载荷,和ack不冲突,可以共存 接收方在向发送方返回 ACK 确认报文时,若同时有数据需要发送,则将 ACK 与数据合并在一个报文中传输

创建一个tcp的socket同时在内核中创建一个发送缓冲区和一个接收缓冲区
在我们读写100个字节的数据,可以一次读写100个字节的数据一次读完,也可以一次读写10个字节的数据分10次读完,此时我们就要考虑"粘包问题" 粘的是TCP携带的载荷(应用层数据包)

如果希望在文件中存储结构化数据,也会存在"粘包问题",所以会使用xml/json这样的格式来存储;UDP这种面向数据报的传输方式不涉及该问题,,send/receive得到的就是一个完整的DatagramPacket,这里携带的二进制的字节数组就是一个完整的应用层数据包了 解决"粘包问题"的关键就是明确"包之间的边界"



b)发送方掉电 A发送数据时突然中断,从B的角度无法判断A是彻底断线还是暂时休息、稍后会恢复。这时B会向A发送一个探测数据包(不含业务数据,仅用于触发ACK),以确认A的状态。如果A回复了ACK,说明A只是短暂停顿并未断线;如果A没有回应任何ACK,则可以判定A已经断开连接。 这样的探测报文是周期性的,同时这个报文是用来探测对方生死的,也就把这样的报文称为"心跳包"

