Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

第十二 网络

计算机网络是现代信息社会的基石。从浏览网页到发送即时消息,从在线视频到分布式计算,网络无处不在。理解网络的基本原理,并掌握 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 对比

对比项TCPUDP
连接方式面向连接无连接
可靠性可靠(不丢包、不重复、按序)不可靠
传输效率较低(有握手、确认开销)较高
延迟较高较低
头部大小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 状态码:

状态码含义说明
200OK请求成功
301/302重定向资源已移动到新的 URL
400Bad Request请求参数错误
401Unauthorized未授权,需要身份验证
403Forbidden禁止访问
404Not Found资源不存在
500Internal Server Error服务器内部错误
502Bad Gateway网关错误
503Service Unavailable服务暂时不可用

4.2 HTTPS 协议

HTTPS(HTTP Secure)是 HTTP 的安全版本,在 HTTP 之下加入了 TLS/SSL 加密层,解决三个核心安全问题:

安全问题解决方案说明
窃听加密传输使用对称加密算法加密数据内容
篡改消息认证码使用 MAC 验证数据完整性
冒充数字证书通过 CA 证书验证服务器身份

HTTPS 的通信过程:

  1. TCP 连接建立(三次握手)
  2. TLS 握手:协商加密算法、交换密钥、验证证书
  3. 加密通信:使用协商好的密钥进行对称加密传输
  4. 连接关闭

本书后续章节(第四十九至五十章)将详细介绍 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 等方法,状态码
HTTPSHTTP + TLS/SSL,加密传输、身份验证、完整性保护
std::netRust 标准库提供同步 TCP/UDP 编程能力
tokioRust 异步运行时,适合高并发网络服务
reqwest流行的 HTTP 客户端库,支持异步请求

练习建议:

  1. 使用 std::net::TcpListenerTcpStream 实现一个简单的 echo 服务端(将收到的内容原样返回)
  2. 使用 reqwest 编写程序,获取并解析一个公开 API 的 JSON 数据
  3. 使用 tokio 实现一个支持多客户端并发聊天的 TCP 服务端
  4. 对比同步和异步 TCP 服务端在大量并发连接下的性能差异
  5. 使用 UdpSocket 实现一个简单的 ping 测试工具