Performance: frame broadcasts once, Socket.Select instead of fixed sleep, reuse message list

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-27 15:03:51 +02:00
co-authored by Claude Opus 5.5
parent 6f8474d66b
commit 3b17baf739
2 changed files with 37 additions and 8 deletions
+20 -2
View File
@@ -154,7 +154,7 @@ public sealed class GameServer
PollClients(); PollClients();
RunCommands(); RunCommands();
Timers(); Timers();
Thread.Sleep(2); WaitForTraffic();
} }
foreach (var c in clients.Values) foreach (var c in clients.Values)
@@ -672,8 +672,26 @@ public sealed class GameServer
void Broadcast(MsgType type, byte[] payload, int except = -1) void Broadcast(MsgType type, byte[] payload, int except = -1)
{ {
byte[] frame = null; // nur einmal rahmen, nicht pro Spieler
foreach (var c in clients.Values) 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<Socket> waitList = new();
/// <summary>
/// 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.
/// </summary>
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 // ---------------------------------------------------------------- Konsole
+17 -6
View File
@@ -101,6 +101,7 @@ public sealed class Connection
DateTime closeDeadline; DateTime closeDeadline;
string closingReason; string closingReason;
readonly byte[] readBuffer = new byte[64 * 1024]; readonly byte[] readBuffer = new byte[64 * 1024];
readonly List<Message> received = new();
byte[] buffer = new byte[64 * 1024]; byte[] buffer = new byte[64 * 1024];
int count; int count;
@@ -113,9 +114,16 @@ public sealed class Connection
RemoteAddress = client.Client.RemoteEndPoint?.ToString() ?? "?"; RemoteAddress = client.Client.RemoteEndPoint?.ToString() ?? "?";
} }
/// <summary>Socket zum Warten auf Daten (Socket.Select im Server-Loop).</summary>
public Socket Socket => client.Client;
/// <summary>
/// Neue Nachrichten. Die Liste wird beim nächsten Aufruf wiederverwendet (keine Allokation pro Durchlauf).
/// </summary>
public List<Message> Poll() public List<Message> Poll()
{ {
var messages = new List<Message>(); var messages = received;
messages.Clear();
if (IsClosed) return messages; if (IsClosed) return messages;
if (closing) if (closing)
{ {
@@ -124,12 +132,13 @@ public sealed class Connection
} }
try 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; return messages;
} }
while (stream.DataAvailable) while (client.Available > 0)
{ {
int read = stream.Read(readBuffer, 0, readBuffer.Length); int read = stream.Read(readBuffer, 0, readBuffer.Length);
if (read <= 0) break; if (read <= 0) break;
@@ -145,12 +154,14 @@ public sealed class Connection
return messages; return messages;
} }
public void Send(MsgType type, byte[] payload = null) public void Send(MsgType type, byte[] payload = null) => SendFrame(Wire.Frame(type, payload));
/// <summary>Fertig gerahmte Nachricht senden (Broadcast: einmal rahmen, an alle schicken).</summary>
public void SendFrame(byte[] frame)
{ {
if (IsClosed || closing) return; if (IsClosed || closing) return;
try try
{ {
byte[] frame = Wire.Frame(type, payload);
stream.Write(frame, 0, frame.Length); stream.Write(frame, 0, frame.Length);
} }
catch (Exception e) catch (Exception e)