概述
学习一些rust的tcp用法
以下是一个使用 Rust 实现的最简单的 TCP Echo 服务器示例代码:
代码示例
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!(“Client disconnected”);
break;
}
Ok(size) => {
// 将收到的消息原样返回
println!(“Received: {}”, String::from_utf8_lossy(&buffer[..size]));
if let Err(e) = stream.write_all(&buffer[..size]) {
eprintln!(“Failed to send data: {}”, e);
break;
}
}
Err(e) => {
// 读取出错
eprintln!(“Error reading from client: {}”, e);
break;
}
}
}
}
fn main() -> std::io::Result<()> {
let listener = TcpListener::bind(“127.0.0.1:8080”)?; // 绑定到本地的 8080 端口
println!(“Server listening on 127.0.0.1:8080”);
for stream in listener.incoming() {
match stream {
Ok(stream) => {
println!("New connection: {}", stream.peer_addr()?);
// 每个连接使用一个新线程处理
thread::spawn(move || handle_client(stream));
}
Err(e) => {
eprintln!("Connection failed: {}", e);
}
}
}
Ok(())
}
代码说明
1. TcpListener: 用于监听指定的地址和端口。
2. TcpStream: 表示与客户端之间的连接。
3. handle_client: 一个处理客户端连接的函数,读取数据并回传(Echo)。
4. 多线程: 每次有新连接时创建一个线程处理,以避免阻塞其他连接。
5. 缓冲区: 使用了一个固定大小的缓冲区(512 字节)来存储客户端发送的数据。
运行方法
1. 编译和运行:
rustc echo_server.rs
./echo_server
2. 测试服务器:
打开另一个终端,使用 telnet 或 nc 工具测试:
nc 127.0.0.1 8080
输入任意文本,服务器会原样返回。
输出示例
1. 服务器终端输出:
Server listening on 127.0.0.1:8080
New connection: 127.0.0.1:52832
Received: Hello, server!
2. 客户端终端:
Hello, server!
此代码简单易懂,可作为入门 TCP 服务器的基础代码。
下面是将 TCP Echo 服务器改造为支持 Protobuf 的简单例子。服务器会接收客户端发送的 Protobuf 消息,解析后打印,并以 Protobuf 格式返回消息。
改造步骤
1. 定义 Protobuf 消息格式:
创建 proto/message.proto 文件:
syntax = “proto3”;
package example;
message EchoMessage {
string content = 1;
}
2. 设置 build.rs:
使用 prost-build 编译 Protobuf 文件:
fn main() {
prost_build::compile_protos(&[“proto/message.proto”], &[“proto/“])
.expect(“Failed to compile Protobuf files”);
}
3. 安装依赖:
在 Cargo.toml 中添加:
[dependencies]
prost = “0.11”
prost-types = “0.11”
tokio = { version = “1.0”, features = [“full”] }
bytes = “1.4”
[build-dependencies]
prost-build = “0.11”
4. 实现服务器:
以下是完整代码:
use prost::Message;
use tokio::net::{TcpListener, TcpStream};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
mod example {
include!(concat!(env!(“OUT_DIR”), “/example.rs”));
}
use example::EchoMessage;
// 处理客户端的连接
async fn handle_client(mut stream: TcpStream) -> tokio::io::Result<()> {
let mut buffer = vec![0; 1024]; // 用于接收数据
loop {
// 读取客户端发送的数据
let bytes_read = stream.read(&mut buffer).await?;
if bytes_read == 0 {
println!("Client disconnected");
return Ok(());
}
// 解码 Protobuf 消息
let received_message = EchoMessage::decode(&buffer[..bytes_read])
.map_err(|e| {
eprintln!("Failed to decode message: {}", e);
tokio::io::Error::new(tokio::io::ErrorKind::InvalidData, "Decode error")
})?;
println!("Received: {}", received_message.content);
// 构造响应消息
let response_message = EchoMessage {
content: format!("Echo: {}", received_message.content),
};
// 编码消息并发送回客户端
let mut encoded = Vec::new();
response_message.encode(&mut encoded).unwrap();
stream.write_all(&encoded).await?;
}
}
[tokio::main]
async fn main() -> tokio::io::Result<()> {
let listener = TcpListener::bind(“127.0.0.1:8080”).await?;
println!(“Server listening on 127.0.0.1:8080”);
loop {
let (stream, addr) = listener.accept().await?;
println!("New connection: {}", addr);
// 每个客户端使用一个异步任务处理
tokio::spawn(async move {
if let Err(e) = handle_client(stream).await {
eprintln!("Error handling client: {}", e);
}
});
}
}
客户端测试代码
这里使用 Rust 实现一个简单的客户端来测试服务器:
use prost::Message;
use tokio::net::TcpStream;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
mod example {
include!(concat!(env!(“OUT_DIR”), “/example.rs”));
}
use example::EchoMessage;
[tokio::main]
async fn main() -> tokio::io::Result<()> {
let mut stream = TcpStream::connect(“127.0.0.1:8080”).await?;
println!(“Connected to server”);
// 创建一个消息
let message = EchoMessage {
content: "Hello, Protobuf!".to_string(),
};
// 编码消息并发送到服务器
let mut encoded = Vec::new();
message.encode(&mut encoded).unwrap();
stream.write_all(&encoded).await?;
// 接收服务器返回的响应
let mut buffer = vec![0; 1024];
let bytes_read = stream.read(&mut buffer).await?;
let response = EchoMessage::decode(&buffer[..bytes_read]).unwrap();
println!("Response from server: {}", response.content);
Ok(())
}
运行与测试
1. 启动服务器:
cargo run —bin server
2. 启动客户端:
在另一个终端运行:
cargo run —bin client
3. 结果示例:
• 服务器终端输出:
Server listening on 127.0.0.1:8080
New connection: 127.0.0.1:53000
Received: Hello, Protobuf!
• 客户端终端输出:
Connected to server
Response from server: Echo: Hello, Protobuf!
说明
• 服务器使用 prost 解析和生成 Protobuf 消息。
• 采用 tokio 提供异步网络支持,能够高效处理多个客户端连接。
• 客户端使用相同的 Protobuf 定义,与服务器实现完全兼容。
这样,您就实现了一个支持 Protobuf 的简单 TCP Echo 服务器!