
目录
1.UDP协议
1.1UDP协议端格式

实际上的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的具体实现细节,我们更关注其核心特性:
固定长度输出:无论输入字符串长度如何,MD5总能生成固定长度的哈希值,这一特性使其非常适合作为校验和算法
强离散性:输入内容的微小变化都会导致MD5值产生显著差异,这种特性使其非常适合作为哈希算法
不可逆性:从输入内容计算MD5非常简单,但根据MD5值反推原始内容在理论上不可行,这一特性使其可用作加密算法基础
离散性越强,哈希冲突(不同key映射到同一数组下标)的概率越低。一旦发生哈希冲突,处理过程都会带来额外开销。
需要注意的是,市面上的MD5"解密"网站并非真正解密,而是预先存储了大量常见字符串及其对应MD5值的映射关系。当输入MD5值时,通过查询哈希表来匹配原始key。这并不改变MD5算法本身不可逆的特性(这也是许多加密算法安全性的基础)。
SHA1和md5非常类似,也是这三个特点~~
1.2UDP特点
- 无连接:知道目标IP地址和端口号后即可直接传输数据,无需预先建立连接;
- 不可靠:没有确认和重传机制,若因网络故障导致数据无法送达,UDP协议层不会向应用层返回任何错误信息;
- 面向数据报:无法灵活控制数据的读写次数和数据量大小。
应用层传递给UDP的数据报文,UDP会原封不动地发送,既不会拆分也不会合并。例如用UDP传输100字节数据时:
- 如果发送端调用一次sendto发送全部100字节,接收端也必须调用一次recvfrom完整接收这100字节;
- 不能通过循环调用10次recvfrom,每次接收10字节的方式来完成接收。
1.3UDP使用注意事项
UDP协议首部包含一个16位的长度字段,这意味着单个UDP数据报的传输上限为64KB(包含首部)。然而,在当前网络环境下,64KB的容量明显不足。
超过上限的处理方式:当传输数据超过64KB时,必须在应用层进行手动分包传输,并在接收端重新组装。
UDP 报文长度由报头长度和载荷长度组成。其报头固定为 8 字节,由于 UDP 报文中的长度字段为 2 字节(16 位),所以整个 UDP 报文的最大理论长度为 2¹⁶字节,即 65536 字节(64KB)
1.4基于UDP的应用层协议
- NFS:网络文件系统
- TFTP:简单文件传输协议
- DHCP:动态主机配置协议
- BOOTP:启动协议(用于无盘设备启动)
- DNS:域名解析协议
2.TCP协议
2.1TCP协议端格式
TCP的全称是"传输控制协议"(Transmission Control Protocol)。

