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

传输层协议——UDP和TCP

一、UDP协议

(一)UDP协议的特点

  • 无连接

无连接指的就是使用UDP协议传输数据的时候,通信双方是不会自动保存对方的信息的(比如IP和端口号),除非在编程的时候自己添加代码使其保存对端信息,在之前的套接字编程中体现出了这一点。

  • 不可靠传输

不可靠传输在代码中不容易体现出来,其形象一点儿的说法就是,发送方在发送完数据报之后,就不管了,不在乎数据报在传输过程中是否发生丢包。

  • 面向数据报

使用UDP发送的数据是以UDP数据报为单位的,发送过程中不能进行拆分,

  • 全双工

使用UDP协议的通信双方都可以发送和接收数据。

(二)UDP协议段格式

UDP数据报分为两个部分:报头和载荷

报头部分的长度为64位,也就是8字节。

  • 16位源端口号和16位目的端口号:

16bit位表示的范围是0~2^16-1=65535,也就是说端口号的范围是0~65535。实际上一般把1024以下的端口号保留,直接写代码的时候都是用1024~65535范围内的。

  • 16位UDP长度

16bit位表示的范围是0~2^16-1=65535,意味着UDP数据报长度的最大值是64KB,在传输大数据的时候要进行拆分组合,很麻烦,而TCP能解决这个问题。

  • 16位UDP校验和

引入校验和是验证数据是否发生修改的手段。在数据传输过程中,电信号/光信号可能受到干扰,会使数据发生“比特反转”(0变成1,1变成0)。

发送方在发送数据之前,对载荷部分计算出一个校验和1,并将校验和1一起发送给接收方,接收方在收到数据报后,计算出一个校验和2,判断两个校验和是否相等,如果不相等,就丢弃掉这个数据报。

二、TCP协议

(一)TCP协议的特点

  • 有连接
  • 面向字节流
  • 可靠传输
  • 全双工

这些特点将会在TCP协议的核心机制中体现出来。

(二)TCP协议段格式

TCP协议段分为两个部分:报头和载荷

  • 16位源端口号和16位目的端口号

是传输层中的核心内容。

  • 4位首部长度

4个比特位表示的范围是0~15,也就是说首部的最大长度是15个字节,但是首部不包含选项的情况下都有20个字节了啊,其实首部的长度是以4个字节为单位的,因此首部的长度是15*4=60个字节。

  • 保留(6位)

基于UDP协议中数据长度不够,又不能扩展,因此TCP报头中就预留了一些“保留位”。

  • 6个标志位

具体内容会在TCP的核心机制中讲到。

  • 16位校验和

用于检验数据是否发生错误。

三、TCP的核心机制

(一)确认应答

TCP的一个特点是可靠性,保证可靠性的一个前提就是,发送方能知道发送的数据是否被接收方收到。

接收方收到数据后,给发送方发送应答报文(acknowledge,ack),发送方就知道对方收到了。

打比方就像社交软件上聊天的时候,发送一句消息,我们这里显示“已到达”,就知道对方已经收到了。

当标志位中的ACK=1,就代表这个是应答报文,只起到告知对方我已经收到数据的作用,这是操作系统内核立刻自动执行的。

如果发送方一次性发送多个请求时,先发送的请求在传输过程中也可能后到达接收方,后发送的请求反而先到达,出现了“后发先至”的情况。

对于这种情况,发送方怎么知道,接收方的ack应答是针对于哪一个请求的呢?

⭐️在TCP协议段格式中,有32位的序号和32位的确认序号。

序号就是将发送方发送的多次请求进行编号得到的序号,确认序号在应答报文中才生效,表示该应答是针对于某个序号的请求的。

TCP是面向字节流的,在编号的时候按照字节来编号,每一个字节分配一个序号,是连续递增的。

⭐️发送数据时,序号字段填写载荷部分的第一个字节的序号。

⭐️接收数据时,确认序号字段填写载荷部分的最后一个字节的序号+1。

如果发送方发送了1~1000,1001~2000,2001~3000……

接收方在收到1~1000后返回一个确认应答,确认序号是1001。其中1001~2000在传输过程中丢失了,即使后面发送方发送了2001~3000,确认应答的确认序号依然是1001,而不是3001,用于提醒发送方1~1000没有收到。

