fix desync & try to fix delay
This commit is contained in:
parent
b077097a33
commit
dc893baf94
5 changed files with 78 additions and 3 deletions
|
|
@ -145,6 +145,15 @@ public class Utils
|
|||
public static ConcurrentDictionary<string, List<System.Net.WebSockets.WebSocket>> ActiveWebSockets = new();
|
||||
public static ConcurrentDictionary<string, List<string>> ActiveCalls = new();
|
||||
|
||||
// Bezczynność app-WS = rozłączony klient. Klient wysyła tekstowy "ping" co
|
||||
// 20 s; brak CZEGOKOLWIEK przez 75 s (3-4 przegapione pingi) oznacza martwy
|
||||
// polłączony TCP (zabita aplikacja / utracony NAT mapping). Bez tego
|
||||
// ReceiveAsync wisiał w nieskończoność, a cleanup ActiveCalls w finally
|
||||
// nigdy nie biegł -> wskaźniki rozmów pokazywały uczestników, którzy
|
||||
// dawno wyłączyli aplikację. Timeout wywołuje normalny finally (cleanup
|
||||
// ActiveCalls + broadcast call_update do członków pokoju).
|
||||
public static readonly TimeSpan AppWsIdleTimeout = TimeSpan.FromSeconds(75);
|
||||
|
||||
public static async Task WsHandler(HttpContext context, System.Net.WebSockets.WebSocket webSocket, IQueryCollection query)
|
||||
{
|
||||
var buffer = new byte[8192];
|
||||
|
|
@ -210,7 +219,17 @@ public class Utils
|
|||
{
|
||||
while (webSocket.State == System.Net.WebSockets.WebSocketState.Open)
|
||||
{
|
||||
result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
|
||||
using var idleCts = new System.Threading.CancellationTokenSource(AppWsIdleTimeout);
|
||||
try
|
||||
{
|
||||
result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), idleCts.Token);
|
||||
}
|
||||
catch (System.OperationCanceledException)
|
||||
{
|
||||
//Brak ramek przez AppWsIdleTimeout - traktujemy jak rozłączenie:
|
||||
//przerywamy pętlę, finally posprząta ActiveCalls i roześle call_update
|
||||
break;
|
||||
}
|
||||
if (result.MessageType == System.Net.WebSockets.WebSocketMessageType.Close)
|
||||
{
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -14,10 +14,45 @@ public class Program
|
|||
// Entries are pruned in the 1s loop below.
|
||||
public static ConcurrentDictionary<string, (int count, long resetAt)> UsersListLimits = new ();
|
||||
|
||||
// Sesja bez ODBIERANEGO ruchu dłużej niż to = martwy TCP (zabita aplikacja,
|
||||
// utracony mapping NAT). Klient pinguje co 2 s - 15 s to 7+ przegapionych
|
||||
// pingów, więc fałszywe pozytywy przy chwilowych zaniżeniach sieci są
|
||||
// praktycznie niemożliwe, a ghost nie zaśmieca rosteru dłużej niż trzeba.
|
||||
public static readonly TimeSpan SessionIdleTimeout = TimeSpan.FromSeconds(15);
|
||||
|
||||
public static ulong TotalBytesSent = 0;
|
||||
public static ulong TotalBytesReceived = 0;
|
||||
private static ulong CurrentBandwidthKps = 0;
|
||||
|
||||
// Zamiata sesje bez odebranego ruchu dłużej niż SessionIdleTimeout. Bez
|
||||
// tego zabita aplikacja (half-open TCP, brak Close frame) zostawała w
|
||||
// rosterze pokoju na zawsze: /room/users zwracało ghostów -> ghost kafelki
|
||||
// i "trwa rozmowa" u innych uczestników. TryRemove(KeyValuePair) jest
|
||||
// warunkowe po wartości - nie da się przypadkiem usunąć NOWEJ sesji, która
|
||||
// w międzyczasie nadpisała wpis tego samego userId (reconnect).
|
||||
private static void SweepStaleSessions()
|
||||
{
|
||||
DateTime cutoff = DateTime.UtcNow - SessionIdleTimeout;
|
||||
foreach (var room in Rooms)
|
||||
{
|
||||
foreach (var kvp in room.Value)
|
||||
{
|
||||
if (kvp.Value.LastSeenUtc >= cutoff) continue;
|
||||
if (!room.Value.TryRemove(new KeyValuePair<ulong, Session>(kvp.Key, kvp.Value))) continue;
|
||||
|
||||
//Zamknięcie kolejki kończy task-pump; Abort wywala zawieszone
|
||||
//ReceiveAsync/SendAsync martwego socketu -> receive-loop wejdzie
|
||||
//we swój finally i posprząta bufor (guard session==currentSession
|
||||
//zapobiega podwójnemu sprzątaniu)
|
||||
kvp.Value.SendQueue.Writer.TryComplete();
|
||||
try { kvp.Value.Socket.Abort(); } catch { }
|
||||
Console.WriteLine($"Voice: swept idle session room={room.Key} user={kvp.Key}");
|
||||
}
|
||||
|
||||
if (room.Value.IsEmpty) Rooms.TryRemove(room.Key, out _);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
Utils.LoadEnv();
|
||||
|
|
@ -39,6 +74,8 @@ public class Program
|
|||
{
|
||||
if (entry.Value.resetAt <= nowSec) UsersListLimits.TryRemove(entry.Key, out _);
|
||||
}
|
||||
|
||||
SweepStaleSessions();
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -145,6 +145,10 @@ public class Requests
|
|||
|
||||
} while (!result.EndOfMessage);
|
||||
|
||||
//Dowolna kompletna ramka = klient żyje (ping typu 5 przychodzi co 2 s
|
||||
//nawet przy ciszy) - odświeża licznik dla idle-sweepera w Program.cs
|
||||
currentSession.Touch();
|
||||
|
||||
if (result.MessageType == WebSocketMessageType.Close) break;
|
||||
if (result.MessageType == WebSocketMessageType.Binary)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -8,6 +8,16 @@ public class Session
|
|||
public WebSocket Socket { get; }
|
||||
public Channel<(byte[] Buffer, int Length)> SendQueue { get; }
|
||||
|
||||
// Ostatni znak życia (dowolna odebrana ramka - audio, wideo, ping typu 5).
|
||||
// Klient pinguje co 2 s, więc sesja bez ruchu przez VOICE_IDLE_TIMEOUT to
|
||||
// martwy polłączony TCP (zabita aplikacja, utracony NAT mapping) - taki wpis
|
||||
// zamiata task w Program.cs. Bez tego ghost-sesje wiszą w rosterze wiecznie
|
||||
// (/room/users zwraca nieżyjących uczestników -> ghost kafelki u innych,
|
||||
// a relay marnuje bandwidth na zamknięte sockety).
|
||||
private long _lastSeenUtcTicks;
|
||||
|
||||
public DateTime LastSeenUtc => new(Interlocked.Read(ref _lastSeenUtcTicks), DateTimeKind.Utc);
|
||||
|
||||
public Session(WebSocket socket)
|
||||
{
|
||||
Socket = socket;
|
||||
|
|
@ -15,5 +25,8 @@ public class Session
|
|||
{
|
||||
FullMode = BoundedChannelFullMode.Wait
|
||||
});
|
||||
_lastSeenUtcTicks = DateTime.UtcNow.Ticks;
|
||||
}
|
||||
|
||||
public void Touch() => Interlocked.Exchange(ref _lastSeenUtcTicks, DateTime.UtcNow.Ticks);
|
||||
}
|
||||
|
|
@ -8,7 +8,9 @@ public class Utils
|
|||
public static string MAIN_URL = "";
|
||||
|
||||
// Rate limits, requests per minute (fixed 60s window).
|
||||
public static int ROOM_USERS_RATE_LIMIT = 120;
|
||||
// 240/min: klient odpytuje roster co ~5 s przez całą rozmowę (12/min) -
|
||||
// kilka osób za jednym NAT-em + początkowe retrye mieszczą się z zapasem.
|
||||
public static int ROOM_USERS_RATE_LIMIT = 240;
|
||||
|
||||
// Shared with the main server: authenticates internal validation requests.
|
||||
public static string VOICE_KEY = "";
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue