Server closes connections gracefully so kick/ban/reset/shutdown messages arrive; status dot and HUD yellow until really connected (also while reconnecting)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-27 11:00:06 +02:00
co-authored by Claude Opus 5.5
parent 08844b18ac
commit 57e1d3d3a8
3 changed files with 70 additions and 8 deletions
+41 -1
View File
@@ -89,6 +89,9 @@ public sealed class Connection
readonly TcpClient client;
readonly NetworkStream stream;
bool closing; // Meldung gesendet, warten bis der Spieler sie gelesen hat (siehe CloseAfterSend)
DateTime closeDeadline;
string closingReason;
readonly byte[] readBuffer = new byte[64 * 1024];
byte[] buffer = new byte[64 * 1024];
int count;
@@ -106,6 +109,11 @@ public sealed class Connection
{
var messages = new List<Message>();
if (IsClosed) return messages;
if (closing)
{
DrainWhileClosing();
return messages;
}
try
{
if (client.Client.Poll(0, SelectMode.SelectRead) && client.Available == 0)
@@ -131,7 +139,7 @@ public sealed class Connection
public void Send(MsgType type, byte[] payload = null)
{
if (IsClosed) return;
if (IsClosed || closing) return;
try
{
byte[] frame = Wire.Frame(type, payload);
@@ -143,6 +151,38 @@ public sealed class Connection
}
}
/// <summary>
/// Sauber trennen, nachdem eine letzte Meldung gesendet wurde (Kick/Ban-Grund, Neu-Verbinden, Shutdown).
/// Sofortiges Close() mit noch ungelesenen Daten des Spielers (er schickt ständig Positionen) führt zu einem
/// TCP-Reset - dann geht die gerade gesendete Meldung verloren. Deshalb: nur die Sende-Richtung schließen,
/// eingehende Daten noch kurz lesen und verwerfen, dann erst schließen.
/// </summary>
public void CloseAfterSend(string reason)
{
if (IsClosed || closing) return;
closing = true;
closingReason = reason;
closeDeadline = DateTime.UtcNow.AddSeconds(3);
try { client.Client.Shutdown(SocketShutdown.Send); } catch { Close(reason); }
}
public bool IsClosing => closing && !IsClosed;
void DrainWhileClosing()
{
try
{
while (stream.DataAvailable)
if (stream.Read(readBuffer, 0, readBuffer.Length) <= 0) break;
bool remoteClosed = client.Client.Poll(0, SelectMode.SelectRead) && client.Available == 0;
if (remoteClosed || DateTime.UtcNow >= closeDeadline) Close(closingReason);
}
catch
{
Close(closingReason);
}
}
public void Close(string reason)
{
if (IsClosed) return;