TCP 与 UDP
问题
Rust 中如何进行 TCP/UDP 编程?
答案
异步 TCP 服务器(tokio)
use tokio::net::TcpListener;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let listener = TcpListener::bind("127.0.0.1:8080").await?;
println!("监听 127.0.0.1:8080");
loop {
// 接受新连接
let (mut socket, addr) = listener.accept().await?;
println!("新连接: {}", addr);
// 每个连接一个 task
tokio::spawn(async move {
let mut buf = [0u8; 1024];
loop {
let n = match socket.read(&mut buf).await {
Ok(0) => return, // 连接关闭
Ok(n) => n,
Err(_) => return,
};
// Echo:原样返回
if socket.write_all(&buf[..n]).await.is_err() {
return;
}
}
});
}
}
TCP 客户端
use tokio::net::TcpStream;
use tokio::io::{AsyncWriteExt, AsyncReadExt};
async fn connect() -> Result<(), Box<dyn std::error::Error>> {
let mut stream = TcpStream::connect("127.0.0.1:8080").await?;
stream.write_all(b"Hello, server!").await?;
let mut buf = [0u8; 1024];
let n = stream.read(&mut buf).await?;
println!("收到: {}", String::from_utf8_lossy(&buf[..n]));
Ok(())
}
TCP 粘包处理
TCP 是字节流协议,没有消息边界。常用分帧方案:
use tokio_util::codec::{Framed, LinesCodec, LengthDelimitedCodec};
use futures::{StreamExt, SinkExt};
// 方案 1:按行分帧
let framed = Framed::new(socket, LinesCodec::new());
// 方案 2:长度前缀分帧(推荐二进制协议)
let framed = Framed::new(socket, LengthDelimitedCodec::new());
| 分帧方式 | 适用场景 | crate |
|---|---|---|
| 换行符分隔 | 文本协议 | LinesCodec |
| 长度前缀 | 二进制协议 | LengthDelimitedCodec |
| 自定义分隔符 | 特殊协议 | 自定义 Decoder |
常见面试问题
Q1: tokio::spawn 的连接处理方式有什么问题?
答案:
每个连接一个 task 在大量连接时消耗内存(每个 task 约几百字节到几 KB)。对于极高并发(10 万+连接),可考虑使用 io_uring(tokio-uring)或连接池。但对大多数场景,tokio 的 task 已经非常轻量。
Q2: 同步和异步 TCP 如何选择?
答案:
| 场景 | 选择 | 原因 |
|---|---|---|
| 高并发服务器 | tokio::net | 一个线程处理数千连接 |
| 简单工具/脚本 | std::net | 不需要异步运行时 |
| 嵌入式 | std::net 或 smoltcp | 资源受限 |