网络中数据传输的具体过程

第一阶段:源主机内部的封装与决策

数据只要还在你的电脑里,它主要发生两件事:封装决策

1. 应用层

一切始于代码。

  • 代码行为: 你创建了一个 Socket,并执行了 send(sockfd, "Hello", 5, 0);
  • 数据形态: 此时数据仅仅是内存里的 5 个字节:H-e-l-l-o
  • 深层动作 (System Call):
    • 应用层位于用户态
    • 当你调用 send 时,发生了一次系统调用 。CPU 从用户态切换到内核态
    • 操作系统内核会将这 5 个字节从你的程序内存缓冲区,拷贝 到内核网络协议栈的发送缓冲区中。
    • 注意: 此时还没有任何网络报头,只是纯粹的有效载荷 (Payload) 。
2. 传输层

数据进入内核后,首先到达传输层(假设使用 TCP 协议)。

  • 核心任务: 识别"是谁发的"以及"发给谁(哪个进程)"。
  • 封装动作: 内核在 "Hello" 前面加了一个 TCP 报头 (TCP Header)
    • 关键字段:
      • 源端口 (Source Port): 54321(系统随机分配给你的客户端程序的)。
      • 目的端口 (Destination Port): 80(你在代码里指定的服务器端口)。
      • 序列号 (Sequence Number): 用于保证数据可靠到达,不丢包、不乱序。

数据形态变化: 此时这坨数据被称为 TCP 段 (Segment)

复制代码
[TCP报头 | Hello]
3. 网络层

TCP 段继续向下传递,来到 IP 层。这里发生了极其重要的一步,很多人容易忽略。

  • 核心任务: 确定"终点"在哪里,并决定"第一步"往哪走。
  • 封装动作: 内核在 TCP 段前面加了一个 IP 报头 (IP Header)
    • 关键字段:
      • 源 IP (Src IP): 192.168.1.5 (主机A自己)。
      • 目的 IP (Dst IP): 1.2.3.4 (主机B)。
      • 协议号 (Protocol): 6

数据形态变化: 此时数据被称为 IP 数据报 (Packet/Datagram)

复制代码
[IP报头 | TCP报头 | Hello]
  • 🔴 核心讲解:路由决策 (Routing Decision) 在封装完 IP 头之后,主机A必须思考一个问题:"我要去的主机B (1.2.3.4),是在我的局域网里,还是在外面?"

主机A会查自己的路由表 (Routing Table)(没错,普通电脑里也有路由表):

    1. 主机A 用自己的子网掩码 (如 255.255.255.0) 计算 1.2.3.4
    2. 判断: 发现目标网络 1.2.3.0 不等于自己的网络 192.168.1.0
    3. 结论: 目标在外网
    4. 决策: 既然在外网,我不能直接发给它,我必须发给我的默认网关 (Gateway) ,也就是路由器 192.168.1.1

关键点: 此时,IP 层的"下一跳 " IP 地址被确定为 192.168.1.1

4. 数据链路层

数据包准备离开网卡,进入网线。这时需要加上 MAC 地址。

  • 核心任务: 解决"下一跳 IP 对应的硬件是谁"的问题。

  • 遇到的问题: IP 层告诉链路层:"把这个包送给下一跳 192.168.1.1"

  • 但是以太网卡听不懂 IP,它只认 MAC 地址 。 此时,需要查 ARP 缓存表

  • 🔴 核心讲解:ARP 解析过程 主机A 检查自己的 ARP 表:

    • 场景一(表里有): 找到了 192.168.1.1 对应的 MAC 是 RR:RR:RR:RR:RR:RR。直接使用。
    • 场景二(表里没有):
      1. 主机A 暂停发送数据。
      2. 主机A 发送一个 ARP 请求 (Broadcast) :"谁是 192.168.1.1?请告诉我你的 MAC!"
      3. 局域网内的路由器收到后,回复 ARP 应答 :"我是 192.168.1.1,我的 MAC 是 RR:RR:RR:RR:RR:RR"。
      4. 主机A 将这对映射写入 ARP 表。
  • 封装动作: 拿到 MAC 地址后,内核在 IP 包前后加上 以太网帧头 (Ethernet Header)帧尾 (FCS)
    • 关键字段:
      • 目的 MAC (Dst MAC): RR:RR:RR:RR:RR:RR
      • 源 MAC (Src MAC): AA:AA:AA:AA:AA:AA (主机A自己)。
      • 帧类型: 0x0800 (代表里面装的是 IPv4 数据)。

