From 3b17baf7391b4f8d92e0c1f4fcdde48b580a18c1 Mon Sep 17 00:00:00 2001 From: MrMaho Date: Sun, 27 Sep 2026 15:03:51 +0200 Subject: [PATCH] Performance: frame broadcasts once, Socket.Select instead of fixed sleep, reuse message list Co-Authored-By: Claude Opus 5.5 --- GameServer.cs | 22 ++++++++++++++++++++-- Protocol.cs | 23 +++++++++++++++++------ 2 files changed, 37 insertions(+), 8 deletions(-) diff --git a/GameServer.cs b/GameServer.cs index 2763ad1..18a6f14 100644 --- a/GameServer.cs +++ b/GameServer.cs @@ -154,7 +154,7 @@ public sealed class GameServer PollClients(); RunCommands(); Timers(); - Thread.Sleep(2); + WaitForTraffic(); } foreach (var c in clients.Values) @@ -672,8 +672,26 @@ public sealed class GameServer void Broadcast(MsgType type, byte[] payload, int except = -1) { + byte[] frame = null; // nur einmal rahmen, nicht pro Spieler foreach (var c in clients.Values) - if (c.Id != except && c.State == ClientState.Joined) c.Conn.Send(type, payload); + if (c.Id != except && c.State == ClientState.Joined) c.Conn.SendFrame(frame ??= Wire.Frame(type, payload)); + } + + readonly List waitList = new(); + + /// + /// Bis zu ein paar Millisekunden warten, aber sofort weitermachen, sobald ein Spieler etwas schickt oder jemand + /// verbinden will - schneller als festes Schlafen und ohne Leerlauf-Last. + /// + void WaitForTraffic() + { + waitList.Clear(); + waitList.Add(listener.Server); + foreach (var c in clients.Values) + if (!c.Conn.IsClosed) waitList.Add(c.Conn.Socket); + int micros = clients.Count == 0 ? 50_000 : 5_000; // leerer Server: seltener aufwachen + try { Socket.Select(waitList, null, null, micros); } + catch { Thread.Sleep(2); } // z.B. Socket wurde gerade geschlossen } // ---------------------------------------------------------------- Konsole diff --git a/Protocol.cs b/Protocol.cs index 0de748a..d0e88e6 100644 --- a/Protocol.cs +++ b/Protocol.cs @@ -101,6 +101,7 @@ public sealed class Connection DateTime closeDeadline; string closingReason; readonly byte[] readBuffer = new byte[64 * 1024]; + readonly List received = new(); byte[] buffer = new byte[64 * 1024]; int count; @@ -113,9 +114,16 @@ public sealed class Connection RemoteAddress = client.Client.RemoteEndPoint?.ToString() ?? "?"; } + /// Socket zum Warten auf Daten (Socket.Select im Server-Loop). + public Socket Socket => client.Client; + + /// + /// Neue Nachrichten. Die Liste wird beim nächsten Aufruf wiederverwendet (keine Allokation pro Durchlauf). + /// public List Poll() { - var messages = new List(); + var messages = received; + messages.Clear(); if (IsClosed) return messages; if (closing) { @@ -124,12 +132,13 @@ public sealed class Connection } try { - if (client.Client.Poll(0, SelectMode.SelectRead) && client.Available == 0) + if (client.Available == 0) { - Close("Connection closed"); + // Getrennt? "lesbar, aber keine Daten" = Gegenseite hat geschlossen + if (client.Client.Poll(0, SelectMode.SelectRead) && client.Available == 0) Close("Connection closed"); return messages; } - while (stream.DataAvailable) + while (client.Available > 0) { int read = stream.Read(readBuffer, 0, readBuffer.Length); if (read <= 0) break; @@ -145,12 +154,14 @@ public sealed class Connection return messages; } - public void Send(MsgType type, byte[] payload = null) + public void Send(MsgType type, byte[] payload = null) => SendFrame(Wire.Frame(type, payload)); + + /// Fertig gerahmte Nachricht senden (Broadcast: einmal rahmen, an alle schicken). + public void SendFrame(byte[] frame) { if (IsClosed || closing) return; try { - byte[] frame = Wire.Frame(type, payload); stream.Write(frame, 0, frame.Length); } catch (Exception e)