⭐️因此确认序号的作用就是:

  • 告知对方<确认序号的数据都已经收到了。
  • 告知发送方下次要从确认序号开始发送数据。
  • 如果中途漏了一段数据,意在提醒对方有一段数据没收到。

❓要是出现“后发先至”的情况,接收方如何能按照发送方发送的顺序来读取数据呢?

TCP在接收方这里,会安排一个“接收缓冲区”,通过网卡读取到的数据,会先存放到接收缓冲区里,后续代码调用read,就从接收缓冲区里面读取数据。

在接收缓冲区内,数据会按序号进行排序,小的在前面大的在后面。

只有当前面的数据都收到了之后,才会解除阻塞,否则会阻塞(后面的数据都收到了,前面的却没收到),并发送确认应答告知接收方哪一段数据没有收到。这样就能防止后发先至的情况造成数据混乱。

(二)超时重传

TCP的可靠性,是确认应答和超时重传来维持的。

发送方发送数据后,在传输过程中可能发送“丢包”的情况,这种情况下,接收方是收不到的。

发送方在接收方收到数据后,会收到ack应答报文,引入超时时间,如果超过这个时间仍然没有收到ack报文,就可以判定为丢包了,此时发送方就会重新发送该数据。

这里有两种情况:

  1. 接收方发送的数据丢失了
  2. 接收方发送的ack报文丢失了

对于这两种情况,解决办法都是超时重传,如果是第二种情况,接收方就会收到两个一模一样的数据。其实接收方的接收缓冲区有去重功能的,根据数据的序号,先在缓冲区里查找一下,如果存在就丢失这个数据,不存在就接收。

⭐️接收缓冲区的特性

  1. 自动排序
  2. 去重
  3. 如果没有收到连续的数据段,会进行阻塞,使接收方发送ack报文提醒发送方

此时缺少了1001~2000,接收方就会发送1001的ack报文,当1001~2000接收到之后,接收方就会发送4001的ack报文。

(三)连接管理

连接管理要从两个方面来:

  1. 建立连接
  2. 断开连接

⭐️建立连接,TCP是靠“三次握手”来实现的。

发送方发送一个不带业务的数据,通过这个数据与接收方“打个招呼”。

发送方告诉接收方:接下来我要和你建立连接,你要把我的关键信息(IP、端口号等)保存好,同时你也把你的信息发给我。

当标志位中的syn=1时,表示这是一个同步报文。

❓在三次握手的过程中,发挥了哪些作用呢

  1. 投石问路,确认通信路径是否通畅
  2. 验证发送方和接收方的发送和接受能力是否正常
  3. 协商一些关键数据,比如保存对方的IP和端口号,协商第一次通信的初始序号是多少(并且两次连接的初始序号往往差异很大,防止第二次连接处理了第一次发送的数据)

⭐️三次握手过程中,服务器的状态变化

CLOSED:是一种假想的情况,表示TCP还没有连接。

LISTEN:表示服务器已启动,执行accept方法,阻塞等待用户连接。

SYN_SENT:发送了syn报文。

SYN_RCVD:接收了syn报文。

ESTABLISHEED:表示客户端与服务器已经建立好连接了,可以开始传输数据了。

⭐️断开连接,TCP是靠“四次挥手”来实现的。

四次挥手,就是一方想要断开连接,发送fin报文告知对方,对方准备好断开连接时,也发送fin报文,从而正常地断开连接。

当标志位中的fin=1时,表示这是一个断开报文。

⭐️四次挥手过程中,服务器的状态变化

CLOSE_WAIT:接收方在收到fin请求后,就会出现这个状态,表示正在等待执行close操作。

TIME_WAIT:在四次挥手的过程中,也是可能发生丢包的,如果A收到fin后返回ack就立马关闭,ack在传输过程中可能会丢失,B就会超时重传。为了避免B额外的重传操作,让A延迟一段时间关闭,这段时间长度为2*MSL(网络上任何两个节点传输过程中消耗的最大时间)。

(四)滑动窗口

TCP在保证可靠性的前提下,会损失效率。

因此TCP会使用到批量传输:

⭐️批量传输的原理就是滑动窗口

