Rust IO魔法:输入输出大揭秘

193 阅读17分钟

一、开启 Rust I/O 大门:Reader 与 Writer

在 Rust 的世界里,输入输出(I/O)操作通过一组强大的 Trait 来实现,其中 Reader 和 Writer 相关的 Trait 扮演着关键角色。

Rust 中有关输入输出的特性围绕着三个trait:Read、BufRead、Write 来组织:

  1. 实现了 Read Trait 的类型,具有读取字节输入的能力,即 Reader (读者)。
  2. 实现了 BufRead Trait 的类型,是 Buffered Reader(有缓存的读者),支持 Read Trait 的所有方法,并且具有读取文本的一行的方法。
  3. 实现了 Write Trait 的类型,支持字节和UTF-8 文本输出,称为Writer(写者)。
  • 常见Reader:
  1. File:用于读取文件内容。
  2. Stdin:用于读取标准输入。
  3. BufReader:用于读取文件内容并提供缓冲功能。
  4. Cursor<&[u8]> 和Cursor<Vec>:用于从内存中的字节数组或vector 中“读取”数据。
  5. TcpStream: 用于与网络连接进行通信。
  • 常见Writer:
  1. File:用于写入文件内容。
  2. Cursor<&mut [u8]> 和Cursor<Vec>:用于向内存中的字节数组或vector 中“写入”数据。
  3. TcpStream: 用于与网络连接进行通信。
  4. Stdout: 用于输出到标准输出。
  5. Stderr: 用于输出到标准错误输出。
  6. Vec: 通过write 方法把数据附加到尾部。
  7. BufWriter: 用于向内存中的字节数组或vector 中“写入”数据,并提供缓冲功能。

I/O Trait 及实现他们的类型:

WX20250116-002547@2x.png

1.1 Reader:数据读取 “精灵”

Read Trait 定义了从字节流中读取数据的基本操作。

下面是一个从文件中读取数据的简单示例:

use std::io;
use std::io::prelude::*;
use std::fs::File;

fn main() -> io::Result<()> {
    let mut f = File::open("foo.txt")?;
    let mut buffer = [0; 10];

    // read up to 10 bytes
    f.read(&mut buffer)?;

    let mut buffer = Vec::new();
    // read the whole file
    f.read_to_end(&mut buffer)?;

    // read into a String, so that you don't need to do the conversion.
    let mut buffer = String::new();
    f.read_to_string(&mut buffer)?;

    // and more! See the other methods for more details.
    Ok(())
}
1.1.1 read方法

read方法从字节流中读取指定数量的字节,并将其存储在提供的缓冲区中。

read方法的签名如下:

fn read(&mut self, buf: &mut [u8]) -> io::Result<usize>

read方法的返回值是一个io::Result类型,它表示读取操作的结果。如果读取成功,返回值是实际读取的字节数;如果读取失败,返回一个错误。

下面是一个示例,展示如何使用read方法从文件中读取数据:

use std::io;
use std::io::prelude::*;
use std::fs::File;

fn main() -> io::Result<()> {
    let mut f = File::open("foo.txt")?;
    let mut buffer = [0; 10];

    // read up to 10 bytes
    let n = f.read(&mut buffer[..])?;

    println!("The bytes: {:?}", &buffer[..n]);
    Ok(())
}
1.1.2 read_to_end方法

read_to_end方法会将整个字节流读取到提供的缓冲区中。

read_to_end方法的签名如下:

fn read_to_end(&mut self, buf: &mut Vec<u8>) -> io::Result<usize>

read_to_end方法的返回值是一个io::Result类型,它表示读取操作的结果。如果读取成功,返回值是实际读取的字节数;如果读取失败,返回一个错误。

下面是一个示例,展示如何使用read_to_end方法从文件中读取数据:

use std::io;
use std::io::prelude::*;
use std::fs::File;

fn main() -> io::Result<()> {
    let mut f = File::open("foo.txt")?;
    let mut buffer = Vec::new();

    // read the whole file
    f.read_to_end(&mut buffer)?;
    Ok(())
}
1.1.3 read_to_string方法

read_to_string方法会将整个字节流读取到提供的字符串中。

read_to_string方法的签名如下:

fn read_to_string(&mut self, buf: &mut String) -> io::Result<usize>

read_to_string方法的返回值是一个io::Result类型,它表示读取操作的结果。如果读取成功,返回值是实际读取的字节数;如果读取失败,返回一个错误。

下面是一个示例,展示如何使用read_to_string方法从文件中读取数据:

use std::io;
use std::io::prelude::*;
use std::fs::File;

fn main() -> io::Result<()> {
    let mut f = File::open("foo.txt")?;
    let mut buffer = String::new();

    f.read_to_string(&mut buffer)?;
    Ok(())
}
1.1.4 read_exact方法

read_exact方法读取恰好足以填满给定缓冲区的数据。

read_exact方法的签名如下:

fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()>

read_exact方法的返回值是一个io::Result类型,它表示读取操作的结果。如果读取成功,返回值是实际读取的字节数;如果读取失败,返回一个错误。

下面是一个示例,展示如何使用read_exact方法从文件中读取数据:

use std::io;
use std::io::prelude::*;
use std::fs::File;

fn main() -> io::Result<()> {
    let mut f = File::open("foo.txt")?;
    let mut buffer = [0; 10];

    // read exactly 10 bytes
    f.read_exact(&mut buffer)?;
    Ok(())
}
1.1.5 bytes方法

bytes方法返回一个输入流的字节的迭代器。

bytes方法的签名如下:

pub fn bytes(self) -> Bytes<Self>
where
    Self: Sized,

bytes方法的返回值是一个Bytes类型,它是一个迭代器,它会逐字节地读取字节流。

下面是一个示例,展示如何使用bytes方法从文件中读取数据:

use std::io;
use std::io::prelude::*;
use std::io::BufReader;
use std::fs::File;

fn main() -> io::Result<()> {
    let f = BufReader::new(File::open("foo.txt")?);

    for byte in f.bytes() {
        println!("{}", byte.unwrap());
    }
    Ok(())
}

1.1.6 chain方法

chain方法会将两个Read类型的实例连接起来,形成一个新的Read类型的实例。

chain方法的签名如下:

pub fn chain<R>(self, next: R) -> Chain<Self, R>
where
    R: Read,
    Self: Sized,

chain方法的返回值是一个Chain类型,首先产生reader 的所有输入,然后产生 next 的所有输入。

下面是一个示例,展示如何使用chain方法从两个文件中读取数据:

use std::io;
use std::io::prelude::*;
use std::fs::File;

fn main() -> io::Result<()> {
    let f1 = File::open("foo.txt")?;
    let f2 = File::open("bar.txt")?;

    let mut handle = f1.chain(f2);
    let mut buffer = String::new();

    // read the value into a String. We could use any Read method here,
    // this is just one example.
    handle.read_to_string(&mut buffer)?;
    Ok(())
}

1.1.7 take方法

take方法返回一个新的reader,从和reader 相同的数据源读取数据,但最多只读取 limit 个字节。

take方法的签名如下:

pub fn take(self, limit: u64) -> Take<Self>
where
    Self: Sized,

take方法的返回值是一个Take类型, 只能读取 limit 个字节。

下面是一个示例,展示如何使用take方法从文件中读取数据:

use std::io;
use std::io::prelude::*;
use std::fs::File;

fn main() -> io::Result<()> {
    let f = File::open("foo.txt")?;
    let mut buffer = [0; 5];

    // read at most five bytes
    let mut handle = f.take(5);

    handle.read(&mut buffer)?;
    Ok(())
}

1.2 BufRead Trait:高效读取 “助手”

BufRead Trait 建立在 Read Trait 之上,它提供了从缓冲字节流中读取数据的功能。

与直接使用 Read Trait 相比,BufRead Trait 通常能提高读取效率,尤其是在处理大量数据或按行读取时。

BufReader类型实现了 BufRead Trait,它为文件读取提供了缓冲机制。

以下是如何使用BufReader逐行读取文件内容的示例:

use std::fs::File;
use std::io::{BufRead, BufReader};

fn main() {
    let file = File::open("example.txt").expect("Failed to open file");
    let reader = BufReader::new(file);
    for line in reader.lines() {
        let line = line.expect("Failed to read line");
        println!("{}", line);
    }
}

1.2.1 read_line方法

read_line方法读取一行文本并将它存储在一个String。

read_line方法的签名如下:

pub fn read_line(&mut self, buf: &mut String) -> Result<usize>
where
    // Bounds from trait:
    Self: Read,

read_line方法的返回值是一个io::Result类型,它表示读取操作的结果。如果读取成功,返回值是实际读取的字节数;如果读取失败,返回一个错误。

下面是一个示例,展示如何使用read_line方法从文件中读取数据:

use std::io::{self, BufRead};

let mut cursor = io::Cursor::new(b"foo\nbar");
let mut buf = String::new();

// cursor is at 'f'
let num_bytes = cursor.read_line(&mut buf)
    .expect("reading from cursor won't fail");
assert_eq!(num_bytes, 4);
assert_eq!(buf, "foo\n");
buf.clear();

// cursor is at 'b'
let num_bytes = cursor.read_line(&mut buf)
    .expect("reading from cursor won't fail");
assert_eq!(num_bytes, 3);
assert_eq!(buf, "bar");
buf.clear();

// cursor is at EOF
let num_bytes = cursor.read_line(&mut buf)
    .expect("reading from cursor won't fail");
assert_eq!(num_bytes, 0);
assert_eq!(buf, "");

1.2.2 lines方法

lines方法返回一个迭代输入流中每一行的迭代器。

lines方法的签名如下:

pub fn lines(self) -> Lines<Self>
where
    Self: Sized,
    // Bounds from trait:
    Self: Read,

lines方法的返回值是一个Lines类型,它是一个迭代器,它会逐行地读取字节流。

下面是一个示例,展示如何使用lines方法从文件中读取数据:

use std::io::{self, BufRead};

let cursor = io::Cursor::new(b"lorem\nipsum\r\ndolor");

let mut lines_iter = cursor.lines().map(|l| l.unwrap());
assert_eq!(lines_iter.next(), Some(String::from("lorem")));
assert_eq!(lines_iter.next(), Some(String::from("ipsum")));
assert_eq!(lines_iter.next(), Some(String::from("dolor")));
assert_eq!(lines_iter.next(), None);

1.2.3 read_until方法

read_until方法会将字节读取到提供的缓冲区中, 直到遇到终止符。

read_until方法的签名如下:

pub fn read_until(&mut self, byte: u8, buf: &mut Vec<u8>) -> Result<usize>
where
    // Bounds from trait:
    Self: Read,

read_until方法的返回值是一个Result类型,它表示读取操作的结果。如果读取成功,返回值是实际读取的字节数;如果读取失败,返回一个错误。

下面是一个示例,展示如何使用read_until方法从文件中读取数据:

use std::io::{self, BufRead};

let mut cursor = io::Cursor::new(b"lorem-ipsum");
let mut buf = vec![];

// cursor is at 'l'
let num_bytes = cursor.read_until(b'-', &mut buf)
    .expect("reading from cursor won't fail");
assert_eq!(num_bytes, 6);
assert_eq!(buf, b"lorem-");
buf.clear();

// cursor is at 'i'
let num_bytes = cursor.read_until(b'-', &mut buf)
    .expect("reading from cursor won't fail");
assert_eq!(num_bytes, 5);
assert_eq!(buf, b"ipsum");
buf.clear();

// cursor is at EOF
let num_bytes = cursor.read_until(b'-', &mut buf)
    .expect("reading from cursor won't fail");
assert_eq!(num_bytes, 0);
assert_eq!(buf, b"");

1.2.4 split方法

split方法通会过切分符,返回一个迭代器。 read_until方法的签名如下:

