Skip to content

std::net

std::net 是 Rust 标准库的网络编程模块(注意: 不是 std::io::net, 那是笔误), 提供了 TCP/UDP 通信所需的类型

介绍

std::net 中比较重要的类型:

类型作用
TcpListenerTCP 服务器端, 监听端口接收连接
TcpStreamTCP 连接(客户端和服务器都能用)
UdpSocketUDP 套接字(无连接, 数据报)
IpAddrIP 地址(IPv4 或 IPv6)
SocketAddr地址: "IP:端口" 的组合

IP 地址与端口

IP 地址解析

方法作用
IpAddr::is_ipv4是否是 IPv4 地址
IpAddr::is_ipv6是否是 IPv6 地址
IpAddr::is_loopback是否是回环地址(127.0.0.1 / ::1)
IpAddr::is_private是否是内网地址(192.168.x.x 等)
rust
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};

fn main() {
    // 1. 字符串解析为 IP 地址
    let ip: IpAddr = "127.0.0.1".parse().expect("解析失败");
    println!("{ip}"); // 127.0.0.1

    // 2. 直接构造
    let v4 = Ipv4Addr::new(192, 168, 1, 100);
    println!("{v4}"); // 192.168.1.100

    // IPv6 地址 ::1 (即 0:0:0:0:0:0:0:1)
    let v6 = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1);
    println!("{v6}"); // ::1

    // 3. 常用判断方法
    let loopback: IpAddr = "127.0.0.1".parse().expect("解析失败");
    println!("is_loopback = {}", loopback.is_loopback()); // is_loopback = true
    println!("is_ipv4 = {}", loopback.is_ipv4()); // is_ipv4 = true

    // is_private 只定义在 Ipv4Addr 上, 需要先取出 IPv4 变体
    let private: IpAddr = "192.168.1.1".parse().expect("解析失败");
    if let IpAddr::V4(v4) = &private {
        println!("is_private = {}", v4.is_private()); // is_private = true
    }

    // 4. 匹配具体的 IPv4 变体
    if let IpAddr::V4(v4) = private {
        println!("octets = {:?}", v4.octets()); // octets = [192, 168, 1, 1]
    }
}

SocketAddr

SocketAddr 是 "IP:端口" 的组合, 可以用一个字符串直接解析

rust
use std::net::{IpAddr, SocketAddr};

fn main() {
    // 1. 字符串解析 "IP:端口"
    let addr: SocketAddr = "127.0.0.1:8080".parse().expect("解析失败");
    println!("{addr}"); // 127.0.0.1:8080

    // 2. 获取 IP 和端口
    println!("ip = {}", addr.ip()); // ip = 127.0.0.1
    println!("port = {}", addr.port()); // port = 8080

    // 3. 手动构造
    let ip: IpAddr = "127.0.0.1".parse().expect("解析失败");
    let addr2 = SocketAddr::new(ip, 8080);
    println!("{addr2}"); // 127.0.0.1:8080

    // 4. 判断 IPv4 还是 IPv6 (IPv6 用 [::1]:8080 这种写法)
    let v6: SocketAddr = "[::1]:8080".parse().expect("解析失败");
    println!("is_ipv6 = {}", v6.is_ipv6()); // is_ipv6 = true
}

TCP 服务器与客户端

项目准备

sh
cargo new tcp_demo

全部代码

rust
use std::io::{self, Read, Write, stdin};
use std::{env, net};

fn main() -> io::Result<()> {
    let args: Vec<String> = env::args().skip(1).collect();

    if args.is_empty() {
        eprintln!("please prvider an arguments(client or server)");
        return Ok(());
    }

    let target = &args[0];
    match target.as_str() {
        "server" | "s" => run_tcp_server(),
        "client" | "c" => run_tcp_client(),
        _ => {
            println!("argument must be 'client' or 'server' ");
            Ok(())
        }
    }
}

fn run_tcp_server() -> io::Result<()> {
    println!("server running on 8000");

    // 绑定监听地址和端口
    let listener = net::TcpListener::bind("127.0.0.1:8000")?;

    // 监听客户端是否有链接进来
    for stream in listener.incoming() {
        let mut stream = stream.expect("failed to receive stream");

        // 主动读取数据(循环读取)直到连接关闭(read 返回 0)或出错
        // 使用固定大小缓冲区(使用 Vec<u8> 可能立刻返回 0)
        // 这里应该使用线程池: 因为可能有多个客户端, 如果单线程,
        // 它只会处理第一个TCP客户端链接的内容, 后续的链接会卡住
        // 直到, 第一个 TCP 客户端断开链接(跳出loop), 才会处理其他链接
        // 这里只是演示下: 客户端与服务端的通信, 就不使用多线程了
        let mut buffer = [0; 1024];
        loop {
            match stream.read(&mut buffer) {
                Ok(0) => {
                    println!("connection closed");
                    break;
                }
                Ok(n) => {
                    // 解析接收的内容
                    let received = String::from_utf8_lossy(&buffer[..n]);
                    println!("[server]: {}", received);

                    // 将收到的内容处理后(添加[from server]后缀)发回给客户端
                    let mut new_msg = received.to_string();
                    new_msg.push_str("[from server]");
                    stream.write_all(&new_msg.into_bytes())?;
                }
                Err(e) => {
                    eprintln!("read error: {}", e);
                    break;
                }
            }
        }
    }

    Ok(())
}

fn run_tcp_client() -> io::Result<()> {
    let mut stream = net::TcpStream::connect("127.0.0.1:8000")?;
    let mut buffer = [0; 1024];

    // 服务器已经链接
    println!("server connected");

    loop {
        // 从命令行中读取输入内容,发送给服务端
        println!("请输入内容: ");
        let mut input = String::new();
        stdin().read_line(&mut input)?;
        let bytes = input.trim().to_string().into_bytes();
        stream.write_all(&bytes)?;

        // 主动从连接中读取服务端发送过来的内容
        match stream.read(&mut buffer) {
            Ok(0) => {
                println!("[client]connection closed");
                break;
            }
            Ok(n) => {
                // 解析接收的内容, 然后原样输出
                let received = String::from_utf8_lossy(&buffer[..n]);
                println!("[client]: {}", received);
            }
            Err(e) => {
                eprintln!("[client]failed to read data from stream: {}", e);
                break;
            }
        }
    }

    Ok(())
}

TCP 服务端

使用 TcpListener 创建 TCP 服务器

方法作用
bind绑定地址和端口(监听)
accept阻塞等待客户端连接
incoming客户端连接的迭代器
local_addr获取监听的地址
rust
fn run_tcp_server() -> io::Result<()> {
    println!("server running on 8000");

    // 绑定监听地址和端口
    let listener = net::TcpListener::bind("127.0.0.1:8000")?;

    // 监听客户端是否有链接进来
    for stream in listener.incoming() {
        let mut stream = stream.expect("failed to receive stream");

        // 主动读取数据(循环读取)直到连接关闭(read 返回 0)或出错
        // 使用固定大小缓冲区(使用 Vec<u8> 可能立刻返回 0)
        // 这里应该使用线程池: 因为可能有多个客户端, 如果单线程,
        // 它只会处理第一个TCP客户端链接的内容, 后续的链接会卡住
        // 直到, 第一个 TCP 客户端断开链接(跳出loop), 才会处理其他链接
        // 这里只是演示下: 客户端与服务端的通信, 就不使用多线程了
        let mut buffer = [0; 1024];
        loop {
            match stream.read(&mut buffer) {
                Ok(0) => {
                    println!("connection closed");
                    break;
                }
                Ok(n) => {
                    // 解析接收的内容
                    let received = String::from_utf8_lossy(&buffer[..n]);
                    println!("[server]: {}", received);

                    // 将收到的内容处理后(添加[from server]后缀)发回给客户端
                    let mut new_msg = received.to_string();
                    new_msg.push_str("[from server]");
                    stream.write_all(&new_msg.into_bytes())?;
                }
                Err(e) => {
                    eprintln!("read error: {}", e);
                    break;
                }
            }
        }
    }

    Ok(())
}

