Abel'Blog

我干了什么?究竟拿了时间换了什么?

0%

rust-tcp-echo-svr

概述

学习一些rust的tcp用法

rusticon

以下是一个使用 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 服务器!