第一阶段:网络的发展与协议的分层
1. 从"独立模式"到"网络互联"
在网络出现之前,计算机是独立模式(Standalone)的。
- 场景回顾: 课件中举了一个生动的例子,小松、小竹、小梅三个人要处理不同的业务,但业务数据分别存在不同的终端(A、B、C)上。如果小松要处理业务②,他必须物理移动到终端B面前,而其他人只能等待 1111。
- 痛点: 效率极低,数据无法共享,资源独占。
- 解决: 网络互联 的出现解决了这个问题。通过将多台计算机连接在一起,实现了数据共享2。
-
- 局域网 (LAN): 通过交换机和路由器连接局部区域内的计算机 3。
- 广域网 (WAN): 将远隔千里的计算机连接在一起 4。
- 核心理念: 计算机是人的工具,为了协同工作,网络的产生是必然的 5。
2. 什么是"协议"?(核心概念)
这是网络中最核心的概念。课件中通过"打电话"和"语言"的例子来解释:
- 通俗理解: 协议就是一种约定6。比如我们打电话,约定响几声接听;或者大家都讲汉语,才能互相听懂。如果一个人讲汉语,另一个人讲葡萄牙语,即便电话线(物理介质)是通的,也无法通信 7。
- 计算机视角(硬核理解):
-
- 计算机传输的是光电信号(0和1)。如果A主机用频率表示0/1,B主机用强弱表示0/1,那就没法聊了 。
- C++视角下的协议(重点):
所谓协议,本质上就是通信双方都认识的结构化数据类型(Structure。
例子:
假设主机A和主机B在代码中定义了完全相同的结构体:
struct protocol {
int a;
int b;
int c;
};
主机A将 struct protocol data = {10, 20, 30}; 发送给主机B 10。
主机B收到一串二进制流,因为它知道这个数据的"协议"(即结构体布局),它就能准确地解析出 a=10, b=20, c=30 11。
-
- 标准化的必要性: 因为世界上有不同的硬件厂商(Intel, AMD)、不同的操作系统(Linux, Windows)、不同的网络设备(Cisco, Huawei),如果没有一个统一的标准(协议),大家就乱套了。于是就有了 IEEE, ISO, IETF 等组织来制定标准。
3. 协议分层:解耦合的艺术
协议非常复杂,为了便于维护和实现,必须进行分层。
- 分层的好处: 模块化、解耦合 。
-
- 两个人通话,可以分为"语言层"(汉语/英语)和"通信设备层"(电话/无线电)。如果你想把电话换成无线电,不需要改变讲什么语言;如果你想把汉语换成英语,不需要换电话机。这就是分层带来的封装 与复用。
4. OSI七层模型 vs TCP/IP四层模型
这是面试和考试的重灾区,我们需要清晰区分"理论"与"实践"。
|---------------|-------------------------------------|--------------------------------------|
| 模型 | 特点 | 备注 |
| OSI 七层模型 | 逻辑清晰,理论完整,但既复杂又不实用16。 | 包含:应用层、表示层、会话层、传输层、网络层、数据链路层、物理层 17。 |
| TCP/IP 模型 | 工业界的实际标准,通常分为5层或4层(物理层往往被忽略或合并) 18。 | 我们重点学习这个模型。 |
TCP/IP 各层详解与操作系统映射(结合你的 OS 背景):
- 应用层 (Application Layer):
-
- 职责: 负责应用程序间的沟通 。
- 协议: HTTP, DNS, FTP, SSH 等 。
- OS位置:用户空间 (User Space)。这是我们写代码(如你的聊天系统、C#项目)主要打交道的地方。
- 传输层 (Transport Layer):
-
- 职责: 负责两台主机之间的数据传输,确保数据可靠性(TCP。
- 协议: TCP, UDP。
- OS位置:操作系统内核 (Kernel)。无论你的APP是用C++还是Java写的,到了这一层,都是调用操作系统内核的代码 。
- 网络层 (Network Layer):
-
- 职责: 地址管理和路由选择(规划路径。
- 协议: IP, ICMP。
- OS位置:操作系统内核 (Kernel)。它负责在复杂的网络中找到通往目标主机的路。
- 数据链路层 (Data Link Layer):
-
- 职责: 设备之间的数据帧传送,处理冲突检测、差错校验 。
- 协议: 以太网协议 (Ethernet)。
- OS位置:驱动程序 (Driver)。通常由网卡驱动处理 。
- 物理层 (Physical Layer):
-
- 职责: 负责光/电信号的物理传输(网线、光纤、WiFi电磁波) 。
- OS位置:硬件 (Hardware)。
第二阶段:数据封装、分用与跨网络传输
1. 封装
当你在 C++ 代码中调用 send("你好") 时,这串数据并不是直接飞到对方电脑里的。它需要经过每一层协议的"加持"。
- 封装的本质: 课件中再次用 C语言结构体 的思维来解释:所谓的"报头"(Header),本质上就是对应协议层的结构体字段 。
-
- 报文 (Packet) = 报头 (Header) + 有效载荷 (Payload)。
- 具体流程(自顶向下):
-
- 应用层: 用户数据
"你好"加上应用层报头。 - 传输层: 加上 TCP 或 UDP 报头。在这一层,数据单元被称为 段 (Segment)。
- 网络层: 加上 IP 报头。在这一层,数据单元被称为 数据报 (Datagram)。
- 数据链路层: 加上以太网帧头(Ethernet Header)和帧尾。在这一层,数据单元被称为 帧 (Frame)。
- 应用层: 用户数据
C++ 视角补充: 在 Linux 内核网络栈(比如你之前关注的 TCP/IP 源码)中,这通常不是真的发生一次又一次的 malloc 和 memcpy(那样效率太低了)。内核通常使用像 sk_buff 这样的结构,通过移动指针来在数据前面"预留"或"添加"报头空间。
2. MAC 地址通信
数据封装好成了"帧"之后,首先要在局域网 (LAN) 内传播。
- MAC 地址 (Media Access Control Address):
-
- 定义: 类似身份证号,48位(6字节),全球唯一,出厂时烧录在网卡上
- 作用: 专门用于局域网内识别相连的节点 。
- 通信原理(广播与碰撞): 一个很有意思的现象:在早期的以太网(或没有交换机环境)中,主机A发消息给主机B,实际上是**"喊"**出来的。
-
- 广播: 主机A发出的帧,局域网内的所有主机(C, D, E...)都能收到 。
- 忽略: 主机C收到后,对比帧头里的
dst: MacB和自己的MacC,发现"俺不是MacB",于是丢弃 。 - 接收: 主机B收到后,发现"俺就是MacB",于是收下处理 。
- 碰撞域: 如果多台机器同时"喊",就会发生数据碰撞,需要碰撞检测和避免算法(这是底层硬件关心的事)。
3. 数据的"拆快递":分用
当数据到达目标主机后,是一个自底向上 的解包过程,这叫分用。
- 关键问题: 每一层怎么知道要把数据交给上一层的哪个协议?
-
- 链路层 -> 网络层: 以太网帧头里有一个字段叫"帧类型",告诉内核是交给 IP 协议还是 ARP 协议 。
- 网络层 -> 传输层: IP 报头里有一个"协议号"字段,告诉内核是交给 TCP 还是 UDP 。
- 传输层 -> 应用层: TCP/UDP 报头里有 "端口号",告诉操作系统交给哪个进程(比如 80 给浏览器,QQ的端口给QQ)。
4. 跨网络传输:IP vs MAC (重难点)
这是网络中最容易混淆的概念:既然有了 IP 地址,为什么还要 MAC 地址?
- 核心区别 :
-
- IP 地址: 代表最终目的地 (长远目标)。在传输过程中,源IP和目的IP通常是不变的(暂不考虑NAT)。
- MAC 地址: 代表下一站 (短期目标)。在传输过程中,每一跳(Hop)都在变。
- 路由器的"换皮"操作: 当数据包经过路由器时,路由器会做以下动作:
-
- 拆包: 拆掉链路层帧头,拿出 IP 数据报。
- 路由决策: 看了看目的 IP(是去往另一个局域网的),查路由表,决定下一站发给谁。
- 重新封装: 加上新的链路层帧头。
-
-
- 新的源 MAC:路由器的 MAC。
- 新的目的 MAC:下一跳设备的 MAC。
-
-
- 转发: 发送出去 。
第三阶段:Socket 编程核心与系统接口
1. 为什么有了 IP 还需要端口号?
- 痛点: IP 地址只能把数据送到主机(比如你的电脑),但这并不是终点 。
-
- 你同时开着 Chrome 浏览网页、QQ 聊天、迅雷下载。数据到了你的网卡,操作系统怎么知道这几个包是给 Chrome 的,那几个包是给 QQ 的?
- 解决: 端口号 (Port)。
-
- 定义: 2字节(16位)整数,用来在主机上唯一标识一个进程。
- 本质: 端口号是传输层(TCP/UDP)的概念。它就像"分机号"。IP 是公司前台总机,端口号是具体员工的分机。
- 范围划分 :
-
-
- 0 - 1023: 知名端口(Well-known Ports)。分配给 HTTP (80), SSH (22), FTP (21) 等系统级服务。
- 1024 - 65535: 动态分配端口。平时写 C++ 或 C# 程序用的都是这些。
-
C++ 系统视角:
经典面试题:进程 PID 也能唯一标识进程,为什么网络不用 PID 而要专门搞个端口号?
* 解耦合: PID 是操作系统内核进程管理的概念,每次重启程序 PID 都会变。如果网络绑定了 PID,那系统管理和网络协议就强耦合了。端口号提供了一层稳定的逻辑抽象 。
2. 什么是 Socket(套接字)?
- 公式:IP 地址 + 端口号 = Socket。
- 意义: 互联网上通信的本质,其实是两个进程在通信。
-
{源IP, 源Port, 目的IP, 目的Port}这样一个四元组,就能在全世界的互联网中唯一标识一条通信通道 。
3. C++ 程序员必须懂的:网络字节序
这是一个涉及内存底层的概念
- 问题背景:
不同的 CPU 架构对多字节整数的存储顺序不同:
-
- 大端 (Big-Endian): 高位字节存放在低地址(符合人类阅读习惯)。
- 小端 (Little-Endian): 低位字节存放在低地址(x86/x64 架构主流)。
- 冲突: 如果你的 PC(小端)发一个整数
0x1234abcd给服务器(可能是大端),服务器如果不转换,读出来的数就完全错了。 - 规定:网络传输一律采用大端字节序。
4 Socket API 与 C 语言的多态:sockaddr
最后是编程接口部分。这里有一个 C 语言实现"多态"的经典设计。
- 系统调用 (System Calls):
socket, bind, listen, accept, connect。这些函数是你从用户态(User Space)进入内核态(Kernel Space)操作网络协议栈的入口 。
- 结构体设计的智慧:
Socket API 需要支持多种协议(IPv4, IPv6, Unix Domain Socket)。
-
- 通用结构体:
struct sockaddr(类似基类)。 - IPv4 专用:
struct sockaddr_in(类似子类)。 - 实现方式:
- 通用结构体:
这两个结构体的前 16 位都是 AF_INET (地址类型) 。
在调用 bind 或 connect 时,我们将 sockaddr_in* 强制转换为 sockaddr* 传进去。内核读取前 16 位,发现是 AF_INET,就知道剩下的部分该按 IPv4 格式解析
// C++ 网络编程常见写法
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(8080); // 注意字节序转换
local.sin_addr.s_addr = htonl(INADDR_ANY);
// 强制类型转换 (C-style polymorphism)
bind(sockfd, (struct sockaddr*)&local, sizeof(local));