一、什么是 std::net

std::net 是 Rust 标准库中用于处理 网络通信 的模块。它封装了 TCP/UDP 协议栈的核心功能,让我们可以通过简单的 API 实现网络通信。

主要组件:

  • TcpListener:用于创建 TCP 服务器,监听客户端连接。
  • TcpStream:表示 TCP 连接的“流”,用于发送和接收数据。
  • UdpSocket:用于 UDP 通信(本篇忽略)。
  • IpAddr / SocketAddr:表示 IP 地址和端口信息。

二、TCP 简介

TCP(Transmission Control Protocol) 是一种可靠的、面向连接的通信协议。它的特点包括:

  • 可靠性:数据不会丢失,顺序正确(通过重传和确认机制)。
  • 面向连接:通信前需要建立连接(三次握手),结束后断开连接(四次挥手)。
  • 适合场景:网页浏览、文件传输、邮件等对数据完整性要求高的场景。

三、TCP 通信的基本流程

  1. 服务器

    • 绑定端口并监听(TcpListener::bind)。
    • 接受客户端连接(TcpListener::incoming())。
    • 读取/写入数据(通过 TcpStream)。
  2. 客户端

    • 连接到服务器(TcpStream::connect)。
    • 发送/接收数据(通过 TcpStream)。

四、实例:TCP 服务器与客户端

1. TCP 服务器代码

rust 复制代码
// 处理客户端连接的函数
fn handle_client(mut stream: TcpStream) {
    let mut buffer = [0; 512];

    loop {
        match stream.read(&mut buffer) {
            Ok(0) => {
                // 连接关闭
                println!("客户端断开连接");
                break;
            }
            Ok(bytes_read) => {
                print!(
                    "收到消息: {}",
                    String::from_utf8_lossy(&buffer[..bytes_read])
                );

                // 原样返回给客户端
                if let Err(e) = stream.write(&buffer[..bytes_read]) {
                    eprintln!("发送消息失败: {}", e);
                }
            }
            Err(e) => {
                eprintln!("读取数据失败: {}", e);
                break;
            }
        }
    }
}

fn start_server() -> std::io::Result<()> {
    let listener = TcpListener::bind("127.0.0.1:8080")?;
    println!("服务器已启动,正在监听 127.0.0.1:8080...");

    for stream in listener.incoming() {
        match stream {
            Ok(stream) => {
                println!("新客户端连接");
                thread::spawn(|| handle_client(stream));
            }
            Err(e) => {
                eprintln!("连接失败: {}", e);
            }
        }
    }

    Ok(())
}

2. TCP 客户端代码

rust 复制代码
fn start_client() -> std::io::Result<()> {
    let mut stream = TcpStream::connect("127.0.0.1:8080")?;
    println!("已连接到服务器");

    let message = "你好,服务器!\n";
    stream.write(message.as_bytes())?;
    stream.flush()?;

    let mut buffer = [0; 512];
    let bytes_read = stream.read(&mut buffer)?;
    println!(
        "收到响应: {}",
        String::from_utf8_lossy(&buffer[..bytes_read])
    );

    Ok(())
}

3. 完整代码及演示

rust 复制代码
use std::io::{Read, Write};
use std::net::{TcpListener, TcpStream};
use std::thread;

// 处理客户端连接的函数
fn handle_client(mut stream: TcpStream) {
    let mut buffer = [0; 512];

    loop {
        match stream.read(&mut buffer) {
            Ok(0) => {
                // 连接关闭
                println!("客户端断开连接");
                break;
            }
            Ok(bytes_read) => {
                print!(
                    "收到消息: {}",
                    String::from_utf8_lossy(&buffer[..bytes_read])
                );

                // 原样返回给客户端
                if let Err(e) = stream.write(&buffer[..bytes_read]) {
                    eprintln!("发送消息失败: {}", e);
                }
            }
            Err(e) => {
                eprintln!("读取数据失败: {}", e);
                break;
            }
        }
    }
}

fn start_server() -> std::io::Result<()> {
    let listener = TcpListener::bind("127.0.0.1:8080")?;
    println!("服务器已启动,正在监听 127.0.0.1:8080...");

    for stream in listener.incoming() {
        match stream {
            Ok(stream) => {
                println!("新客户端连接");
                thread::spawn(|| handle_client(stream));
            }
            Err(e) => {
                eprintln!("连接失败: {}", e);
            }
        }
    }

    Ok(())
}

fn start_client() -> std::io::Result<()> {
    let mut stream = TcpStream::connect("127.0.0.1:8080")?;
    println!("已连接到服务器");

    let message = "你好,服务器!\n";
    stream.write(message.as_bytes())?;
    stream.flush()?;

    let mut buffer = [0; 512];
    let bytes_read = stream.read(&mut buffer)?;
    println!(
        "收到响应: {}",
        String::from_utf8_lossy(&buffer[..bytes_read])
    );

    Ok(())
}

