自顶向下 | 带你遨游运输层
前言#
本文已经收录到我的
Github
个人博客,欢迎大佬们光临寒舍:
学习导图:#
一.运输层概述#
我们知道运输层位于网络层之上,网络层提供了主机之间的逻辑通道。
Q1:那既然已经把一个数据包从一个主机发到另一个主机上面了,为什么还需要运输层呢?
A1:这是因为运输层提供了应用进程之间的端-端连接。
Q2:我们知道一个电脑可能有多个进程同时在使用网络连接,那么网络包到达主机之后,怎么区分自己属于那个进程?
So,如果你对运输层感兴趣的话,可以跟笔者一起来简要了解下运输层,了解下
UDP
和TCP
两大面试常客
- 运输层为运行在不同主机上的应用程序之间提供逻辑通信
- 应用报文加上运输层首部形成运输层报文段,报文段通过网络层被封装成网络层分组(数据报)向目的地发送
Q1:运输层和网络层的关系
- 运输层:运行在不同主机上的应用程序之间提供逻辑通信
- 网络层:提供主机之间的通信
举个例子来说明两者关系:
有两个家庭,一家位于广州,一家位于北京,每家有 3个孩子。这两个家庭的孩子们喜欢彼此通信,每封信都用单独的信封通过传统的邮政服务发送。每个家庭有一个孩子负责收发邮件,北京家庭是 阿京,而广州家庭是 阿州。每周阿京去她所有的兄弟姐妹那里收集邮件,并将这些邮件交到邮递员处上。当信件到达北京家庭时,阿京也负责将信件发到她的兄弟姐妹手上,广州家庭中 阿州也负责类似工作
- 网络层——邮递员
- 运输层——阿京和阿州
- 应用程序——兄弟姐妹
- 主机——两个家庭
通过运输层协议,两台电脑仿佛直接相连一样。应用无需知道底层内部实现的原理和细节,比如怎么把远隔世界两地电脑上的数据进行相互传输
Q2:注意点
- 运输层协议仅仅实现在网络的边缘处,例如主机,电脑,手机等。如路由器,交换机这些网络核心设备,是没有实现运输层协议的
- 每一层协议仅仅检查分组相应的协议层字段
- 最常用的两种输入层协议,
TCP
和UDP
- 运输层的下面是网络层,网络层的目的在于为不同的主机提供逻辑通信,而非主机上的进程
- 网络层常用协议是
IP
,其提供的是一种不可靠的数据传输服务 - 为了做到为不同主机 (
host
) 上的应用或者说进程提供逻辑通信这一目的,运输层协议必须能分辨出数据的来源和去向。为此,运输层存在着两种行为,多路复用和多路分解
二.多路复用与多路分解#
- 多路复用:当运输层收到来自上方应用层的数据时,运输层会为数据封上一些头部信息,根据所有协议不同,封上的信息也不一样
用上面的两个家庭的例子进行形象化地阐述就是:多路复用就是阿州和阿京将兄弟姐妹的信一起交给邮递员
- 多路分解:当运输层收到下方网络层传输来的数据时,运输层会检查多路复用时封上的信息,从而正确的把数据定向到相应的进程
Q1:如何使用运输层的协议?
操作系统提供了被称为 Socket
的接口 API
供编程人员调用,对 Socket
的形象理解是其是一种抽象,将复杂的实现 (TCP/UDP
) 协议的各种行为抽形成简单的几个函数给开发人员使用。就像浏览器将发送请求报文这一 Http
协议规定的行为,抽象成我们只需要输入 Url
然后回车即可
这里需要注意的一点是:
- 在一般情况下,一个计算机端口只能被一个进程占用
- 一个进程可以创建多个
Socket
,多个TCP
Socket
可以监听同一个端口,并保证接受的数据依旧是正确的 - 多个
UDP Socket
就无法监听同一端口,这其中的差异源于TCP
和UDP
协议的不同
TCP
是面向连接的,其有足够状态的信息来分辨数据来源,后定向到正确的Socket
UDP
不需要维持连接,仅仅通过端口号来决定数据的去向,所以会导致冲突
三.UDP
和TCP
的多路复用和分解#
Q1:UDP
的多路复用和分解
一个 UDP Socket
通过一个二元组 (目的 IP
地址,目的端口号) 来标识,当输入层收到数据时,通过检查这个二元组,来定向数据该去往哪一个 UDP Socket
。这也是多个 UDP Socket
无法监听同一个端口的原因
Q2:TCP
的多路复用分解
一个 TCP Socket
通过一个四元组 (源 IP
,源端口,目的 IP
,目的端口号) 来标识,这也解释为什么多个 TCP Socket
可以监听同一个端口,尽管目的 IP
和目的端口号是一样的,但是源 IP
和源端口的组合总是不同的
四.UDP
#
4.1 UDP
基本概念#
相比于 TCP
来讲,UDP
是一个简单的协议,就是把网络层 IP
提供的服务封装了下,实现了多路复用和分解,提供了端到端进程间的通信和错误检验服务
相对于 TCP
来说:
缺点:
UDP
是不可靠的传输服务- 没有流量和拥塞控制
优点:
- 能够够精细的控制数据的发送时间和速率
- 无需事先建立连接
- 无连接状态
- 分组首部开销小
UDP
报文端结构:
- 源端口号:发送方的端口号
- 目的端口号:接收方端口号
- 长度:包括首部在内的报文长度
- 检验和:用来差错检验。只发现错误不纠正,错了就扔。然后重发
4.2 可靠数据传输原理#
虽然
UDP
并不可靠,但是我们要分析其为啥不可靠,从而引出可靠数据传输原理,有利于理解下文对TCP
协议的可靠数据传输的机制
Q1:数据传输可能遇到的问题:
- 传输中数据被损坏
- 数据丢失
- 数据可能乱序到达
Q2:解决方法:
- 检验和
- 序号
- 定时器
- 肯定和否定反馈分组
- 重传
Q3:如何在保证可靠性的前提下,提高其性能?
A.通过引入流水线 (pipelining
) 技术
引入流水线导致了:
- 序号范围需要增加
- 收发双方可能需要缓存乱序到达的分组
- 以上两个的具体实现取决于传输协议如何处理分组丢失、损坏的问题 (是选择回退
N
步,还是选择重传)
B.通过缓存处理
读者如果对详细的缓存机制感兴趣的话,可以看下这篇文章:HTTP----HTTP缓存机制
- 缓存处理的规则:分为强制缓存和协商缓存
- 缓存的优点:
- 减少了冗余的数据传递,节省宽带流量
- 减少了服务器的负担,大大提高了网站性能
- 加快了客户端加载网页的速度 这也正是
HTTP
缓存属于客户端缓存的原因
Q4:如何处理分组丢失、损坏的问题
A.回退 N
步
-
其核心在于,发送方会维持一个窗口,发送方能发送的数据量取决于窗口长度,并且当丢失时会重送所有未确认的分组
-
接收方会丢弃乱序到达的缓存
-
特点:
1.累计性
ACK
2.单一定时器
B.选择重传
-
核心在于,收发双方都会维持一个窗口,并且尽力保证窗口的状态是同步的,因此当分包丢失时,发送方只会重送丢失的分组
-
接收方会缓存乱序到达的分组
-
特点:
1.独立性
ACK
2.多个定时器
五.TCP
#
5.1 TCP
基本概念#
A.特点:
- 面向连接
- 全双工的
- 点对点,不存在一次发送将数据传递给多个接收方、
- 在合适的时候发送 发送缓存 里的数据
- 为每个数据封上一个
TCP
头部 TCP
连接的每一端都具有发送缓存和接受缓存
B.报文段结构
部分参数解释:
-
序号 (
seq
) :所带数据的第一个比特的序号,同时也是接收方期待的序号,等于接收方回复报文中的ACK
(n) 中的 n -
确认号 (
ack
) : 对于一个ACK
(n) 来说,告诉对方 n-1 前的数据已经收到,下一次期待的序列号为 n -
ACK
:指示,用于指示报文中确认号字段的值是有效的 -
PSH
:指示,立即发送_发送缓存_里的数据 -
RST
:指示,用于强制关闭连接 -
SYN
: 指示,用于握手阶段也就是建立连接的阶段 -
FIN
:指示,用于正常关闭连接 -
接受窗口 :用于
TCP
的流量控制功能
5.2 可靠数据传输#
-
TCP
协议为数据的每一Byte
都编号,而非针对报文段 -
总是维持最老未经确认的 1
Byte
的计时器 -
每一次超时重置的计时器时间会加倍
-
其错误恢复机制是回退 N 步和选择重传的混合体
- 不会丢弃乱序到达的分组,而是缓存起来(选择重传的特性)
- 采用累计性
ACK
(回退N
步的特性)- 只会重传丢失报文段中的数据(选择重传的特性)
- 快速重传:当接收到连续的三个重复冗余
ACK
(其实是收到四个同样的ACK
,第一个是正常的,后三个才是冗余的),会触发快速重传,立即重发分组
为什么是三个重复冗余
ACK
呢?答案:把三次冗余
ACK
作为判定丢失的准则其本身就是概率估计值所得出的(换句话说,出现三次冗余ACK
大概率是分组丢失)对详细的探究过程感兴趣的读者,可以看下这篇文章:TCP的快速重传机制
5.3 流量控制#
- 为了防止过高数据流量导致接收者的接受缓存爆掉,接收者会在其
TCP
报文中通过 接受窗口 指示发送者还能发送多少数据
接受窗口 (rwnd
) 公式:
rwnd = RcvBuffer - [LastByteRead - LastbyteRead]
- 且:
LastByteSent - LastByteAcked <= rwnd
5.4 TCP
连接管理#
Q1:建立连接(三次握手)
注意:
Server
端的ack
和data
是一起发送的
- 客户端发送
SYN
位置 1 的报文段 - 服务端返回
SYN
为 1,ACK
为 1 的报文段 - 客户端发送
ACK
为 1,且附带数据的报文段
形象化地理解:
TCP 三次握手
就好比两个人在街上隔着50米看见了对方,但是因为雾霾等原因不能100%确认,所以要通过招手的方式相互确定对方是否认识自己。张三首先向李四招手(
syn
),李四看到张三向自己招手后,向对方点了点头挤出了一个微笑(ack
)。张三看到李四微笑后确认了李四成功辨认出了自己(进入estalished
状态)但是李四还有点狐疑,向四周看了一看,有没有可能张三是在看别人呢,他也需要确认一下。所以李四也向张三招了招手(
syn
),张三看到李四向自己招手后知道对方是在寻求自己的确认,于是也点了点头挤出了微笑(ack
),李四看到对方的微笑后确认了张三就是在向自己打招呼(进入established
状态)。于是两人加快步伐,走到了一起,相互拥抱
Q2:断开连接(四次挥手)
- 客户发送
FIN
为 1 的报文段 - 服务端返回
ACK
为 1 的报文段 - 服务端发送
FIN
为 1 的报文段 - 客户端返回
ACK
为 1 的报文段 - 客户端在一段时间后,关闭连接
形象化地理解:
张三挥手(
fin
)——李四伤感地微笑(ack
)——李四挥手(fin
)——张三伤感地微笑
六.拥塞控制#
Q1:拥塞的代价
- 导致分组过长的排队时延
- 需要重传因缓存溢出丢失的分组
- 高延时导致重送分组
- 丢包导致运输相关分组的分组交换器所作的工作全部白费
Q2:TCP
的拥塞控制
-
TCP
采用端到端的拥塞控制 -
三个主要问题:
- 一个
TCP
的发送方如何限制自己的发送流量的速率?
通过设置一个拥塞窗口 (cwnd
), 并且保证:LastByteSent - LastByteAcked <= min{cwnd, rwnd}
- 如何感知其发送路径拥塞了?
timeout
- 收到一次正常
ACK
后连续收到三次冗余ACK
- 感到拥塞时,采用什么样的算法改变发送速率?
- 慢启动
cwnd
的值从 1MSS
开始,并且对每一个ACK
,cwnd
值变为原来的 2 倍,直到超过阈值 (ssthresh
),转为拥塞避免模式
- 拥塞避免
在每一个
RRT
时间,cwnd
的值增加一个MSS
- 快速恢复
cwnd
的值降为一半加上重复收到的重复ACK
的数量,并且每一个ACK
,cwnd
的值增加一个MSS
在实践中,一旦
timeout
就会会到慢启动的状态,多次重复ACK
则会进入快速恢复状态
Q3:TCP
公平
TCP
的公平性在于保证每个连接的吞吐量是平均的,而不是应用或进程间
七.再谈握手和挥手#
7.1 为啥一定要三次握手,两次不行吗?#
弄清这个问题,我们需要先弄明白三次握手的目的是什么,能不能只用两次握手来达到同样的目的。
- 第一次握手:客户端发送网络包,服务端收到了。 这样服务端就能得出结论:客户端的发送能力、服务端的接收能力是正常的。
- 第二次握手:服务端发包,客户端收到了。 这样客户端就能得出结论:服务端的接收、发送能力,客户端的接收、发送能力是正常的。不过此时服务器并不能确认客户端的接收能力是否正常。
- 第三次握手:客户端发包,服务端收到了。 这样服务端就能得出结论:客户端的接收、发送能力正常,服务器自己的发送、接收能力也正常。
因此,需要三次握手才能双方确认双方的接收与发送能力是否正常
如果将发送比喻为挥手,接收比喻为视力正常的话,就有下面的例子
试想如果是用两次握手,可能会出现下面这种情况:
如客户端发出连接请求,但因连接请求报文丢失而未收到确认,于是客户端再重传一次连接请求。后来收到了确认,建立了连接。数据传输完毕后,就释放了连接,客户端共发出了两个连接请求报文段,其中第一个丢失,第二个到达了服务端,但是第一个丢失的报文段只是在某些网络结点长时间滞留了,延误到连接释放以后的某个时间才到达服务端,此时服务端误认为客户端又发出一次新的连接请求,于是就向客户端发出确认报文段,同意建立连接,不采用三次握手,只要服务端发出确认,就建立新的连接了,此时客户端忽略服务端发来的确认,也不发送数据,则服务端一直等待客户端发送数据,浪费资源
7.2 为啥挥手要四次?#
这是因为服务端在LISTEN
状态下,收到建立连接请求的SYN
报文后,把ACK
和SYN
放在一个报文里发送给客户端。而关闭连接时,当收到对方的FIN
报文时,仅仅表示对方不再发送数据了但是还能接收数据,所以服务端可以立即close
,也可以发送一些数据给客户端后,再发送FIN
报文给客户端来表示同意现在关闭连接,因此,服务端ACK
和FIN
一般都会分开发送。
八.碎碎念#
恭喜你,成功看到了这里,现在的你,想必已经对运输层有自己的体会了;其实,运输层的知识点远不止这些,限于篇幅,把所有运输层的知识全部塞进这篇文章中,也不是特别现实;我们可以尝试看多几本相关的书籍,从而丰富自己的知识体系,从而得到更深的见解。
笔者也会不断学习,不断丰富自己,到时候(待老师讲解到这里了/看了别的书)如果有比较好的观点,本文也会持续改进更新,感谢您的支持。
最后笔者夹带一下私货,附上比较完整的计网面试题:计算机网络太难?了解这一篇就够了
如果文章对您有一点帮助的话,希望您能点一下赞,您的点赞,是我前进的动力
本文参考链接:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 周边上新:园子的第一款马克杯温暖上架
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?
· 使用C#创建一个MCP客户端