协议为什么存在:协议栈、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。
每一层协议收到数据后怎么解包?只需要回答两个问题:
- 怎么分离——报头和有效载荷怎么分开(定长报头?自描述字段?分隔符?);
- 交给谁——载荷里的内容该交付给上层的哪个协议(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:下一篇把请求和应答的报文格式全部铺开看。