pub fn split(self, byte: u8) -> Split<Self>
where
    Self: Sized,
    // Bounds from trait:
    Self: Read,

split方法的返回值是一个Split类型,它是一个迭代器。

下面是一个示例,展示如何使用split方法从文件中读取数据:

use std::io::{self, BufRead};

let cursor = io::Cursor::new(b"lorem-ipsum-dolor");

let mut split_iter = cursor.split(b'-').map(|l| l.unwrap());
assert_eq!(split_iter.next(), Some(b"lorem".to_vec()));
assert_eq!(split_iter.next(), Some(b"ipsum".to_vec()));
assert_eq!(split_iter.next(), Some(b"dolor".to_vec()));
assert_eq!(split_iter.next(), None);

1.3 Write Trait:数据写入 “使者”

Write Trait 负责将数据写入字节流。

与 Read Trait 类似,许多类型都实现了 Write Trait,如标准输出Stdout、文件File。

通过实现 Write Trait,这些类型提供了将数据持久化到文件或输出到控制台等目标的能力。

下面是一个向文件写入数据的示例:

use std::fs::File;
use std::io::{self, Write};

fn main() -> io::Result<()> {
    let mut file = File::create("output.txt")?;
    let data = "Hello, Rust!";
    file.write_all(data.as_bytes())?;
    println!("Data written successfully");
    Ok(())
}

1.3.1 write方法

write方法将切片buf 中的字节写入到底层的流中。

write方法的签名如下:

fn write(&mut self, buf: &[u8]) -> io::Result<usize>

write方法的返回值是一个Result类型,它表示写入操作的结果。如果写入成功,返回值是实际写入的字节数;如果写入失败,返回一个错误。

下面是一个示例,展示如何使用write方法向文件写入数据:

use std::io::prelude::*;
use std::fs::File;

fn main() -> std::io::Result<()> {
    let mut buffer = File::create("foo.txt")?;

    // Writes some prefix of the byte string, not necessarily all of it.
    buffer.write(b"some bytes")?;
    Ok(())
}

1.3.2 write_all方法

write_all方法会将buf中的字节全部写入到提供的缓冲区中。

write_all方法的签名如下:

pub fn write_all(&mut self, mut buf: &[u8]) -> Result<()>

write_all方法的返回值是一个Result类型,它表示写入操作的结果。如果写入失败,返回一个错误。

下面是一个示例,展示如何使用write_all方法向文件写入数据:

use std::io::prelude::*;
use std::fs::File;

fn main() -> std::io::Result<()> {
    let mut buffer = File::create("foo.txt")?;

    buffer.write_all(b"some bytes")?;
    Ok(())
}

1.3.3 flush方法

flush方法将冲洗底层流中所有缓存的数据。

flush方法的签名如下:

fn flush(&mut self) -> io::Result<()>

flush方法的返回值是一个Result类型,它表示写入操作的结果。如果写入失败,返回一个错误。

下面是一个示例,展示如何使用flush方法向文件写入数据:

use std::io::prelude::*;
use std::io::BufWriter;
use std::fs::File;

fn main() -> std::io::Result<()> {
    let mut buffer = BufWriter::new(File::create("foo.txt")?);

    buffer.write_all(b"some bytes")?;
    buffer.flush()?;
    Ok(())
}

二、探索文件与目录 “王国”

文件和目录操作是与外部存储交互的关键环节。

Rust 的标准库提供了丰富的功能,让我们能够轻松地进行文件和目录的创建、读取、写入、删除等操作。

2.1 OsStr和Path

在 Rust 中,文件和目录操作涉及到两个重要的类型:OsStr和Path。

Rust 的字符串总是有效的Unicode。在实践中文件名几乎总是Unicode,但也需要应对少数不是Unicode 的情况。

OsStr 是一个作为UTF-8 超集的字符串类型。它能表示当前系统中的所有文件名、命令行参数、环境变量,不管它们是不是Unicode。

在Unix 上,OsStr 可以存储任意字节序列。

在Windows 上,OsStr 以UTF-8 的扩展格式存储,它可以编码任何16 位值的序列。

下面是使用OsStr的例子:

use std::ffi::OsStr;
use std::ffi::OsString;
use std::path::Path;
use std::fs;

fn main() {
    // 创建一个 OsStr
    let os_str: &OsStr = OsStr::new("example.txt");

    // 将 OsStr 转换为 OsString
    let os_string: OsString = os_str.to_os_string();
    println!("OsString: {:?}", os_string);

    // 将 OsStr 转换为 Path
    let path = Path::new(os_str);
    println!("Path: {:?}", path);

    // 检查文件是否存在
    if path.exists() {
        println!("The file exists!");
    } else {
        println!("The file does not exist.");
    }

    // 检查是否是文件
    if path.is_file() {
        println!("It is a file.");
    } else {
        println!("It is not a file.");
    }

    // 检查是否是目录
    if path.is_dir() {
        println!("It is a directory.");
    } else {
        println!("It is not a directory.");
    }

    // 遍历目录下的文件
    if path.is_dir() {
        if let Ok(entries) = fs::read_dir(path) {
            for entry in entries {
                if let Ok(entry) = entry {
                    let entry_path = entry.path();
                    println!("Entry: {:?}", entry_path);
                }
            }
        }
    }

    // 拼接路径
    let new_path = path.join("subdirectory");
    println!("New path: {:?}", new_path);
}

2.2 Path和PathBuf

Path和PathBuf是用于处理文件和目录路径的类型。它们提供了一系列方法来操作路径,包括创建、拼接、解析、查询等。

  • Path类型是一个路径类型,可以表示文件或目录的路径。
  • PathBuf类型是一个可变的路径类型,可以表示文件或目录的路径。

Path和PathBuf的签名:

pub struct Path {
    inner: OsStr,
}
pub struct PathBuf {
    inner: OsString,
}

Path和PathBuf的区别在于,Path是不可变的,而PathBuf是可变的。

Path是一个不可变的路径类型,可以表示文件或目录的路径。

PathBuf是一个可变的路径类型,可以表示文件或目录的路径。

2.2.1 Path::new方法

Path::new方法用于创建一个新的Path实例,它接受一个字符串作为参数,返回一个Path实例。

Path::new方法的签名如下:

pub fn new<S>(s: &S) -> &Path
where
    S: AsRef<OsStr> + ?Sized,

下面是一个示例,展示如何使用Path::new方法创建一个Path实例:

use std::path::Path;

let string = String::from("foo.txt");
let from_string = Path::new(&string);
let from_path = Path::new(&from_string);
assert_eq!(from_string, from_path);

2.2.2 path.parent方法

path.parent方法用于获取路径的父路径。

path.parent方法的签名如下:

pub fn parent(&self) -> Option<&Path>

path.parent方法的返回值是一个Option<&Path>实例。

下面是一个示例,展示如何使用path.parent方法获取路径的父路径:

use std::path::Path;

let path = Path::new("/foo/bar");
let parent = path.parent().unwrap();
assert_eq!(parent, Path::new("/foo"));

let grand_parent = parent.parent().unwrap();
assert_eq!(grand_parent, Path::new("/"));
assert_eq!(grand_parent.parent(), None);

let relative_path = Path::new("foo/bar");
let parent = relative_path.parent();
assert_eq!(parent, Some(Path::new("foo")));
let grand_parent = parent.and_then(Path::parent);
assert_eq!(grand_parent, Some(Path::new("")));
let great_grand_parent = grand_parent.and_then(Path::parent);
assert_eq!(great_grand_parent, None);

2.2.3 path.file_name方法

path.file_name方法用于获取路径的文件名。

path.file_name方法的签名如下:

pub fn file_name(&self) -> Option<&OsStr>

下面是一个示例,展示如何使用path.file_name方法获取路径的文件名:

use std::path::Path;
use std::ffi::OsStr;