这张图中,窗口中有四段数据,主机A将窗口内的数据一起发送出去。

主机A收到了B的2001应答报文,此时窗口向后滑动一个单位。

窗口越大,效率就越高,但是窗口的大小不能无限大,会影响可靠性。

  • 这里不考虑丢包的情况:

如果主机A先收到的是3001的ack报文呢,那此时窗口就往后滑动两位,因为返回的是3001,说明<3001的数据都已经接收到了,返不返回2001已经没关系了。

  • 考虑丢包的情况:

⭐️接收方返回的ack报文丢失

这种情况下,部分ack丢失了不要紧,因为可以通过后续的ack来确认。

⭐️发送方的数据包丢了

快重传机制:

  • 当某一段报文段丢失之后,发送端会一直收到1001这样的ACK,就像是在提醒发送端"我想要的是1001"一样;
  • 如果发送端主机连续三次收到了同样一个"1001"这样的应答,就会将对应的数据1001 - 2000重新发送; 
  • 这个时候接收端收到了1001之后,再次返回的ACK就是7001了(因为2001 - 7000)接收端其实之前就已经收到了,被放到了接收端操作系统内核的接收缓冲区中; 

快重传机制与超时重传并不矛盾:

当要发送的数据量大的时候——>使用滑动窗口——>快重传

数据量小的时候——>超时重传

(五)流量控制

滑动窗口能提高数据发送的效率,但是如果发送方的发送速度远远大于了接收方的接收速度,就会导致数据大量丢失。

想象接收方的接收缓冲区是一个蓄水池,发送方发送数据相当于往水池里面加水,接收方从缓冲区里读取数据,相当于给水池放水,当接水速度远大于放水速度,水就会溢出。

⭐️流量控制就是给对方踩刹车,让它发的慢点。

流量控制就是让接收方,根据自身处理数据的速度,反馈给发送方,来限制发送方的速度。

在ack报文中,有16位的窗口大小,接收方将缓冲区的剩余空间大小填入这个属性中,发送方就按照这个数字来重新设定滑动窗口大小。

16比特位表示的最大范围是64KB,但是滑动窗口的最大长度不是64KB,在报文中还有“选项”这个属性,里面可以调整窗口扩展因子,来扩大窗口最大长度。

⭐️窗口探测包

(六)拥塞控制

流量控制是根据接收方接收数据的能力来控制的。

拥塞控制是根据传输链路的转发能力来控制的,如果发送方发送的速率大于传输链路的转发速率,那么就会导致丢包。

⭐️少量的丢包,我们仅仅是触发超时重传;大量的丢包,我们就认为网络拥塞,这时候就执行拥塞控制。

⭐️如果不好衡量链路中某个设备的转发速率,可以把整个链路看做成一个整体,通过做实验的方式(慢启动机制)来调节窗口大小:

  • 先以小窗口来发送数据,如果很顺利、不丢包那就增大窗口的大小。
  • 如果出现了丢包,那就减小窗口大小。

流量控制和拥塞控制都能限制发送方发送数据的速率,哪个值小,窗口大小就以哪个值为准。

⭐️执行拥塞控制的具体情况:

此处引入一个概念称为拥塞窗口

  • 发送开始的时候,定义拥塞窗口大小为1;
  • 每次收到一个ACK应答,拥塞窗口加1;
  • 每次发送数据包的时候,将拥塞窗口和接收端主机反馈的窗口大小做比较,取较小的值作为实际发送的窗口;这么一来,相当于拥塞控制是包含了流量控制的。

像上面这样的拥塞窗口增长速度,是指数级别的。“慢启动”只是指初始时慢,但是增长速度非常快。
为了不增长的那么快,因此不能使拥塞窗口单纯的加倍。此处引入一个叫做慢启动的阈值:

  • 当拥塞窗口超过这个阈值的时候,不再按照指数方式增长,而是按照线性方式增长
  • 当TCP开始启动的时候,慢启动阈值等于接收方返回的窗口最大值;
  • 在大量收到3个重复的ack报文后,就执行超时重传,慢启动阈值会变成原来的一半,同时拥塞窗口置回1;(另一个策略是拥塞窗口从新的慢启动阈值开始)

(七)延迟应答

