

**前引:**当你在手机上刷着高清直播,画面流畅无延迟;转头用办公软件传一份重要报告,即便网络波动也能确保文件完整------这两种截然不同的网络体验,背后藏着传输层两大核心协议的"功劳":TCP与UDP。前者像严谨的"快递员",全程跟踪、确认签收,绝不丢失包裹;后者则是高效的"急行军",直奔目标、无需等待,用速度优先完成任务。看似功能对立的它们,共同构成了网络传输的基础框架。本文将带你走进传输层的世界,剖析TCP与UDP的核心差异、设计逻辑,以及如何在不同场景中做出最优选择!
目录
【一】UDP协议
(1)报文结构认识
我们在进入网络时知道,每层的报文都需要加上对应的报头,UDP的报文格式如下结构:

16位源端口号:客户端的端口
16位目的端口号:服务器对应进程的端口
(一个进程可以绑定多个端口,但是一个端口只能被一个窗口绑定)
16位UDP长度:整个数据报(UDP⾸部+UDP数据)的最⼤⻓度
16位UDP检验和:判断报文是否可靠
(2)通信特点
(1)不需要和对方确立联系,知道对方的IP和端口,直接开始传数据
(2)没有确认机制,即应答机制,不知道本次数据传输是否被对方接收
(3)面向数据包:好比一个大小固定的快递盒
(4)没有发送缓冲区,只有接收缓冲区(无法保证数据顺序,缓冲区满了就会直接丢弃)
【二】TCP协议
(1)基本概念
首先我们从最外面的缓冲区开始讲解,然后进入报文里面的端口大小,首都长度这些!
(1)缓冲区
双方如果都采用TCP协议作为传输协议,那么单方都具有接收缓冲区和发送缓冲区:
**注意:**缓冲区的数据都是报文格式的

(2)6位标志位
这里先只输出每个标志位的作用,后面会再次进行理解:

ACK:确认对方是否收到
PSH:提⽰接收端应⽤程序⽴刻从TCP缓冲区把数据读⾛
RST:对⽅要求重新建⽴连接;我们把携带RST标识的称为复位报⽂段
SYN:请求建⽴连接;我们把携带SYN标识的称为同步报⽂段
FIN:通知对⽅,本端要关闭了,我们称携带FIN标识的为结束报⽂段
(3)32位序号/确认序号
TCP的每个报文都是如下形式,可以没有数据,但至少有TCP报头:

TCP将每个数据以字节为单位都进行了编号,例如:1001,1000.........

当发送第一批数据的时候,假设是1000字节,那么发送方的32位序号就是1000,如果正常接收方正常接收,那么接收方的32位确认序号就是1001。注意:服务端也可能在给客户端发送消息
即表示发送方已经完成了1000以内的所有数据发送,1001表示接收方完成1001之前全部数据接收
(4)16位窗口大小

含义:自己接受缓冲区中的剩余空间大小
(5)16位紧急指针

含义:标志紧急数据,需要被立刻处理的
(6)16位检验和
含义:发送端填充,表示数据大小(不算报头),通常由接收端进行检验
(7)4位首都长度
在TCP中,存在度量"单位数"------4字节,TCP报文会一般会将本次报头存在多少"单位数"放在4位首都长度,比如4位首都长度为5,那么报头的大小就是4*5=20字节,假设报文总大小是100字节,100字节减去报头20字节,就是(数据+选项)大小,方便快速将数据和报头分离!
(2)应答机制
含义:向对方发送信息,对方会回你,即应答机制
(3)超时重传机制
假设现在客户端和服务端正在进行通信,那么可能存在如下情况:
(1)客户端的数据在传输过程中丢包
(2)客户端的数据传给了服务端,但是服务端的反馈丢包
(3)双方都正常,但是自己网络导致数据单方拿不到数据

在通信过程中,一般都是客户端主动向服务端发起请求,那么就可能出现上面的几种非正常通信情况,此时,会存在"超时重传"机制,当发送给对方的数据在一个规定时间,比如超过500ms(存在差异),那么发送方就会重新发送数据------即规定时间未收到回复,将重新发送;如果时间太长还未收到回复,会直接断开连接
但是这样就会出现一个新的问题:对方可能接收的是重复数据
此时因为"序号(例如1000)"的存在,会达到去重的效果,这里需要"滑动窗口",我们后面理解!
(4)捎带应答机制
含义:将确认是否收到(ACK)和要发送的数据一起发送给对方,减少频繁发送提高效率,例如:

(5)三次握手+四次挥手
6位标志位的用法其实是用来标签不同的报文类型,比如客户端和服务端有建立连接、确认对方是否收到、是否和对方断开连接.......不同的报文类型,它们可能只有报头!
三次握手:

(1)客户端向服务端发起连接请求 SYN
(2)服务端回复:确认请求(ACK)+尝试和客户端发起请求(SYN)
(3)客户端回复:确认请求(ACK)
注意:三次握手中的第二次,其实是使用了"捎带应答"机制,本质上也算是四次握手!
为什么存在应答机制,客户端最后一次方式ACK确认之后,服务端不用回?
因为在三次握手中,双方已经确认是否可以正常通信,没有必要执行第四次,否则会陷入问答循环
第三次握手如果失败会出现什么情况?
前两次握手,服务端和客户端一问一答有回应,若失败,客户端会执行超时重传机制;但是如果客户端在第三次向服务端发送ACK确认应答之后,服务端没有收到第三次客户端的ACK呢?客户端会自以为已经完成三次握手,当客户端再发送数据时,服务端会响应RST(要求对方重新建立连接)
这三次握手和应用层的接口有什么关系?
客户端的connect()和服务端的listen()其实就对应前两次握手,第三次握手是connect()的返回结果;如果客户端一直没有收到对方的ACK应答,就会卡在一个名为"中间队列"中;当三次顺利完成,此时accept()会给这个客户端返回新的文件描述符,即三次握手是在accept()之前的,accept()也会形成一个队列,名为"全链接队列",服务端处理完毕上一批客户端,立马从"全链接队列"开始拿新的客户端,队列的存在也是一种提高效率的行为
四次挥手:

