第十二 网络
计算机网络是现代信息社会的基石。从浏览网页到发送即时消息,从在线视频到分布式计算,网络无处不在。理解网络的基本原理,并掌握 Rust 中的网络编程技术,是开发高性能网络应用的关键。
一、网络协议分层
计算机网络通信涉及复杂的软硬件协作。为了降低复杂度,人们将网络通信的功能划分为若干层次,每一层只负责特定的任务,上层依赖下层提供的服务。
1.1 OSI 七层模型
OSI(Open Systems Interconnection,开放式系统互联)模型是国际标准化组织(ISO)提出的理论框架,将网络通信分为七个层次:
| 层次 | 名称 | 功能描述 | 典型协议/设备 |
|---|---|---|---|
| 第七层 | 应用层 | 为应用程序提供网络服务接口 | HTTP、HTTPS、FTP、SMTP |
| 第六层 | 表示层 | 数据格式转换、加密解密、压缩解压 | SSL/TLS、JPEG、ASCII |
| 第五层 | 会话层 | 建立、管理和终止会话 | NetBIOS、RPC |
| 第四层 | 传输层 | 端到端的数据传输、流量控制、差错恢复 | TCP、UDP |
| 第三层 | 网络层 | 逻辑寻址、路由选择、分组转发 | IP、ICMP、路由器 |
| 第二层 | 数据链路层 | 物理寻址、帧的封装与传输 | MAC、以太网、交换机 |
| 第一层 | 物理层 | 比特流的物理传输 | 网线、光纤、集线器 |
记忆口诀: “物数网传会表应”(从下到上)或 “应表会传网数物”(从上到下)。
1.2 TCP/IP 四层模型
在实际工程中,TCP/IP 协议栈更为常用,它将 OSI 的七层简化为四层:
| 层次 | 名称 | 对应 OSI 层次 | 核心协议 |
|---|---|---|---|
| 第四层 | 应用层 | 应用层 + 表示层 + 会话层 | HTTP、FTP、DNS、SSH |
| 第三层 | 传输层 | 传输层 | TCP、UDP |
| 第二层 | 网络层 | 网络层 | IP、ICMP、ARP |
| 第一层 | 网络接口层 | 数据链路层 + 物理层 | 以太网、Wi-Fi |
TCP/IP 模型更加简洁实用,是互联网实际运行的标准。Rust 网络编程主要关注应用层和传输层。
1.3 数据封装过程
数据在发送时自上而下逐层封装,每一层都会添加自己的头部信息:
应用数据
↓ + HTTP 头
TCP 段
↓ + TCP 头
IP 包
↓ + IP 头
以太网帧
↓ + 帧头帧尾
比特流(物理传输)
接收端则自下而上逐层解封装,最终还原为应用数据。
二、IP 地址与端口
2.1 IP 地址
IP(Internet Protocol)地址是网络中设备的唯一标识。目前主要使用两个版本:
IPv4: 32 位地址,通常表示为四段十进制数,如 192.168.1.1。理论上有约 43 亿个地址,但由于互联网设备激增,IPv4 地址已接近枯竭。
IPv6: 128 位地址,表示为八组十六进制数,如 2001:0db8:85a3::8a2e:0370:7334。地址空间极大,足以给地球上每一粒沙子分配一个地址。
在 Rust 中,IP 地址由 std::net::IpAddr 枚举表示:
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
fn main() {
// IPv4 地址
let v4 = Ipv4Addr::new(192, 168, 1, 1);
println!("IPv4: {}", v4); // 192.168.1.1
// IPv6 地址
let v6 = Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0x1, 0x1);
println!("IPv6: {}", v6); // 2001:db8::1:1
// IpAddr 枚举可以同时持有两种类型
let addr_v4: IpAddr = v4.into();
let addr_v6: IpAddr = v6.into();
println!("是否是 IPv4: {}", addr_v4.is_ipv4()); // true
println!("是否是 IPv6: {}", addr_v6.is_ipv6()); // true
// 特殊地址
println!("本地回环: {}", Ipv4Addr::LOCALHOST); // 127.0.0.1
println!("任意地址: {}", Ipv4Addr::UNSPECIFIED); // 0.0.0.0
}
2.2 端口号
端口号(Port)用于区分同一台主机上的不同网络服务,是一个 16 位无符号整数(0~65535)。
| 端口范围 | 类型 | 说明 |
|---|---|---|
| 0 ~ 1023 | 知名端口 | 由 IANA 统一分配,如 HTTP(80)、HTTPS(443)、SSH(22) |
| 1024 ~ 49151 | 注册端口 | 需要向 IANA 注册,如 MySQL(3306)、Redis(6379) |
| 49152 ~ 65535 | 动态/私有端口 | 客户端程序临时使用 |
use std::net::SocketAddrV4;
fn main() {
// SocketAddr 包含 IP 地址和端口号
let addr = SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 8080);
println!("服务器地址: {}", addr); // 127.0.0.1:8080
// 常见服务端口
let common_ports = [
("HTTP", 80),
("HTTPS", 443),
("SSH", 22),
("FTP", 21),
("DNS", 53),
("MySQL", 3306),
("Redis", 6379),
];
for (name, port) in &common_ports {
println!("{} 服务默认端口: {}", name, port);
}
}
2.3 域名与 DNS
人类更容易记住域名(如 www.example.com),而不是 IP 地址。DNS(Domain Name System)负责将域名解析为 IP 地址。
use std::net::ToSocketAddrs;
fn main() {
// 将域名解析为 SocketAddr
let addrs = "www.rust-lang.org:443".to_socket_addrs();
match addrs {
Ok(iter) => {
for addr in iter {
println!("解析结果: {}", addr);
}
}
Err(e) => println!("解析失败: {}", e),
}
}
三、TCP 与 UDP
传输层是网络编程的核心,提供了两种主要的传输协议:TCP 和 UDP。
3.1 TCP:传输控制协议
TCP(Transmission Control Protocol)是一种面向连接、可靠、基于字节流的传输协议。
TCP 的核心特性:
| 特性 | 说明 |
|---|---|
| 面向连接 | 通信前需要建立连接(三次握手),通信后需要断开连接(四次挥手) |
| 可靠传输 | 通过确认应答(ACK)、超时重传、滑动窗口等机制保证数据不丢失 |
| 按序到达 | 数据包按发送顺序到达接收方 |
| 流量控制 | 通过滑动窗口机制防止发送方发送过快 |
| 拥塞控制 | 根据网络状况动态调整发送速率 |
TCP 三次握手:
客户端 服务器
| SYN(seq=x) |
| ----------------> |
| |
| SYN(seq=y,ACK=x+1) |
| <---------------- |
| |
| ACK(y+1) |
| ----------------> |
| |
[连接建立,开始传输数据]
适用场景: 文件传输、网页浏览、电子邮件、数据库连接等对可靠性要求高的场景。
3.2 UDP:用户数据报协议
UDP(User Datagram Protocol)是一种无连接、不可靠、基于数据报的传输协议。
UDP 的核心特性:
| 特性 | 说明 |
|---|---|
| 无连接 | 不需要建立连接,直接发送数据 |
| 不可靠 | 不保证数据到达,不保证顺序,没有重传机制 |
| 低开销 | 头部仅 8 字节,比 TCP 的 20 字节更小 |
| 低延迟 | 无需握手等待,适合实时性要求高的场景 |
适用场景: 视频直播、在线游戏、DNS 查询、实时语音等对延迟敏感、可容忍少量丢包的场景。
3.3 TCP 与 UDP 对比
| 对比项 | TCP | UDP |
|---|---|---|
| 连接方式 | 面向连接 | 无连接 |
| 可靠性 | 可靠(不丢包、不重复、按序) | 不可靠 |
| 传输效率 | 较低(有握手、确认开销) | 较高 |
| 延迟 | 较高 | 较低 |
| 头部大小 | 20 字节 | 8 字节 |
| 数据边界 | 字节流(无边界) | 数据报(保留边界) |
| 拥塞控制 | 有 | 无 |
| 适用场景 | 文件传输、网页、邮件 | 直播、游戏、DNS |
四、HTTP 与 HTTPS
4.1 HTTP 协议
HTTP(HyperText Transfer Protocol,超文本传输协议)是互联网上应用最广泛的协议,基于请求-响应模型。
HTTP 请求格式:
GET /index.html HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0
Accept: text/html
HTTP 响应格式:
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 1234
<html>...</html>
常见 HTTP 方法:
| 方法 | 说明 | 幂等性 |
|---|---|---|
| GET | 获取资源 | 是 |
| POST | 创建资源 | 否 |
| PUT | 更新资源(完整替换) | 是 |
| PATCH | 部分更新资源 | 否 |
| DELETE | 删除资源 | 是 |
| HEAD | 获取响应头(不返回体) | 是 |
常见 HTTP 状态码:
| 状态码 | 含义 | 说明 |
|---|---|---|
| 200 | OK | 请求成功 |
| 301/302 | 重定向 | 资源已移动到新的 URL |
| 400 | Bad Request | 请求参数错误 |
| 401 | Unauthorized | 未授权,需要身份验证 |
| 403 | Forbidden | 禁止访问 |
| 404 | Not Found | 资源不存在 |
| 500 | Internal Server Error | 服务器内部错误 |
| 502 | Bad Gateway | 网关错误 |
| 503 | Service Unavailable | 服务暂时不可用 |
4.2 HTTPS 协议
HTTPS(HTTP Secure)是 HTTP 的安全版本,在 HTTP 之下加入了 TLS/SSL 加密层,解决三个核心安全问题:
| 安全问题 | 解决方案 | 说明 |
|---|---|---|
| 窃听 | 加密传输 | 使用对称加密算法加密数据内容 |
| 篡改 | 消息认证码 | 使用 MAC 验证数据完整性 |
| 冒充 | 数字证书 | 通过 CA 证书验证服务器身份 |
HTTPS 的通信过程:
- TCP 连接建立(三次握手)
- TLS 握手:协商加密算法、交换密钥、验证证书
- 加密通信:使用协商好的密钥进行对称加密传输
- 连接关闭
本书后续章节(第四十九至五十章)将详细介绍 PKI、TLS 和数字证书的原理与实现。
五、Rust 网络编程
Rust 的标准库 std::net 提供了基础的网络编程能力,而 tokio 等异步运行时则提供了高性能的异步网络编程能力。
5.1 使用 std::net 进行 TCP 编程
TCP 服务端
use std::io::{Read, Write};
use std::net::{TcpListener, TcpStream};
use std::thread;
fn handle_client(mut stream: TcpStream) {
let mut buffer = [0u8; 1024];
loop {
match stream.read(&mut buffer) {
Ok(0) => {
println!("客户端断开连接");
break;
}
Ok(n) => {
let received = String::from_utf8_lossy(&buffer[..n]);
println!("收到: {}", received.trim());
let response = format!("服务端已收到: {}", received.trim());
if stream.write_all(response.as_bytes()).is_err() {
break;
}
}
Err(e) => {
println!("读取错误: {}", e);
break;
}
}
}
}
fn main() -> std::io::Result<()> {
let listener = TcpListener::bind("127.0.0.1:7878")?;
println!("TCP 服务端已启动,监听 127.0.0.1:7878");
for stream in listener.incoming() {
match stream {
Ok(stream) => {
println!("新客户端连接: {:?}", stream.peer_addr()?);
thread::spawn(|| handle_client(stream));
}
Err(e) => println!("连接失败: {}", e),
}
}
Ok(())
}
TCP 客户端
use std::io::{self, Read, Write};
use std::net::TcpStream;
fn main() -> std::io::Result<()> {
let mut stream = TcpStream::connect("127.0.0.1:7878")?;
println!("已连接到服务端");
// 发送消息
let msg = "Hello, Rust Network!\n";
stream.write_all(msg.as_bytes())?;
// 读取响应
let mut buffer = [0u8; 1024];
let n = stream.read(&mut buffer)?;
println!("服务端响应: {}", String::from_utf8_lossy(&buffer[..n]));
Ok(())
}
5.2 使用 std::net 进行 UDP 编程
UDP 编程不需要建立连接,直接使用 UdpSocket 发送和接收数据报。
use std::net::UdpSocket;
fn main() -> std::io::Result<()> {
// UDP 服务端
let socket = UdpSocket::bind("127.0.0.1:8080")?;
println!("UDP 服务端已启动,监听 127.0.0.1:8080");
let mut buf = [0u8; 1024];
loop {
// 接收数据报
let (amt, src) = socket.recv_from(&mut buf)?;
let received = String::from_utf8_lossy(&buf[..amt]);
println!("来自 {} 的消息: {}", src, received.trim());
// 发送响应
let response = format!("已收到: {}", received.trim());
socket.send_to(response.as_bytes(), &src)?;
}
}
use std::net::UdpSocket;
fn main() -> std::io::Result<()> {
// UDP 客户端
let socket = UdpSocket::bind("127.0.0.1:0")?; // 让系统分配端口
println!("UDP 客户端已启动");
let server = "127.0.0.1:8080";
let msg = "Hello, UDP!";
// 发送数据报
socket.send_to(msg.as_bytes(), server)?;
println!("已发送: {}", msg);
// 接收响应
let mut buf = [0u8; 1024];
let (amt, _src) = socket.recv_from(&mut buf)?;
println!("收到响应: {}", String::from_utf8_lossy(&buf[..amt]));
Ok(())
}
5.3 使用 reqwest 进行 HTTP 请求
在实际开发中,我们通常使用成熟的 HTTP 客户端库。reqwest 是 Rust 生态中最流行的 HTTP 客户端库之一。
Cargo.toml 依赖:
[dependencies]
reqwest = { version = "0.11", features = ["blocking", "json"] }
tokio = { version = "1", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
异步 GET 请求:
use reqwest;
use serde::Deserialize;
#[derive(Debug, Deserialize)]
struct Post {
userId: u32,
id: u32,
title: String,
body: String,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// 发送 GET 请求
let resp = reqwest::get("https://jsonplaceholder.typicode.com/posts/1")
.await?
.json::<Post>()
.await?;
println!("获取到的文章: {:?}", resp);
Ok(())
}
POST 请求与自定义请求头:
use reqwest::header::{HeaderMap, HeaderValue, CONTENT_TYPE};
use serde_json::json;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = reqwest::Client::new();
// 构建请求体
let body = json!({
"title": "Rust 网络编程",
"body": "这是一篇关于 Rust 网络编程的文章",
"userId": 1
});
// 发送 POST 请求
let resp = client
.post("https://jsonplaceholder.typicode.com/posts")
.header(CONTENT_TYPE, "application/json")
.json(&body)
.send()
.await?;
println!("状态码: {}", resp.status());
println!("响应体: {}", resp.text().await?);
Ok(())
}
5.4 使用 tokio 进行异步 TCP 编程
对于高并发网络服务,异步编程是更好的选择。tokio 是 Rust 最流行的异步运行时。
Cargo.toml 依赖:
[dependencies]
tokio = { version = "1", features = ["full"] }
异步 TCP 服务端:
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::{TcpListener, TcpStream};
async fn handle_client(mut socket: TcpStream) {
let mut buf = [0u8; 1024];
loop {
match socket.read(&mut buf).await {
Ok(0) => {
println!("客户端断开");
return;
}
Ok(n) => {
let received = String::from_utf8_lossy(&buf[..n]);
println!("收到: {}", received.trim());
let response = format!("异步服务端响应: {}", received.trim());
if socket.write_all(response.as_bytes()).await.is_err() {
return;
}
}
Err(e) => {
println!("读取错误: {}", e);
return;
}
}
}
}
#[tokio::main]
async fn main() -> tokio::io::Result<()> {
let listener = TcpListener::bind("127.0.0.1:8080").await?;
println!("异步 TCP 服务端已启动,监听 127.0.0.1:8080");
loop {
let (socket, addr) = listener.accept().await?;
println!("新连接: {}", addr);
// 每个连接生成一个独立的异步任务
tokio::spawn(async move {
handle_client(socket).await;
});
}
}
异步 TCP 客户端:
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream;
#[tokio::main]
async fn main() -> tokio::io::Result<()> {
let mut stream = TcpStream::connect("127.0.0.1:8080").await?;
println!("已连接到服务端");
// 异步发送
stream.write_all(b"Hello, Async Rust!\n").await?;
// 异步读取
let mut buf = [0u8; 1024];
let n = stream.read(&mut buf).await?;
println!("收到: {}", String::from_utf8_lossy(&buf[..n]));
Ok(())
}
5.5 简单的 HTTP 服务端
使用 hyper 库可以快速构建 HTTP 服务端:
use hyper::service::{make_service_fn, service_fn};
use hyper::{Body, Request, Response, Server};
use std::convert::Infallible;
use std::net::SocketAddr;
async fn hello(_req: Request<Body>) -> Result<Response<Body>, Infallible> {
Ok(Response::new(Body::from("Hello, Rust HTTP Server!")))
}
#[tokio::main]
async fn main() {
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
let make_svc = make_service_fn(|_conn| async {
Ok::<_, Infallible>(service_fn(hello))
});
let server = Server::bind(&addr).serve(make_svc);
println!("HTTP 服务已启动: http://{}", addr);
if let Err(e) = server.await {
eprintln!("服务器错误: {}", e);
}
}
六、总结
| 概念 | 要点 |
|---|---|
| OSI 七层模型 | 物数网传会表应,理论参考模型 |
| TCP/IP 四层模型 | 网络接口层、网络层、传输层、应用层,实际工程标准 |
| IP 地址 | IPv4(32 位)与 IPv6(128 位),逻辑寻址 |
| 端口号 | 0~65535,区分同一主机上的不同服务 |
| TCP | 面向连接、可靠传输、按序到达、流量与拥塞控制 |
| UDP | 无连接、不可靠、低开销、低延迟 |
| HTTP | 请求-响应模型,GET/POST 等方法,状态码 |
| HTTPS | HTTP + TLS/SSL,加密传输、身份验证、完整性保护 |
| std::net | Rust 标准库提供同步 TCP/UDP 编程能力 |
| tokio | Rust 异步运行时,适合高并发网络服务 |
| reqwest | 流行的 HTTP 客户端库,支持异步请求 |
练习建议:
- 使用
std::net::TcpListener和TcpStream实现一个简单的 echo 服务端(将收到的内容原样返回)- 使用
reqwest编写程序,获取并解析一个公开 API 的 JSON 数据- 使用
tokio实现一个支持多客户端并发聊天的 TCP 服务端- 对比同步和异步 TCP 服务端在大量并发连接下的性能差异
- 使用
UdpSocket实现一个简单的 ping 测试工具