默认情况下,接收方收到数据后会立即返回一个ack报文。为了提高效率,返回ack的时机可以进行延迟。

⭐️延迟应答在于尽可能放大滑动窗口的大小,从而提高传输效率:

  • 如果接收数据的主机立刻返回ACK应答,这时候返回的窗口可能比较小。
  • 假设接收端缓冲区为1M。一次收到了500K的数据;如果立刻应答,返回的窗口就是500K;
  • 但实际上可能处理端处理的速度很快,10ms之内就把500K数据从缓冲区消费掉了;
  • 在这种情况下,接收端处理还远没有达到自己的极限,即使窗口再放大一些,也能处理过来;
  • 如果接收端稍微等一会再应答,比如等待200ms再应答,那么这个时候返回的窗口大小就是1M;

那也并不是每一个包都要执行延迟应答:

  • 数量限制: 每隔N个包就应答一次;
  • 时间限制: 超过最大延迟时间就应答一次;

(八)捎带应答

TCP已经有了延迟应答,基于延迟应答,引入了“捎带应答”,“捎带应答”就是将返回业务数据的时候,顺便把ack给带过去。

将两个报文合并成一个报文,提高了效率。

如果没有延迟应答,那么返回ack和响应的时机就是不一样的。

捎带应答将ack也代入到响应报文中,在响应报文的报头中ack设置为1、窗口大小设置为接收缓冲区剩余值、确认序号选择合适的序号。设置这些并不影响响应的数据。

(九)面向字节流

TCP在传输过程中,是以字节为单位的,这是具体的传输过程:

⭐️创建一个TCP的socket,同时在内核中创建一个 发送缓冲区 和一个 接收缓冲区;

  • 调用write时,数据会先写入发送缓冲区中;
  • 如果发送的字节数太长,会被拆分成多个TCP的数据包发出;
  • 如果发送的字节数太短,就会先在缓冲区里等待,等到缓冲区长度差不多了,或者其他合适的时机发送出去;
  • 接收数据的时候,数据也是从网卡驱动程序到达内核的接收缓冲区;
  • 然后应用程序可以调用read从接收缓冲区拿数据;
  • 另一方面,TCP的一个连接,既有发送缓冲区,也有接收缓冲区,那么对于这一个连接,既可以读数据,也可以写数据.这个概念叫做 全双工

由于缓冲区的存在,TCP程序的读和写不需要一一匹配,例如:

  • 写100个字节数据时,可以调用一次write写100个字节,也可以调用100次write,每次写一个字节;
  • 读100个字节数据时,也完全不需要考虑写的时候是怎么写的,既可以一次read 100个字节,也可以一次read一个字节,重复100次;

⭐️上面TCP传输的流程,会出现“粘包问题”:

  • 首先要明确,粘包问题中的 "包", 是指的应用层的数据包.
  • 在 TCP 的协议头中,没有如同 UDP 一样的 "报文长度" 这样的字段,但是有一个序号这样的字段.
  • 站在传输层的角度,TCP 是一个一个报文过来的。按照序号排好序放在缓冲区中.
  • 站在应用层的角度,看到的只是一串连续的字节数据.
  • 那么应用程序看到了这么一连串的字节数据,就不知道从哪个部分开始到哪个部分,是一个完整的应用层数据包.

❓那么如何避免粘包问题呢?归根结底就是一句话,明确两个包之间的边界.

  • 对于定长的包,保证每次都按固定大小读取即可;例如上面的 Request 结构,是固定大小的,那么就从缓冲区从头开始按 sizeof (Request) 依次读取即可;
  • 对于变长的包,可以在包头的位置,约定一个包总长度的字段,从而就知道了包的结束位置;
  • 对于变长的包,还可以在包和包之间使用明确的分隔符 (应用层协议,是程序猿自己来定的,只要保证分隔符不和正文冲突即可);

HTTP是基于TCP实现的,解决粘包问题,在HTTP中也有体现到:

  • GET请求:没有body,报头以空行符作为结束标记。
  • POST请求:有body,但报头中有Content_Length来标明body长度。

(十)异常情况的处理

TCP进行数据传输的时候,通信双方可能都会出现异常,分为大致两种情况:

  • 进程崩溃或主机关机重启

