课程导入:学习目标、课程结构、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() 调用到网卡驱动的核心路径,以及各层添加的协议头。

发送路径 (TX) 应用层 send() / write() sys_sendto() socket层 传输层 (TCP/UDP) tcp_sendmsg() tcp_write_xmit() 网络层 (IP) ip_queue_xmit() 链路层 (以太网) dev_queue_xmit() 网卡驱动 ndo_start_xmit() 接收路径 (RX) 网卡中断 netif_rx() 链路层 netif_receive_skb() 网络层 (IP) ip_rcv() 传输层 (TCP/UDP) tcp_v4_rcv() udp_rcv() socket层 sock_queue_rcv_skb() 应用层 recv() / read() 数据包在各层的头部封装 应用数据 (payload) TCP头 (20~60B) | 应用数据 IP头 (20~60B) | TCP头 | 应用数据 Eth头 (14B) | IP头 | TCP头 | 应用数据 Eth头 | IP头 | TCP头 | 应用数据 | FCS(4B) ▲ 每层只处理自己的头部,payload对上层透明 ▲ 接收时逐层剥离头部,方向相反 核心结构:struct sk_buff 贯穿全栈

上图左侧展示了发送路径中,数据依次经过 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,内核中大致发生以下步骤:

  1. 应用层:curl 调用 send() 系统调用,传入HTTP请求文本 "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"。
  2. Socket层:内核找到对应socket,调用 tcp_sendmsg() 将数据复制到sk_buff中。
  3. TCP层:建立连接后,调用 tcp_write_xmit() 将数据分段(MSS通常1460字节),添加TCP头(源端口、目的端口、序列号、校验和),然后交给IP层。
  4. IP层:ip_queue_xmit() 添加IP头(源IP、目的IP、TTL、协议号6表示TCP),查询路由表确定下一跳。
  5. 链路层: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三次握手过程。