Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions CatCore/CatCoreInstance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,14 @@
using CatCore.Services;
using CatCore.Services.Interfaces;
using CatCore.Services.Multiplexer;
using CatCore.Services.Sockets;
using CatCore.Services.Sockets.Packets;
using CatCore.Services.Twitch;
using CatCore.Services.Twitch.Interfaces;
using CatCore.Services.Twitch.Media;
using DryIoc;
using JetBrains.Annotations;
using ImTools;
using Serilog;
using Serilog.Events;
using Serilog.Formatting.Display;
Expand Down Expand Up @@ -153,6 +156,32 @@ private void CreateContainer()
_container.Register<ChatServiceMultiplexer>(Reuse.Singleton);
_container.Register<ChatServiceMultiplexerManager>(Reuse.Singleton);

// Register socket services
_container.Register<IKittenRawSocketProvider, KittenRawSocketProvider>(Reuse.Singleton);
_container.RegisterInitializer<IKittenRawSocketProvider>((service, context) => service.Initialize());

_ = Task.Run(() =>
{
var socket = _container.Resolve<IKittenRawSocketProvider>();
socket.Initialize();
socket.OnConnect += clientSocket =>
{
Log.Logger.Information($"Client connected from {clientSocket.WorkSocket.RemoteEndPoint} and {clientSocket.Uuid}");
};

socket.OnReceive += (clientSocket, data, str) =>
{
Log.Logger.Information($"Received from {clientSocket.Uuid}: {str}");

clientSocket.QueueSend(new RespondHello(str));
};

socket.OnDisconnect += clientSocket =>
{
Log.Logger.Information($"Client disconnected from {clientSocket.WorkSocket.RemoteEndPoint} and {clientSocket.Uuid}");
};
});

// Spin up internal web api service
_ = Task.Run(() =>
{
Expand Down
19 changes: 19 additions & 0 deletions CatCore/Services/Interfaces/IKittenRawSocketProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System;
using CatCore.Services.Sockets;
using CatCore.Services.Sockets.Packets;

namespace CatCore.Services.Interfaces
{
internal interface IKittenRawSocketProvider : INeedInitialization, IDisposable
{
bool isServerRunning();

#pragma warning disable 649
event Action<ClientSocket>? OnConnect;
event Action<ClientSocket, Packet?, string>? OnReceive;
event Action<ClientSocket>? OnDisconnect;
#pragma warning restore 649


}
}
188 changes: 188 additions & 0 deletions CatCore/Services/KittenRawSocketProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
using System;
using System.Collections.Concurrent;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using CatCore.Services.Interfaces;
using CatCore.Services.Sockets;
using CatCore.Services.Sockets.Packets;
using Serilog;

namespace CatCore.Services
{
// Just copied sample code for socket server
internal class KittenRawSocketProvider : IKittenRawSocketProvider
{
private const int SOCKET_PORT = 8338;

// Not sure if concurrent is needed here
private readonly ConcurrentDictionary<Guid, ClientSocket> _connectedClients = new ConcurrentDictionary<Guid, ClientSocket>();

private static SemaphoreSlim NewSemaphore()
{
return new SemaphoreSlim(0, 1);
}

// Thread signal.
private SemaphoreSlim _allDone = NewSemaphore();
private readonly ILogger _logger;

private bool _isServerRunning;

internal CancellationTokenSource? ServerCts { get; private set; }

#pragma warning disable 649
public event Action<ClientSocket>? OnConnect;
public event Action<ClientSocket, Packet?, string>? OnReceive;
public event Action<ClientSocket>? OnDisconnect;
#pragma warning restore 649

public KittenRawSocketProvider(ILogger logger)
{
_logger = logger;
}

private bool ValidateServerNotRunning()
{
return !_isServerRunning && !ServerCts?.IsCancellationRequested == null;
}

private async void StartListening(CancellationTokenSource cts)
{
ServerCts = cts;

// Establish the local endpoint for the socket.
IPAddress ipAddress = IPAddress.Any;
IPEndPoint localEndPoint = new IPEndPoint(ipAddress, SOCKET_PORT);

// Create a TCP/IP socket.
Socket listener = new Socket(ipAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp);

// Bind the socket to the local endpoint and listen for incoming connections.
try
{
_logger.Information($"Binding to port {localEndPoint.Address.MapToIPv4()}:{localEndPoint.Port}");
listener.Bind(localEndPoint);
listener.Listen(2); //back log is amount of clients allowed to wait

// Set the event to nonsignaled state.
_allDone = NewSemaphore();

_isServerRunning = true;
while (!ServerCts.IsCancellationRequested)
{
// Start an asynchronous socket to listen for connections.
_logger.Information("Waiting for a connection...");
listener.BeginAccept(
AcceptCallback,
listener);


// Wait until a connection is made before continuing.
// this avoids eating CPU cycles
await _allDone.WaitAsync(ServerCts.Token).ConfigureAwait(false);
}

}
catch (Exception e)
{
_logger.Error(e, "Failed to start Kitty socket listening server");
}

_isServerRunning = false;
}

private void AcceptCallback(IAsyncResult ar)
{
// Signal the main thread to continue.
_allDone.Release();

if (ServerCts is null || ServerCts.IsCancellationRequested)
{
return;
}

// Get the socket that handles the client request.
Socket listener = (Socket) ar.AsyncState;
Socket handler = listener.EndAccept(ar);

var guid = Guid.NewGuid();

// Never have a duplicate
while (_connectedClients.ContainsKey(guid))
{
guid = Guid.NewGuid();
}

ClientSocket clientSocket = new ClientSocket(handler, guid, ServerCts!, HandleDisconnect, HandleRead);
_connectedClients[guid] = clientSocket;

OnConnect?.Invoke(clientSocket);
}

private void HandleRead(ClientSocket clientSocket, string receivedData)
{
var p = Packet.TryGetPacketFromJson(receivedData, out _);

if (p is null)
{
Log.Logger.Warning($"Unable to parse packet from {clientSocket.Uuid}, type is unknown");
// return;
}

OnReceive?.Invoke(clientSocket, p, receivedData);
}


private void HandleDisconnect(ClientSocket clientSocket)
{
var socket = clientSocket.WorkSocket;

if (socket.Connected)
{
socket.Shutdown(SocketShutdown.Both);
socket.Close();
}

_connectedClients.TryRemove(clientSocket.Uuid, out _);

OnDisconnect?.Invoke(clientSocket);
}

public void Initialize()
{
if (!ValidateServerNotRunning())
{
_logger.Warning("(This can be ignored if intentional) The server is still running, what is wrong with you? The poor kitty can't handle two socket servers! ;-;");
return;
}

_logger.Information("Starting socket server");

ServerCts = new CancellationTokenSource();
Task.Run(() =>
{
try
{
StartListening(ServerCts);
}
catch (Exception e)
{
_logger.Error(e, "Failed to start Kitty socket server");
}
});
}

public void Dispose()
{

ServerCts!.Dispose();
}

public bool isServerRunning()
{
return _isServerRunning;
}
}
}
Loading