课程导入:学习目标、课程结构、TCP/IP协议栈概述
欢迎进入《TCP/IP网络案例源码》课程。本课程不打算从枯燥的RFC文档逐字讲解,而是以真实网络场景中的源码片段为切入点,带你观察数据包在协议栈中如何被封装、解析、路由和交付。你将看到的不只是理论,而是每一层协议在代码层面究竟做了什么。
一、学习目标:学完本章,你应该能回答三个问题
- 问题1:当你在浏览器输入
www.example.com并回车,从应用层到链路层,数据经历了哪些关键函数调用与协议头变更? - 问题2:TCP三次握手在Linux内核源码中对应哪几个核心函数?
tcp_v4_connect()与tcp_rcv_state_process()如何协作? - 问题3:如果网络出现丢包,TCP的拥塞控制算法(如Cubic)在源码中如何动态调整拥塞窗口?
围绕这三个问题,本课程将带你阅读精简后的内核网络源码片段(以Linux 5.x为参考),并结合抓包工具(tcpdump/Wireshark)验证行为。最终目标是建立“协议状态机 → 源码实现 → 实际报文”的三维映射能力。
二、课程结构:30章的三段式进阶
整个课程分为三个阶段,每阶段10章,难度螺旋上升:
| 阶段 | 章节范围 | 核心主题 | 源码侧重点 |
|---|---|---|---|
| 基础篇 | 第1~10章 | 协议栈框架、以太网帧处理、IP路由与分片 | sk_buff 结构、netif_receive_skb() |
| 进阶篇 | 第11~20章 | TCP状态机、重传与流量控制、UDP与QUIC基础 | tcp_v4_rcv()、tcp_transmit_skb() |
| 实战篇 | 第21~30章 | Socket API与内核交互、性能优化、网络诊断工具实现 | inet_sendmsg()、dev_queue_xmit() |
每章遵循固定节奏:场景描述 → 抓包观察 → 源码精读 → 实验验证 → 常见误区。建议你准备好Linux环境(虚拟机或云主机均可),并安装 gcc、make、tcpdump 和 Wireshark。
三、TCP/IP协议栈概述:从宏观分层到微观数据流
TCP/IP协议栈通常被描述为四层模型:应用层、传输层、网络层、链路层。但在Linux内核中,实际处理路径远比这个模型复杂。下图展示了数据发送时,从用户态 send() 调用到网卡驱动的核心路径,以及各层添加的协议头。
上图左侧展示了发送路径中,数据依次经过 send() → tcp_sendmsg() → ip_queue_xmit() → dev_queue_xmit() 的调用链;右侧是接收路径的逆过程。中间部分则直观呈现了每一层添加的头部:以太网头(14字节)、IP头(通常20字节)、TCP头(通常20字节),最终在网线上传输的是完整的帧。
四、核心原理:sk_buff——协议栈的“快递盒”
在Linux内核中,struct sk_buff(socket buffer)是贯穿所有网络层的核心数据结构。你可以把它想象成一个可扩展的快递盒:各层协议在传递数据时,只需要在盒子前面“加一张标签”(添加协议头),或者撕掉一张标签(剥离协议头),而无需复制整个数据内容。
// 简化版 sk_buff 关键字段(linux/skbuff.h)
struct sk_buff {
struct sk_buff *next; // 链表指针
struct sock *sk; // 所属socket
union {
struct tcphdr *th; // TCP头指针
struct udphdr *uh; // UDP头指针
struct iphdr *iph; // IP头指针
struct ethhdr *eth; // 以太网头指针
} header;
unsigned int len; // 总长度
unsigned int data_len; // 数据长度
__u32 priority; // QoS优先级
unsigned char *head; // 缓冲区起始
unsigned char *data; // 当前协议层数据起始
unsigned char *tail; // 当前协议层数据结束
unsigned char *end; // 缓冲区结束
};
关键操作是 skb_push()(添加头部)、skb_pull()(剥离头部)、skb_put()(添加尾部数据)。例如,TCP层在发送时调用 skb_push(skb, tcp_header_size) 在数据前腾出空间填入TCP头,然后IP层再次 skb_push(skb, ip_header_size) 添加IP头。这种设计避免了数据拷贝,是高性能网络栈的基础。
五、案例场景:一次HTTP请求的协议栈之旅
假设你在终端执行 curl http://example.com,内核中大致发生以下步骤:
- 应用层:
curl调用send()系统调用,传入HTTP请求文本"GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"。 - Socket层:内核找到对应socket,调用
tcp_sendmsg()将数据复制到sk_buff中。 - TCP层:建立连接后,调用
tcp_write_xmit()将数据分段(MSS通常1460字节),添加TCP头(源端口、目的端口、序列号、校验和),然后交给IP层。 - IP层:
ip_queue_xmit()添加IP头(源IP、目的IP、TTL、协议号6表示TCP),查询路由表确定下一跳。 - 链路层:
dev_queue_xmit()通过ARP获取目的MAC地址,添加以太网头,最终由网卡驱动发送。
接收端则执行完全相反的过程:网卡收到帧后,通过中断通知内核,netif_receive_skb() 开始逐层剥离头部,最终将HTTP响应数据通过 read() 返回给 curl 进程。
六、常见误区与复盘
误区1:认为“TCP/IP协议栈”只有四层。实际上,Linux内核中还有网络设备层(netdev)和邻居子系统(ARP)等逻辑层。四层模型只是教学简化。
误区2:混淆“用户态缓冲区”与“内核sk_buff”。send() 只是将数据从用户态拷贝到内核态sk_buff,真正的网络传输由内核异步完成。如果socket发送缓冲区满,send() 会阻塞或返回EAGAIN。
复盘要点:本章最重要的心智模型是——协议栈就是一组“头部操作”的流水线。发送时逐层加头,接收时逐层去头。后续所有章节的源码分析,都将围绕 sk_buff 的头部指针移动展开。建议你亲手在Linux中执行 cat /proc/net/sockstat 观察socket统计,并用 tcpdump -i any port 80 抓包验证HTTP请求的TCP三次握手过程。