(tools, client, server) feat: Complete ProtoBuf message transmission with both TCP and UDP
This commit is contained in:
@ -584,7 +584,7 @@ GameObject:
|
|||||||
m_Icon: {fileID: 0}
|
m_Icon: {fileID: 0}
|
||||||
m_NavMeshLayer: 0
|
m_NavMeshLayer: 0
|
||||||
m_StaticEditorFlags: 0
|
m_StaticEditorFlags: 0
|
||||||
m_IsActive: 0
|
m_IsActive: 1
|
||||||
--- !u!4 &1388451206
|
--- !u!4 &1388451206
|
||||||
Transform:
|
Transform:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
|
@ -8,6 +8,8 @@ namespace Network
|
|||||||
{
|
{
|
||||||
public class UnityTcpClient : Singleton<UnityTcpClient>, IDisposable
|
public class UnityTcpClient : Singleton<UnityTcpClient>, IDisposable
|
||||||
{
|
{
|
||||||
|
private const int TcpMaxPayloadSize = 1460;
|
||||||
|
|
||||||
private TcpClient _client;
|
private TcpClient _client;
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
|
|
||||||
@ -34,9 +36,9 @@ namespace Network
|
|||||||
|
|
||||||
await stream.WriteAsync(data, 0, data.Length);
|
await stream.WriteAsync(data, 0, data.Length);
|
||||||
|
|
||||||
var buffer = new byte[1024];
|
var buffer = new byte[TcpMaxPayloadSize];
|
||||||
await stream.ReadAsync(buffer);
|
var len = await stream.ReadAsync(buffer);
|
||||||
return buffer;
|
return buffer[..len];
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
@ -30,11 +30,13 @@ namespace Protocol {
|
|||||||
"Eg8KB21lc3NhZ2UYAiABKAkiMwoNU2lnbnVwUmVxdWVzdBIQCgh1c2VybmFt",
|
"Eg8KB21lc3NhZ2UYAiABKAkiMwoNU2lnbnVwUmVxdWVzdBIQCgh1c2VybmFt",
|
||||||
"ZRgBIAEoCRIQCghwYXNzd29yZBgCIAEoCSJKCg5TaWdudXBSZXNwb25zZRIn",
|
"ZRgBIAEoCRIQCghwYXNzd29yZBgCIAEoCSJKCg5TaWdudXBSZXNwb25zZRIn",
|
||||||
"CgZyZXN1bHQYASABKA4yFy5wcm90b2NvbC5SZXF1ZXN0UmVzdWx0Eg8KB21l",
|
"CgZyZXN1bHQYASABKA4yFy5wcm90b2NvbC5SZXF1ZXN0UmVzdWx0Eg8KB21l",
|
||||||
"c3NhZ2UYAiABKAkqJgoNUmVxdWVzdFJlc3VsdBILCgdTdWNjZXNzEAASCAoE",
|
"c3NhZ2UYAiABKAkqWQoLTWVzc2FnZVR5cGUSEAoMbG9naW5SZXF1ZXN0EAAS",
|
||||||
"RmFpbBABYgZwcm90bzM="));
|
"EQoNbG9naW5SZXNwb25zZRABEhEKDXNpZ251cFJlcXVlc3QQAhISCg5zaWdu",
|
||||||
|
"dXBSZXNwb25zZRADKiYKDVJlcXVlc3RSZXN1bHQSCwoHU3VjY2VzcxAAEggK",
|
||||||
|
"BEZhaWwQAWIGcHJvdG8z"));
|
||||||
descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData,
|
descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData,
|
||||||
new pbr::FileDescriptor[] { },
|
new pbr::FileDescriptor[] { },
|
||||||
new pbr::GeneratedClrTypeInfo(new[] {typeof(global::Protocol.RequestResult), }, null, new pbr::GeneratedClrTypeInfo[] {
|
new pbr::GeneratedClrTypeInfo(new[] {typeof(global::Protocol.MessageType), typeof(global::Protocol.RequestResult), }, null, new pbr::GeneratedClrTypeInfo[] {
|
||||||
new pbr::GeneratedClrTypeInfo(typeof(global::Protocol.LoginRequest), global::Protocol.LoginRequest.Parser, new[]{ "Username", "Password" }, null, null, null, null),
|
new pbr::GeneratedClrTypeInfo(typeof(global::Protocol.LoginRequest), global::Protocol.LoginRequest.Parser, new[]{ "Username", "Password" }, null, null, null, null),
|
||||||
new pbr::GeneratedClrTypeInfo(typeof(global::Protocol.LoginResponse), global::Protocol.LoginResponse.Parser, new[]{ "Result", "Message" }, null, null, null, null),
|
new pbr::GeneratedClrTypeInfo(typeof(global::Protocol.LoginResponse), global::Protocol.LoginResponse.Parser, new[]{ "Result", "Message" }, null, null, null, null),
|
||||||
new pbr::GeneratedClrTypeInfo(typeof(global::Protocol.SignupRequest), global::Protocol.SignupRequest.Parser, new[]{ "Username", "Password" }, null, null, null, null),
|
new pbr::GeneratedClrTypeInfo(typeof(global::Protocol.SignupRequest), global::Protocol.SignupRequest.Parser, new[]{ "Username", "Password" }, null, null, null, null),
|
||||||
@ -45,6 +47,13 @@ namespace Protocol {
|
|||||||
|
|
||||||
}
|
}
|
||||||
#region Enums
|
#region Enums
|
||||||
|
public enum MessageType {
|
||||||
|
[pbr::OriginalName("loginRequest")] LoginRequest = 0,
|
||||||
|
[pbr::OriginalName("loginResponse")] LoginResponse = 1,
|
||||||
|
[pbr::OriginalName("signupRequest")] SignupRequest = 2,
|
||||||
|
[pbr::OriginalName("signupResponse")] SignupResponse = 3,
|
||||||
|
}
|
||||||
|
|
||||||
public enum RequestResult {
|
public enum RequestResult {
|
||||||
[pbr::OriginalName("Success")] Success = 0,
|
[pbr::OriginalName("Success")] Success = 0,
|
||||||
[pbr::OriginalName("Fail")] Fail = 1,
|
[pbr::OriginalName("Fail")] Fail = 1,
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
|
using Google.Protobuf;
|
||||||
using Network;
|
using Network;
|
||||||
using System.Text;
|
using Protocol;
|
||||||
|
using System.Collections.Generic;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace Test
|
namespace Test
|
||||||
@ -8,12 +10,30 @@ namespace Test
|
|||||||
{
|
{
|
||||||
private async void Start()
|
private async void Start()
|
||||||
{
|
{
|
||||||
var sendBytes = Encoding.UTF8.GetBytes("This is a test string sent via TCP.");
|
var request = new LoginRequest
|
||||||
|
{
|
||||||
|
Username = "原神,启动!(通过TCP)",
|
||||||
|
Password = "20200928",
|
||||||
|
};
|
||||||
|
|
||||||
var receivedBytes = await UnityTcpClient.Instance.SendAndReceiveData(sendBytes);
|
var requestBytes = new byte[request.CalculateSize()];
|
||||||
var receivedString = Encoding.UTF8.GetString(receivedBytes);
|
request.WriteTo(requestBytes);
|
||||||
|
|
||||||
Debug.Log($"Received string: {receivedString}");
|
var sendBytes = new List<byte>
|
||||||
|
{
|
||||||
|
(byte)MessageType.LoginRequest
|
||||||
|
};
|
||||||
|
sendBytes.AddRange(requestBytes);
|
||||||
|
|
||||||
|
var responseBytes = await UnityTcpClient.Instance.SendAndReceiveData(sendBytes.ToArray());
|
||||||
|
|
||||||
|
if (responseBytes.Length == 0) return;
|
||||||
|
else if (responseBytes[0] == (byte)MessageType.LoginResponse)
|
||||||
|
{
|
||||||
|
var response = LoginResponse.Parser.ParseFrom(responseBytes[1..]);
|
||||||
|
|
||||||
|
Debug.Log($"Received response: {response}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,5 +1,7 @@
|
|||||||
|
using Google.Protobuf;
|
||||||
using Network;
|
using Network;
|
||||||
using System.Text;
|
using Protocol;
|
||||||
|
using System.Collections.Generic;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace Test
|
namespace Test
|
||||||
@ -8,12 +10,30 @@ namespace Test
|
|||||||
{
|
{
|
||||||
private async void Start()
|
private async void Start()
|
||||||
{
|
{
|
||||||
var sendBytes = Encoding.UTF8.GetBytes("This is a test string sent via UDP.");
|
var request = new LoginRequest
|
||||||
|
{
|
||||||
|
Username = "原神,启动!(谁会通过UDP启动啊喂!)",
|
||||||
|
Password = "20200928",
|
||||||
|
};
|
||||||
|
|
||||||
var receivedBytes = await UnityUdpClient.Instance.SendAndReceiveData(sendBytes);
|
var requestBytes = new byte[request.CalculateSize()];
|
||||||
var receivedString = Encoding.UTF8.GetString(receivedBytes);
|
request.WriteTo(requestBytes);
|
||||||
|
|
||||||
Debug.Log($"Received string: {receivedString}");
|
var sendBytes = new List<byte>
|
||||||
|
{
|
||||||
|
(byte)MessageType.LoginRequest
|
||||||
|
};
|
||||||
|
sendBytes.AddRange(requestBytes);
|
||||||
|
|
||||||
|
var responseBytes = await UnityUdpClient.Instance.SendAndReceiveData(sendBytes.ToArray());
|
||||||
|
|
||||||
|
if (responseBytes.Length == 0) return;
|
||||||
|
else if (responseBytes[0] == (byte)MessageType.LoginResponse)
|
||||||
|
{
|
||||||
|
var response = LoginResponse.Parser.ParseFrom(responseBytes[1..]);
|
||||||
|
|
||||||
|
Debug.Log($"Received response: {response}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,8 +1,8 @@
|
|||||||
mod command_helper;
|
mod command_helper;
|
||||||
|
mod message_dispatcher;
|
||||||
mod protocol;
|
mod protocol;
|
||||||
mod server_logger;
|
mod server_logger;
|
||||||
mod servers;
|
mod servers;
|
||||||
mod services;
|
|
||||||
|
|
||||||
use server_logger::ServerLogger;
|
use server_logger::ServerLogger;
|
||||||
use servers::tcp_server::TCP_SERVER;
|
use servers::tcp_server::TCP_SERVER;
|
||||||
|
48
Server/src/message_dispatcher.rs
Normal file
48
Server/src/message_dispatcher.rs
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
use prost::Message;
|
||||||
|
|
||||||
|
use crate::protocol::{
|
||||||
|
LoginRequest, LoginResponse, MessageType, RequestResult, SignupRequest, SignupResponse,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub(crate) fn dispatch_message(msg_type: u8, msg: &[u8]) -> Vec<u8> {
|
||||||
|
let mut buf = Vec::new();
|
||||||
|
|
||||||
|
match msg_type {
|
||||||
|
// Owing to Rust disallows converting an integer to an
|
||||||
|
// enumeration (yet allows in reverse! ಠ_ಠ), we have to
|
||||||
|
// use such this way to make this pattern matching works.
|
||||||
|
//
|
||||||
|
// This seems not as elegable as what we are expected,
|
||||||
|
// but it could work well, provided that items in
|
||||||
|
// `MessageType` doesn't excceed 256.
|
||||||
|
val if val == MessageType::LoginRequest as u8 => {
|
||||||
|
let msg = LoginRequest::decode(msg).unwrap();
|
||||||
|
|
||||||
|
log::info!("{msg:?}");
|
||||||
|
|
||||||
|
LoginResponse {
|
||||||
|
result: RequestResult::Success.into(),
|
||||||
|
message: "Successfully logged in!".into(),
|
||||||
|
}
|
||||||
|
.encode(&mut buf)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
[vec![MessageType::LoginResponse as u8], buf].concat()
|
||||||
|
}
|
||||||
|
val if val == MessageType::SignupRequest as u8 => {
|
||||||
|
let msg = SignupRequest::decode(msg).unwrap();
|
||||||
|
|
||||||
|
log::info!("{msg:?}");
|
||||||
|
|
||||||
|
SignupResponse {
|
||||||
|
result: RequestResult::Success.into(),
|
||||||
|
message: "Successfully signed up!".into(),
|
||||||
|
}
|
||||||
|
.encode(&mut buf)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
[vec![MessageType::SignupResponse as u8], buf].concat()
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
@ -2,3 +2,5 @@ pub(crate) mod tcp_server;
|
|||||||
pub(crate) mod udp_server;
|
pub(crate) mod udp_server;
|
||||||
|
|
||||||
const SERVER_ADDR: &str = "127.0.0.1:12345";
|
const SERVER_ADDR: &str = "127.0.0.1:12345";
|
||||||
|
const TCP_MAX_PAYLOAD_SIZE: usize = 1460;
|
||||||
|
const UDP_MAX_PAYLOAD_SIZE: usize = 1472;
|
||||||
|
@ -5,10 +5,12 @@ use std::sync::LazyLock;
|
|||||||
|
|
||||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||||
use tokio::net::{TcpListener, TcpStream};
|
use tokio::net::{TcpListener, TcpStream};
|
||||||
use tokio::sync::{Mutex, mpsc};
|
use tokio::sync::Mutex;
|
||||||
|
use tokio::sync::mpsc::{self, Sender};
|
||||||
use tokio::task::JoinHandle;
|
use tokio::task::JoinHandle;
|
||||||
|
|
||||||
use super::SERVER_ADDR;
|
use super::{SERVER_ADDR, TCP_MAX_PAYLOAD_SIZE};
|
||||||
|
use crate::message_dispatcher;
|
||||||
|
|
||||||
pub(crate) static TCP_SERVER: LazyLock<Mutex<TcpServer>> =
|
pub(crate) static TCP_SERVER: LazyLock<Mutex<TcpServer>> =
|
||||||
LazyLock::new(|| Mutex::new(TcpServer::new()));
|
LazyLock::new(|| Mutex::new(TcpServer::new()));
|
||||||
@ -16,7 +18,7 @@ pub(crate) static TCP_SERVER: LazyLock<Mutex<TcpServer>> =
|
|||||||
pub(crate) struct TcpServer {
|
pub(crate) struct TcpServer {
|
||||||
is_running: bool,
|
is_running: bool,
|
||||||
clients: HashMap<SocketAddr, JoinHandle<()>>,
|
clients: HashMap<SocketAddr, JoinHandle<()>>,
|
||||||
shutdown_tx: Option<mpsc::Sender<()>>,
|
shutdown_tx: Option<Sender<()>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TcpServer {
|
impl TcpServer {
|
||||||
@ -31,13 +33,14 @@ impl TcpServer {
|
|||||||
pub(crate) async fn start(&mut self) {
|
pub(crate) async fn start(&mut self) {
|
||||||
if self.is_running {
|
if self.is_running {
|
||||||
log::warn!("TCP server is already running");
|
log::warn!("TCP server is already running");
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
match TcpListener::bind(SERVER_ADDR).await {
|
match TcpListener::bind(SERVER_ADDR).await {
|
||||||
Ok(listener) => {
|
Ok(listener) => {
|
||||||
let (shutdown_tx, shutdown_rx) = mpsc::channel(1);
|
|
||||||
self.is_running = true;
|
self.is_running = true;
|
||||||
|
let (shutdown_tx, shutdown_rx) = mpsc::channel(1);
|
||||||
self.shutdown_tx = Some(shutdown_tx);
|
self.shutdown_tx = Some(shutdown_tx);
|
||||||
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
@ -63,6 +66,7 @@ impl TcpServer {
|
|||||||
|
|
||||||
for (addr, connection) in self.clients.drain() {
|
for (addr, connection) in self.clients.drain() {
|
||||||
log::info!("Closing connection to {}", addr);
|
log::info!("Closing connection to {}", addr);
|
||||||
|
|
||||||
connection.abort();
|
connection.abort();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -79,6 +83,7 @@ impl TcpServer {
|
|||||||
if let Err(e) = Self::handle_client(socket, addr).await {
|
if let Err(e) = Self::handle_client(socket, addr).await {
|
||||||
log::error!("Client {addr} error: {e}");
|
log::error!("Client {addr} error: {e}");
|
||||||
}
|
}
|
||||||
|
|
||||||
log::info!("Client {addr} disconnected");
|
log::info!("Client {addr} disconnected");
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -91,6 +96,7 @@ impl TcpServer {
|
|||||||
|
|
||||||
_ = shutdown_rx.recv() => {
|
_ = shutdown_rx.recv() => {
|
||||||
log::info!("TCP Server shutting down");
|
log::info!("TCP Server shutting down");
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -98,18 +104,18 @@ impl TcpServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_client(mut socket: TcpStream, addr: SocketAddr) -> io::Result<()> {
|
async fn handle_client(mut socket: TcpStream, addr: SocketAddr) -> io::Result<()> {
|
||||||
let mut buffer = [0; 1024];
|
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
|
let mut buffer = [0; TCP_MAX_PAYLOAD_SIZE];
|
||||||
let len = socket.read(&mut buffer).await?;
|
let len = socket.read(&mut buffer).await?;
|
||||||
if len == 0 {
|
if len == 0 {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
log::debug!("Received {} bytes from {}", len, addr);
|
log::info!("Received {} bytes from {}", len, addr);
|
||||||
|
|
||||||
// TODO: Deserialize data
|
let response = message_dispatcher::dispatch_message(buffer[0], &buffer[1..len]);
|
||||||
socket.write_all(&buffer[..len]).await?;
|
|
||||||
|
socket.write_all(&response).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut server = TCP_SERVER.lock().await;
|
let mut server = TCP_SERVER.lock().await;
|
||||||
|
@ -4,7 +4,8 @@ use std::sync::LazyLock;
|
|||||||
use tokio::net::UdpSocket;
|
use tokio::net::UdpSocket;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
use super::SERVER_ADDR;
|
use super::{SERVER_ADDR, UDP_MAX_PAYLOAD_SIZE};
|
||||||
|
use crate::message_dispatcher;
|
||||||
|
|
||||||
pub(crate) static UDP_SERVER: LazyLock<Mutex<UdpServer>> =
|
pub(crate) static UDP_SERVER: LazyLock<Mutex<UdpServer>> =
|
||||||
LazyLock::new(|| Mutex::new(UdpServer::new()));
|
LazyLock::new(|| Mutex::new(UdpServer::new()));
|
||||||
@ -21,6 +22,7 @@ impl UdpServer {
|
|||||||
pub(crate) async fn start(&mut self) {
|
pub(crate) async fn start(&mut self) {
|
||||||
if self.is_running {
|
if self.is_running {
|
||||||
log::warn!("UDP server is already running");
|
log::warn!("UDP server is already running");
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,17 +52,17 @@ impl UdpServer {
|
|||||||
|
|
||||||
async fn handle_client(socket: &UdpSocket) -> io::Result<()> {
|
async fn handle_client(socket: &UdpSocket) -> io::Result<()> {
|
||||||
loop {
|
loop {
|
||||||
let mut buffer = [0; 1024];
|
let mut buffer = [0; UDP_MAX_PAYLOAD_SIZE];
|
||||||
let (len, addr) = socket.recv_from(&mut buffer).await?;
|
let (len, addr) = socket.recv_from(&mut buffer).await?;
|
||||||
if len == 0 {
|
if len == 0 {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
log::info!("Received message from client {addr}");
|
log::info!("Received {} bytes from {}", len, addr);
|
||||||
|
|
||||||
// TODO: Deserialize data
|
let response = message_dispatcher::dispatch_message(buffer[0], &buffer[1..len]);
|
||||||
let buffer = &buffer[..len];
|
|
||||||
socket.send_to(buffer, addr).await?;
|
socket.send_to(&response, addr).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -1 +0,0 @@
|
|||||||
pub(crate) mod game_service;
|
|
@ -1 +0,0 @@
|
|||||||
|
|
@ -2,6 +2,13 @@ syntax = "proto3";
|
|||||||
|
|
||||||
package protocol;
|
package protocol;
|
||||||
|
|
||||||
|
enum MessageType {
|
||||||
|
loginRequest = 0;
|
||||||
|
loginResponse = 1;
|
||||||
|
signupRequest = 2;
|
||||||
|
signupResponse = 3;
|
||||||
|
}
|
||||||
|
|
||||||
enum RequestResult {
|
enum RequestResult {
|
||||||
Success = 0;
|
Success = 0;
|
||||||
Fail = 1;
|
Fail = 1;
|
||||||
|
Reference in New Issue
Block a user