assert_eq!(Some(OsStr::new("bin")), Path::new("/usr/bin/").file_name());
assert_eq!(Some(OsStr::new("foo.txt")), Path::new("tmp/foo.txt").file_name());
assert_eq!(Some(OsStr::new("foo.txt")), Path::new("foo.txt/.").file_name());
assert_eq!(Some(OsStr::new("foo.txt")), Path::new("foo.txt/.//").file_name());
assert_eq!(None, Path::new("foo.txt/..").file_name());
assert_eq!(None, Path::new("/").file_name());

2.2.4 path.is_absolute和path.is_relative方法

path.is_absolute和path.is_relative方法用于判断路径是否为绝对路径和相对路径。

path.is_absolute和path.is_relative方法的签名如下:

pub fn is_absolute(&self) -> bool
pub fn is_relative(&self) -> bool

下面是一个示例,展示如何使用path.is_absolute和path.is_relative方法判断路径是否为绝对路径和相对路径:

use std::path::Path;

assert!(!Path::new("foo.txt").is_absolute());
assert!(Path::new("foo.txt").is_relative());

2.2.5 path.join方法

path.join方法用于拼接路径。

path.join方法的签名如下:

pub fn join<P>(&self, path: P) -> PathBuf
where
    P: AsRef<Path>,

path.join方法的返回值是一个PathBuf实例。

下面是一个示例,展示如何使用path.join方法拼接路径:

use std::path::{Path, PathBuf};

assert_eq!(Path::new("/etc").join("passwd"), PathBuf::from("/etc/passwd"));
assert_eq!(Path::new("/etc").join("/bin/sh"), PathBuf::from("/bin/sh"));

2.2.6 path.components方法

path.components方法返回一个从左到右迭代给定路径的所有部分的迭代器。

path.components方法的签名如下:

pub fn components(&self) -> Components<'_>

path.components方法的返回值是一个PathComponents实例。

下面是一个示例,展示如何使用path.components方法获取路径的组件:

use std::path::{Path, Component};
use std::ffi::OsStr;

let mut components = Path::new("/tmp/foo.txt").components();

assert_eq!(components.next(), Some(Component::RootDir));
assert_eq!(components.next(), Some(Component::Normal(OsStr::new("tmp"))));
assert_eq!(components.next(), Some(Component::Normal(OsStr::new("foo.txt"))));
assert_eq!(components.next(), None)

2.2.7 path.ancestors方法

path.ancestors方法返回一个从path 一直回溯到根目录的迭代器。

path.ancestors方法的签名如下:

pub fn ancestors(&self) -> Ancestors<'_>

path.ancestors方法的返回值是一个Ancestors实例。

下面是一个示例,展示如何使用path.ancestors方法获取路径的祖先路径:

use std::path::Path;

let mut ancestors = Path::new("/foo/bar").ancestors();
assert_eq!(ancestors.next(), Some(Path::new("/foo/bar")));
assert_eq!(ancestors.next(), Some(Path::new("/foo")));
assert_eq!(ancestors.next(), Some(Path::new("/")));
assert_eq!(ancestors.next(), None);

let mut ancestors = Path::new("../foo/bar").ancestors();
assert_eq!(ancestors.next(), Some(Path::new("../foo/bar")));
assert_eq!(ancestors.next(), Some(Path::new("../foo")));
assert_eq!(ancestors.next(), Some(Path::new("..")));
assert_eq!(ancestors.next(), Some(Path::new("")));
assert_eq!(ancestors.next(), None);

2.2.8 path.display方法

path.display方法用于获取路径的显示字符串。

path.display方法的签名如下:

pub fn display(&self) -> Display<'_>

path.display方法的返回值是一个Display实例。

下面是一个示例,展示如何使用path.display方法获取路径的显示字符串:

use std::path::Path;

let path = Path::new("/tmp/foo.rs");

println!("{}", path.display());

2.2.9 平台特定特性

Rust 中的std::os 模块包含很多平台特定的特性,例如symlink。

std::os 在标准库中的实现类似:

//! OS 特定的功能
#[cfg(unix)] pub mod unix;
#[cfg(windows)] pub mod windows;
#[cfg(target_os = "ios")] pub mod ios;
#[cfg(target_os = "linux")] pub mod linux;
#[cfg(target_os = "macos")] pub mod macos;

