文章

TCP/IP 协议族

前言

在计算机网络中,操作系统之间的通信最终都要落到一套具体的协议之上,这套协议就是我们常说的 TCP/IP 协议族
网上关于 TCP/IP 的资料很多,但如果缺少体系化的视角,很容易变成「记一堆名词,却不知道它们如何协同工作」。

本文尝试以「学习归纳」的方式,从整体到细节梳理 TCP/IP 协议族的核心概念和运行机制,帮助你建立起一个可反复回顾的知识框架,而不是零散记忆点。


一、TCP/IP 协议族概览

1.1 TCP/IP 是什么?

  • 不是单一协议,而是一整套协议的集合
    • IP:负责寻址和路由转发,是整个协议族的核心。
    • TCP / UDP:负责端到端的传输。
    • HTTP、DNS、SMTP、FTP 等:建立在 TCP 或 UDP 之上,面向应用。
    • ARP、ICMP、DHCP、路由协议 等:为网络运行提供各种辅助功能。
  • 设计目标
    • 异构互联:不同厂商、不同操作系统、不同硬件架构,都可以通过统一协议互相通信。
    • 可扩展性:从小型局域网扩展到互联网级别。
    • 鲁棒性:在不可靠的底层物理网络上,构建尽量可靠的数据传输能力。

1.2 TCP/IP 与 OSI 模型

在学习网络时,常会遇到两个模型:

  • OSI 七层模型(概念模型)
    • 物理层、数据链路层、网络层、传输层、会话层、表示层、应用层。
  • TCP/IP 四层模型(工程实现中常用)
    • 网络接口层、网络层、传输层、应用层。

两者的对应关系可以概括为:

  • TCP/IP 的应用层 ≈ OSI 的 应用层 + 表示层 + 会话层。
  • TCP/IP 的传输层 ≈ OSI 的传输层。
  • TCP/IP 的网络层 ≈ OSI 的网络层。
  • TCP/IP 的网络接口层 ≈ OSI 的数据链路层 + 物理层。

学习时可以用 OSI 帮助理解概念,但在具体协议和实现上,基本都以 TCP/IP 为主。


二、TCP/IP 四层模型总览

从上到下,TCP/IP 通常被划分为四层,每一层解决不同层次的问题:

2.1 应用层

  • 作用:为具体应用提供网络功能接口,使应用程序不用直接处理底层细节。
  • 常见协议
    • HTTP / HTTPS:Web 浏览。
    • FTP / SFTP:文件传输。
    • SMTP / POP3 / IMAP:电子邮件。
    • DNS:域名解析。
    • SSH:安全远程登录。

程序员通常直接与这一层打交道,如浏览器、Web 服务、数据库客户端等。

2.2 传输层

  • 作用:为应用层提供「端到端」的数据传输服务。
  • 核心协议
    • TCP(Transmission Control Protocol)
      • 面向连接、可靠、有序、带流量控制和拥塞控制。
      • 适用于对可靠性要求高的场景,如 Web、数据库、文件传输等。
    • UDP(User Datagram Protocol)
      • 无连接、不保证可靠、不保证顺序。
      • 适用于实时性强、允许一定丢包的场景,如语音通话、视频直播、游戏等。

2.3 网络层

  • 作用:在不同网络之间转发数据,实现「端到端」的逻辑寻址和路由选择。
  • 核心协议
    • IP(Internet Protocol):IPv4、IPv6。
    • ICMP:网络测试与错误报告(如 ping)。
    • ARP / NDP:地址解析(IP ↔ MAC)。
    • 各种 路由协议:RIP、OSPF、BGP 等。

2.4 网络接口层(数据链路 + 物理)

  • 作用:与具体的物理网络打交道,将 IP 数据报封装为帧进行传输。
  • 典型技术
    • 以太网(Ethernet)、Wi-Fi、PPP 等。
    • 交换机工作在数据链路层,以 MAC 地址为依据转发帧。

可以把这一层理解为:如何在「一跳之内」把比特送到下一台设备


三、从应用到物理:数据如何在网络中传输?

理解「一条 HTTP 请求是如何在网络中完成一次来回」是学习 TCP/IP 的关键。

3.1 发送方向:一层层封装

