计算机网络基础
从零理解计算机网络基础:协议、分层、封装与 socket
摘要:本文从计算机为什么需要网络讲起,逐步梳理协议、协议分层、TCP/IP 模型、数据封装与分用、IP 地址、MAC 地址、端口号、socket、TCP/UDP、网络字节序和 socket 常见 API。适合刚开始学习 Linux 网络编程的同学建立整体框架。
前言
很多同学刚学网络时会直接接触 socket、bind、listen、accept 这些接口,但如果不知道数据在网络中到底经历了什么,就很容易把 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 |
封装过程可以这样理解:
接收端则反过来:
学习任何具体协议时,都可以先问两个问题:
- 这个协议怎么知道自己的首部有多长、有效载荷是什么?
- 这个协议怎么知道应该把有效载荷交给哪一个上层协议?
这两个问题想清楚,封装和解包就不再抽象。
六、局域网通信与 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 服务器的大致流程如下:
一个 TCP 客户端的大致流程如下:
一个重要设计: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。一个进程可以绑定多个端口,一个端口通常不能被多个进程同时绑定。
网络字节序默认采用大端。写网络程序时,端口号、整数地址等字段要注意 htons、htonl 等转换。
sockaddr 是通用接口类型,IPv4 编程时常用的是 sockaddr_in。
总结
网络基础的主线可以串成一句话:
应用程序产生数据,经由协议栈逐层封装,通过 MAC 完成局域网内下一跳转发,通过 IP 完成跨网络路径选择,到达目标主机后,再根据端口号交给对应进程。
理解了这条链路,再去学习 TCP、UDP、HTTP、Socket 编程,就不会只是在背接口,而是在理解每个接口背后解决的问题。
这也是网络学习最重要的起点:先建立宏观流程,再深入每一层协议的细节。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐


所有评论(0)