Table of contents
Open Table of contents
项目结构
mini_tarkov_server/
├── Cargo.toml
├── build.rs # protobuf 编译脚本
├── proto/
│ └── game.proto # protobuf 服务定义
└── src/
└── main.rs # server 入口
1. Protobuf 服务定义 (proto/game.proto)
syntax = "proto3";
package game;
service GameService {
rpc Login (LoginRequest) returns (LoginResponse);
rpc GetInventory (GetInventoryRequest) returns (GetInventoryResponse);
}
message LoginRequest {
string username = 1;
string password = 2;
}
message LoginResponse {
string token = 1;
string player_id = 2;
}
message GetInventoryRequest {
string player_id = 1;
}
message Item {
string id = 1;
string name = 2;
int32 quantity = 3;
}
message GetInventoryResponse {
repeated Item items = 1;
}
2. Cargo.toml
[package]
name = "mini_tarkov_server"
version = "0.1.0"
edition = "2024"
[dependencies]
tonic = "0.13"
prost = "0.14"
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
[build-dependencies]
tonic-build = "0.13"
关键依赖说明:
- tonic: gRPC 框架本体(server + client)
- prost: protobuf 序列化/反序列化
- tokio: async runtime(tonic 基于 tokio)
- tonic-build: 编译 .proto 文件生成 Rust 代码(仅构建时使用)
3. 构建脚本 (build.rs)
fn main() -> Result<(), Box<dyn std::error::Error>> {
tonic_build::compile_protos("proto/game.proto")?;
Ok(())
}
cargo build 时,tonic-build 会:
- 读取
proto/game.proto - 生成消息结构体(
LoginRequest,LoginResponse等) - 生成 server trait(
GameService)和 client stub - 输出到
target/下的 OUT_DIR,通过include_proto!引入
4. Server 实现 (src/main.rs)
use tonic::{transport::Server, Request, Response, Status};
// 引入 protobuf 生成的代码
pub mod game {
tonic::include_proto!("game");
}
use game::game_service_server::{GameService, GameServiceServer};
use game::{
GetInventoryRequest, GetInventoryResponse, Item, LoginRequest, LoginResponse,
};
// 定义 service 实现结构体
#[derive(Debug, Default)]
pub struct GameServiceImpl {}
// 实现生成的 trait
#[tonic::async_trait]
impl GameService for GameServiceImpl {
async fn login(
&self,
request: Request<LoginRequest>,
) -> Result<Response<LoginResponse>, Status> {
let req = request.into_inner();
println!("Login attempt: {}", req.username);
// 实际项目中这里做认证逻辑
let response = LoginResponse {
token: "fake-jwt-token".to_string(),
player_id: "player_001".to_string(),
};
Ok(Response::new(response))
}
async fn get_inventory(
&self,
request: Request<GetInventoryRequest>,
) -> Result<Response<GetInventoryResponse>, Status> {
let req = request.into_inner();
println!("GetInventory for player: {}", req.player_id);
let response = GetInventoryResponse {
items: vec![
Item {
id: "item_1".to_string(),
name: "AK-47".to_string(),
quantity: 1,
},
Item {
id: "item_2".to_string(),
name: "Bandage".to_string(),
quantity: 5,
},
],
};
Ok(Response::new(response))
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let addr = "[::1]:50051".parse()?;
let service = GameServiceImpl::default();
println!("GameService listening on {}", addr);
Server::builder()
.add_service(GameServiceServer::new(service))
.serve(addr)
.await?;
Ok(())
}
5. 核心模式总结
.proto 文件
│
▼ (build.rs / tonic-build)
生成的 Rust 代码 (trait + 消息结构体)
│
▼ (impl trait for YourStruct)
你的业务逻辑
│
▼ (Server::builder().add_service())
运行中的 gRPC server
流程就三步:
- 写
.proto定义接口和消息 build.rs编译 proto → 生成 traitimpl这个 trait,填入业务逻辑,挂到Server::builder()上
6. Streaming RPC(补充)
tonic 支持四种 RPC 模式:
| 模式 | proto 写法 | Rust 返回类型 |
|---|---|---|
| Unary | rpc Foo(Req) returns (Res) | Result<Response<Res>, Status> |
| Server streaming | rpc Foo(Req) returns (stream Res) | Result<Response<Self::FooStream>, Status> |
| Client streaming | rpc Foo(stream Req) returns (Res) | Result<Response<Res>, Status>,参数为 Request<Streaming<Req>> |
| Bidirectional | rpc Foo(stream Req) returns (stream Res) | 两者结合 |
Server streaming 示例:
type GetEventsStream = Pin<Box<dyn Stream<Item = Result<Event, Status>> + Send>>;
async fn get_events(
&self,
request: Request<EventFilter>,
) -> Result<Response<Self::GetEventsStream>, Status> {
let (tx, rx) = tokio::sync::mpsc::channel(32);
tokio::spawn(async move {
// 持续推送事件
tx.send(Ok(Event { ... })).await.unwrap();
});
Ok(Response::new(Box::pin(ReceiverStream::new(rx))))
}
7. 运行和测试
# 构建并运行
cargo run
# 用 grpcurl 测试(需安装 grpcurl)
grpcurl -plaintext -d '{"username":"player1","password":"123"}' \
localhost:50051 game.GameService/Login