mirror of
https://github.com/LHY0125/Gobang-Game.git
synced 2026-06-28 16:35:55 +08:00
feat: 实现 NetworkLoop::run — Server/Client renet 主循环
添加 renet2_netcode 依赖,使用 renet2 + renet2_netcode + renetcode2 三 crate 架构实现完整网络循环。Server 端监听 UDP 端口并通过 channel 广播游戏消息,Client 端连接并双工通信。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -11,4 +11,5 @@ serde = { version = "1", features = ["derive"] }
|
|||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
reqwest = { version = "0.12", features = ["json", "blocking"] }
|
reqwest = { version = "0.12", features = ["json", "blocking"] }
|
||||||
renet2 = "0.15"
|
renet2 = "0.15"
|
||||||
|
renet2_netcode = "0.15"
|
||||||
bincode = "1"
|
bincode = "1"
|
||||||
|
|||||||
+239
-2
@@ -81,6 +81,234 @@ impl NetworkLoop {
|
|||||||
event_tx,
|
event_tx,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 启动网络主循环(阻塞,在独立线程中调用)
|
||||||
|
pub fn run(&mut self, server_addr: &str, protocol_id: u64) -> Result<(), String> {
|
||||||
|
self.running = true;
|
||||||
|
match self.role {
|
||||||
|
NetworkRole::Server => self.run_server(protocol_id),
|
||||||
|
NetworkRole::Client => self.run_client(server_addr, protocol_id),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_server(&mut self, protocol_id: u64) -> Result<(), String> {
|
||||||
|
use std::net::UdpSocket;
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
let socket = UdpSocket::bind("0.0.0.0:0").map_err(|e| format!("Server 绑定失败: {}", e))?;
|
||||||
|
let local_addr = socket.local_addr().map_err(|e| e.to_string())?;
|
||||||
|
let local_port = local_addr.port();
|
||||||
|
let _ = self.event_tx.send(NetworkEvent::Listening(local_port));
|
||||||
|
|
||||||
|
let connection_config =
|
||||||
|
renet2::ConnectionConfig::from_shared_channels(vec![renet2::ChannelConfig {
|
||||||
|
channel_id: 0,
|
||||||
|
max_memory_usage_bytes: 5 * 1024 * 1024,
|
||||||
|
send_type: renet2::SendType::ReliableOrdered {
|
||||||
|
resend_time: Duration::from_millis(300),
|
||||||
|
},
|
||||||
|
}]);
|
||||||
|
let mut server = renet2::RenetServer::new(connection_config);
|
||||||
|
|
||||||
|
let current_time = std::time::SystemTime::now()
|
||||||
|
.duration_since(std::time::UNIX_EPOCH)
|
||||||
|
.unwrap_or_default();
|
||||||
|
let server_config = renet2_netcode::ServerSetupConfig {
|
||||||
|
current_time,
|
||||||
|
max_clients: 1,
|
||||||
|
protocol_id,
|
||||||
|
socket_addresses: vec![vec![local_addr]],
|
||||||
|
authentication: renet2_netcode::ServerAuthentication::Unsecure,
|
||||||
|
};
|
||||||
|
let native_socket = renet2_netcode::NativeSocket::new(socket)
|
||||||
|
.map_err(|e| format!("创建 NativeSocket 失败: {}", e))?;
|
||||||
|
let mut transport =
|
||||||
|
renet2_netcode::NetcodeServerTransport::new(server_config, native_socket)
|
||||||
|
.map_err(|e| format!("创建传输层失败: {}", e))?;
|
||||||
|
|
||||||
|
let tick = Duration::from_millis(16);
|
||||||
|
|
||||||
|
while self.running {
|
||||||
|
let now = Instant::now();
|
||||||
|
|
||||||
|
// 处理 commands 层发来的指令
|
||||||
|
while let Ok(cmd) = self.cmd_rx.try_recv() {
|
||||||
|
match cmd {
|
||||||
|
NetworkCmd::SendMove { x, y, turn } => {
|
||||||
|
let msg = NetMessage::Move { x, y, turn };
|
||||||
|
for cid in server.clients_id() {
|
||||||
|
server.send_message(cid, 0u8, msg.to_bytes());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NetworkCmd::SendUndo { steps } => {
|
||||||
|
let msg = NetMessage::Undo { steps };
|
||||||
|
for cid in server.clients_id() {
|
||||||
|
server.send_message(cid, 0u8, msg.to_bytes());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NetworkCmd::SendResign => {
|
||||||
|
for cid in server.clients_id() {
|
||||||
|
server.send_message(cid, 0u8, NetMessage::Resign.to_bytes());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NetworkCmd::Shutdown => {
|
||||||
|
self.running = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
server.update(tick);
|
||||||
|
transport
|
||||||
|
.update(tick, &mut server)
|
||||||
|
.map_err(|e| format!("传输层更新失败: {e:?}"))?;
|
||||||
|
|
||||||
|
// 接收客户端消息
|
||||||
|
for cid in server.clients_id() {
|
||||||
|
while let Some(data) = server.receive_message(cid, 0u8) {
|
||||||
|
if let Some(msg) = NetMessage::from_bytes(&data) {
|
||||||
|
match msg {
|
||||||
|
NetMessage::Move { x, y, .. } => {
|
||||||
|
let _ = self.event_tx.send(NetworkEvent::RemoteMove { x, y });
|
||||||
|
}
|
||||||
|
NetMessage::Undo { steps } => {
|
||||||
|
let _ = self.event_tx.send(NetworkEvent::RemoteUndo { steps });
|
||||||
|
}
|
||||||
|
NetMessage::Resign => {
|
||||||
|
let _ = self.event_tx.send(NetworkEvent::RemoteResign);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理连接事件
|
||||||
|
while let Some(event) = server.get_event() {
|
||||||
|
match event {
|
||||||
|
renet2::ServerEvent::ClientConnected { .. } => {
|
||||||
|
let _ = self.event_tx.send(NetworkEvent::ClientConnected);
|
||||||
|
}
|
||||||
|
renet2::ServerEvent::ClientDisconnected { .. } => {
|
||||||
|
let _ = self.event_tx.send(NetworkEvent::ClientDisconnected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
transport.send_packets(&mut server);
|
||||||
|
|
||||||
|
let elapsed = now.elapsed();
|
||||||
|
if elapsed < tick {
|
||||||
|
std::thread::sleep(tick - elapsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_client(&mut self, server_addr: &str, protocol_id: u64) -> Result<(), String> {
|
||||||
|
use std::net::{SocketAddr, UdpSocket};
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
let server_addr: SocketAddr = server_addr
|
||||||
|
.parse()
|
||||||
|
.map_err(|e| format!("地址解析失败: {}", e))?;
|
||||||
|
let socket = UdpSocket::bind("0.0.0.0:0").map_err(|e| format!("Client 绑定失败: {}", e))?;
|
||||||
|
|
||||||
|
let connection_config =
|
||||||
|
renet2::ConnectionConfig::from_shared_channels(vec![renet2::ChannelConfig {
|
||||||
|
channel_id: 0,
|
||||||
|
max_memory_usage_bytes: 5 * 1024 * 1024,
|
||||||
|
send_type: renet2::SendType::ReliableOrdered {
|
||||||
|
resend_time: Duration::from_millis(300),
|
||||||
|
},
|
||||||
|
}]);
|
||||||
|
let mut client = renet2::RenetClient::new(connection_config, false);
|
||||||
|
|
||||||
|
let current_time = std::time::SystemTime::now()
|
||||||
|
.duration_since(std::time::UNIX_EPOCH)
|
||||||
|
.unwrap_or_default();
|
||||||
|
let authentication = renet2_netcode::ClientAuthentication::Unsecure {
|
||||||
|
server_addr,
|
||||||
|
client_id: current_time.as_millis() as u64,
|
||||||
|
user_data: None,
|
||||||
|
protocol_id,
|
||||||
|
socket_id: 0,
|
||||||
|
};
|
||||||
|
let native_socket = renet2_netcode::NativeSocket::new(socket)
|
||||||
|
.map_err(|e| format!("创建 NativeSocket 失败: {}", e))?;
|
||||||
|
let mut transport = renet2_netcode::NetcodeClientTransport::new(
|
||||||
|
current_time,
|
||||||
|
authentication,
|
||||||
|
native_socket,
|
||||||
|
)
|
||||||
|
.map_err(|e| format!("创建传输层失败: {}", e))?;
|
||||||
|
|
||||||
|
let tick = Duration::from_millis(16);
|
||||||
|
let mut was_connected = false;
|
||||||
|
|
||||||
|
while self.running {
|
||||||
|
let now = Instant::now();
|
||||||
|
|
||||||
|
while let Ok(cmd) = self.cmd_rx.try_recv() {
|
||||||
|
match cmd {
|
||||||
|
NetworkCmd::SendMove { x, y, turn } => {
|
||||||
|
let msg = NetMessage::Move { x, y, turn };
|
||||||
|
client.send_message(0u8, msg.to_bytes());
|
||||||
|
}
|
||||||
|
NetworkCmd::SendUndo { steps } => {
|
||||||
|
let msg = NetMessage::Undo { steps };
|
||||||
|
client.send_message(0u8, msg.to_bytes());
|
||||||
|
}
|
||||||
|
NetworkCmd::SendResign => {
|
||||||
|
client.send_message(0u8, NetMessage::Resign.to_bytes());
|
||||||
|
}
|
||||||
|
NetworkCmd::Shutdown => {
|
||||||
|
self.running = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
client.update(tick);
|
||||||
|
transport
|
||||||
|
.update(tick, &mut client)
|
||||||
|
.map_err(|e| format!("传输层更新失败: {e:?}"))?;
|
||||||
|
|
||||||
|
if client.is_connected() && !was_connected {
|
||||||
|
was_connected = true;
|
||||||
|
let _ = self.event_tx.send(NetworkEvent::Connected);
|
||||||
|
}
|
||||||
|
if !client.is_connected() && was_connected {
|
||||||
|
was_connected = false;
|
||||||
|
let _ = self.event_tx.send(NetworkEvent::ClientDisconnected);
|
||||||
|
}
|
||||||
|
|
||||||
|
while let Some(data) = client.receive_message(0u8) {
|
||||||
|
if let Some(msg) = NetMessage::from_bytes(&data) {
|
||||||
|
match msg {
|
||||||
|
NetMessage::Move { x, y, .. } => {
|
||||||
|
let _ = self.event_tx.send(NetworkEvent::RemoteMove { x, y });
|
||||||
|
}
|
||||||
|
NetMessage::Undo { steps } => {
|
||||||
|
let _ = self.event_tx.send(NetworkEvent::RemoteUndo { steps });
|
||||||
|
}
|
||||||
|
NetMessage::Resign => {
|
||||||
|
let _ = self.event_tx.send(NetworkEvent::RemoteResign);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
transport
|
||||||
|
.send_packets(&mut client)
|
||||||
|
.map_err(|e| format!("发送数据包失败: {e}"))?;
|
||||||
|
|
||||||
|
let elapsed = now.elapsed();
|
||||||
|
if elapsed < tick {
|
||||||
|
std::thread::sleep(tick - elapsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -89,7 +317,11 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_net_message_move_roundtrip() {
|
fn test_net_message_move_roundtrip() {
|
||||||
let msg = NetMessage::Move { x: 7, y: 7, turn: 0 };
|
let msg = NetMessage::Move {
|
||||||
|
x: 7,
|
||||||
|
y: 7,
|
||||||
|
turn: 0,
|
||||||
|
};
|
||||||
let bytes = msg.to_bytes();
|
let bytes = msg.to_bytes();
|
||||||
let decoded = NetMessage::from_bytes(&bytes).unwrap();
|
let decoded = NetMessage::from_bytes(&bytes).unwrap();
|
||||||
match decoded {
|
match decoded {
|
||||||
@@ -121,7 +353,12 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_network_cmd_channel() {
|
fn test_network_cmd_channel() {
|
||||||
let (tx, rx) = std::sync::mpsc::channel();
|
let (tx, rx) = std::sync::mpsc::channel();
|
||||||
tx.send(NetworkCmd::SendMove { x: 7, y: 7, turn: 0 }).unwrap();
|
tx.send(NetworkCmd::SendMove {
|
||||||
|
x: 7,
|
||||||
|
y: 7,
|
||||||
|
turn: 0,
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
tx.send(NetworkCmd::Shutdown).unwrap();
|
tx.send(NetworkCmd::Shutdown).unwrap();
|
||||||
|
|
||||||
let mut received = Vec::new();
|
let mut received = Vec::new();
|
||||||
|
|||||||
Reference in New Issue
Block a user