4位首部长度:TCP报头的长度,UDP协议报头固定就是8字节,对于TCP来说,报头长度是可变长的,此处的长度单位是4字节,不是字节
6位保留位:用于考虑未来的可扩展性,当TCP需要新增属性/某个属性的长度不够用就可以把保留位拿出来用作对应的作用,充分吸取了UDP的教训报文长度字段没法扩展
URG: 紧急指针是否有效;
ACK: 确认号是否有效;
PSH: 提示接收端应用程序立刻从TCP缓冲区把数据读走;
RST: 对方要求重新建立连接,我们把携带RST标识的称为复位报文段;
SYN: 请求建立连接;我们把携带SYN标识的称为同步报文段;
FIN: 通知对方,本端要关闭了,我们称携带FIN标识的为结束报文段
URG是和紧急指针配合使用的.
时紧急指针能够生效
紧急指针里保存的是一个偏移量
TCP正常情况来说,都是按照顺序来传输数据
紧急指针,就是让后面的数据插队,根据紧急指针的偏移量,把指定位置的数据,优先发送出去
PSH催促标志位,带有这个标志位的数据,就相当于提醒接收方,要尽快的来处理这个数据
(也是特殊场景下的特殊方案)
TCP可靠传输的体现: 不能做到100%的送达,只能尽可能的使数据能到达对方
- 能感知到对方是否收到
- 如果对方没收到,就要进行重试
3.TCP的核心机制
3.1确认应答
确认应答:需要对方明确告知是否收到信息,接收方收到数据之后,就要给发送方,返回一个"应答报文"(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重新设置
3.2超时重传
网络传输中可能会出现"丢包"的情况
产生丢包的原因有很多种,是完全随机不可预测的,比如
- 数据传输过程中,发生了bit翻转,收到这个数据的接收方/中间的路由器出现了这种情况,发现校验和对不上,此时发现错误就要直接把数据包丢弃掉,不继续往后转发交给应用层使用
- 数据传输到某个节点(路由器/交换机)之后,这个节点负载太高(某个路由器,单位时间内只能转发N个包,网络高峰期这个路由器单位时间内需要转发的包超过了N),后续传过来的包就可能被路由器直接丢弃掉
TCP感知到数据发生丢包就会重新再发一次,
- 是否丢包是要通过应答报文来区分,收到应答报文说明数据没丢包,没收到则说明数据丢包了;发送方发送数据之后,会给出一个"时间限制"(超时时间)如果在这个时间限制之内,没有收到ack,就视为是数据丢包了
- 假设网络丢包率位10%那么数据报到达对方的概率就死90%,此时进行一次重传,两次传输至少一次到达对方的概率为:1-10%*10% = 90%,因此传输次数越多数据到达对方的概率就越大
数据丢了
主机A发送数据给B之后,可能因为网络拥堵等原因,数据无法到达主机B;
如果主机A在一个特定时间间隔内没有收到B发来的确认应答,就会进行重发;
但是,主机A未收到B发来的确认应答,也可能是因为ACK丢失了;
ACK丢了
因此主机B会收到很多重复数据,接收方有一个接收缓冲区,收到的数据先进入缓冲区里,后续再收到数据先根据序号进入到缓冲区里找对应的位置,如果发现当前序号1-1000这个数据已经在缓冲区中存在了,就直接把新收到的数据丢弃,
确保应用程序,调用read读出来的数据是唯一的,不重复的,TCP会根据序号进行去重
超时重传的时间是如何设定的?
超时重传的时间不是固定值,而是动态变化的,发送方第一次重传,超时时间是t1.如果重传之后,仍然没有ack,还会继续重传,第二次重传,超时时间是t2
t2 > t1每多重传一次,超时时间的间隔,会变大/重传的频次,会降低
经过一次重传之后,就能让数据到达对方的概率,提升很多,再重传一次,又会提升很多
反之,如果重传几次,都没有顺利到达,说明网络的丢包率,已经达到了一个非常高的程度=>网络发生了严重故障,大概率没法继续使用了.
重传也不会无休止的进行,当重传达到一定次数之后,TCP不会尝试重传,就认为这个连接已经G了
先尝试进行"重置/复位连接",发送一个特殊的数据包"复位报文".如果网络这会恢复了,复位报文就会重置连接,使通信可以继续进行.
如果网络还是有严重问题,复位报文也没有得到回应,此时TCP就会单方面放弃连接
(连接就是通信双方各自保存对方的信息,发送方释放掉,之前保存的接收方的相关信息,这个连接也就无了)
超时重传导致的单方面释放连接是指:当发送方发送的数据因丢包等原因未被接收方确认,且多次重传失败后,发送方主动断开连接的行为
丢包 → 超时重传 → 重传失败次数超限 → 发送方单方面释放连接。
确认应答和超时建传,相互补充,共同构建了TCP"可靠传输机制"
3.3连接管理
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是一个假想的起始点,不是真实状态;
- LISTEN :服务器进入的状态,服务器把端口绑定好,相当于进入了listen状态了.
此时服务器就已经初始化完毕,准备好随时迎接客户端了。
手机开机,信号良好.随时可以有人来打电话了。- ESTABLISHED :客户端和服务器都会进入的状态,建立好的
TCP连接建立完成(保存了对方的信息了)
接下来就可以进行业务数据的通信了.
电话接通,可以说话了.- CLOSE_WAIT :被动断开连接的一方,会进入这个状态
先收到FIN的一方"等待代码执行close方法"
如果发现,服务器这端,存在大量的CLOSEWAIT状态的TCP连接,此时,说明服务器代码可能有bug,排查close是否写了,以及是否及时执行到了.- TIME_WAIT : 主动断开连接的一方,会进入这个状态
此处的TIMEWAIT按照时间来等待.达到一定时间之后,连接就也释放了.
为啥不直接释放,而是要等待一定时间呢??
就是为了防一手最后的ACK丢包
3.4滑动窗口
TCP引入了滑动窗口是希望能在可靠传输的基础上也有一个不错的效率,这里的提高效率,只是"亡羊补牢",使传输效率的损失,尽可能降低,引入滑动窗口,不能使传输效率比UDP还高的
滑动窗口是批量传输数据的一种实现方式
窗口大小指的是无需等待确认应答而可以继续发送数据的最大值,上图的窗口大小就是4000个字节(四个段).
滑动窗口中出现丢包如何处理?
数据包已经抵达,ACK被丢了
丢失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。
3.5流量控制
滑动窗口的窗口大小对于传输数据的性能是直接相关的但窗口大小能无限大吗?很显然不能,
通信是双方的事情因此当发送方传输数据快了也得确保接收方能处理过来
滑动窗口,提高速度(踩油门)
流量控制,制约速度(踩刹车)可以通过"定量"的方式,来实现制约,看接收缓冲区剩余空间的大小
TCP中接收方收到数据的时候,就会把接收缓冲区剩余空间的大小通过ACK数据报反馈给发送方,下一步发送方就可以根据根据这个数据来设置发送窗口的大小了
流量控制,也不是TCP独有的机制.其他的协议,也可能会涉及到流量控制(比如,数据链路层中有的协议,也支持流量控制)
3.6拥塞控制
流量控制,站在接收方的视角来限制发送方的速度的
拥塞控制,站在传输链路的视角来限制发送方的速度的
拥塞控制的具体流程
- 先按照一个比较小的速度发送数据
- 数据非常流畅,没有丢包,说明网络上传输数据整体是比较流畅的,就可以加快传输数据的速度
- 增大到一定速度之后,发现出现丢包了,说明网络上可能存在拥堵了,就减慢传输数据的速度
- 减速之后,发现有不丢包了,继续在加速
- 加速之后发现有丢包了,继续减速
持续的动态变化,类似于"水多加面,面多加水"的过程
流量控制,会限制发送窗口
拥塞控制,也会限制发送窗口
这两个机制,会同时起作用,最终实际的发送窗口大小,取决于上述两个机制得到的发送窗口较小值拥塞窗口中窗口大小的具体变化过程
单位理解成"份"
- 刚开始传输数据,拥塞窗口会非常小,用一个很小的速度来发送数据
当前网络是否拥堵是未知的刚启动时,发数据的速度很慢- 不丢包就增大窗口大小(指数增长)
增长速度特别快,短时间内达到很大的窗口大小- 增长到一定程度,达到某个阈值,此时即使没丢包也会停止指数增删,变成线性增长
不至于太快的进入到丢包的节奏- 线性增长,也会持续使发送速度越来越快,达到某个情况就会出现丢包
一旦出现丢包,接下来就需要减少发送速度,减小窗口大小
两种处理方式:
1)经典的方案(Tahoe):回归慢开始非常小的初始值,指数增长,线性增长
2)现在的方案(Reno):回归到新的阈值上,线性增长(以后都不会指数增长了)
3.7延时应答
延时应答:接收方收到数据后,不立即发送 ACK,而是延迟一段时间(通常 200ms 左右)再响应。
提升效率机制,尽可能降低可靠传输带来的性能的影响
提升性能=>让窗口变大
目的:
- 减少 ACK 报文数量,降低网络开销;
- 等待应用层读取数据,让接收缓冲区释放更多空间,从而反馈更大的窗口大小。
窗口的大小就为接收缓冲区的剩余空间
假设让ack100ms之后再返回,这就意味着此时在100ms内应用程序可能又消费掉2KB的数据了,此时返回的ack携带的窗口大小就是6KB
延时返回的ack的窗口大小,大概率就要比立即返回ack的窗口大小更大,在这个时间里,会有一个消费数据的过程(大概率,取决于应用程序的代码怎么写,是否是不停的读取数据;延时时间内是否发送方会发新的数据过来)
3.8捎带应答
在延时应答基础上,引入的提升效率的机制,把返回的业务数据和ack两者合二为一了
ack是内核返回的.是收到请求之后,立即就返回ack.
响应,则是应用程序返回的.代码中,根据请求计算得到响应,再把响应写回到客户端~~
正常情况下,ack和响应是不同的时机,无法合并,但ack涉及到"延时应答",延时应答就会使ack返回的时间被往后拖,这样一延时就可能赶上接下来发送响应数据的操作了,于是就可以在发送响应的时候把刚才的ack信息也带上,响应数据主要是设置载荷,和ack不冲突,可以共存
接收方在向发送方返回 ACK 确认报文时,若同时有数据需要发送,则将 ACK 与数据合并在一个报文中传输
3.9面向字节流
创建一个tcp的socket同时在内核中创建一个发送缓冲区 和一个接收缓冲区
- 调用write时,数据会先写入发送缓冲区中
- 如果发送的字节数太长,会被拆分成多个TCP的数据包发出
- 如果发送的字节数太短,就会在发送缓冲区里等待,等到缓冲区长度差不多了,或者其他合适的时机发送出去
- 接受数据的时候,数据也是从网卡驱动程序到达内核的接收缓冲区
- 然后应用程序可以调用read从接受缓冲区拿数据
- TCP的一个连接,既有发送缓冲区,也有接收缓冲区,对于这一个连接,既可以读数据,也可以写一些数据,这叫全双工
在我们读写100个字节的数据,可以一次读写100个字节的数据一次读完,也可以一次读写10个字节的数据分10次读完,此时我们就要考虑"粘包问题"
粘的是TCP携带的载荷(应用层数据包)
如果希望在文件中存储结构化数据,也会存在"粘包问题",所以会使用xml/json这样的格式来存储;UDP这种面向数据报的传输方式不涉及该问题,,send/receive得到的就是一个完整的DatagramPacket,这里携带的二进制的字节数组就是一个完整的应用层数据包了
解决"粘包问题"的关键就是明确"包之间的边界"
- 指定分隔符,适用于文本类的数据,比如xml/yml/json
之前写的TcpEchoServer约定请求响应,都是以\n结尾,发送请求响应的时候,专门使用println进行写数据,读取请求响应的时候,专门使用scanner.next按照\n进行解析
(需要确认数据内容的正文中,不能包含分隔符)
如果传输的数据,是纯文本数据的话,此时使用\n或者;之类的可能都不合适,但是可以使用ascii中靠前的"控制字符"
- 指定数据的长度,如protobuf
比如,约定在每个应用层数据包,开头的2/4个字节,表示数据包的长度
如果是传输二进制数据,这个方案就很有用了。
3.10异常情况处理
进程崩溃
Java中的体现,就是抛出异常,但是没人catch,最终异常到了jvm这里,jvm进程就会直接嘎.当进程崩溃时进程中的PCB就要被回收,PCB中的文件描述符表里对应的所有文件,也都会被系统自动关闭,其中针对socket文件,也就会触发正常的关闭流程(TCP四次挥手)主机关机(正常流程的关机)
正常流程点击关机按钮,此时操作系统就会先销毁所有的进程,销毁的过程中同样会触发四次挥手
a)四次挥手非常快,四次挥手已经完成了,关机动作才真正完成
b)四次挥手没来得及挥完,关机就完成了
主机掉电(拔电源)
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已经断开连接。
这样的探测报文是周期性的,同时这个报文是用来探测对方生死的,也就把这样的报文称为"心跳包"
网线断开