fn main() {
    // 启动服务器或客户端取决于参数
    let args: Vec<String> = std::env::args().collect();
    match args.get(1).map(|s| s.as_str()) {
        Some("server") => {
            if let Err(e) = start_server() {
                eprintln!("服务器错误: {}", e);
            }
        }
        Some("client") => {
            if let Err(e) = start_client() {
                eprintln!("客户端错误: {}", e);
            }
        }
        _ => {
            println!("用法:");
            println!("cargo run -- server   # 启动服务端");
            println!("cargo run -- client   # 启动客户端");
        }
    }
}

🔹 启动服务端

在一个终端窗口运行:

bash 复制代码
cargo run -- server

输出:

复制代码
服务器已启动,正在监听 127.0.0.1:8080...

🔹 启动客户端

在另一个终端窗口运行:

bash 复制代码
cargo run -- client

输出:

复制代码
已连接到服务器
收到响应: 你好,服务器!

服务端也会输出:

复制代码
新客户端连接
收到消息: 你好,服务器!

五、代码详解

1. TcpListener::bind

rust 复制代码
let listener = TcpListener::bind("127.0.0.1:8080")?;
  • 127.0.0.1:8080 表示本地 IP 和端口。
  • ? 表示如果出错(如端口被占用),直接返回错误。

2. TcpStream::connect

rust 复制代码
let mut stream = TcpStream::connect("127.0.0.1:8080")?;
  • 客户端主动连接服务器。

3. 多线程处理客户端

rust 复制代码
thread::spawn(move || {
    handle_client(stream);
});
  • 使用 thread::spawn 创建新线程处理每个客户端,避免阻塞主线程。

4. 读取和写入数据

  • 读取stream.read(&mut buffer) 将数据存入缓冲区。
  • 写入stream.write(...) 发送数据。
  • 刷新缓冲区stream.flush() 确保数据立即发送。

六、SocketAddr 和 IpAddr 什么区别?

1. 核心区别

类型 定义 用途 示例值
IpAddr 表示 IP 地址(仅包含 IP 网络地址标识 127.0.0.1::1
SocketAddr 表示套接字地址(包含 IP + 端口 唯一标识网络连接端点 127.0.0.1:8080[::1]:80

2. 代码示例

2.1 IpAddr 示例

rust 复制代码
use std::net::IpAddr;

fn main() {
    // 创建 IPv4 地址
    let ipv4: IpAddr = "127.0.0.1".parse().unwrap();
    // 等价于
    let ipv4 = IpAddr::V4("127.0.0.1".parse().unwrap());

    // 创建 IPv6 地址
    let ipv6: IpAddr = "::1".parse().unwrap();
    // 等价于
    let ipv6 = IpAddr::V6("::1".parse().unwrap());

    // 用途:判断地址类型
    match ipv4 {
        IpAddr::V4(v4) => println!("IPv4: {}", v4),
        IpAddr::V6(v6) => println!("IPv6: {}", v6),
    }
}

2.2 SocketAddr 示例

rust 复制代码
use std::net::{SocketAddr, Ipv4Addr};

fn main() {
    // 方式1:从字符串解析
    let addr1: SocketAddr = "127.0.0.1:8080".parse().unwrap();
    let addr2: SocketAddr = "[::1]:80".parse().unwrap(); // IPv6 需要[]包裹

    // 方式2:手动构建
    let ip = Ipv4Addr::new(127, 0, 0, 1);
    let addr3 = SocketAddr::new(ip.into(), 8080); // ip.into() 将 Ipv4Addr 转为 IpAddr

    // 用途:作为网络连接的端点
    println!("监听地址: {}", addr1); // 输出: 127.0.0.1:8080
}

3. 核心方法对比

方法 IpAddr SocketAddr
获取 IP 部分 自身即 IP,无需额外方法 .ip() 返回 IpAddr
获取端口号 不支持(无端口概念) .port() 返回 u16 类型端口
判断地址类型 .is_ipv4() / .is_ipv6() 先获取 IP 再判断
字符串解析 "127.0.0.1".parse() "127.0.0.1:8080".parse()

七、其它

1. 并发处理多个客户端

  • 使用多线程(如上面的代码)或异步(tokio/async-std)处理高并发。
  • 示例中使用 thread::spawn 是最简单的并发方式。

2. 错误处理

  • Rust 强调错误处理,使用 Result? 操作符优雅处理错误。
  • 示例中使用 .unwrap() 是为了简化代码,实际项目应添加具体错误提示。

3. 自定义协议

  • 可以通过定义数据格式(如 JSON、Protobuf)实现更复杂的通信逻辑。

4. 如何关闭连接?

  • 使用 stream.shutdown(Shutdown::Both) 显式关闭连接。

5. 如何处理中文乱码?

  • 使用 String::from_utf8_lossy(...) 转换字节为字符串,即使包含无效 UTF-8 数据也能安全处理。