数据形态变化: 此时数据被称为 以太网帧 (Ethernet Frame)

复制代码
[以太网头(Dst:Router, Src:HostA) | IP头(Dst:HostB) | TCP头 | Hello | 帧尾]
5. 物理层
  • 动作: 网卡 (NIC) 驱动程序将这一长串二进制数字(帧),通过数模转换,变成网线上的电压高低变化(电信号)或者光纤里的光亮灭(光信号)。
  • 结果: 信号顺着网线,冲向了路由器。

第二阶段:路由器内部的中转与重封装

1. 物理接收与链路层校验

当电信号到达路由器的网卡接口时,网卡芯片会首先还原出二进制数据。

  • 身份核对(MAC 过滤):

路由器网卡检查帧头里的 目的 MAC 地址。

    • 发现写的是 RR:RR:RR:RR:RR:RR(路由器自己的 MAC)。
    • 判定: "这是给我的信。"
    • 动作: 接收 (Accept) 并触发中断,通知 CPU 处理。如果是发给别人的(MAC 不匹配),直接丢弃。
  • 拆包 (Decapsulation) ------ 第一次手术:

路由器将以太网的 帧头 和 帧尾 全部剥离/撕掉 1。

    • 结果: 此时,原本的 MAC 地址信息(主机A 和 路由器入接口的 MAC)彻底消失了。
    • 保留: 剩下的部分是 IP 数据报(包含 IP 头 + TCP 头 + Data)。
2. 网络层路由决策

现在,路由器手里拿着这个裸露的 IP 数据报。它需要决定下一步怎么走。

  • 读取 IP 头:路由器提取出 目的 IP 地址:1.2.3.4。
  • 查路由表 :路由器搜索自己的路由表,寻找匹配 1.2.3.4 的条目。
    • 路由表逻辑: "要去 1.2.3.4,请走 WAN 接口(出口),下一跳网关是 ISP(运营商)的路由器 202.10.1.1。"
3. 构建新的链路层封装

路由器已经决定把包从 WAN 口扔出去,发给 ISP 路由器(IP: 202.10.1.1)。

但是,WAN 口连接的是另一段物理链路(可能是光纤或另一根网线),数据要发出去,必须再次封装成 帧。这就需要新的 MAC 地址。

  • 查 ARP 表 (ARP Cache Lookup):

路由器查询自己的 ARP 表(针对 WAN 口的):

    • 问: "下一跳 202.10.1.1 的 MAC 是什么?"
    • 答: "是 ISP 路由器的 MAC II:II:II:II:II:II。"
    • (如果没查到,路由器会在 WAN 口发起 ARP 请求,就像第一阶段主机A做的那样)
  • 重新封装 (Re-encapsulation):

路由器给 IP 数据报穿上一件全新的"衣服"(新的帧头):

    • 新的 源 MAC (Src MAC): Router_WAN_MAC(路由器出口的身份证)。
    • 新的 目的 MAC (Dst MAC): ISP_Router_MAC下一跳设备的身份证)。

注意: 此时的 MAC 地址已经完全变了!旧的(主机A -> 路由器内网口)已经被扔掉了。

4. 物理发送

路由器将这个崭新的帧,推送到 WAN 口的发送队列,转换成信号发往互联网。


