
🎬 个人主页 :道尔柯南
❄专栏传送门 :《C语言》《C++》《Linux操作系统》
昙花一现,却等待了整个白昼;蝉鸣一夏,却蛰伏了好几个四季。 摘要:
计算机网络看似复杂,但如果从"通信"这个最基本的问题出发,就会发现网络中的许多概念其实都围绕着一件事情展开:如何让不同主机上的不同进程可靠、准确地交换数据。
本文从计算机网络的发展背景出发,逐步理解网络协议、协议分层、OSI 与 TCP/IP 模型,并进一步深入 MAC 地址、IP 地址、端口号、TCP/UDP、网络字节序以及 Socket 编程接口,最终建立一套完整的计算机网络知识框架。
一、计算机网络为什么会出现?
最初的计算机是一台一台独立工作的。
可以简单理解成:
text
计算机 A 计算机 B 计算机 C
↓ ↓ ↓
各自独立工作,彼此之间没有直接的数据交换
随着计算机数量越来越多,人们很快发现:
一台计算机解决不了所有问题。
计算机本质上是人的工具,而人需要协同工作,因此计算机之间进行数据共享和资源共享就成为必然需求。
于是,计算机从最初的独立模式 逐渐发展到了网络互联。
课件将这一过程概括为:
- 独立模式:计算机之间相互独立;
- 网络互联:多台计算机连接起来,实现数据共享;
- 局域网 LAN:通过交换机、路由器等设备连接更多计算机;
- 广域网 WAN:进一步把相距很远的计算机连接起来。
所以,从本质上看:
计算机网络,就是为了让不同计算机之间能够进行通信和资源共享。
二、网络通信首先要解决的问题:大家必须"说同一种语言"
两台计算机连接起来之后,是不是就可以直接通信了?
答案是:不一定。
假设:
text
计算机 A:我用"频率"表示 0 和 1
计算机 B:我用"电压强弱"表示 0 和 1
即使两台计算机都在传输数据,它们也无法正确理解对方。
这就像两个人:
text
A:你好,我说中文。
B:Hello,我说英语。
两个人都在说话,但由于缺少共同约定,仍然无法正常交流。
所以,计算机之间通信需要一套共同遵守的规则。
这就是:
协议(Protocol)
课件对协议给出了一个非常朴素的理解:
"协议"是一种约定。
进一步来看,协议不仅仅是"约定语言",还需要规定:
- 数据应该是什么格式;
- 数据如何开始;
- 数据如何结束;
- 数据应该如何解释;
- 出错之后怎么办;
- 下一步应该交给谁处理。
由于计算机厂商、操作系统、网络设备非常多,因此必须存在一套大家共同遵守的标准,才能让不同设备之间顺畅通信。
三、协议为什么要分层?
如果把整个网络通信过程设计成一个巨大的协议,会出现一个非常严重的问题:
太复杂了。
例如,一次网络通信可能同时涉及:
text
应用程序
↓
数据格式
↓
可靠传输
↓
IP寻址
↓
局域网通信
↓
网卡
↓
网线 / Wi-Fi
如果所有功能全部揉在一起,那么任何一个地方发生变化,都可能影响整个系统。
因此,协议也采用了软件工程中非常经典的思想:
模块化 + 解耦合
课件指出,协议本质上也是软件,因此为了更好地进行模块化和解耦,也被设计成层状结构。
可以把它理解成:
text
┌──────────────┐
│ 应用层 │
├──────────────┤
│ 传输层 │
├──────────────┤
│ 网络层 │
├──────────────┤
│ 数据链路层 │
├──────────────┤
│ 物理层 │
└──────────────┘
每一层只负责自己的事情,并为上一层提供服务。
这就是协议分层。
四、OSI 七层模型:理解网络的经典框架
网络协议分层最经典的模型之一就是:
OSI 七层模型
OSI,即:
Open System Interconnection,开放系统互连。
它从逻辑上把网络通信划分成七层,是一种理论上的参考模型。
通常可以表示为:
text
7 应用层
6 表示层
5 会话层
4 传输层
3 网络层
2 数据链路层
1 物理层
OSI 模型的优点非常明显:
- 概念清晰;
- 层次明确;
- 服务、接口和协议进行了区分;
- 有利于理解不同系统之间如何通信。
但问题也很明显:
模型比较复杂,在实际工程中并没有完全按照七层实现。
因此,实际学习和网络编程中,我们更多使用 TCP/IP 模型。课件也明确指出,OSI 七层模型虽然理论完整,但复杂且不够实用,因此后续按照 TCP/IP 模型进行讲解。
五、TCP/IP 五层模型
TCP/IP 是一组协议的统称,它实际上包含大量不同协议,共同组成 TCP/IP 协议簇。
课件采用的五层模型为:
text
┌────────────────────┐
│ 应用层 │
├────────────────────┤
│ 传输层 │
├────────────────────┤
│ 网络层 │
├────────────────────┤
│ 数据链路层 │
├────────────────────┤
│ 物理层 │
└────────────────────┘
下面逐层理解。
1. 物理层
物理层解决的是最底层的问题:
数据到底通过什么信号传输?
例如:
- 双绞线;
- 同轴电缆;
- 光纤;
- Wi-Fi 电磁波。
物理层决定了很多底层通信能力,例如:
- 最大传输速率;
- 传输距离;
- 抗干扰能力。
课件中还提到:
Hub(集线器)工作在物理层。
2. 数据链路层
数据链路层主要负责:
相邻设备之间的数据传输。
它涉及:
- 数据帧;
- 网卡;
- 帧同步;
- 冲突检测;
- 数据差错校验;
- MAC 地址。
典型的设备是:
交换机 Switch
课件明确指出,交换机主要工作在数据链路层。
3. 网络层
网络层解决的是:
数据应该往哪里走?
这里最重要的概念就是:
IP 地址
IP 地址用来标识网络中的主机,同时网络层还需要进行路由选择。
例如:
text
主机 A
↓
路由器 1
↓
路由器 2
↓
主机 B
这个过程中,数据需要经过一个或多个路由器。
典型的网络层设备就是:
路由器 Router
4. 传输层
传输层负责:
两台主机之间的数据传输。
这里最重要的两个协议就是:
text
TCP
UDP
TCP 的特点:
- 面向连接;
- 可靠传输;
- 面向字节流。
UDP 的特点:
- 无连接;
- 不可靠传输;
- 面向数据报。
5. 应用层
应用层离用户最近。
例如:
text
浏览网页
发送邮件
文件传输
远程登录
对应的应用层协议可以包括:
- HTTP;
- SMTP;
- FTP;
- Telnet。
课件指出:
网络编程主要就是针对应用层。
六、真正理解网络:数据到底是怎么发送出去的?
到这里,我们知道了很多概念:
text
应用层
传输层
网络层
数据链路层
物理层
但是一个非常重要的问题出现了:
当程序发送一段数据时,它究竟经历了什么?
答案就是:
封装与解包
假设我们有这样一段应用数据:
text
Hello Linux
数据从应用层向下传输。
每经过一层,该层协议都会增加自己的控制信息。
可以简单理解为:
text
应用层:
┌──────────────┐
│ Hello Linux │
└──────────────┘
↓
传输层:
┌──────────┬──────────────┐
│ TCP头部 │ Hello Linux │
└──────────┴──────────────┘
↓
网络层:
┌──────────┬──────────┬──────────────┐
│ IP头部 │ TCP头部 │ Hello Linux │
└──────────┴──────────┴──────────────┘
↓
数据链路层:
┌──────────┬──────────┬──────────┬──────────────┐
│ MAC头部 │ IP头部 │ TCP头部 │ Hello Linux │
└──────────┴──────────┴──────────┴──────────────┘
这个过程叫:
封装(Encapsulation)
课件将报文概括为:
报文 = 报头 + 有效载荷。
七、不同层的数据为什么叫不同名字?
网络数据在不同层有不同的名称。
| 所处层次 | 常见名称 |
|---|---|
| 应用层 | 数据 |
| 传输层 | Segment / Datagram |
| 网络层 | Datagram |
| 数据链路层 | Frame |
例如:
text
应用数据
↓
TCP Segment
↓
IP Datagram
↓
Ethernet Frame
↓
物理介质
到达目标主机之后,过程反过来:
text
Frame
↓
去掉链路层头部
↓
Datagram
↓
去掉网络层头部
↓
Segment
↓
去掉传输层头部
↓
应用数据
这个过程就是:
解包 / 分用
课件特别强调,学习任何一个网络协议时,都应该首先从宏观上考虑两个问题:
- 这个协议是如何解包的?
- 它如何把自己的有效载荷交给上层协议?
因为:
理解了解包,也就更容易理解封包。
八、MAC 地址:局域网中的"身份证"
如果两台计算机处于同一个局域网中,它们如何知道数据到底发给谁?
这时候就需要:
MAC 地址
MAC 地址用于识别数据链路层中相连的节点。
课件中给出的特点是:
- 长度为 48 bit;
- 即 6 个字节;
- 通常使用十六进制表示;
- 常见格式类似:
text
08:00:27:03:fb:19
在局域网通信过程中,主机可以根据目标 MAC 地址判断:
这个数据是不是发给我的?
九、IP 地址:解决"跨网络通信"
MAC 地址解决了局域网中的设备识别问题。
但是,如果:
text
主机 A
需要和另一个完全不同网络中的:
text
主机 B
通信,仅靠 MAC 地址就不够了。
这时候就需要:
IP 地址
IPv4 地址本质上是:
32 位整数
通常使用点分十进制表示:
text
192.168.0.1
每一个数字代表一个字节,范围:
text
0 ~ 255
十、IP 地址和 MAC 地址到底有什么区别?
这是网络学习过程中非常重要的一组概念。
可以简单理解:
IP 是长期目标,MAC 是下一跳目标。
例如:
text
主机 A
↓
路由器 R1
↓
路由器 R2
↓
主机 B
整个过程中:
text
目的 IP
────────────────────────────→
最终目标:主机 B
而 MAC 地址则随着一跳一跳的转发发生变化。
课件对此进行了非常重要的总结:
IP 地址在路由过程中具有持续性的目标意义,而 MAC 地址用于下一阶段的局域网转发。
所以:
text
IP:我要最终去哪里?
MAC:我这一跳应该交给谁?
十一、数据到达主机还不够:还要找到正确的进程
这里是网络学习中一个非常关键的转折。
假设:
text
数据 → 目标主机
是不是就结束了?
答案是:
不是。
因为真正使用数据的是:
text
QQ
浏览器
迅雷
服务器程序
这些实际上都是:
进程
所以真正的通信目标不是:
text
主机
而是:
text
主机上的某一个进程
课件给出了一个非常重要的结论:
数据传输到主机不是目的,而是手段;到达主机内部,再交给目标进程才是真正的目的。
那么问题来了:
操作系统怎么知道应该把数据交给哪个进程?
答案:
端口号
十二、端口号:找到主机里的目标进程
端口号属于传输层协议。
它是一个:
text
16 bit
的整数。
端口号的作用就是:
标识主机上的网络进程。
例如:
text
IP = 192.168.1.100
Port = 8080
就可以表示:
text
192.168.1.100 这台主机上的某一个网络进程
因此:
text
IP + Port
可以定位到:
某台主机上的某一个网络进程。
十三、从 IP + Port 理解 Socket
到了这里,我们已经有:
text
IP → 找到主机
Port → 找到进程
所以:
text
IP + Port
就可以理解为一个网络通信端点。
这就是:
Socket
课件进一步将网络通信描述为两个进程之间的通信:
text
srcIP
srcPort
dstIP
dstPort
这四个信息共同描述通信双方。
因此可以得到一个非常重要的结论:
网络通信的本质,也是进程间通信。
而:
text
IP + Port
可以理解为 Socket 的核心标识。
十四、TCP 和 UDP:两种不同的通信方式
在传输层,我们最常见的两个协议就是:
text
TCP
UDP
TCP
TCP 的特点:
text
有连接
可靠传输
面向字节流
可以理解为:
我不仅要把数据发出去,还希望能够可靠地传递。
UDP
UDP 的特点:
text
无连接
不可靠传输
面向数据报
可以理解为:
我更关注快速发送数据,而不是建立复杂的可靠传输机制。
需要注意的是,这里只是对 TCP 和 UDP 建立一个初步认识,真正理解它们,还需要进一步学习 TCP 的连接管理、可靠性、流量控制、拥塞控制等机制。
十五、网络字节序:为什么网络数据必须统一格式?
在计算机中,多字节数据存在:
text
大端
小端
的问题。
例如一个整数在不同 CPU 上可能采用不同的字节存储顺序。
但是网络通信必须解决一个问题:
不同机器的数据表示方式可能不同,怎么办?
TCP/IP 规定:
网络字节序采用大端字节序
也就是说:
所有发送到网络上的数据,都必须按照网络规定的字节序进行处理。
因此在 Linux 网络编程中,我们经常会看到:
c
htons()
htonl()
ntohs()
ntohl()
其中:
text
h = host
n = network
s = short
l = long
例如:
c
htonl()
表示:
host to network long
也就是:
将主机字节序的 32 位整数转换为网络字节序。
十六、进入 Linux 网络编程:Socket API
当我们真正开始写网络程序时,就会接触:
Socket API
最基本的 Socket 接口包括:
c
socket()
bind()
listen()
accept()
connect()
其中:
socket()
创建 Socket。
c
int socket(int domain, int type, int protocol);
bind()
将地址和端口绑定到 Socket。
c
int bind(int socket,
const struct sockaddr *address,
socklen_t address_len);
通常服务器需要执行。
listen()
让 TCP 服务器进入监听状态。
c
int listen(int socket, int backlog);
accept()
接收客户端连接。
c
int accept(int socket,
struct sockaddr* address,
socklen_t* address_len);
connect()
客户端主动连接服务器。
c
int connect(int sockfd,
const struct sockaddr *addr,
socklen_t addrlen);
这些 API 是 Linux Socket 网络编程的基础。
十七、一个最经典的 TCP 通信流程
把前面的知识串起来,一个 TCP 服务器大致可以理解为:
text
服务器
socket()
↓
bind()
↓
listen()
↓
accept()
↓
read / write
↓
close()
客户端:
text
socket()
↓
connect()
↓
read / write
↓
close()
可以画成:
text
TCP服务器 TCP客户端
socket() socket()
↓ ↓
bind() connect()
↓ ↓
listen() ───────────────→ 建立连接
↓
accept()
↓
数据读写 ←──────────────→ 数据读写
↓
close() close()
这样一来,我们就把前面学习的:
text
应用层
↓
传输层
↓
网络层
↓
数据链路层
↓
物理层
与 Linux 中实际可以调用的:
text
socket()
bind()
listen()
accept()
connect()
联系起来了。
十八、sockaddr:Socket API 如何表示地址?
Socket API 是一套比较通用的网络编程接口。
不同协议的地址结构可能不同,因此 Linux 使用:
c
struct sockaddr
作为通用接口。
而在 IPv4 网络编程中,我们真正经常使用的是:
c
struct sockaddr_in
它主要包含:
text
地址类型
端口号
IP地址
课件指出,IPv4 地址使用 sockaddr_in 表示,而 sockaddr_in 中包含地址类型、16 位端口号和 32 位 IP 地址。
所以可以简单理解:
text
sockaddr
↓
通用地址接口
sockaddr_in
↓
IPv4 地址结构
十九、把整篇文章串起来:一次网络通信究竟发生了什么?
现在我们可以把整个过程完整地串起来。
假设:
浏览器访问一个服务器。
首先:
text
浏览器
是一个进程。
它需要把数据发送出去。
↓
通过:
text
Socket
进入操作系统网络协议栈。
↓
传输层:
text
TCP / UDP
负责主机之间的数据传输。
↓
网络层:
text
IP
负责寻找目标主机以及进行路由。
↓
数据链路层:
text
MAC
负责当前局域网中的数据传输。
↓
物理层:
text
网线 / 光纤 / Wi-Fi
最终把数据转换成信号发送出去。
完整过程可以抽象成:
text
应用程序
│
▼
Socket API
│
▼
┌─────────┐
│ 应用层 │
├─────────┤
│ 传输层 │ ← TCP / UDP / Port
├─────────┤
│ 网络层 │ ← IP / Routing
├─────────┤
│链路层 │ ← MAC / Frame
├─────────┤
│ 物理层 │ ← 网线/Wi-Fi/光纤
└─────────┘
│
▼
网络
│
▼
目标主机
│
▼
目标进程
这就是整个网络通信的宏观视角。
二十、网络学习最重要的一张"地图"
如果刚开始学习 Linux 网络编程,很容易被大量概念弄得混乱。
其实只需要抓住下面这张关系图:
text
网络通信
│
┌─────────┴─────────┐
│ │
主机之间 进程之间
│ │
IP Port
│ │
└───────┬───────────┘
│
Socket
│
TCP / UDP
│
网络协议栈
│
┌────────────┼────────────┐
│ │ │
应用层 传输层 网络层
│
IP
│
数据链路层
│
MAC
│
物理层
理解这张图之后,后续学习:
text
TCP
UDP
HTTP
DNS
Socket
路由
交换机
网络编程
都会容易很多。
二十一、总结:网络的本质是什么?
学习完这一部分,我们可以用几个问题来总结整个网络体系。
1. 为什么需要网络?
因为不同计算机之间需要:
通信和共享数据。
2. 为什么需要协议?
因为不同计算机之间必须:
遵守共同的通信规则。
3. 为什么协议要分层?
因为:
分层能够模块化、解耦合,降低系统复杂度。
4. MAC 地址解决什么问题?
解决局域网内设备的识别与转发问题。
5. IP 地址解决什么问题?
解决跨网络的主机寻址和路由问题。
6. Port 解决什么问题?
解决一台主机内部不同网络进程的识别问题。
7. Socket 是什么?
可以从网络编程的角度理解为:
网络通信端点。
8. 网络通信的本质是什么?
最终可以归纳为:
不同主机上的不同进程之间进行数据交换。
写在最后
计算机网络刚开始学习时,看起来会充满各种陌生概念:
text
MAC
IP
TCP
UDP
Port
Socket
Frame
Segment
Datagram
Router
Switch
但如果把这些概念拆开来看,它们其实并不是孤立存在的。
它们共同解决的是一个问题:
如何让一个主机上的进程,把数据准确地交给另一台主机上的目标进程。
因此,可以把整个网络通信过程浓缩成一句话:
text
进程
↓
Socket
↓
Port
↓
TCP/UDP
↓
IP
↓
MAC
↓
物理介质
↓
网络
↓
目标主机
↓
目标Port
↓
目标进程
这条链路,就是理解 Linux 网络编程最重要的主线。
当这条主线真正建立起来之后,后面再学习 TCP 三次握手、四次挥手、UDP、HTTP、DNS、路由、NAT、网络抓包以及 Linux Socket 编程,就不再是一个个孤立的知识点,而是在不断填充这张网络知识地图。
而这也正是从"会使用网络"走向"真正理解网络"的开始。
结尾
uu们,本文的内容到这里就全部结束了,道尔在这里再次感谢您的阅读!
|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| ### 道尔柯南 C/C++ & Linux 底层探索者 | 一个正在努力学习的技术博主 *** ** * ** *** 👀 【关注】 跟随我一起深耕技术领域,见证每一次成长。 ❤️ 【点赞】 让优质内容被更多人看见,让知识传递更有力量。 ⭐ 【收藏】 把核心知识点存好,在需要时随时查、随时用。 💬 【评论】 分享你的经验或疑问,评论区一起交流避坑! 不要忘记给博主"一键四连"哦! "今日目标达成!" |
结语:希望对学习Linux相关内容的uu有所帮助,不要忘记给博主"一键四连"哦!