#[cfg] 属性表示条件编译:这些模块中的每一个都只在特定平台上可用。

比如实现一个可以在所有平台可以使用的 symlink 时,最简单的方法是在Unix 上时导入symlink,而在其它系统上自定义一个symlink:

#[cfg(unix)]
use std::os::unix::fs::symlink;
/// 为不支持`symlink`的平台的实现
#[cfg(not(unix))]
fn symlink<P: AsRef<Path>, Q: AsRef<Path>>(src: P, _dst: Q)
-> std::io::Result<()>
{
    Err(io::Error::new(io::ErrorKind::Other, format!("can't copy symbolic link: {}",
src.as_ref().display())))
}

三、驰骋网络 I/O “战场”

在网络通信领域,Rust 同样提供了强大的工具和库,使得网络输入输出操作变得高效且可靠。

通过 Rust 的标准库,我们可以轻松实现 TCP、UDP 等常见的网络通信协议。

3.1 TCP 通信 “桥梁”

TCP(传输控制协议)是一种面向连接的、可靠的传输协议,常用于需要确保数据准确性和顺序性的场景,如文件传输、网页浏览 。

在 Rust 中,使用std::net::TcpStream和std::net::TcpListener来进行 TCP 通信。 以下是一个简单的 TCP 服务器端和客户端的代码示例:

  • 服务器端:
use std::io::{Read, Write};
use std::net::{TcpListener, TcpStream};

fn main() -> std::io::Result<()> {
    let listener = TcpListener::bind("127.0.0.1:8080")?;
    for stream in listener.incoming() {
        let mut stream = stream?;
        let mut buffer = [0; 1024];
        let bytes_read = stream.read(&mut buffer)?;
        let request = std::str::from_utf8(&buffer[..bytes_read]).unwrap();
        println!("Received request: {}", request);
        let response = "Hello, client!";
        stream.write(response.as_bytes())?;
    }
    Ok(())
}

在服务器端代码中,首先使用TcpListener::bind绑定到本地地址127.0.0.1:8080,然后通过循环接受客户端的连接。 对于每个连接,读取客户端发送的数据,并返回一个固定的响应。

  • 客户端:
use std::io::{Read, Write};
use std::net::TcpStream;

fn main() -> std::io::Result<()> {
    let mut stream = TcpStream::connect("127.0.0.1:8080")?;
    let request = "Hello, server!";
    stream.write(request.as_bytes())?;
    let mut buffer = [0; 1024];
    let bytes_read = stream.read(&mut buffer)?;
    let response = std::str::from_utf8(&buffer[..bytes_read]).unwrap();
    println!("Received response: {}", response);
    Ok(())
}

客户端代码中,使用TcpStream::connect连接到服务器地址127.0.0.1:8080,发送一个请求,并读取服务器返回的响应。

3.2 UDP 通信 “信使”

UDP(用户数据报协议)是一种无连接的、不可靠的传输协议,它的优势在于传输速度快、开销小,适用于对数据准确性要求不高但对实时性要求较高的场景,如视频流、音频流传输。

在 Rust 中,通过std::net::UdpSocket来实现 UDP 通信。

以下是一个 UDP 通信的示例:

use std::net::UdpSocket;

fn main() -> std::io::Result<()> {
    let socket = UdpSocket::bind("127.0.0.1:8080")?;
    let message = "Hello, UDP!";
    socket.send_to(message.as_bytes(), "127.0.0.1:9090")?;
    let mut buffer = [0; 1024];
    let (bytes_read, src) = socket.recv_from(&mut buffer)?;
    let response = std::str::from_utf8(&buffer[..bytes_read]).unwrap();
    println!("Received response from {}: {}", src, response);
    Ok(())
}

在这个示例中,首先创建一个UdpSocket并绑定到本地地址127.0.0.1:8080,然后向目标地址127.0.0.1:9090发送一条消息,并等待接收对方的响应。