一、开启 Rust I/O 大门:Reader 与 Writer
在 Rust 的世界里,输入输出(I/O)操作通过一组强大的 Trait 来实现,其中 Reader 和 Writer 相关的 Trait 扮演着关键角色。
Rust 中有关输入输出的特性围绕着三个trait:Read、BufRead、Write 来组织:
- 实现了 Read Trait 的类型,具有读取字节输入的能力,即 Reader (读者)。
- 实现了 BufRead Trait 的类型,是 Buffered Reader(有缓存的读者),支持 Read Trait 的所有方法,并且具有读取文本的一行的方法。
- 实现了 Write Trait 的类型,支持字节和UTF-8 文本输出,称为Writer(写者)。
-
常见Reader:
- File:用于读取文件内容。
- Stdin:用于读取标准输入。
- BufReader:用于读取文件内容并提供缓冲功能。
- Cursor<&[u8]> 和Cursor<Vec>:用于从内存中的字节数组或vector 中“读取”数据。
- TcpStream: 用于与网络连接进行通信。
-
常见Writer:
- File:用于写入文件内容。
- Cursor<&mut [u8]> 和Cursor<Vec>:用于向内存中的字节数组或vector 中“写入”数据。
- TcpStream: 用于与网络连接进行通信。
- Stdout: 用于输出到标准输出。
- Stderr: 用于输出到标准错误输出。
- Vec: 通过write 方法把数据附加到尾部。
- BufWriter: 用于向内存中的字节数组或vector 中“写入”数据,并提供缓冲功能。
I/O Trait 及实现他们的类型:
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发送一条消息,并等待接收对方的响应。