一、什么是 std::net
?
std::net
是 Rust 标准库中用于处理 网络通信 的模块。它封装了 TCP/UDP 协议栈的核心功能,让我们可以通过简单的 API 实现网络通信。
主要组件:
TcpListener
:用于创建 TCP 服务器,监听客户端连接。TcpStream
:表示 TCP 连接的“流”,用于发送和接收数据。UdpSocket
:用于 UDP 通信(本篇忽略)。IpAddr
/SocketAddr
:表示 IP 地址和端口信息。
二、TCP 简介
TCP(Transmission Control Protocol) 是一种可靠的、面向连接的通信协议。它的特点包括:
- 可靠性:数据不会丢失,顺序正确(通过重传和确认机制)。
- 面向连接:通信前需要建立连接(三次握手),结束后断开连接(四次挥手)。
- 适合场景:网页浏览、文件传输、邮件等对数据完整性要求高的场景。
三、TCP 通信的基本流程
-
服务器:
- 绑定端口并监听(
TcpListener::bind
)。 - 接受客户端连接(
TcpListener::incoming()
)。 - 读取/写入数据(通过
TcpStream
)。
- 绑定端口并监听(
-
客户端:
- 连接到服务器(
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 数据也能安全处理。