(tools, server) feat: Replace TCP server with gRPC server
This commit is contained in:
810
Server/Cargo.lock
generated
810
Server/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -8,4 +8,10 @@ description = "The game server."
|
|||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
colored = "3"
|
colored = "3"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
# The prost version must match with that of tonic, or the building will fail.
|
||||||
|
prost = "0.13"
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
|
tonic = "0.13"
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
tonic-build = "0.13"
|
||||||
|
10
Server/build.rs
Normal file
10
Server/build.rs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
use std::env;
|
||||||
|
use std::io;
|
||||||
|
|
||||||
|
fn main() -> io::Result<()> {
|
||||||
|
unsafe {
|
||||||
|
env::set_var("PROTOC", "../Tools/ProtoBuf/bin/protoc.exe");
|
||||||
|
}
|
||||||
|
tonic_build::compile_protos("../Tools/ProtoBuf/proto/message.proto")?;
|
||||||
|
Ok(())
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
mod command_helper;
|
mod command_helper;
|
||||||
mod network_service;
|
mod network_service;
|
||||||
|
mod protocol;
|
||||||
mod server_logger;
|
mod server_logger;
|
||||||
|
|
||||||
use network_service::NetworkService;
|
use network_service::NetworkService;
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
mod message_dispatcher;
|
mod grpc_server;
|
||||||
mod tcp_server;
|
|
||||||
|
|
||||||
use tcp_server::TcpServer;
|
use grpc_server::GrpcServer;
|
||||||
|
|
||||||
pub(crate) struct NetworkService;
|
pub(crate) struct NetworkService;
|
||||||
|
|
||||||
@ -9,7 +8,7 @@ impl NetworkService {
|
|||||||
pub(crate) async fn init() {
|
pub(crate) async fn init() {
|
||||||
log::info!("Starting network service...");
|
log::info!("Starting network service...");
|
||||||
|
|
||||||
tokio::spawn(async { TcpServer::init().await });
|
tokio::spawn(async { GrpcServer::init().await });
|
||||||
|
|
||||||
log::info!("network service successfully started!");
|
log::info!("network service successfully started!");
|
||||||
}
|
}
|
||||||
|
73
Server/src/network_service/grpc_server.rs
Normal file
73
Server/src/network_service/grpc_server.rs
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
use std::net::SocketAddr;
|
||||||
|
|
||||||
|
use tonic::transport::Server;
|
||||||
|
use tonic::{Request, Response, Status};
|
||||||
|
|
||||||
|
use crate::protocol::game_service_server::{GameService, GameServiceServer};
|
||||||
|
use crate::protocol::general_service_server::{GeneralService, GeneralServiceServer};
|
||||||
|
use crate::protocol::{
|
||||||
|
Empty, LoginRequest, LoginResponse, RequestResult, ServerInfo, SignupRequest, SignupResponse,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub(crate) struct GrpcServer;
|
||||||
|
|
||||||
|
impl GrpcServer {
|
||||||
|
pub(crate) async fn init() {
|
||||||
|
let addr = SocketAddr::new([127, 0, 0, 1].into(), 12345);
|
||||||
|
|
||||||
|
let general_service = GeneralServiceServer::new(GeneralServiceImpl);
|
||||||
|
let game_service = GameServiceServer::new(GameServiceImpl);
|
||||||
|
|
||||||
|
Server::builder()
|
||||||
|
.add_service(general_service)
|
||||||
|
.add_service(game_service)
|
||||||
|
.serve(addr)
|
||||||
|
.await
|
||||||
|
.unwrap_or_else(|e| log::error!("Failed to build server: {e}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define server implementations
|
||||||
|
|
||||||
|
struct GeneralServiceImpl;
|
||||||
|
struct GameServiceImpl;
|
||||||
|
|
||||||
|
#[tonic::async_trait]
|
||||||
|
impl GeneralService for GeneralServiceImpl {
|
||||||
|
async fn get_server_info(
|
||||||
|
&self,
|
||||||
|
_request: Request<Empty>,
|
||||||
|
) -> Result<Response<ServerInfo>, Status> {
|
||||||
|
Ok(Response::new(ServerInfo {
|
||||||
|
lang: "Rust".into(),
|
||||||
|
ver: "0.1.0".into(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tonic::async_trait]
|
||||||
|
impl GameService for GameServiceImpl {
|
||||||
|
async fn login(
|
||||||
|
&self,
|
||||||
|
request: Request<LoginRequest>,
|
||||||
|
) -> Result<Response<LoginResponse>, Status> {
|
||||||
|
log::info!("User {} logged in!", request.get_ref().username);
|
||||||
|
|
||||||
|
Ok(Response::new(LoginResponse {
|
||||||
|
result: RequestResult::Success.into(),
|
||||||
|
message: "".into(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn signup(
|
||||||
|
&self,
|
||||||
|
request: Request<SignupRequest>,
|
||||||
|
) -> Result<Response<SignupResponse>, Status> {
|
||||||
|
log::info!("User {} signed up!", request.get_ref().username);
|
||||||
|
|
||||||
|
Ok(Response::new(SignupResponse {
|
||||||
|
result: RequestResult::Success.into(),
|
||||||
|
message: "".into(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
@ -1,68 +0,0 @@
|
|||||||
use std::collections::HashMap;
|
|
||||||
use std::sync::LazyLock;
|
|
||||||
|
|
||||||
use tokio::sync::RwLock;
|
|
||||||
|
|
||||||
pub(crate) struct MessageDispatcher<H>
|
|
||||||
where
|
|
||||||
H: Fn(String) + Send + Sync,
|
|
||||||
{
|
|
||||||
handlers: RwLock<HashMap<String, H>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) static DISPATCHER: LazyLock<MessageDispatcher<Box<dyn Fn(String) + Send + Sync>>> =
|
|
||||||
LazyLock::new(MessageDispatcher::new);
|
|
||||||
|
|
||||||
impl<H> MessageDispatcher<H>
|
|
||||||
where
|
|
||||||
H: Fn(String) + Send + Sync,
|
|
||||||
{
|
|
||||||
fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
handlers: RwLock::new(HashMap::new()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) async fn register_handler(&self, handler_name: String, handler: H) {
|
|
||||||
let mut handlers = self.handlers.write().await;
|
|
||||||
handlers.insert(handler_name, handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) async fn dispatch(&self, handler_name: String, message: String) {
|
|
||||||
let handlers = self.handlers.read().await;
|
|
||||||
|
|
||||||
match handlers.get(&handler_name) {
|
|
||||||
Some(handler) => handler(message),
|
|
||||||
None => log::error!(
|
|
||||||
"The handler corresponds to this kind of message hasn't been registered!"
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mod test {
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_dispatch() {
|
|
||||||
use super::DISPATCHER;
|
|
||||||
|
|
||||||
DISPATCHER
|
|
||||||
.register_handler(
|
|
||||||
"UserLogin".into(),
|
|
||||||
Box::new(|message| println!("Received message: {message}")),
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
DISPATCHER
|
|
||||||
.register_handler(
|
|
||||||
"UserSignUp".into(),
|
|
||||||
Box::new(|message| println!("Received message: {message}")),
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
DISPATCHER
|
|
||||||
.dispatch("UserLogin".into(), "I want to login".into())
|
|
||||||
.await;
|
|
||||||
DISPATCHER
|
|
||||||
.dispatch("UserSignUp".into(), "I want to signup".into())
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,72 +0,0 @@
|
|||||||
use tokio::io::{self, ErrorKind, Interest};
|
|
||||||
use tokio::net::{TcpListener, TcpStream};
|
|
||||||
use tokio::time::{self, Duration};
|
|
||||||
|
|
||||||
pub(crate) struct TcpServer;
|
|
||||||
|
|
||||||
impl TcpServer {
|
|
||||||
pub(crate) async fn init() {
|
|
||||||
match TcpListener::bind("127.0.0.1:12345").await {
|
|
||||||
Ok(listener) => loop {
|
|
||||||
match listener.accept().await {
|
|
||||||
Ok((socket, addr)) => {
|
|
||||||
log::info!("New client connected: {addr}");
|
|
||||||
|
|
||||||
tokio::spawn(async move {
|
|
||||||
let mut retry_times = 0;
|
|
||||||
|
|
||||||
while retry_times <= 3 {
|
|
||||||
let result = Self::process(&socket).await;
|
|
||||||
|
|
||||||
match result {
|
|
||||||
Ok(_) => continue,
|
|
||||||
Err(e) if e.kind() == ErrorKind::WouldBlock => continue,
|
|
||||||
Err(e) => {
|
|
||||||
if retry_times == 3 {
|
|
||||||
log::error!("Failed to process this socket: {e}");
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
retry_times += 1;
|
|
||||||
|
|
||||||
log::error!(
|
|
||||||
"Failed to process this socket: {e}. Retry {retry_times} time(s)..."
|
|
||||||
);
|
|
||||||
|
|
||||||
time::sleep(Duration::from_secs(1)).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Err(e) => log::error!("Couldn't get client: {e:?}"),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(e) => log::error!("Failed to bind port: {e:?}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn process(socket: &TcpStream) -> io::Result<()> {
|
|
||||||
let ready = socket
|
|
||||||
.ready(Interest::READABLE | Interest::WRITABLE)
|
|
||||||
.await?;
|
|
||||||
if ready.is_readable() {
|
|
||||||
// Size of per received data shound be smaller than 1KiB.
|
|
||||||
let mut buffer = [0; 1024];
|
|
||||||
let size = socket.try_read(&mut buffer)?;
|
|
||||||
|
|
||||||
log::info!("Received {size} Bytes message.")
|
|
||||||
|
|
||||||
// TODO: Start dispatching messages here.
|
|
||||||
}
|
|
||||||
if ready.is_writable() {
|
|
||||||
// TODO: Start sending back message here.
|
|
||||||
let size = socket.try_write(b"Hello from server written in Rust!")?;
|
|
||||||
|
|
||||||
log::info!("Sent {size} Bytes message.")
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
1
Server/src/protocol.rs
Normal file
1
Server/src/protocol.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
tonic::include_proto!("protocol");
|
@ -1,3 +1,52 @@
|
|||||||
syntax = "proto3";
|
syntax = "proto3";
|
||||||
|
|
||||||
option csharp_namespace = "Protocol";
|
package protocol;
|
||||||
|
|
||||||
|
// Define services
|
||||||
|
|
||||||
|
service GeneralService {
|
||||||
|
// Get server info from server.
|
||||||
|
//
|
||||||
|
// This parameter actually doesn't accept any arguments,
|
||||||
|
// but it is still required owing to Protobuf grammar.
|
||||||
|
rpc GetServerInfo(Empty) returns (ServerInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
service GameService {
|
||||||
|
rpc Login(LoginRequest) returns (LoginResponse);
|
||||||
|
rpc Signup(SignupRequest) returns (SignupResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define messages
|
||||||
|
|
||||||
|
enum RequestResult {
|
||||||
|
Success = 0;
|
||||||
|
Fail = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Empty {}
|
||||||
|
|
||||||
|
message ServerInfo {
|
||||||
|
string Lang = 1;
|
||||||
|
string ver = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message LoginRequest {
|
||||||
|
string Username = 1;
|
||||||
|
string Password = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message LoginResponse {
|
||||||
|
RequestResult result = 1;
|
||||||
|
string message = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SignupRequest {
|
||||||
|
string Username = 1;
|
||||||
|
string Password = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SignupResponse {
|
||||||
|
RequestResult result = 1;
|
||||||
|
string message = 2;
|
||||||
|
}
|
Reference in New Issue
Block a user