以浏览器访问某个网站为例(简化流程):

  • 应用层(HTTP)
    • 浏览器构造 HTTP 请求报文(请求行、请求头、请求体)。
    • 把这个报文交给传输层。
  • 传输层(TCP)
    • 为 HTTP 报文加上 TCP 头部,形成一个 TCP 段
      • 包含:源端口、目的端口、序列号、确认号、窗口大小等。
    • 交给网络层。
  • 网络层(IP)
    • 为 TCP 段加上 IP 头部,形成 IP 数据报
      • 包含:源 IP、目的 IP、TTL、协议号等。
    • 根据路由表确定下一跳。
  • 网络接口层(以太网等)
    • 在 IP 数据报外面加上 MAC 头部和尾部,形成 以太网帧
      • 包含:源 MAC、目的 MAC、类型、FCS 校验等。
    • 通过网线或无线信号发送出去。

可以用一个简化的封装示意:

1
[ 以太网头 ][  IP 头  ][ TCP 头 ][ HTTP 数据 ][ 以太网尾 ]

3.2 接收方向:一层层解封装

对端接收到帧后,方向完全相反:

  • 网卡收帧 → 网络接口层检查并去掉以太网头尾,交给 IP。
  • IP 检查 IP 头,确认目的 IP、协议类型等。
  • 如果是 TCP,交给 TCP 处理。
  • TCP 根据端口号找到对应的应用进程,把 HTTP 数据交给应用层。
  • Web 服务器解析 HTTP 请求,生成响应,再沿着同样的路径反方向送回。

关键理解:

  • 每一层只关心自己的头部信息,对上层的内容一律视为「数据负载」。
  • 整个过程可以抽象为:封装(往下走) / 解封装(往上走)。

四、地址与标识:IP、MAC、端口

在 TCP/IP 协议栈里,主机和应用的身份由不同层次的标识共同确定。

4.1 MAC 地址:链路层标识

  • 位置:网卡上烧录的物理地址,通常写成 AA:BB:CC:DD:EE:FF。
  • 作用:在同一个局域网中,识别具体的网络接口。
  • 设备:交换机就是通过学习 MAC 地址表来转发帧的。

4.2 IP 地址:网络层标识

  • 位置:由操作系统配置,可以通过 DHCP 自动获取,也可以手动配置。
  • 作用:在跨网络的范围内,标识一台主机(或一个接口)的地址。
  • 结构(IPv4)
    • 32 位二进制,一般用点分十进制表示,例如 192.168.1.10。
    • 配合 子网掩码 划分为「网络号 + 主机号」。

同一网段判断(简化):

  • 对两台主机的 IP 分别与子网掩码做 AND 运算,得到各自的网络号。
  • 如果网络号相同,则认为在同一子网,可以直接通信;否则需要通过路由器。

4.3 端口:传输层标识

  • 作用:区分同一台主机上的不同应用进程。
  • 范围:0–65535,其中 0–1023 为「知名端口」(如 80、443、22 等)。
  • TCP/UDP 四元组
    • 一条 TCP 连接可以用 源 IP、源端口、目的 IP、目的端口 唯一标识。
    • 这也是网络抓包和排查问题时常用的定位方式。

4.4 ARP 与 DNS:两个重要的解析服务

  • ARP(Address Resolution Protocol)
    • 作用:在同一局域网中,已知对方 IP 时,解析出对方的 MAC 地址。
    • 机制:广播 ARP 请求,然后目标主机单播 ARP 响应。
  • DNS(Domain Name System)
    • 作用:把人类易记的域名(例如 example.com)解析为 IP 地址。
    • 常用 UDP 53 端口,也可以在某些情况下使用 TCP。

五、IP 协议与路由基础

5.1 IP 协议的核心特性

  • 无连接:发送前不需要建立专门的「连接」,每个 IP 数据报独立发送。
  • 尽力而为,不保证可靠
  • 不保证一定送达,也不保证顺序。
  • 不负责重传、流量控制等高级功能,这些由上层(如 TCP)完成。

5.2 子网与子网掩码

  • 子网掩码 的作用是确定 IP 地址中哪一部分是「网络号」,哪一部分是「主机号」。
  • 例如:
    • IP:192.168.1.10
    • 子网掩码:255.255.255.0
    • 网络号:192.168.1.0,主机号:10。

掌握「IP + 子网掩码 → 网络号」的计算,对理解路由行为非常重要。

5.3 路由与默认网关

  • 路由表:记录「目标网段 → 下一跳」的映射关系。
  • 默认网关(Default Gateway):当目标不在本地子网时,本机会把 IP 数据报发给默认网关,由它继续转发。

一个简化的发送决策流程:

  1. 判断目标 IP 是否在本机子网内:
    • 是:使用 ARP 获取对方 MAC,直接发送帧。
    • 否:以默认网关为目标,发送帧给网关,由网关继续路由转发。

六、传输层:TCP 的可靠传输机制

TCP 是最常被详细考察的协议之一,但从学习角度看,有几个核心点要牢牢掌握:连接建立/释放、可靠性、流量控制和拥塞控制。

6.1 TCP 的主要特点

  • 面向连接:通信前必须先建立连接(三次握手)。
  • 可靠传输:
    • 使用序列号(Sequence Number)和确认号(Acknowledgment Number)。
    • 用超时重传机制处理丢包。
    • 有序传输:接收方可以根据序列号对数据重新排序。
  • 面向字节流:应用数据在 TCP 看起来是一条连续的字节流,而不是一个个独立报文。
  • 流量控制和拥塞控制:通过窗口机制和相关算法控制发送速度。

6.2 三次握手:连接建立

简化过程如下(只看关键标志位):

  1. SYN:客户端 → 服务器
    • 客户端发送一个 SYN 报文,表示希望建立连接,并携带初始序列号 seq = x。
  2. SYN + ACK:服务器 → 客户端
    • 服务器收到后,如果同意连接,发送 SYN + ACK 报文,
    • 自己的初始序列号 seq = y,同时确认号 ack = x + 1。
  3. ACK:客户端 → 服务器
    • 客户端收到后,发送一个 ACK 报文,ack = y + 1。
    • 至此,双方都确认了对方的收、发能力,连接建立完成。

从学习角度,关键是理解 「为什么需要三次」: 需要在不可靠网络上,让双方都确认对方具备收发能力,并完成初始序列号的同步。

6.3 四次挥手:连接释放

连接释放比建立要复杂一些,因为数据的发送和接收是双向的,每个方向都需要单独关闭。

简化流程如下:

  • 第一次挥手:主动关闭方发送 FIN,表示「我这边的数据发完了」。
  • 第二次挥手:被动关闭方回复 ACK,进入 CLOSE_WAIT 状态,此时可能还有数据要发送。
  • 第三次挥手:被动关闭方发送自己的 FIN,表示「我也发完了」。
  • 第四次挥手:主动关闭方回复 ACK,进入 TIME_WAIT 状态,等待一段时间(一般为 2 倍 MSL)后彻底关闭。

TIME_WAIT 的存在是为了解决「延迟的旧报文」和「对方最后 ACK 丢失」等问题,保证连接关闭的可靠性。

6.4 可靠性:确认和重传

  • 确认机制:接收方通过 ACK 告诉发送方「哪些数据已经收到」。
  • 超时重传:发送方在一定时间内没有收到 ACK,就认为报文丢失,会重传该报文。
  • 序列号:确保数据在接收端可以按顺序重组。

在编程调试中,如果抓包工具中看到大量重传和乱序,很可能说明网络质量较差。

6.5 流量控制与拥塞控制(概念层面)

  • 流量控制(Flow Control)
    • 目标:避免发送方过快,导致接收方缓存被填满。
    • 手段:利用 TCP 头部中的 窗口大小(Window Size) 字段,接收方根据自身缓冲能力动态调整。
  • 拥塞控制(Congestion Control)
    • 目标:防止过多数据注入到网络,使整个网络发生拥塞。
    • 常见算法(只需在概念层面理解):
      • 慢开始:一开始拥塞窗口很小,指数增长。
      • 拥塞避免:达到一定阈值后改为线性增长。
      • 快重传 / 快恢复:通过重复 ACK 快速发现丢包并适当减小窗口。

初学阶段不必死记算法细节,更重要的是理解:TCP 会根据网络反馈动态调整发送速度。

七、传输层:UDP 的简单与高效

7.1 UDP 的特点

  • 无连接:发送数据前不需要建立连接,发送方直接把数据扔出去。
  • 尽最大努力交付:不保证送达、不保证顺序、不做重传。
  • 报文边界清晰:一发一收对应一个报文,应用层容易区分消息。
  • 头部开销小:相比 TCP 更轻量,适合对实时性敏感、对丢包较为容忍的场景。

7.2 典型应用场景

  • 实时音视频:如语音通话、直播、在线会议。
  • 网络游戏:丢掉几个位置更新一般问题不大,延迟反而更加关键。
  • DNS 查询:单次查询短小,重试成本低。

7.3 与 TCP 的对比(学习视角)

  • 可靠性:TCP 有,UDP 没有。
  • 建立连接:TCP 有握手,UDP 没有。
  • 传输方式:TCP 面向字节流,UDP 面向报文。
  • 应用层复杂度:
    • 使用 TCP 时,可靠性由协议栈保证。
    • 使用 UDP 时,如需可靠性,需要在应用层自己实现确认和重传逻辑。

理解这一点有助于在系统设计时选择合适的传输协议。

八、典型应用层协议与 TCP/IP 的关系

8.1 HTTP / HTTPS

  • HTTP
    • 构建在 TCP 之上(默认端口 80)。
    • 无状态协议,结合 Cookie、Session 等实现会话管理。
  • HTTPS
    • 在 HTTP 与 TCP 之间加入了 TLS/SSL 安全层。
    • 提供加密、完整性校验和身份认证(默认端口 443)。

8.2 DNS

  • 多数情况下使用 UDP 53 端口。
  • 当响应数据过大或进行区域传送时,会使用 TCP。
  • 是访问互联网几乎所有服务的前置步骤。

8.3 其他常见协议

  • SMTP / POP3 / IMAP:电子邮件传输与收取(基于 TCP)。
  • FTP / SFTP:文件传输协议。
  • SSH:安全远程登录,基于 TCP。

这些协议都建立在 TCP/IP 提供的基础通信能力之上,展现出「分层设计」的优势:上层专注业务语义,下层负责可靠传输。

Go代码示例

服务端 server.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package main

import (
    "bufio"
    "fmt"
    "net"
)

func main() {
    // 1. 监听指定地址和端口
    listener, err := net.Listen("tcp", "127.0.0.1:8080")
    if err != nil {
        panic(err)
    }
    defer listener.Close()

    fmt.Println("TCP 服务器启动,监听 127.0.0.1:8080 ...")

    for {
        // 2. 等待客户端连接
        conn, err := listener.Accept()
        if err != nil {
            // 一般可以选择继续 Accept
            fmt.Println("接受连接错误:", err)
            continue
        }

        // 3. 为每个连接启动一个 goroutine 进行处理
        go handleConn(conn)
    }
}

// 处理单个客户端连接
func handleConn(conn net.Conn) {
    defer conn.Close()

    addr := conn.RemoteAddr().String()
    fmt.Println("新连接:", addr)

    reader := bufio.NewReader(conn)

    for {
        // 4. 按行读取客户端数据(以 '\n' 结尾)
        line, err := reader.ReadString('\n')
        if err != nil {
            // 这里简单打印错误并退出
            fmt.Println("连接关闭:", addr, "错误:", err)
            return
        }

        fmt.Printf("收到来自 %s 的数据: %s", addr, line)

        // 5. 将收到的数据原样回写给客户端(简单 Echo)
        _, err = conn.Write([]byte("服务器收到: " + line))
        if err != nil {
            fmt.Println("发送数据给客户端失败:", err)
            return
        }
    }
}

客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package main

import (
    "bufio"
    "fmt"
    "net"
    "os"
    "strings"
)

func main() {
    // 1. 连接到 TCP 服务器
    conn, err := net.Dial("tcp", "127.0.0.1:8080")
    if err != nil {
        panic(err)
    }
    defer conn.Close()

    fmt.Println("已连接到服务器 127.0.0.1:8080")
    fmt.Println("请输入内容并回车,输入 exit 退出。")

    // 从标准输入读取
    stdinReader := bufio.NewReader(os.Stdin)
    // 从连接读取
    connReader := bufio.NewReader(conn)

    for {
        fmt.Print(">> ")
        text, err := stdinReader.ReadString('\n')
        if err != nil {
            fmt.Println("读取输入失败:", err)
            return
        }

        text = strings.TrimSpace(text)
        if text == "exit" {
            fmt.Println("退出客户端。")
            return
        }

        // 2. 发送到服务器(加上换行符以便服务端按行读取)
        _, err = conn.Write([]byte(text + "\n"))
        if err != nil {
            fmt.Println("发送数据失败:", err)
            return
        }

        // 3. 读取服务器响应
        reply, err := connReader.ReadString('\n')
        if err != nil {
            fmt.Println("读取服务器响应失败:", err)
            return
        }

        fmt.Printf("服务器响应: %s", reply)
    }
}

小结

TCP/IP 协议族为互联网提供了一个分层、可扩展、健壮的通信架构。

从上到下,它把复杂问题拆解为应用、传输、网络、链路多个层次,每一层关注不同的职责:

应用层关心业务语义,传输层负责端到端可靠或高效的传输,网络层解决跨网络的寻址与转发,网络接口层对接具体物理网络。

在学习时,一方面要掌握各层的关键协议和概念,另一方面更要通过抓包、命令行等方式观察真实网络行为,把「抽象的协议」变成「眼前可见的报文」。当你能顺畅地从一条 HTTP 请求一路追踪到以太网帧,并反推回来时,你对 TCP/IP 的理解就已经迈入了一个扎实的台阶。

本文由作者按照 CC BY 4.0 进行授权