总结:数据包的"变"与"不变"

这是理解网络传输最关键的对照表,请务必仔细对比:

|------------|------------------|--------------------|-------------------------------------|---------------|
| 字段 | 在主机A发出时 | 在路由器转发出去时 | 变化情况 | 原因 |
| 源 IP | 192.168.1.5 | 192.168.1.5 | 不变 | 我是谁(发件人)不能变 3 |
| 目的 IP | 1.2.3.4 | 1.2.3.4 | 不变 | 我去哪(收件人)不能变 4 |
| 源 MAC | HostA_MAC | Router_WAN_MAC | <font color="red">变了</font> | 这一程的起点变了 5 |
| 目的 MAC | Router_LAN_MAC | ISP_Router_MAC | <font color="red">变了</font> | 这一程的终点变了 6 |
| TTL | 64 (假设) | 63 | 变了 | 每过一关脱一层皮 |

第四阶段:从"目标路由器"到"目标主机"

1. 路由器接收与查表

路由器从 WAN 口收到数据包,拆掉外网的帧头,露出 IP 包。

  • 读取目的 IP: 1.2.3.4
  • 查路由表: 路由器发现:1.2.3.4 这个 IP 属于 1.2.3.0/24 网段。
    • 关键发现: 路由表显示,1.2.3.0/24 网段是 直连 (Directly Connected) 在我的 LAN 接口上的。
    • 决策: "目标就在我的眼皮子底下(内网里),我不需要再转发给其他路由器了,我要直接交给它!"
2. 关键的地址解析 (ARP again)

路由器现在知道要给 1.2.3.4,但它需要知道 1.2.3.4MAC 地址 才能在局域网里发帧。

  • 查 ARP 缓存表 (路由器内部):
    • 问: "我的内网里,谁是 1.2.3.4?"
    • 场景 A (表中已有): 找到了!对应 MAC 是 BB:BB:BB:BB:BB:BB
    • 场景 B (表中没有):
      1. 路由器在 LAN 口发起 ARP 广播:"Who has 1.2.3.4? Tell 1.2.3.1"
      2. 局域网里的所有机器都收到广播。
      3. 主机B 惊醒:"是我!"
      4. 主机B 回复单播:"我是 1.2.3.4,我的 MAC 是 BB:BB..."。
      5. 路由器将这条记录写入 ARP 表。
3. 最后的封装 (Final Encapsulation)

拿到主机B 的 MAC 后,路由器进行最后一次换头手术。它构建一个新的以太网帧:

  • 新的 源 MAC (Src MAC): GG:GG:GG:GG:GG:GG (路由器的 LAN 口 MAC)。
  • 新的 目的 MAC (Dst MAC): BB:BB:BB:BB:BB:BB (主机B 的 MAC)。
  • 有效载荷: 依然是那个原封不动的 IP 数据报(Src: 192.168.1.5, Dst: 1.2.3.4)。
4. 局域网传输 (Switching)

路由器通过 LAN 口将这个帧发送出去。 如果路由器和主机B之间还连着一个 交换机 (Switch)

  1. 交换机收到帧,看一眼目的 MAC (BB:BB...)。
  2. 交换机查自己的 MAC 地址表 ,知道 BB:BB... 在哪个物理端口(比如 Port 3)。
  3. 交换机把帧精准地从 Port 3 弹送给 主机B。

第四阶段:终点站的接收与分用

数据包(电信号)撞击到主机B的网卡。

  • MAC 地址校验: 网卡芯片检查帧头的 目的 MAC
    • 这一次,目的 MAC 终于写的是 主机B 自己的 MAC (例如 BB:BB:BB:BB:BB:BB)。
    • 动作: 网卡接收数据,产生硬中断,告诉 CPU:"有货到了,快来处理!"
  • 拆包与分发: 操作系统内核(驱动程序)接管数据,剥掉以太网的帧头和帧尾。
    • 关键问题: 剩下的数据给谁?
    • 查看帧头字段: 帧头里有一个 Frame Type(帧类型)字段,写着 0x0800(IPv4)。
    • 分用决策: 内核知道,要把剩下的 payload 交给 网络层 (IP 协议栈) 处理 。
