跳到主要内容

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_uringtokio-uring)或连接池。但对大多数场景,tokio 的 task 已经非常轻量。

Q2: 同步和异步 TCP 如何选择?

答案

场景选择原因
高并发服务器tokio::net一个线程处理数千连接
简单工具/脚本std::net不需要异步运行时
嵌入式std::netsmoltcp资源受限

相关链接