(1)客户端可能有数据给服务端,服务端应答ACK
(2)服务端可能有数据给客户端,客户端应答ACK
(3)客户端发起断连请求,服务端应答ACK
(4)服务端发起断连请求,客户端应答ACK
**TCP协议规定:**主动断开连接的那一方,会进入TIME_WAIT状态,等待两个MSL的时间后才能回到CLOSED状态,MSL的大小可以使用如下指令查看:cat /proc/sys/net/ipv4/tcp_fin_timeout
为什么要有TIME_WAIT状态?
(1)保证双方之前的迟到的、出现错误的报文全部处理完毕
(2)同时也是在理论上保证最后⼀个报文可靠到达,假设最后一个ACK丢失,服务器就会重发, 此时虽然客户端已经断开,但是它的TCP还在(IP端口还在使用)就可以对服务器做出应答
如何解决TIME_WAIT状态导致无法立刻重复绑定?
使⽤ setsockopt ()设置 socket 描述符的选项 SO_REUSEADDR 为 1
含义:表⽰允许创建端⼝号相同 但IP地址不同的多个 socket 描述符

如何解决服务端大量CLOSE_WAIT状态?
及时使用close()关闭对应的文件描述符,服务器才会向客户端发送断连请求
(6)滑动窗口
在上面我们学过"应答"机制,即发生方给接收方的报文,接收方都要有应答,即一对一,但是频繁的一对一发送与应答难免出现效率问题,如果中间网络很拥堵呢?或者对方没有接收到呢?

那么最佳实践方案:一次性发送多条数据,对应多次应答。即滑动窗口
在发送缓冲区中其实是存在数据分组的:已发送已确认、已发送未确认、未发送

那么我们再次细分,来理解滑动窗口:缓冲区中类似数据存储数据,通过下标访问

那么每次就可以发送滑动窗口包含的数据:对方再回复对应的序号即可
(注意:序号(如1001,2001......)表示之前的报文已经收到)

滑动窗口是如何移动的?
假如现在要发送0~3000的报文,但是1000~2000的丢了,如图:

满足如下规律:左边由是否确认应答决定(不会向左移动),右边受限于对方窗口大小,动态调整

滑动窗口如何解决各种情况的?
情况1:数据包已收到,但是应答丢了,如图。此时可以通过后面的应答报文知道情况

情况2:数据包直接丢了,如图。此时没有接收方在1001~2000的应答ACK**,如果发送方连续三次接收到相同的应答ACK,那么就会重新发送对应的报文**,例如:发送方发送1000~7000的报文
此时1000~2000的报文丢了,接收方就只会返回2001,发送方连续三次收到的话,就会重新发送,此时接收方再返回7001的应答,因为之前部分报文已经存到了接收方的缓冲区中

(7)流量控制
当客户端和服务端在三次握手的时候,双方会协商窗口大小,表示适合对方的一次性发送的数据控制大小,也就是流量控制------一般在报文中的"16位窗口大小"会告诉对方
滑动窗口会根据对方的窗口反映情况动态变化发送的数据大小,例如:

(8)拥塞控制
避免问题:网络拥堵造成数据大量丢失或者大量延迟
解决办法:慢启动 ,发送方开始会发送少量的数据,查看当前网络情况,再根据情况增加数据
如果发生大量数据的时候,网络突然波动,那么就会重新试探网络的情况,重新开始热发送
引入名词:拥塞窗口,刚开始定义拥塞窗⼝⼤⼩为1,每次收到⼀个ACK应答,拥塞窗⼝加1
每次发送数据包的时候,将 拥塞窗⼝ 和 接收端主机反馈的窗⼝⼤⼩ 做⽐较,取较⼩的值作为实际发送的窗⼝------min(拥塞窗口、接收端主机反馈的窗口大小)

(9)延迟应答
如果接收数据的主机⽴刻返回ACK应答,这时候返回的窗⼝可能⽐较⼩
TCP存在优化方案:我感觉就是(捎带应答的进阶版,哈哈哈!)

只要网络不拥堵,那么窗⼝越⼤,⽹络吞吐量就越⼤,传输效率就越高
(10)面向字节流
**全双工:**对于TCP协议,既可以读数据,也可以写数据(存在发送和接收两个缓冲区)
**面向字节流:**数据的读取和发送不需要一一匹配,可以发100次/字节,也可以只读一次/100字节
(11)粘包问题
什么是粘包问题?应用层获取到的数据报文并不是完整的,传输层只负责拿报文,可能是1.5个...
解决本质:通过应用层自定义协议,划分出一个个报文
如何解决粘包问题:定长报文(例如给数据包固定在10字节)
使用特殊字符区分
在发送数据之前,在数据前面先发送数据大小(例如"5字节""Hello")
