从零开始学协议栈开发:手把手教你写网络通信底层代码

很多人觉得协议开发是高深莫测的技术,只适合大厂底层工程师去碰。其实只要摸清套路,普通人也能写出能跑的协议栈代码。尤其是在嵌入式设备、物联网模块这些资源紧张的场景里,自己实现一个轻量协议栈反而更灵活。

什么是协议栈?用快递打包来比喻

你可以把数据发送想象成寄快递。应用层是你要寄的物品,传输层(比如TCP)像是打包盒子并写好运单号,网络层(IP)决定走哪家快递公司,链路层则是装车发车。每一层都按规则封装,接收方再层层拆解——这就是协议栈的基本逻辑。

从最简单的以太网帧开始

别一上来就搞TCP/IP全套,先从链路层动手。比如在STM32上接了个W5500网卡芯片,第一步就是构造以太网帧头:

typedef struct {
    uint8_t dest_mac[6];
    uint8_t src_mac[6];
    uint16_t ethertype; // >= 1500 表示类型,如0x0800代表IP
} ethernet_header_t;

填好目标MAC、源MAC和协议类型,就能发出去了。虽然现在看不到结果,但用Wireshark抓包能看到你发出的原始帧,那种“我造的数据真的飞出去了”的感觉特别爽。

IP层不是背公式,而是填表格

很多人记不住IP头的字段顺序,其实把它当成一张固定格式的登记表就行。版本、首部长度、服务类型、总长度……每个位置对应一个格子:

typedef struct {
    uint8_t  version_ihl;      // 版本+首部长度
    uint8_t  tos;              // 服务类型
    uint16_t total_length;     // 总长度
    uint16_t id;
    uint16_t flags_fragment;   // 标志+片偏移
    uint8_t  ttl;
    uint8_t  protocol;        // 6表示TCP,17表示UDP
    uint16_t checksum;
    uint32_t src_ip;
    uint32_t dest_ip;
} ip_header_t;

每次发包就像填快递单,改个IP地址就跟换收件人一样简单。校验和可以先置0,等硬件自动补,降低初期门槛。

TCP握手?先让它能回‘你好’

三次握手听着复杂,但第一步只需让设备能响应SYN包。收到对方发来的连接请求后,构造一个ACK包回过去,关键字段设置如下:

  • seq_num = 收到的ack_num
  • ack_num = 收到的seq_num + 1
  • 标志位ACK = 1

不用一开始就支持窗口缩放、时间戳这些高级功能。先让两台板子能互相点个头,后续再逐步加料。

用Excel理清状态机逻辑

TCP连接有十几种状态,画流程图容易乱。不妨用Excel列个状态转移表,行是当前状态,列是收到的事件,单元格填下一步动作和跳转状态。

比如当前是SYN_SENT,收到SYN+ACK,就回复ACK,并进入ESTABLISHED。这种表格直接能转成switch-case代码,还能标红重点路径防遗漏。

调试别硬扛,用Wireshark当眼睛

自己写的协议栈最大的问题不是写不出,而是看不出错在哪。建议一边发包一边用Wireshark监听,看到自己发的包出现在列表里,哪怕被标记为‘malformed’也说明已经迈出了第一步。

有个学员做温湿度传感器,死活连不上服务器,抓包才发现IP头长度填成了字节数而不是4字节单位。这种低级错误看代码很难发现,但一眼就能从工具里看出来。

小步迭代比完美设计更重要

别想着第一版就支持分片重组、超时重传。先让ping通,再让它能get网页,最后再考虑HTTPS。每实现一层,就相当于给设备穿上一件衣服,从裸奔到全副武装,过程本身就很有成就感。