From 86161b5c95952babc2de559cf8683595e6de18c0 Mon Sep 17 00:00:00 2001 From: Timerix22 Date: Thu, 12 Oct 2023 13:31:10 +0600 Subject: [PATCH] TCP client and server --- DTLib.Network/DTLib.Network.csproj | 2 +- DTLib.Network/FSP2/Client.cs | 18 +++++ DTLib.Network/FSP2/Request.cs | 13 ++++ DTLib.Network/FSP2/Response.cs | 13 ++++ DTLib.Network/FSP2/Server.cs | 26 ++++++++ DTLib.Network/Functions.cs | 19 ++++++ DTLib.Network/OldNetwork.cs | 41 ------------ DTLib.Network/TCPSocketClient.cs | 45 +++++++++++++ DTLib.Network/TCPSocketServer.cs | 101 +++++++++++++++++++++++++++++ 9 files changed, 236 insertions(+), 42 deletions(-) create mode 100644 DTLib.Network/FSP2/Client.cs create mode 100644 DTLib.Network/FSP2/Request.cs create mode 100644 DTLib.Network/FSP2/Response.cs create mode 100644 DTLib.Network/FSP2/Server.cs create mode 100644 DTLib.Network/Functions.cs delete mode 100644 DTLib.Network/OldNetwork.cs create mode 100644 DTLib.Network/TCPSocketClient.cs create mode 100644 DTLib.Network/TCPSocketServer.cs diff --git a/DTLib.Network/DTLib.Network.csproj b/DTLib.Network/DTLib.Network.csproj index 25c9a9a..1948be1 100644 --- a/DTLib.Network/DTLib.Network.csproj +++ b/DTLib.Network/DTLib.Network.csproj @@ -15,7 +15,7 @@ embedded 10 - disable + enable disable diff --git a/DTLib.Network/FSP2/Client.cs b/DTLib.Network/FSP2/Client.cs new file mode 100644 index 0000000..b2ac795 --- /dev/null +++ b/DTLib.Network/FSP2/Client.cs @@ -0,0 +1,18 @@ +using System.Net; + +namespace DTLib.Network.FSP2; + +public class Client : IDisposable +{ + private TCPSocketClient _tcp; + + public Client(IPEndPoint serverEndpoint) + { + _tcp = new TCPSocketClient(serverEndpoint); + } + + public void Dispose() + { + _tcp.Dispose(); + } +} \ No newline at end of file diff --git a/DTLib.Network/FSP2/Request.cs b/DTLib.Network/FSP2/Request.cs new file mode 100644 index 0000000..a77788d --- /dev/null +++ b/DTLib.Network/FSP2/Request.cs @@ -0,0 +1,13 @@ +namespace DTLib.Network.FSP2; + +public enum RequestType +{ + Message, + DownloadFile, + UploadFile +} + +public record Request(RequestType Type) +{ + +} \ No newline at end of file diff --git a/DTLib.Network/FSP2/Response.cs b/DTLib.Network/FSP2/Response.cs new file mode 100644 index 0000000..48c20fd --- /dev/null +++ b/DTLib.Network/FSP2/Response.cs @@ -0,0 +1,13 @@ +namespace DTLib.Network.FSP2; + +public enum ResponseStatus +{ + OK, + InvalidRequest, + AccessDenied +} + +public record Response(ResponseStatus Status) +{ + +} \ No newline at end of file diff --git a/DTLib.Network/FSP2/Server.cs b/DTLib.Network/FSP2/Server.cs new file mode 100644 index 0000000..666e4d6 --- /dev/null +++ b/DTLib.Network/FSP2/Server.cs @@ -0,0 +1,26 @@ +using System.Net; + +namespace DTLib.Network.FSP2; + +public class Server : IDisposable +{ + + private TCPSocketServer _tcp; + private readonly Func _requestHandler; + + public Server(IPEndPoint localEndpoint, Func requestHandler, Action exceptionHandler) + { + _requestHandler = requestHandler; + _tcp = new TCPSocketServer(localEndpoint, HandleFSP2Connection, exceptionHandler); + } + + void HandleFSP2Connection(TCPSocketServer.ConnectionParams p) + { + + } + + public void Dispose() + { + _tcp.Dispose(); + } +} \ No newline at end of file diff --git a/DTLib.Network/Functions.cs b/DTLib.Network/Functions.cs new file mode 100644 index 0000000..7dab119 --- /dev/null +++ b/DTLib.Network/Functions.cs @@ -0,0 +1,19 @@ +global using System.Net.Sockets; +global using System; +global using System.Threading; +global using System.Collections.Generic; +global using System.Linq; +global using System.Text; +global using DTLib.Extensions; +global using DTLib.Filesystem; +global using static DTLib.Logging.InternalLog; +using System.Diagnostics; +using System.Net.Http; + +namespace DTLib.Network; + +public static class Functions +{ + /// gets public ip of this machine from ifconfig.me + public static string GetPublicIP() => new HttpClient().GetStringAsync("https://ifconfig.me/ip").GetAwaiter().GetResult(); +} diff --git a/DTLib.Network/OldNetwork.cs b/DTLib.Network/OldNetwork.cs deleted file mode 100644 index 06aa6b8..0000000 --- a/DTLib.Network/OldNetwork.cs +++ /dev/null @@ -1,41 +0,0 @@ -global using System.Net.Sockets; -global using System; -global using System.Threading; -global using System.Collections.Generic; -global using System.Linq; -global using System.Text; -global using DTLib.Extensions; -global using DTLib.Filesystem; -global using static DTLib.Logging.InternalLog; -using System.Diagnostics; -using System.Net.Http; - -namespace DTLib.Network; - -// -// пара почти никогда не используемых методов -// -public static class OldNetwork -{ - - - // получает с сайта публичный ip - public static string GetPublicIP() => new HttpClient().GetStringAsync("https://ifconfig.me/ip").GetAwaiter().GetResult(); - - // пингует айпи с помощью встроенной в винду проги, возвращает задержку - public static string PingIP(string address) - { - var proc = new Process(); - proc.StartInfo.FileName = "cmd.exe"; - proc.StartInfo.Arguments = "/c @echo off & chcp 65001 >nul & ping -n 5 " + address; - proc.StartInfo.CreateNoWindow = true; - proc.StartInfo.UseShellExecute = false; - proc.StartInfo.RedirectStandardOutput = true; - proc.Start(); - System.IO.StreamReader outStream = proc.StandardOutput; - string rezult = outStream.ReadToEnd(); - rezult = rezult.Remove(0, rezult.LastIndexOf('=') + 2); - return rezult.Remove(rezult.Length - 4); - } - -} diff --git a/DTLib.Network/TCPSocketClient.cs b/DTLib.Network/TCPSocketClient.cs new file mode 100644 index 0000000..511ede9 --- /dev/null +++ b/DTLib.Network/TCPSocketClient.cs @@ -0,0 +1,45 @@ +using System.Net; + +namespace DTLib.Network; + +public class TCPSocketClient : IDisposable +{ + protected Socket? _mainSocket; + public readonly IPEndPoint ServerEndpoint; + + public TCPSocketClient(IPEndPoint serverEndpoint) + { + ServerEndpoint = serverEndpoint; + } + + public virtual void Connect() + { + _mainSocket = new(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + _mainSocket.Connect(ServerEndpoint); + } + + public virtual void Stop() + { + if(_mainSocket is null) + return; + _mainSocket.Shutdown(SocketShutdown.Both); + _mainSocket.Close(); + _mainSocket = null; + } + + public void Dispose() => Stop(); + + public void Send(byte[] buffer) + { + if (_mainSocket is null) + throw new NullReferenceException("TCP socket is null! You have to call Connect() befure calling Send()"); + _mainSocket.Send(buffer); + } + + public int Recieve(ref byte[] buffer) + { + if (_mainSocket is null) + throw new NullReferenceException("TCP socket is null! You have to call Connect() befure calling Recieve()"); + _mainSocket.Receive(buffer); + } +} \ No newline at end of file diff --git a/DTLib.Network/TCPSocketServer.cs b/DTLib.Network/TCPSocketServer.cs new file mode 100644 index 0000000..b09852a --- /dev/null +++ b/DTLib.Network/TCPSocketServer.cs @@ -0,0 +1,101 @@ +using System.Net; + +namespace DTLib.Network; + +/// +/// Multi-threaded TCP +/// +public class TCPSocketServer : IDisposable +{ + public readonly IPEndPoint LocalEndPoint; + + public record ConnectionParams(Socket ConnectionSocket, Thread ConnectionThread); + + protected Action? _connectionHandler; + protected Action _exceptionCallback; + protected Socket? _mainSocket; + + /// address and port of the server in the local network + /// this delegate is called on every incoming socket connection + /// this delegate is called on every Exception throwed in threads created by the server + public TCPSocketServer(IPEndPoint localEndpoint, + Action? incomingConnectionHandler, + Action exceptionCallback) + { + LocalEndPoint = localEndpoint; + _connectionHandler = incomingConnectionHandler; + _exceptionCallback = exceptionCallback; + } + + private volatile int acceptLoopRunning = 0; + + /// + /// Starts server on a new thread + /// + /// the server is already started + public virtual void Listen() + { + if (Interlocked.CompareExchange(ref acceptLoopRunning, 1, 1) == 1) + throw new Exception("Server is already started. Stop it to "); + + _mainSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + _mainSocket.Bind(LocalEndPoint); + _mainSocket.Listen(64); + Thread loopThread = new Thread(AcceptLoop); + loopThread.Start(); + } + + /// server runs this loop in special thread + private void AcceptLoop() + { + try + { + // atomic (thread-safe) comparison acceptLoopRunning==1 + while (Interlocked.CompareExchange(ref acceptLoopRunning, 1, 1) == 1) + { + Socket incomingConnection = _mainSocket!.Accept(); + Thread connectionThread = new Thread(HandleConnection); + connectionThread.Start(new ConnectionParams(incomingConnection, connectionThread)); + } + } + catch (Exception e) + { + _exceptionCallback(e); + Stop(); + } + } + + + private void HandleConnection(object? _p) + { + var p = (ConnectionParams)_p!; + try + { + _connectionHandler?.Invoke(p); + } + catch (Exception e) + { + _exceptionCallback(e); + } + finally + { + p.ConnectionSocket.Shutdown(SocketShutdown.Both); + p.ConnectionSocket.Close(); + } + } + + public virtual void Stop() + { + // atomic (thread-safe) comparison acceptLoopRunning==0 and assignement acceptLoopRunning=0 + if (Interlocked.CompareExchange(ref acceptLoopRunning, 0, 1) == 0) + throw new Exception("Server isn't running"); + + if(_mainSocket is null) + return; + + _mainSocket.Shutdown(SocketShutdown.Both); + _mainSocket.Close(); + } + + public void Dispose() => Stop(); +} \ No newline at end of file