First commit
This commit is contained in:
commit
0ac6ff9196
26 changed files with 2836 additions and 0 deletions
8
LarpixVoice/JsonAppSerializerContext.cs
Normal file
8
LarpixVoice/JsonAppSerializerContext.cs
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace LarpixVoice;
|
||||
|
||||
[JsonSerializable(typeof(string[]))]
|
||||
internal partial class JsonAppSerializerContext : JsonSerializerContext
|
||||
{
|
||||
}
|
||||
10
LarpixVoice/LarpixVoice.csproj
Normal file
10
LarpixVoice/LarpixVoice.csproj
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<NoDefaultLaunchSettingsFile>True</NoDefaultLaunchSettingsFile>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
95
LarpixVoice/Program.cs
Normal file
95
LarpixVoice/Program.cs
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
using System.Collections.Concurrent;
|
||||
|
||||
namespace LarpixVoice;
|
||||
|
||||
public class Program
|
||||
{
|
||||
public static ConcurrentDictionary<string, ConcurrentDictionary<ulong, Session>> Rooms = new ();
|
||||
|
||||
public static ulong TotalBytesSent = 0;
|
||||
public static ulong TotalBytesReceived = 0;
|
||||
private static ulong CurrentBandwidthKps = 0;
|
||||
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
Utils.LoadEnv();
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
await Task.Delay(1000);
|
||||
|
||||
ulong sentDelta = Interlocked.Read(ref TotalBytesSent);
|
||||
ulong receivedDelta = Interlocked.Read(ref TotalBytesReceived);
|
||||
Interlocked.Exchange(ref TotalBytesReceived, 0);
|
||||
Interlocked.Exchange(ref TotalBytesSent, 0);
|
||||
CurrentBandwidthKps = (sentDelta + receivedDelta);
|
||||
}
|
||||
});
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
builder.Logging.ClearProviders();
|
||||
|
||||
builder.WebHost.ConfigureKestrel(serverOptions =>
|
||||
{
|
||||
serverOptions.AddServerHeader = false;
|
||||
serverOptions.ListenAnyIP(Utils.PORT);
|
||||
});
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
app.UseWebSockets();
|
||||
|
||||
app.Use(async (HttpContext context, Func<Task> next) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
context.Response.StatusCode = 200;
|
||||
var path = context.Request.Path.Value;
|
||||
var url = $"{context.Request.Scheme}://{context.Request.Host}";
|
||||
if (path == "" || path?.Length > 512)
|
||||
{
|
||||
context.Response.Redirect(url + "/");
|
||||
return;
|
||||
}
|
||||
|
||||
context.Response.Headers["Access-Control-Allow-Origin"] = "*";//(context.Request.Headers["Origin"]).ToString().Replace("http://", "https://");
|
||||
context.Response.Headers["Access-Control-Allow-Methods"]= "GET, POST, PUT, DELETE, OPTIONS";
|
||||
context.Response.Headers["Access-Control-Allow-Headers"]= "Content-Type, Authorization";
|
||||
context.Response.Headers["Access-Control-Allow-Credentials"]= "true";
|
||||
switch (path)
|
||||
{
|
||||
case "/ws":
|
||||
await Requests.Websocket(context);
|
||||
return;
|
||||
case "/load":
|
||||
await context.Response.WriteAsync(Interlocked.Read(ref CurrentBandwidthKps).ToString());
|
||||
return;
|
||||
case "/room/users":
|
||||
string roomId = context.Request.Query["room"].ToString();
|
||||
context.Response.ContentType = "application/json";
|
||||
|
||||
if (Rooms.TryGetValue(roomId, out var room))
|
||||
{
|
||||
string[] users = room.Keys.Select(k => k.ToString()).ToArray();
|
||||
await context.Response.WriteAsJsonAsync(users, JsonAppSerializerContext.Default.StringArray);
|
||||
}
|
||||
else
|
||||
{
|
||||
await context.Response.WriteAsJsonAsync(Array.Empty<string>(), JsonAppSerializerContext.Default.StringArray);
|
||||
}
|
||||
return;
|
||||
default:
|
||||
await next();
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
});
|
||||
Console.WriteLine("Starting server at port: " + Utils.PORT);
|
||||
app.Run();
|
||||
}
|
||||
}
|
||||
124
LarpixVoice/Requests.cs
Normal file
124
LarpixVoice/Requests.cs
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
using System.Buffers;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Net.WebSockets;
|
||||
|
||||
using static LarpixVoice.Program;
|
||||
|
||||
namespace LarpixVoice;
|
||||
|
||||
public class Requests
|
||||
{
|
||||
public static async Task Websocket(HttpContext context)
|
||||
{
|
||||
if (!context.WebSockets.IsWebSocketRequest)
|
||||
{
|
||||
context.Response.StatusCode = 400;
|
||||
return;
|
||||
}
|
||||
|
||||
string roomId = context.Request.Query["room"].ToString();
|
||||
string userIdStr = context.Request.Query["userId"].ToString();
|
||||
string secret = context.Request.Query["secret"].ToString();
|
||||
|
||||
if (string.IsNullOrEmpty(roomId) || !ulong.TryParse(userIdStr, out ulong clientId))
|
||||
{
|
||||
context.Response.StatusCode = 400;
|
||||
return;
|
||||
}
|
||||
|
||||
using var webSocket = await context.WebSockets.AcceptWebSocketAsync();
|
||||
var roomClients = Rooms.GetOrAdd(roomId, _ => new ConcurrentDictionary<ulong, Session>());
|
||||
|
||||
if (roomClients.TryGetValue(clientId, out var oldSession) && oldSession.Socket.State == WebSocketState.Open)
|
||||
{
|
||||
try
|
||||
{
|
||||
await oldSession.Socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Kicked", CancellationToken.None);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
var currentSession = new Session(webSocket);
|
||||
roomClients[clientId] = currentSession;
|
||||
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
await foreach (var packet in currentSession.SendQueue.Reader.ReadAllAsync())
|
||||
{
|
||||
try
|
||||
{
|
||||
if (currentSession.Socket.State == WebSocketState.Open)
|
||||
{
|
||||
await currentSession.Socket.SendAsync(new ArraySegment<byte>(packet.Buffer, 0, packet.Length),
|
||||
WebSocketMessageType.Binary, true, CancellationToken.None);
|
||||
|
||||
Interlocked.Add(ref TotalBytesSent, (ulong)packet.Length);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(packet.Buffer);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var receiveBuffer = ArrayPool<byte>.Shared.Rent(1024 * 1024);
|
||||
try
|
||||
{
|
||||
while (webSocket.State == WebSocketState.Open)
|
||||
{
|
||||
int totalBytes = 0;
|
||||
WebSocketReceiveResult result;
|
||||
|
||||
do
|
||||
{
|
||||
result = await webSocket.ReceiveAsync(
|
||||
new ArraySegment<byte>(receiveBuffer, totalBytes, receiveBuffer.Length - totalBytes),
|
||||
CancellationToken.None);
|
||||
|
||||
totalBytes += result.Count;
|
||||
Interlocked.Add(ref TotalBytesReceived, (ulong)result.Count);
|
||||
|
||||
} while (!result.EndOfMessage);
|
||||
|
||||
if (result.MessageType == WebSocketMessageType.Close) break;
|
||||
if (result.MessageType == WebSocketMessageType.Binary)
|
||||
{
|
||||
int payloadLength = totalBytes + 8;
|
||||
|
||||
foreach (var (id, client) in roomClients)
|
||||
{
|
||||
if (id != clientId && client.Socket.State == WebSocketState.Open)
|
||||
{
|
||||
byte[] payload = ArrayPool<byte>.Shared.Rent(payloadLength);
|
||||
BitConverter.TryWriteBytes(payload, clientId);
|
||||
Array.Copy(receiveBuffer, 0, payload, 8, totalBytes);
|
||||
|
||||
if (!client.SendQueue.Writer.TryWrite((payload, payloadLength)))
|
||||
ArrayPool<byte>.Shared.Return(payload);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (WebSocketException)
|
||||
{
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(receiveBuffer);
|
||||
if (roomClients.TryGetValue(clientId, out var session) && session == currentSession)
|
||||
{
|
||||
roomClients.TryRemove(clientId, out _);
|
||||
session.SendQueue.Writer.Complete();
|
||||
}
|
||||
|
||||
if (roomClients.IsEmpty) Rooms.TryRemove(roomId, out _);
|
||||
}
|
||||
}
|
||||
}
|
||||
19
LarpixVoice/Session.cs
Normal file
19
LarpixVoice/Session.cs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
using System.Net.WebSockets;
|
||||
using System.Threading.Channels;
|
||||
|
||||
namespace LarpixVoice;
|
||||
|
||||
public class Session
|
||||
{
|
||||
public WebSocket Socket { get; }
|
||||
public Channel<(byte[] Buffer, int Length)> SendQueue { get; }
|
||||
|
||||
public Session(WebSocket socket)
|
||||
{
|
||||
Socket = socket;
|
||||
SendQueue = Channel.CreateBounded<(byte[] Buffer, int Length)>(new BoundedChannelOptions(256)
|
||||
{
|
||||
FullMode = BoundedChannelFullMode.Wait
|
||||
});
|
||||
}
|
||||
}
|
||||
42
LarpixVoice/Utils.cs
Normal file
42
LarpixVoice/Utils.cs
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
namespace LarpixVoice;
|
||||
|
||||
public class Utils
|
||||
{
|
||||
public static int PORT = 8091;
|
||||
public static int MAIN_PORT = 8090;
|
||||
public static string SERVER_KEY = "SecretThingOMGSoHardToGuess";
|
||||
|
||||
public static void LoadEnv()
|
||||
{
|
||||
if (File.Exists(".env"))
|
||||
{
|
||||
|
||||
foreach (var line in File.ReadAllLines(".env"))
|
||||
{
|
||||
var trimmed = line.Trim();
|
||||
if (string.IsNullOrWhiteSpace(trimmed) || trimmed.StartsWith("#")) continue;
|
||||
|
||||
var parts = trimmed.Split('=', 2, StringSplitOptions.None);
|
||||
if (parts.Length == 2)
|
||||
{
|
||||
var key = parts[0].Trim();
|
||||
var val = parts[1].Trim();
|
||||
|
||||
Environment.SetEnvironmentVariable(key, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
string? port = Environment.GetEnvironmentVariable("PORT_VOICE");
|
||||
if (port != null)
|
||||
{
|
||||
PORT = int.Parse(port);
|
||||
}
|
||||
port = Environment.GetEnvironmentVariable("PORT");
|
||||
if (port != null)
|
||||
{
|
||||
MAIN_PORT = int.Parse(port);
|
||||
}
|
||||
SERVER_KEY = Environment.GetEnvironmentVariable("SERVER_KEY") ?? SERVER_KEY;
|
||||
}
|
||||
}
|
||||
9
LarpixVoice/appsettings.Development.json
Normal file
9
LarpixVoice/appsettings.Development.json
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
9
LarpixVoice/appsettings.json
Normal file
9
LarpixVoice/appsettings.json
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue