从零理解计算机网络基础:协议、分层、封装与 socket

摘要:本文从计算机为什么需要网络讲起,逐步梳理协议、协议分层、TCP/IP 模型、数据封装与分用、IP 地址、MAC 地址、端口号、socket、TCP/UDP、网络字节序和 socket 常见 API。适合刚开始学习 Linux 网络编程的同学建立整体框架。

前言

很多同学刚学网络时会直接接触 socketbindlistenaccept 这些接口,但如果不知道数据在网络中到底经历了什么,就很容易把 API 背成“咒语”。

网络基础真正要解决的问题并不复杂:两台甚至更多台主机,隔着不同设备、不同线路、不同操作系统,如何把数据准确交给对方的某个进程

为了回答这个问题,我们需要先理解几个核心概念:协议、分层、封装、解包、IP、MAC、端口号和 socket。

一、计算机网络为什么会出现

最早的计算机可以理解为“独立模式”:每台机器各自工作,互相之间没有直接联系。

当人们需要协同工作、共享数据时,就自然产生了网络互联:

阶段 特点
独立模式 计算机之间相互独立
网络互联 多台计算机连接在一起,完成数据共享
局域网 LAN 一定范围内的多台主机通过交换机、路由器等设备连接
广域网 WAN 把远距离的计算机连接起来

局域网和广域网只是相对概念。一个办公室里的网络通常叫局域网,一个城市、国家甚至全球范围的网络通常叫广域网。但从本质上看,它们都在解决同一件事:让不同主机能够交换数据。

二、协议是什么

协议就是一种约定

人与人打电话时,需要约定语言、号码、铃声、通话方式。计算机之间通信也一样,只是它们传输的是光信号、电信号或无线电信号,本质上是一串 0 和 1。

如果通信双方没有共同约定,就会出现类似下面的问题:

主机 A:我用频率变化表示 0 和 1
主机 B:我用强弱变化表示 0 和 1
结果:双方都在“说话”,但互相听不懂

所以,协议至少要约定:

需要约定的内容 说明
数据格式 一段数据每个字段表示什么
编码方式 如何把信息转换为 0 和 1
传输规则 谁先发、怎么发、出错怎么办
解析规则 收到数据后如何拆解和识别

从程序员视角看,协议也可以理解为:通信双方都认识的一种结构化数据格式

例如双方都按照同一个结构体解释数据:

struct Protocol {
    int a;
    int b;
    int c;
};

只要发送方和接收方都遵守这个结构,就能知道哪些字节表示 a,哪些字节表示 b,哪些字节表示 c。真实网络协议当然比这个复杂得多,但底层思想并没有变。

三、为什么网络协议要分层

网络协议本质上也是软件设计。只要软件足够复杂,就需要拆分模块,降低耦合。

协议分层的好处可以概括为:

好处 说明
解耦 每层只关注自己的问题
易维护 某一层升级不必推翻所有设计
易复用 上层可以复用下层提供的能力
易标准化 不同厂商只要遵守同一层接口,就能互相通信

一个生活化类比是寄快递:

用户只关心要寄什么
快递员关心取件和派件
运输系统关心中转路线
车辆系统关心怎么把货物移动到下一站

每一层只处理自己负责的事情,上层不需要知道下层的全部细节。

四、OSI 七层模型与 TCP/IP 模型

OSI 七层模型是一个逻辑上的参考模型,把网络通信分为七层。它概念清晰、理论完整,但在工程实践中相对复杂。

Linux 网络编程中更常接触的是 TCP/IP 五层或四层模型。

层次 主要职责 常见协议或设备
应用层 应用程序之间沟通 HTTP、FTP、SMTP、Telnet
传输层 主机到主机之间的数据传输 TCP、UDP
网络层 地址管理和路由选择 IP,路由器
数据链路层 相邻设备之间的数据帧传输 以太网、网卡驱动、交换机
物理层 光/电信号传递 网线、光纤、无线电、集线器

很多时候我们会把物理层暂时放到一边,重点讨论软件相关内容,于是也常说 TCP/IP 四层模型:

应用层

传输层

网络层

数据链路层

不同设备实现的层次也不同:

设备 通常涉及的层次
主机 从应用层到物理层
路由器 网络层到物理层
交换机 数据链路层到物理层
集线器 物理层

这里需要注意,真实设备并不总是这么“纯粹”。有些交换机也具备三层转发能力,有些路由器也会涉及端口转发等传输层相关能力。

五、数据是如何被封装和分用的

网络传输不是应用层数据直接飞到对端应用程序,而是要沿着协议栈逐层向下交付。

发送时,每一层都会给数据加上自己的首部;接收时,每一层再把自己的首部拆掉,并根据首部信息交给上一层。

先明确几个概念:

概念 说明
首部 当前协议层添加的控制信息
有效载荷 当前层真正要交给上层或下层处理的数据
报文 首部 + 有效载荷

不同层对完整数据包的叫法也不同:

协议层 常见称呼
传输层 段 segment
网络层 数据报 datagram
数据链路层 帧 frame

封装过程可以这样理解:

应用层数据

TCP 首部 + 应用层数据

IP 首部 + TCP 段

以太网首部 + IP 数据报 + 以太网尾部

发送到网络介质

接收端则反过来:

网络介质收到帧

数据链路层去掉以太网首部/尾部

网络层去掉 IP 首部

传输层去掉 TCP/UDP 首部

交给应用层

学习任何具体协议时,都可以先问两个问题:

  1. 这个协议怎么知道自己的首部有多长、有效载荷是什么?
  2. 这个协议怎么知道应该把有效载荷交给哪一个上层协议?

这两个问题想清楚,封装和解包就不再抽象。

六、局域网通信与 MAC 地址

同一个局域网内,两台主机可以直接通信。但直接通信也需要解决一个问题:怎么知道一份数据是发给谁的

这就需要 MAC 地址。

项目 说明
作用 识别数据链路层中相连的节点
长度 48 位,也就是 6 个字节
常见格式 08:00:27:03:fb:19
来源 通常在网卡出厂时确定

以太网中,一个局域网内的主机通过目标 MAC 地址判断报文是否发给自己。

碰撞域:在没有交换机的情况下,一个以太网可以看成一个碰撞域。多台机器同时发送数据会发生数据干扰,因此需要碰撞检测和碰撞避免。

现代网络里交换机已经非常常见,很多底层细节被设备处理掉了,但理解 MAC 地址仍然很重要,因为后面理解 ARP、以太网帧、跨网段转发都会用到它。

七、IP 地址:跨网络通信的核心

MAC 地址解决的是局域网内相邻节点识别问题。跨网段通信时,只靠 MAC 地址不够,还需要 IP 地址。

默认讨论 IPv4:

项目 说明
IPv4 地址长度 4 字节,32 位
常见表示方式 点分十进制
示例 192.168.0.1
每段范围 0 到 255

IP 地址用于在网络层中识别不同主机,并帮助路由器做路径选择。

跨网段传输可以概括为:

源主机

路由器

可能经过更多路由器

目标主机

这里有一个非常关键的区别:

对比项 IP 地址 MAC 地址
所在层次 网络层 数据链路层
作用范围 用于跨网络路径选择 用于局域网内下一跳转发
变化情况 在一次路由过程中通常保持不变 每经过一段链路都可能变化
类比 最终目的地 下一站收件人

可以把目的 IP 理解为“长远目标”,把目的 MAC 理解为“下一阶段目标”。路由器根据 IP 决定下一跳,再在当前局域网中使用 MAC 完成转发。

八、端口号:数据最终要交给哪个进程

数据到达目标主机还不算结束。因为主机上可能同时运行浏览器、聊天软件、下载程序、数据库服务等很多进程。

网络通信真正的目标不是“到达主机”,而是“到达主机上的某个进程”。

端口号就是为了解决这个问题。

项目 说明
所属层次 传输层
长度 2 字节,16 位
作用 标识主机上的网络进程
组合能力 IP 地址 + 端口号可以定位某台主机上的某个进程

端口范围:

范围 说明
0 - 1023 知名端口,如 HTTP、FTP、SSH 等常用服务
1024 - 65535 通常由操作系统动态分配给客户端程序

端口号和进程 ID 都能在某种意义上区分进程,但它们不是一回事:

对比项 端口号 进程 ID
所属领域 网络通信 操作系统进程管理
是否直接暴露给网络协议
一个进程能否使用多个 可以绑定多个端口 一个进程通常只有一个 pid
一个端口能否被多个进程占用 通常不能 不适用

如果直接用 pid 做网络通信中的进程识别,会把系统进程管理和网络强耦合,设计上并不合适。

九、socket:网络通信也是进程间通信

有了 IP 和端口号之后,就可以描述网络通信双方:

源 IP + 源端口号
目的 IP + 目的端口号

也就是常说的四元组:

{srcIp, srcPort, dstIp, dstPort}

它能描述互联网中正在通信的两个进程。因此,网络通信本质上也是进程间通信,只不过这两个进程可能分布在不同主机上。

我们通常把 IP + Port 称为 socket。更准确地说,在编程接口中,socket 也是操作系统提供给用户层程序访问网络协议栈的一套抽象。

十、TCP 和 UDP 的直观区别

协议 特点
TCP 传输层协议,有连接,可靠传输,面向字节流
UDP 传输层协议,无连接,不可靠传输,面向数据报

可以先这样理解:

TCP 更像打电话,通信前要建立连接,通信过程强调可靠性。

UDP 更像发一张明信片,直接发出去,不保证一定到,也不保证对方一定按预期收到。

这只是入门级理解。真正学习 TCP 时,还要继续研究连接管理、确认应答、超时重传、滑动窗口、拥塞控制等内容。

十一、网络字节序

不同主机对多字节整数的存储方式可能不同,也就是常说的大端和小端。

网络数据流也必须有统一规则,否则一台机器发出的整数,另一台机器可能会解析错。

TCP/IP 规定:网络字节序采用大端字节序

常用转换函数:

函数 含义
htons host to network short,16 位主机字节序转网络字节序
ntohs network to host short,16 位网络字节序转主机字节序
htonl host to network long,32 位主机字节序转网络字节序
ntohl network to host long,32 位网络字节序转主机字节序

下面是补充示例,用于理解端口号和 IP 地址为什么要做转换:

#include <arpa/inet.h>
#include <stdint.h>

uint16_t port = 8080;
uint16_t net_port = htons(port);

uint32_t ip = inet_addr("192.168.0.1");

htons(8080) 会把本机表示方式转换为网络统一表示方式。inet_addr 会把点分十进制 IP 字符串转换为网络字节序的 32 位整数。

十二、socket 常见 API

// 创建 socket 文件描述符,TCP/UDP 客户端和服务器都会用
int socket(int domain, int type, int protocol);

// 绑定端口号,TCP/UDP 服务器常用
int bind(int socket, const struct sockaddr *address,
         socklen_t address_len);

// 开始监听,TCP 服务器使用
int listen(int socket, int backlog);

// 接收连接请求,TCP 服务器使用
int accept(int socket, struct sockaddr *address,
           socklen_t *address_len);

// 建立连接,TCP 客户端使用
int connect(int sockfd, const struct sockaddr *addr,
            socklen_t addrlen);

一个 TCP 服务器的大致流程如下:

socket 创建套接字

bind 绑定 IP 和端口

listen 进入监听状态

accept 获取客户端连接

read/write 或 recv/send 通信

一个 TCP 客户端的大致流程如下:

socket 创建套接字

connect 连接服务器

read/write 或 recv/send 通信

一个重要设计:socket API 使用 struct sockaddr * 作为通用地址类型。

不同网络协议的地址格式不一样,例如 IPv4、IPv6、UNIX Domain Socket 都有自己的地址结构。为了让接口通用,API 使用统一的 sockaddr *,实际写 IPv4 程序时再使用 sockaddr_in 并做强制转换。

IPv4 常用结构如下:

struct sockaddr_in {
    sa_family_t sin_family;  // 地址类型,例如 AF_INET
    in_port_t sin_port;      // 端口号
    struct in_addr sin_addr; // IPv4 地址
};

in_addr 本质上保存的是一个 32 位 IPv4 地址。

一个典型的 IPv4 地址初始化片段如下:

#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>

struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));

addr.sin_family = AF_INET;
addr.sin_port = htons(8080);
addr.sin_addr.s_addr = inet_addr("0.0.0.0");

这段代码的关键在于:

sin_family 指明使用 IPv4。

sin_port 必须使用网络字节序。

sin_addr.s_addr 保存 IPv4 地址,0.0.0.0 通常表示绑定本机所有可用网卡地址。

十三、常见问题与易错点

不要把协议理解成某个单独 API。协议更像通信双方都认可的数据格式和处理规则。

不要把 TCP/IP 只理解成 TCP 和 IP 两个协议。TCP/IP 是一组协议的统称,包含多个层次和多种协议。

学习网络协议时,不要只盯着发送端。接收端如何解包、如何分用,往往更能体现协议设计。

IP 地址不是用来区分进程的,它用来定位主机;端口号才用来区分主机上的网络进程。

MAC 地址不是跨互联网的最终目的地址,它更像当前局域网内的下一跳地址。

端口号不是进程 ID。一个进程可以绑定多个端口,一个端口通常不能被多个进程同时绑定。

网络字节序默认采用大端。写网络程序时,端口号、整数地址等字段要注意 htonshtonl 等转换。

sockaddr 是通用接口类型,IPv4 编程时常用的是 sockaddr_in

总结

网络基础的主线可以串成一句话:

应用程序产生数据,经由协议栈逐层封装,通过 MAC 完成局域网内下一跳转发,通过 IP 完成跨网络路径选择,到达目标主机后,再根据端口号交给对应进程。

理解了这条链路,再去学习 TCP、UDP、HTTP、Socket 编程,就不会只是在背接口,而是在理解每个接口背后解决的问题。

这也是网络学习最重要的起点:先建立宏观流程,再深入每一层协议的细节。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