进程崩溃和主动退出连接没有本质区别,进程结束会释放文件描述符表,表中的资源都会调用socket中的close方法,完成四次挥手。

❓要是挥手过程中,接收方发来的fin太迟了,发送方已经提前关闭了呢?

  • 主机掉电或者网线断开

⭐️接收方掉电

此时发送方不会收到接收方的ack报文,就会触发超时重传。

这种情况超时重传并不能解决问题,当发送方超时重传一定次数后,会发送一次“复位报文”触发“重置TCP连接”。

当rst=1时,表示这个报文是复位报文。

如果发送的复位报文依旧没有收到ack应答,发送方就单方面删除对方的数据,断开连接了。

⭐️发送方掉电

接收方会发现,发送方不再发送数据了,不知道对方是掉电了还是在休息。

等待一段时间后,接收方会给发送方发送一个“心跳包”,心跳包不携带业务数据,就是为了测试是否能得到对方的ack应答。

  • 如果对方返回ack报文,说明有心跳,就继续等待。
  • 如果对方没有心跳,就发送一次rst复位报文,还是不行就单方面断开连接。

四、TCP协议段格式中的其他字段

  • URG:紧急指针位,TCP正常来说是按照序号顺序来发送和接收的,紧急指针相当于“插队”。
  • 16位紧急指针:跳过前面的数据,直接从某个指定的序号开始read。
  • PSH:催促标志位,发送方给接收方发的数据中带有这个标志位,接收方就能尽快将这个数据read到应用程序中。

五、TCP和UDP对比

我们说了 TCP 是可靠连接,那么是不是 TCP 一定就优于 UDP 呢?TCP 和 UDP 之间的优点和缺点,不能简单,绝对的进行比较:

  • TCP 用于可靠传输的情况,应用于文件传输,重要状态更新等场景;
  • UDP 用于对高速传输和实时性要求较高的通信领域,例如,早期的 QQ, 视频传输等。另外 UDP 可以用于广播;

归根结底,TCP 和 UDP 都是程序员的工具,什么时机用,具体怎么用,还是要根据具体的需求场景去判定。

http://www.lryc.cn/news/625823.html

相关文章:

  • 如何理解关系型数据库的ACID?
  • 【集合框架LinkedList底层添加元素机制】
  • ⭐CVPR2025 建模部件级动态的 4D 重建框架
  • 数据安全治理——解读67页2024金融数据安全治理白皮书【附全文阅读】
  • 路由器详解
  • Java JDK官网下载渠道
  • 使用 Ansys Discovery 探索外部空气动力学
  • 《算法导论》第 32 章 - 字符串匹配
  • 【深度学习计算性能】06:多GPU的简洁实现
  • 接口性能测试工具 - JMeter
  • JB4-9-任务调度
  • 《飞算Java AI使用教程:从安装入门到实践项目》
  • 12.3.2设置背景色12.3.3 创建设置类12.4 添加飞船图像 12.4.1 创建Ship 类 12.4.2 在屏幕上绘制飞船
  • 用MacBook进行LLM简单人类指令微调
  • 蓝凌EKP产品:JSP 项目性能基于业务维度的 JS 压缩合并方案优化实战
  • 供水设备智慧化管理物联网解决方案:远程监控与运维
  • 操作系统:多线程、进程管理、内存分配、任务调度等
  • IC验证 AHB-RAM 项目(二)——接口与事务代码的编写
  • 比赛准备之环境配置
  • Nginx前后端分离反代(VUE+FastAPI)
  • 卫生许可证识别技术:通过OCR与NLP实现高效合规管理,提升审核准确性与效率
  • Apache IoTDB 大版本升级记录(成熟的2.0.2版本)
  • 汇编语言学习2---GNU Debugger (GDB)
  • PiscCode迅速集成YOLO-Pose 实现姿态关键点轨迹跟踪应用
  • 疏老师-python训练营-Day50预训练模型+CBAM注意力
  • PHP如何使用JpGraph生成折线图?
  • NVIDIA 优化框架:Jetson 平台 PyTorch 安装指南
  • vue,H5车牌弹框定制键盘包括新能源车牌
  • 楼宇自控系统的应用,已然成为智能建筑行业发展方向
  • 【网络运维】Playbook部署文件:Files模块库&JINJA2模板