注意点

accept阻塞的, 一次只能处理一个客户端, 真正的服务器会在循环中不断 accept。 上面示例处理一次连接就退出, 是为了演示方便

TCP 客户端

使用 TcpStream 连接服务器

方法作用
connect连接服务器(返回 Result)
read读取数据(Read trait)
write_all写入数据(Write trait)
set_read_timeout设置读取超时, 防止一直阻塞

经典场景: 一个程序内同时启动服务器和客户端, 完成一次通信(回显)

rust
fn run_tcp_client() -> io::Result<()> {
    let mut stream = net::TcpStream::connect("127.0.0.1:8000")?;
    let mut buffer = [0; 1024];

    // 服务器已经链接
    println!("server connected");

    loop {
        // 从命令行中读取输入内容,发送给服务端
        println!("请输入内容: ");
        let mut input = String::new();
        stdin().read_line(&mut input)?;
        let bytes = input.trim().to_string().into_bytes();
        stream.write_all(&bytes)?;

        // 主动从连接中读取服务端发送过来的内容
        match stream.read(&mut buffer) {
            Ok(0) => {
                println!("[client]connection closed");
                break;
            }
            Ok(n) => {
                // 解析接收的内容, 然后原样输出
                let received = String::from_utf8_lossy(&buffer[..n]);
                println!("[client]: {}", received);
            }
            Err(e) => {
                eprintln!("[client]failed to read data from stream: {}", e);
                break;
            }
        }
    }

    Ok(())
}

UDP

UDP 是无连接的协议, 使用 UdpSocket 发送/接收数据报

方法作用
bind绑定地址和端口
send_to发送数据到指定地址
recv_from接收数据(返回 数据 和 来源地址)
connect只和指定地址通信(之后可用 send/recv)
rust
use std::net::UdpSocket;

fn main() {
    // 1. 创建两个 socket: 一个当服务器, 一个当客户端
    let server = UdpSocket::bind("127.0.0.1:0").expect("绑定失败");
    let client = UdpSocket::bind("127.0.0.1:0").expect("绑定失败");

    let server_addr = server.local_addr().expect("获取地址失败");
    println!("服务器地址: {server_addr}");

    // 2. 客户端发送数据
    client.send_to(b"hello udp", server_addr).expect("发送失败");

    // 3. 服务器接收数据
    // recv_from 返回: (收到的字节数, 发送方地址)
    let mut buf = [0u8; 1024];
    let (n, from) = server.recv_from(&mut buf).expect("接收失败");
    println!("收到 {n} 字节, 来自 {from}"); // 收到 9 字节, 来自 127.0.0.1:xxxxx
    println!("内容: {}", String::from_utf8_lossy(&buf[..n])); // 内容: hello udp
}

HTTP 相关开源库

标准库的 std::net 只提供最底层的 TCP/UDP, 但是由于开发时用到最多的协议并不是底层的 TCP/UDP 而是基于 TCP 封装而来的 HTTP/SMTP/FTP/SSH 等等:

  • hyper: HTTP 服务端库(基于 TCP 服务端封装而来)
  • reqwest: 最流行的 HTTP 客户端, 同时支持同步/异步
  • ureq: 轻量级同步 HTTP 客户端(可用于树莓派等资源有限的环境)
  • russh: Rust生态 最流行的 SSH 协议实现, 同时支持 SFTP
  • mail-send: 用于发邮件的库
  • websocket: websocket 的 Rust 标准实现
  • dav-server: webdav 服务端

Released under the MIT License.