协议为什么存在:协议栈、IP 与 MAC、缓冲区里的生产者消费者

我的github:(https://github.com/xcx55/ubuntu-linux-project)
感谢各位大佬参观我的github!!

源笔记:http3(26-9-10)

学 HTTP 学到一半,这篇笔记突然掉头往下挖:为什么需要协议?协议栈为什么叫"栈"?IP 和 MAC 各管什么?发送缓冲区为什么是生产者消费者模型? 这些问题平时不敢问,手搓过协议之后终于有底气问了。

一、为什么需要协议

反着问:计算机之间不约定行不行?——不行。

  • 计算机本身就是一种协议、一种约定——CPU 的指令集、总线的时序、文件的格式,全是约定;
  • 具体到通信:发给谁?保证传输控制特性等等!——传输需要协议,处理数据也需要协议;
  • 还有一个精妙的说法:消息是临界资源。缓冲区里的数据被两个"人"(用户和内核)共同读写,没有约定就会乱套。

而手搓过协议之后,你对协议的描述会变得更精确:协议 = 双方都认的报文格式 + 解析规则 + 行为约定。它不再是教科书上一句空话,而是你亲手写过的 len\r\n value \r\n。

二、协议栈为什么叫"栈"

因为报头的添加和拆除,类似栈的加减!

  • 发送方向:数据从应用层往下走,每过一层就压入一层报头(应用层加包 → TCP 报头 → IP 报头 → MAC 帧)——像 push;
  • 接收方向:数据从底层往上走,每过一层就弹出对应报头、解析后交给上层——像 pop。

每一层协议收到数据后怎么解包?只需要回答两个问题:

  1. 怎么分离——报头和有效载荷怎么分开(定长报头?自描述字段?分隔符?);
  2. 交给谁——载荷里的内容该交付给上层的哪个协议(IP 报头里的上层协议字段、TCP 报头里的端口号,都是在回答这个问题)。

这两个问题是每一层的通用考题,从链路层一路问到应用层。

三、IP 与 MAC:目标不变,接力棒一直在换

一个反直觉的事实:

IP 不变,但 IP 指向的中转站——MAC 帧——是一直在变化的。IP 只是目标!

数据包从你的电脑到太平洋对面的服务器,路上要跳过很多路由器:每跳一跳,源 MAC 和目的 MAC 就被换一次(这一段的接力棒),但源 IP 和目的 IP 从头到尾不变(终点不变)。

  • IP 地址只需要 4 个字节就能表示;
  • 还可以做一个推断:通过发送去那里的目的 IP,推断其目的 IP 是不是当前局域网的——网段一比对就知道对方是本网段还是外网,这直接决定了是直接投递还是交给网关。

网络层:屏蔽底层网络差异

为什么要发明 IP 这一层?往历史看:

  • 先有局域网,后有广域网;
  • 每一个局域网发明的技术是不一样的(以太网、令牌环……),后面广域网出来了,要全世界互联,必然要规避这种差异;
  • 所以分工定了:底层数据链路层保证局域网的实现,网络层 IP 协议保证广域网的实现;
  • 网络层以上,大家都是 IP 协议,一样!——让我们程序员看待网络:一切皆 IP。

一句话:网络层屏蔽了底层网络的差异。底层是什么类型的网络无所谓,IP 层以上统一世界观。

再补一条时间线:先有 MAC 地址,才有 IP 地址——网卡出厂全球唯一是硬件层的事,IP 是后来为了组大网而设计的逻辑层。

至于路由怎么找到全网唯一的 IP?靠逐跳转发 + 路由表(目的网段 → 下一跳),目标 IP 不变,接力棒一棒一棒传过去。

(插一句随手记:为什么使用大端?因为大端比较利于人类——高位在前的顺序和人类读数字的顺序一致,抓包打印出来一眼能对上。)

四、缓冲区:又一个生产者消费者模型

发送和接收数据,各有两个缓冲区。以发送缓冲区为例:

  • 进程向里面写数据,操作系统往里面读数据——生产者消费者模型!

所以两个主机之间跑 TCP:TCP 是由 4 个生产者消费者模型构成的(每台主机发送缓冲区一个 + 接收缓冲区一个)。

这个过程和文件何其相似——一切皆文件:

网络缓冲区文件内核缓冲区
用户做什么向缓冲区写数据write 到内核缓冲区
内核做什么创建线程把数据发送到网卡异步线程把数据刷入磁盘
何时刷,用户管不管管不了,OS 决定管不了,OS 决定
同步约束OS 往里放的时候用户不能写;OS 读的时候用户最好别读同左
  • 我们写文件,也只是写到内核缓冲区,后面操作系统什么时候刷到磁盘自己决定——内核缓冲区将数据刷入磁盘,走的是异步线程;
  • 网络这边一模一样:网络协议栈就是创建了一个线程,把数据发送到网卡;文件系统线程则是把数据给到磁盘;
  • 所以规则也一样:1、操作系统往里面放的时候,用户不能写;2、操作系统往里面读取的时候,用户最好不要读——这就是 read/write 阻塞的又一层解释。

线程章的同步互斥、文件章的缓冲区、网络章的收发缓冲区——三个章节,一个模型。

五、报头与可拓展性

最后收回到协议本身:因为 TCP 是面向字节流的,所以我们需要添加报头(就是多了一些字段),每一次 read 都要依据这个报头来决定怎么处理。

而只要报文结构里有"类型""长度"这些字段,序列化和反序列化就有拓展性——新功能加字段,老代码照样能解析老报文。这就是协议设计的余量思维。

总结

  • 协议 = 约定:传输需要它,处理数据需要它,因为消息是临界资源;手搓过协议才有资格精确描述它;
  • 协议栈的"栈":报头层层压入、层层弹出;每层解包只需回答"怎么分离"和"交给谁";
  • IP 不变,MAC 一直在换:IP 是目标,MAC 是接力棒;4 字节 IP 还能推断是否本局域网;
  • 网络层屏蔽底层差异:先有局域网后有广域网,链路层保局域网、IP 保广域网,程序员眼里一切皆 IP;先有 MAC 后有 IP;
  • 每个缓冲区都是生产者消费者模型,TCP 两主机 4 个模型;网络发网卡 ≈ 文件刷磁盘,都是异步线程,OS 说了算;
  • 面向字节流 → 加报头 → 每次 read 依据报头 → 序列化反序列化有拓展性。

回到 HTTP:下一篇把请求和应答的报文格式全部铺开看。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