2. 网络层 ------ 身份确认

现在,IP 协议栈拿到了数据包。

  • IP 地址校验: 内核检查 IP 头里的 目的 IP
    • 发现写的是 1.2.3.4,正是 本机 IP
    • 判定: "这是给我的,不用再转发了。"
    • (注:如果目的 IP 不是本机,且本机没开启路由转发功能,内核会直接丢弃并可能发送 ICMP 报错)
  • 拆包与分发: 内核剥掉 IP 报头。
    • 关键问题: 剩下的数据给 TCP 还是 UDP?
    • 查看 IP 头字段: IP 头里有一个 Protocol(协议号)字段,写着 6(TCP)。
    • 分用决策: 内核知道,要把剩下的 payload 交给 传输层 (TCP 协议栈) 处理 。
3. 传输层 ------ 寻找接收人

这是最关键的一步,对应你关心的 Socket端口 。 TCP 协议栈拿到了 TCP 段

  • 端口映射 (Port Mapping): 内核检查 TCP 头里的 目的端口 (Destination Port) ,发现是 80
  • 查找 Socket 表: 操作系统内核里维护着一张 Open File Table 或者专门的 Socket 哈希表。
    • 内核查询:"当前有没有哪个进程,正在监听 (Listen) 或者绑定 (Bind) 了 80 端口?"
    • 结果: 找到了!有一个 Web 服务器进程(比如 Nginx 或你自己写的 C++ Server),它的 Socket 对应着端口 80。
  • 数据放入缓冲区: 内核剥掉 TCP 报头,将剩下的 有效载荷 ("Hello") ,复制到该 Socket 对应的 接收缓冲区 (Receive Buffer) 中 。
    • 同时,TCP 协议栈会根据 TCP 头里的序列号,回复一个 ACK 确认包 给主机A(表示"我收到了,请放心"),但这属于幕后工作,应用程序不知道。
4. 应用层 ------ 终于见面

数据现在已经在操作系统内核的内存里躺好了,就等应用程序来拿。

  • 唤醒进程: 如果你的服务器程序正在调用 recv()read() 并且因为没数据而阻塞(睡眠),此时内核会把它唤醒
  • 数据拷贝: recv() 函数返回,数据从 内核空间 (Kernel Space) 的接收缓冲区,被拷贝到 用户空间 (User Space) 的 buffer 里(比如你代码里的 char buf[1024])。
  • 处理数据: 你的代码终于打印出了:"Received: Hello"
相关推荐
charlie1145141912 小时前
现代C++嵌入式教程:C++98基础特性:从C到C++的演进(1)
c语言·开发语言·c++·笔记·学习·教程
世转神风-2 小时前
linux使用终端打开当前文件夹界面
linux
汤愈韬3 小时前
TK_网络基础和常见攻击(笔记)
网络·笔记
刘某的Cloud3 小时前
列表、元组、字典、集合-组合数据类型
linux·开发语言·python
学烹饪的小胡桃3 小时前
【运维学习】实时性能监控工具 WGCLOUD v3.6.2 更新介绍
linux·运维·服务器·学习·工单系统
nnsix3 小时前
QFramework学习笔记
笔记·学习
XFF不秃头4 小时前
力扣刷题笔记-全排列
c++·笔记·算法·leetcode
北邮刘老师4 小时前
【智能体互联协议解析】需要“智能体名字系统”(ANS)吗?
网络·人工智能·大模型·智能体·智能体互联网
知识分享小能手4 小时前
Ubuntu入门学习教程,从入门到精通,Ubuntu 22.04的桌面环境 (4)
linux·学习·ubuntu