From 17351283ee501d037b5fc5d30d7cc338411b5ee8 Mon Sep 17 00:00:00 2001 From: Christoph Date: Wed, 15 Oct 2025 15:41:57 +0200 Subject: [PATCH 1/4] Add example projects Add SendingUpgradeRequestAsync event to client Change argument of client disconnected event from client id to the client object --- Jung.SimpleWebSocket.sln | 39 +++++ .../BasicClientExample.csproj | 14 ++ examples/BasicClientExample/Program.cs | 59 ++++++++ .../BasicServerExample.csproj | 14 ++ examples/BasicServerExample/Program.cs | 93 ++++++++++++ .../BasicUserHandlingClientExample.csproj | 14 ++ .../BasicUserHandlingClientExample/Program.cs | 70 +++++++++ .../BasicUserHandlingServerExample.csproj | 14 ++ .../BasicUserHandlingServerExample/Program.cs | 135 ++++++++++++++++++ .../Contracts/IWebSocketClient.cs | 10 ++ .../Contracts/IWebSocketServer.cs | 9 ++ .../Flows/ClientHandlingFlow.cs | 11 +- .../EventArguments/ClientDisconnectedArgs.cs | 8 +- .../ClientUpgradeRequestReceivedArgs.cs | 4 +- .../SendingUpdateRequestArgs.cs | 13 ++ .../SimpleWebSocketClient.cs | 28 +++- .../SimpleWebSocketServer.cs | 40 ++++-- .../Wrappers/WebSocketUpgradeHandler.cs | 19 ++- .../Tests/DisplayEventsServerTest.cs | 2 +- .../Tests/SendMessagesLoopTest.cs | 2 +- .../SimpleWebSocketTest.cs | 4 +- 21 files changed, 581 insertions(+), 21 deletions(-) create mode 100644 examples/BasicClientExample/BasicClientExample.csproj create mode 100644 examples/BasicClientExample/Program.cs create mode 100644 examples/BasicServerExample/BasicServerExample.csproj create mode 100644 examples/BasicServerExample/Program.cs create mode 100644 examples/BasicUserHandlingClientExample/BasicUserHandlingClientExample.csproj create mode 100644 examples/BasicUserHandlingClientExample/Program.cs create mode 100644 examples/BasicUserHandlingServerExample/BasicUserHandlingServerExample.csproj create mode 100644 examples/BasicUserHandlingServerExample/Program.cs create mode 100644 src/Jung.SimpleWebSocket/Models/EventArguments/SendingUpdateRequestArgs.cs diff --git a/Jung.SimpleWebSocket.sln b/Jung.SimpleWebSocket.sln index 6e0dc3c..fec2fcb 100644 --- a/Jung.SimpleWebSocket.sln +++ b/Jung.SimpleWebSocket.sln @@ -3,12 +3,26 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.11.35222.181 MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "examples", "examples", "{0487DC39-481D-4828-81A5-58CF9BCA2E98}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{68AB7986-ED88-4C74-A447-934ED6D1B657}" +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jung.SimpleWebSocket", "src\Jung.SimpleWebSocket\Jung.SimpleWebSocket.csproj", "{793B04E9-6326-425A-A29C-A736CFD1E0C0}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jung.SimpleWebSocket.UnitTests", "tests\Jung.SimpleWebSocket.UnitTests\Jung.SimpleWebSocket.UnitTests.csproj", "{26725C3C-8E90-49AC-9EE4-2A77ADB2229D}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jung.SimpleWebSocket.IntegrationTests", "tests\Jung.SimpleWebSocket.IntegrationTests\Jung.SimpleWebSocket.IntegrationTests.csproj", "{D052400A-9F1E-4F2E-98B9-AF74A7A16A2F}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BasicServerExample", "examples\BasicServerExample\BasicServerExample.csproj", "{0C73E461-DE3D-4D14-B81B-732B7C6971A1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BasicClientExample", "examples\BasicClientExample\BasicClientExample.csproj", "{9D4AD09E-B6FF-4E2A-894E-49B97729E190}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BasicUserHandlingServerExample", "examples\BasicUserHandlingServerExample\BasicUserHandlingServerExample.csproj", "{A538895A-481B-44A5-8E6F-6D617C3F5378}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BasicUserHandlingClientExample", "examples\BasicUserHandlingClientExample\BasicUserHandlingClientExample.csproj", "{C79EBA14-EFA6-424D-9C6E-609C98994473}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -27,10 +41,35 @@ Global {D052400A-9F1E-4F2E-98B9-AF74A7A16A2F}.Debug|Any CPU.Build.0 = Debug|Any CPU {D052400A-9F1E-4F2E-98B9-AF74A7A16A2F}.Release|Any CPU.ActiveCfg = Release|Any CPU {D052400A-9F1E-4F2E-98B9-AF74A7A16A2F}.Release|Any CPU.Build.0 = Release|Any CPU + {0C73E461-DE3D-4D14-B81B-732B7C6971A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0C73E461-DE3D-4D14-B81B-732B7C6971A1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0C73E461-DE3D-4D14-B81B-732B7C6971A1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0C73E461-DE3D-4D14-B81B-732B7C6971A1}.Release|Any CPU.Build.0 = Release|Any CPU + {9D4AD09E-B6FF-4E2A-894E-49B97729E190}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9D4AD09E-B6FF-4E2A-894E-49B97729E190}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9D4AD09E-B6FF-4E2A-894E-49B97729E190}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9D4AD09E-B6FF-4E2A-894E-49B97729E190}.Release|Any CPU.Build.0 = Release|Any CPU + {A538895A-481B-44A5-8E6F-6D617C3F5378}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A538895A-481B-44A5-8E6F-6D617C3F5378}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A538895A-481B-44A5-8E6F-6D617C3F5378}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A538895A-481B-44A5-8E6F-6D617C3F5378}.Release|Any CPU.Build.0 = Release|Any CPU + {C79EBA14-EFA6-424D-9C6E-609C98994473}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C79EBA14-EFA6-424D-9C6E-609C98994473}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C79EBA14-EFA6-424D-9C6E-609C98994473}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C79EBA14-EFA6-424D-9C6E-609C98994473}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {793B04E9-6326-425A-A29C-A736CFD1E0C0} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + {26725C3C-8E90-49AC-9EE4-2A77ADB2229D} = {68AB7986-ED88-4C74-A447-934ED6D1B657} + {D052400A-9F1E-4F2E-98B9-AF74A7A16A2F} = {68AB7986-ED88-4C74-A447-934ED6D1B657} + {0C73E461-DE3D-4D14-B81B-732B7C6971A1} = {0487DC39-481D-4828-81A5-58CF9BCA2E98} + {9D4AD09E-B6FF-4E2A-894E-49B97729E190} = {0487DC39-481D-4828-81A5-58CF9BCA2E98} + {A538895A-481B-44A5-8E6F-6D617C3F5378} = {0487DC39-481D-4828-81A5-58CF9BCA2E98} + {C79EBA14-EFA6-424D-9C6E-609C98994473} = {0487DC39-481D-4828-81A5-58CF9BCA2E98} + EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {5F0E3FEC-7DDE-4E02-941B-CEF2DE33DB1C} EndGlobalSection diff --git a/examples/BasicClientExample/BasicClientExample.csproj b/examples/BasicClientExample/BasicClientExample.csproj new file mode 100644 index 0000000..6467dee --- /dev/null +++ b/examples/BasicClientExample/BasicClientExample.csproj @@ -0,0 +1,14 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + diff --git a/examples/BasicClientExample/Program.cs b/examples/BasicClientExample/Program.cs new file mode 100644 index 0000000..fec5318 --- /dev/null +++ b/examples/BasicClientExample/Program.cs @@ -0,0 +1,59 @@ +// This file is part of the Jung SimpleWebSocket project. +// The project is licensed under the MIT license. + +using Jung.SimpleWebSocket; + +namespace BasicClientExample +{ + internal class Program + { + /// + /// An example of a basic WebSocket client using the Jung.SimpleWebSocket library. + /// + /// + static void Main(string[] args) + { + // Create the WebSocket client and connect to the server at ws://127.0.0.1:8085/chat + using var simpleWebSocketClient = new SimpleWebSocketClient("127.0.0.1", 8085, "/chat"); + + // Subscribe to client events + simpleWebSocketClient.Disconnected += (s, e) => Console.WriteLine($"Disconnected from the server. Reason: {e.ClosingStatusDescription}"); + simpleWebSocketClient.MessageReceived += (s, e) => Console.WriteLine($"Message received from server: {e.Message}"); + simpleWebSocketClient.BinaryMessageReceived += (s, e) => Console.WriteLine($"Binary message received from server: {BitConverter.ToString(e.Message)}"); + + try + { + // Connect to the server + simpleWebSocketClient.ConnectAsync().GetAwaiter().GetResult(); + + // Simulate any delay + Thread.Sleep(1000); + + // Send a message to the server + Console.WriteLine("Sending message to the server: Hello, Server!"); + simpleWebSocketClient.SendMessageAsync("Hello, Server!").GetAwaiter().GetResult(); + + // Keep the server running until a key is pressed + Console.WriteLine("Press Enter to stop the server..."); + Console.ReadKey(); + + // You do not have to explicitly disconnect the client because of the using statement + // simpleWebSocketClient.DisconnectAsync("Client is shutting down").Wait(); + } + catch (Exception exception) + { + + var exceptionMessage = exception.Message; + if (exception.InnerException != null) + { + exceptionMessage += $" Inner exception: {exception.InnerException.Message}"; + } + Console.WriteLine($"An error occurred: {exceptionMessage}"); + + // Keep the console application running until a key is pressed + Console.WriteLine("Press Enter close this window..."); + Console.ReadKey(); + } + } + } +} diff --git a/examples/BasicServerExample/BasicServerExample.csproj b/examples/BasicServerExample/BasicServerExample.csproj new file mode 100644 index 0000000..6467dee --- /dev/null +++ b/examples/BasicServerExample/BasicServerExample.csproj @@ -0,0 +1,14 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + diff --git a/examples/BasicServerExample/Program.cs b/examples/BasicServerExample/Program.cs new file mode 100644 index 0000000..fcd20e2 --- /dev/null +++ b/examples/BasicServerExample/Program.cs @@ -0,0 +1,93 @@ +// This file is part of the Jung SimpleWebSocket project. +// The project is licensed under the MIT license. + +using Jung.SimpleWebSocket; +using Jung.SimpleWebSocket.Models; +using Jung.SimpleWebSocket.Models.EventArguments; + +namespace BasicServerExample +{ + internal class Program + { + /// + /// An example of a basic WebSocket server using the Jung.SimpleWebSocket library. + /// + /// + static void Main(string[] args) + { + // Create server options + var serverOptions = new SimpleWebSocketServerOptions() + { + // Set the server to listen on port 8080 and localhost + Port = 8085, + LocalIpAddress = new System.Net.IPAddress([127, 0, 0, 1]) + }; + + // Create the WebSocket server + using var simpleWebSocketServer = new SimpleWebSocketServer(serverOptions); + + // Subscribe to the server events + simpleWebSocketServer.ClientConnected += (s, e) => Console.WriteLine($"Client connected: {e.ClientId}"); + simpleWebSocketServer.ClientDisconnected += (s, e) => Console.WriteLine($"Client disconnected: {e.Client.Id}, Reason: {e.ClosingStatusDescription}"); + simpleWebSocketServer.MessageReceived += (s, e) => Console.WriteLine($"Message received from {e.ClientId}: {e.Message}"); + simpleWebSocketServer.BinaryMessageReceived += SimpleWebSocketServer_BinaryMessageReceived; + simpleWebSocketServer.ClientUpgradeRequestReceivedAsync += SimpleWebSocketServer_ClientUpgradeRequestReceivedAsync; + + // Start the server + simpleWebSocketServer.Start(); + Console.WriteLine("Server started on ws://127.0.0.1:8085"); + + // Keep the server running until a key is pressed + Console.WriteLine("Press Enter to stop the server..."); + Console.ReadKey(); + + // You do not have to explicitly shutdown the server because of the using statement + // simpleWebSocketServer.ShutdownServer().Wait(); + } + + /// + /// This event is triggered when a client sends a binary message to the server. + /// + /// The server that received the binary message. + /// The event arguments containing the client ID and the binary message. + private static void SimpleWebSocketServer_BinaryMessageReceived(object? sender, ClientBinaryMessageReceivedArgs e) + { + // Convert the binary message to a hex string + string hex = BitConverter.ToString(e.Message).Replace("-", " "); + Console.WriteLine($"Binary message received from {e.ClientId}: {hex}"); + } + + /// + /// This event is triggered when a client sends an upgrade request to the server. + /// + /// The server that received the upgrade request. + /// The event arguments containing the client and the request details. + /// The cancellation token of the server. + /// A task that represents the asynchronous operation. + private static Task SimpleWebSocketServer_ClientUpgradeRequestReceivedAsync(object sender, ClientUpgradeRequestReceivedArgs e, CancellationToken cancellationToken) + { + Console.WriteLine($"Upgrade request received from {e.Client.Id} for {e.WebContext.RequestPath}"); + + // Do something with the upgrade request + // For example save request path to client properties + e.Client.Properties["RequestPath"] = e.WebContext.RequestPath; + + // Or reject the upgrade request if the request path is not /chat + if (e.WebContext.RequestPath != "/chat") + { + // It is recommended to set a status code higher than 400 to reject the upgrade request. + e.ResponseContext.StatusCode = System.Net.HttpStatusCode.Forbidden; // 403 Forbidden + // Handle the request and reject it + e.AcceptRequest = false; + } + else + { + // Handle the request and accept it + e.AcceptRequest = true; + } + + // Because this is an async event, we need to return a completed task. + return Task.CompletedTask; + } + } +} diff --git a/examples/BasicUserHandlingClientExample/BasicUserHandlingClientExample.csproj b/examples/BasicUserHandlingClientExample/BasicUserHandlingClientExample.csproj new file mode 100644 index 0000000..6467dee --- /dev/null +++ b/examples/BasicUserHandlingClientExample/BasicUserHandlingClientExample.csproj @@ -0,0 +1,14 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + diff --git a/examples/BasicUserHandlingClientExample/Program.cs b/examples/BasicUserHandlingClientExample/Program.cs new file mode 100644 index 0000000..67eff18 --- /dev/null +++ b/examples/BasicUserHandlingClientExample/Program.cs @@ -0,0 +1,70 @@ +// This file is part of the Jung SimpleWebSocket project. +// The project is licensed under the MIT license. + +using Jung.SimpleWebSocket; +using Jung.SimpleWebSocket.Models.EventArguments; + +namespace BasicUserHandlingClientExample +{ + internal class Program + { + /// + /// An example of a basic WebSocket client using the Jung.SimpleWebSocket library. + /// + /// + static void Main(string[] args) + { + // Create the WebSocket client and connect to the server at ws://127.0.0.1:8085/chat + using var simpleWebSocketClient = new SimpleWebSocketClient("127.0.0.1", 8085, "/chat"); + + // Subscribe to client events + simpleWebSocketClient.Disconnected += (s, e) => Console.WriteLine($"Disconnected from the server. Reason: {e.ClosingStatusDescription}"); + simpleWebSocketClient.MessageReceived += (s, e) => Console.WriteLine($"Message received from server: {e.Message}"); + simpleWebSocketClient.BinaryMessageReceived += (s, e) => Console.WriteLine($"Binary message received from server: {BitConverter.ToString(e.Message)}"); + simpleWebSocketClient.SendingUpgradeRequestAsync += SimpleWebSocketClient_SendingUpgradeRequestAsync; + + try + { + // Connect to the server + simpleWebSocketClient.ConnectAsync().GetAwaiter().GetResult(); + + // Simulate any delay + Thread.Sleep(1000); + + // Send a message to the server + Console.WriteLine("Sending message to the server: Hello, Server!"); + simpleWebSocketClient.SendMessageAsync("Hello, Server!").GetAwaiter().GetResult(); + + // Keep the server running until a key is pressed + Console.WriteLine("Press Enter to stop the server..."); + Console.ReadKey(); + + // You do not have to explicitly disconnect the client because of the using statement + // simpleWebSocketClient.DisconnectAsync("Client is shutting down").GetAwaiter().GetResult(); + } + catch (Exception exception) + { + var exceptionMessage = exception.Message; + if (exception.InnerException != null) + { + exceptionMessage += $" Inner exception: {exception.InnerException.Message}"; + } + Console.WriteLine($"An error occurred: {exceptionMessage}"); + + // Keep the console application running until a key is pressed + Console.WriteLine("Press Enter close this window..."); + Console.ReadKey(); + } + } + + private static Task SimpleWebSocketClient_SendingUpgradeRequestAsync(object sender, SendingUpgradeRequestArgs e, CancellationToken cancellationToken) + { + // Add a custom header to the upgrade request + e.WebContext.Headers["User-Name"] = "Alice"; + + // Because this is a synchronous method, we return a completed task. + return Task.CompletedTask; + + } + } +} diff --git a/examples/BasicUserHandlingServerExample/BasicUserHandlingServerExample.csproj b/examples/BasicUserHandlingServerExample/BasicUserHandlingServerExample.csproj new file mode 100644 index 0000000..6467dee --- /dev/null +++ b/examples/BasicUserHandlingServerExample/BasicUserHandlingServerExample.csproj @@ -0,0 +1,14 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + diff --git a/examples/BasicUserHandlingServerExample/Program.cs b/examples/BasicUserHandlingServerExample/Program.cs new file mode 100644 index 0000000..437f1ab --- /dev/null +++ b/examples/BasicUserHandlingServerExample/Program.cs @@ -0,0 +1,135 @@ +// This file is part of the Jung SimpleWebSocket project. +// The project is licensed under the MIT license. + +using Jung.SimpleWebSocket; +using Jung.SimpleWebSocket.Models; +using Jung.SimpleWebSocket.Models.EventArguments; +using System.Collections.Concurrent; + +namespace BasicUserHandlingServerExample +{ + internal class Program + { + // A thread-safe dictionary to store connected users + private static readonly ConcurrentDictionary _connectedUsers = []; + + /// + /// An example of a basic WebSocket server using the Jung.SimpleWebSocket library. + /// + /// + static void Main(string[] args) + { + // Create server options + var serverOptions = new SimpleWebSocketServerOptions() + { + // Set the server to listen on port 8080 and localhost + Port = 8085, + LocalIpAddress = new System.Net.IPAddress([127, 0, 0, 1]) + }; + + // Create the WebSocket server + using var simpleWebSocketServer = new SimpleWebSocketServer(serverOptions); + + // Subscribe to the server events + simpleWebSocketServer.ClientConnected += SimpleWebSocketServer_ClientConnected; + simpleWebSocketServer.ClientDisconnected += SimpleWebSocketServer_ClientDisconnected; + simpleWebSocketServer.MessageReceived += SimpleWebSocketServer_MessageReceived; + simpleWebSocketServer.BinaryMessageReceived += SimpleWebSocketServer_BinaryMessageReceived; + simpleWebSocketServer.ClientUpgradeRequestReceivedAsync += SimpleWebSocketServer_ClientUpgradeRequestReceivedAsync; + + // Start the server + simpleWebSocketServer.Start(); + Console.WriteLine($"Server started on ws://{serverOptions.LocalIpAddress}:{serverOptions.Port}"); + + // Keep the server running until a key is pressed + Console.WriteLine("Press Enter to stop the server..."); + Console.ReadKey(); + + // You do not have to explicitly shutdown the server because of the using statement + // simpleWebSocketServer.ShutdownServer().Wait(); + } + + + private static void SimpleWebSocketServer_ClientConnected(object? sender, ClientConnectedArgs e) + { + if (((SimpleWebSocketServer)sender!).GetClientById(e.ClientId) is WebSocketServerClient client) + { + Console.WriteLine($"User name {client.Properties["UserName"]} connected successfully."); + } + } + + private static void SimpleWebSocketServer_ClientDisconnected(object? sender, ClientDisconnectedArgs e) + { + // Remove the user from the connected users list + var userName = e.Client.Properties["UserName"]?.ToString() ?? "Unknown"; + _connectedUsers.TryRemove(userName, out _); + Console.WriteLine($"User name {userName} disconnected."); + } + + private static void SimpleWebSocketServer_MessageReceived(object? sender, ClientMessageReceivedArgs e) + { + if (((SimpleWebSocketServer)sender!).GetClientById(e.ClientId) is WebSocketServerClient client) + { + Console.WriteLine($"Message received from {client.Properties["UserName"]}: {e.Message}"); + } + } + + /// + /// This event is triggered when a client sends a binary message to the server. + /// + /// The server that received the binary message. + /// The event arguments containing the client ID and the binary message. + private static void SimpleWebSocketServer_BinaryMessageReceived(object? sender, ClientBinaryMessageReceivedArgs e) + { + // Convert the binary message to a hex string + string hex = BitConverter.ToString(e.Message).Replace("-", " "); + Console.WriteLine($"Binary message received from {e.ClientId}: {hex}"); + } + + /// + /// This event is triggered when a client sends an upgrade request to the server. + /// + /// The server that received the upgrade request. + /// The event arguments containing the client and the request details. + /// The cancellation token of the server. + /// A task that represents the asynchronous operation. + private static Task SimpleWebSocketServer_ClientUpgradeRequestReceivedAsync(object sender, ClientUpgradeRequestReceivedArgs e, CancellationToken cancellationToken) + { + Console.WriteLine($"Upgrade request received from {e.Client.Id} for {e.WebContext.RequestPath}"); + + // Get the user name from the request headers + var userName = e.WebContext.Headers["User-Name"]; + if (userName != null) + { + Console.WriteLine($"Request with user name: {userName}"); + + // Check if the user name is already connected + if (_connectedUsers.ContainsKey(userName)) + { + Console.WriteLine($"User name {userName} is already connected. Rejecting the upgrade request."); + e.ResponseContext.StatusCode = System.Net.HttpStatusCode.Conflict; // 409 Conflict + e.ResponseContext.BodyContent = "User name is already connected"; + e.AcceptRequest = false; + } + else + { + // Add the user name to the connected users list + _connectedUsers.TryAdd(userName, e.Client.Id); + // Store the user name in the client properties for future reference + e.Client.Properties["UserName"] = userName; + } + } + else + { + // It is recommended to set a status code higher than 400 to reject the upgrade request. + e.ResponseContext.StatusCode = System.Net.HttpStatusCode.BadRequest; // 400 Bad Request + e.ResponseContext.BodyContent = "Missing User-Name header"; + // Handle the request and reject it + e.AcceptRequest = false; + } + + // Because this is an async event, we need to return a completed task. + return Task.CompletedTask; + } + } +} diff --git a/src/Jung.SimpleWebSocket/Contracts/IWebSocketClient.cs b/src/Jung.SimpleWebSocket/Contracts/IWebSocketClient.cs index 1a73240..9c00f62 100644 --- a/src/Jung.SimpleWebSocket/Contracts/IWebSocketClient.cs +++ b/src/Jung.SimpleWebSocket/Contracts/IWebSocketClient.cs @@ -2,6 +2,7 @@ // The project is licensed under the MIT license. using Jung.SimpleWebSocket.Delegates; +using Jung.SimpleWebSocket.Models.EventArguments; namespace Jung.SimpleWebSocket.Contracts; @@ -45,6 +46,15 @@ public interface IWebSocketClient : IDisposable /// event DisconnectedEventHandler? Disconnected; + /// + /// Occurs before an upgrade request is sent, allowing the request to be inspected or modified asynchronously. + /// + /// This event is triggered when an upgrade request is about to be sent. Subscribers can use this + /// event to inspect or modify the request by handling the parameter. The + /// event handler is asynchronous, so any modifications or operations should be performed within the provided + /// asynchronous context. + event AsyncEventHandler? SendingUpgradeRequestAsync; + /// /// Sends a message to all connected clients asynchronously. /// diff --git a/src/Jung.SimpleWebSocket/Contracts/IWebSocketServer.cs b/src/Jung.SimpleWebSocket/Contracts/IWebSocketServer.cs index a12093e..03d8b27 100644 --- a/src/Jung.SimpleWebSocket/Contracts/IWebSocketServer.cs +++ b/src/Jung.SimpleWebSocket/Contracts/IWebSocketServer.cs @@ -5,6 +5,7 @@ using Jung.SimpleWebSocket.Exceptions; using Jung.SimpleWebSocket.Models; using Jung.SimpleWebSocket.Models.EventArguments; +using System.Diagnostics.CodeAnalysis; using System.Net; namespace Jung.SimpleWebSocket.Contracts; @@ -71,6 +72,14 @@ public interface IWebSocketServer : IDisposable /// The client WebSocketServerClient GetClientById(string clientId); + /// + /// Attempts to get a client by its id. + /// + /// The id of the client + /// The client if found, otherwise null + /// if the client was found, otherwise ." + bool TryGetClientById(string clientId, [NotNullWhen(true)] out WebSocketServerClient? client); + /// /// Sends a message to all connected clients asynchronously. /// diff --git a/src/Jung.SimpleWebSocket/Flows/ClientHandlingFlow.cs b/src/Jung.SimpleWebSocket/Flows/ClientHandlingFlow.cs index c5f68b8..fa1288d 100644 --- a/src/Jung.SimpleWebSocket/Flows/ClientHandlingFlow.cs +++ b/src/Jung.SimpleWebSocket/Flows/ClientHandlingFlow.cs @@ -89,7 +89,16 @@ internal async Task AcceptWebSocketAsync() /// The response context to send to the client. internal async Task RejectWebSocketAsync(WebContext responseContext) { - // The client is rejected + // If the status code is SwitchingProtocols, change it to BadRequest + if (responseContext.StatusCode == System.Net.HttpStatusCode.SwitchingProtocols) + { + // If we would send a SwitchingProtocols status code, the client would expect a WebSocket connection. + // We want to reject the connection, so we send a BadRequest status code. + // We could get here if the user sets the status code to SwitchingProtocols in the upgrade event. + responseContext.StatusCode = System.Net.HttpStatusCode.BadRequest; + } + + // Reject the client await _upgradeHandler!.RejectWebSocketAsync(responseContext, _cancellationToken).ConfigureAwait(false); Cleanup(); } diff --git a/src/Jung.SimpleWebSocket/Models/EventArguments/ClientDisconnectedArgs.cs b/src/Jung.SimpleWebSocket/Models/EventArguments/ClientDisconnectedArgs.cs index 51cf852..eda1d8e 100644 --- a/src/Jung.SimpleWebSocket/Models/EventArguments/ClientDisconnectedArgs.cs +++ b/src/Jung.SimpleWebSocket/Models/EventArguments/ClientDisconnectedArgs.cs @@ -7,5 +7,9 @@ namespace Jung.SimpleWebSocket.Models.EventArguments; /// Represents the arguments of the event when a client disconnects from the server. /// /// The reason for the connection closure. if the remote party closed the WebSocket connection without completing the close handshake. -/// The unique identifier of the client that disconnected from the server. -public record ClientDisconnectedArgs(string? ClosingStatusDescription, string ClientId); +/// The Client that disconnected from the server. +public record ClientDisconnectedArgs( + string? ClosingStatusDescription, + // We use the client object here instead of just the client ID to give more context about the disconnected client. + // When the event is fired, the client is already removed from the active clients list, so we can't access it there. + WebSocketServerClient Client); diff --git a/src/Jung.SimpleWebSocket/Models/EventArguments/ClientUpgradeRequestReceivedArgs.cs b/src/Jung.SimpleWebSocket/Models/EventArguments/ClientUpgradeRequestReceivedArgs.cs index 0f238b0..e7b3150 100644 --- a/src/Jung.SimpleWebSocket/Models/EventArguments/ClientUpgradeRequestReceivedArgs.cs +++ b/src/Jung.SimpleWebSocket/Models/EventArguments/ClientUpgradeRequestReceivedArgs.cs @@ -16,9 +16,9 @@ public record ClientUpgradeRequestReceivedArgs(WebSocketServerClient Client, Web private WebContext? _responseContext; /// - /// Gets or sets a value indicating whether the upgrade request should be handled. + /// Gets or sets a value indicating whether the upgrade request should be Accepted. Default is true. /// - public bool Handle { get; set; } = true; + public bool AcceptRequest { get; set; } = true; /// /// The context that is being use to response to the client. diff --git a/src/Jung.SimpleWebSocket/Models/EventArguments/SendingUpdateRequestArgs.cs b/src/Jung.SimpleWebSocket/Models/EventArguments/SendingUpdateRequestArgs.cs new file mode 100644 index 0000000..4d59437 --- /dev/null +++ b/src/Jung.SimpleWebSocket/Models/EventArguments/SendingUpdateRequestArgs.cs @@ -0,0 +1,13 @@ +// This file is part of the Jung SimpleWebSocket project. +// The project is licensed under the MIT license. + +using Microsoft.Extensions.Logging; + +namespace Jung.SimpleWebSocket.Models.EventArguments; + +/// +/// Represents the arguments of the event when a upgrade request is sent to a server. +/// +/// The context of the request. +/// The current Logger. +public record SendingUpgradeRequestArgs(WebContext WebContext, ILogger? Logger); diff --git a/src/Jung.SimpleWebSocket/SimpleWebSocketClient.cs b/src/Jung.SimpleWebSocket/SimpleWebSocketClient.cs index 89e5383..f77671e 100644 --- a/src/Jung.SimpleWebSocket/SimpleWebSocketClient.cs +++ b/src/Jung.SimpleWebSocket/SimpleWebSocketClient.cs @@ -6,6 +6,7 @@ using Jung.SimpleWebSocket.Exceptions; using Jung.SimpleWebSocket.Models; using Jung.SimpleWebSocket.Models.EventArguments; +using Jung.SimpleWebSocket.Utility; using Jung.SimpleWebSocket.Wrappers; using Microsoft.Extensions.Logging; using System.Net.Sockets; @@ -43,6 +44,9 @@ public class SimpleWebSocketClient(string hostName, int port, string requestPath /// public event BinaryMessageReceivedEventHandler? BinaryMessageReceived; + /// + public event AsyncEventHandler? SendingUpgradeRequestAsync; + /// /// The CancellationTokenSource for the client. /// @@ -114,7 +118,7 @@ public async Task ConnectAsync(CancellationToken? cancellationToken = null) { throw new WebSocketConnectionException(message: "Error connecting to Server", innerException: exception); } - else if (exception is WebSocketException) + else if (exception is WebSocketException || exception is WebSocketUpgradeException) { throw; } @@ -173,6 +177,7 @@ private async Task HandleWebSocketInitiation(TcpClientWrapper client, Cancellati var socketWrapper = new WebSocketUpgradeHandler(_stream); var requestContext = WebContext.CreateRequest(HostName, Port, RequestPath); + requestContext = await RaiseUpgradeEventAsync(requestContext, cancellationToken).ConfigureAwait(false); await socketWrapper.SendUpgradeRequestAsync(requestContext, cancellationToken).ConfigureAwait(false); var response = await socketWrapper.AwaitContextAsync(cancellationToken).ConfigureAwait(false); WebSocketUpgradeHandler.ValidateUpgradeResponse(response, requestContext); @@ -180,6 +185,19 @@ private async Task HandleWebSocketInitiation(TcpClientWrapper client, Cancellati _webSocket = socketWrapper.CreateWebSocket(isServer: false); } + /// + /// Raises the upgrade event. + /// + /// The request context to use for the upgrade event + /// The cancellation token + /// The event arguments of the upgrade request. + internal async Task RaiseUpgradeEventAsync(WebContext requestContext, CancellationToken cancellationToken) + { + var eventArgs = new SendingUpgradeRequestArgs(requestContext, _logger); + await AsyncEventRaiser.RaiseAsync(SendingUpgradeRequestAsync, this, eventArgs, cancellationToken).ConfigureAwait(false); + return requestContext; + } + /// public async Task SendMessageAsync(string message, CancellationToken? cancellationToken = null) { @@ -264,9 +282,17 @@ public void Dispose() { if (Interlocked.Exchange(ref _disposed, 1) == 0) { + // Unsubscribe all event handlers + Disconnected = null; + MessageReceived = null; + BinaryMessageReceived = null; + SendingUpgradeRequestAsync = null; + + // Dispose managed resources _cancellationTokenSource?.Cancel(); _stream?.Dispose(); _client?.Dispose(); + GC.SuppressFinalize(this); } } diff --git a/src/Jung.SimpleWebSocket/SimpleWebSocketServer.cs b/src/Jung.SimpleWebSocket/SimpleWebSocketServer.cs index fd296a6..6d02221 100644 --- a/src/Jung.SimpleWebSocket/SimpleWebSocketServer.cs +++ b/src/Jung.SimpleWebSocket/SimpleWebSocketServer.cs @@ -12,6 +12,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using System.Collections.Concurrent; +using System.Diagnostics.CodeAnalysis; using System.Net; using System.Net.WebSockets; using System.Text; @@ -267,8 +268,26 @@ public WebSocketServerClient GetClientById(string clientId) { ThrowIfDisposed(); - if (!ActiveClients.TryGetValue(clientId, out var client)) throw new WebSocketServerException(message: "Client not found"); - return client; + if (TryGetClientById(clientId, out var client)) + { + return client; + } + throw new WebSocketServerException(message: "Client not found"); + } + + /// + public bool TryGetClientById(string clientId, [NotNullWhen(true)] out WebSocketServerClient? client) + { + ThrowIfDisposed(); + + if (ActiveClients.TryGetValue(clientId, out client)) + { + if (client != null) + { + return true; + } + } + return false; } /// @@ -305,7 +324,7 @@ private async Task HandleClientAsync(WebSocketServerClient client, CancellationT var eventArgs = await flow.RaiseUpgradeEventAsync(ClientUpgradeRequestReceivedAsync).ConfigureAwait(false); // Respond to the upgrade request - if (eventArgs.Handle) + if (eventArgs.AcceptRequest) { // Accept the WebSocket connection await flow.AcceptWebSocketAsync().ConfigureAwait(false); @@ -333,10 +352,6 @@ private async Task HandleClientAsync(WebSocketServerClient client, CancellationT { // Ignore the exception, because it is thrown when cancellation is requested } - catch (UserNotHandledException userNotHandledException) - { - await flow.RejectWebSocketAsync(userNotHandledException.ResponseContext).ConfigureAwait(false); - } catch (Exception exception) { Logger?.LogError(exception, "Error while handling the Client {clientId}", flow.Client.Id); @@ -407,7 +422,7 @@ private async Task ProcessWebSocketMessagesAsync(WebSocketServerClient client, C // if we leave the loop, the client disconnected if (!IsShuttingDown) { - AsyncEventRaiser.RaiseAsyncInNewTask(ClientDisconnected, this, new ClientDisconnectedArgs(closeStatusDescription, client.Id), cancellationToken); + AsyncEventRaiser.RaiseAsyncInNewTask(ClientDisconnected, this, new ClientDisconnectedArgs(closeStatusDescription, client), cancellationToken); } } } @@ -422,10 +437,19 @@ public void Dispose() try { + // unsubscribe all event handlers + ClientConnected = null; + ClientDisconnected = null; + MessageReceived = null; + BinaryMessageReceived = null; + ClientUpgradeRequestReceivedAsync = null; + + // shutdown server and free resources ShutdownServer().GetAwaiter().GetResult(); _cancellationTokenSource?.Cancel(); _tcpListener?.Dispose(); _tcpListener = null; + GC.SuppressFinalize(this); } finally diff --git a/src/Jung.SimpleWebSocket/Wrappers/WebSocketUpgradeHandler.cs b/src/Jung.SimpleWebSocket/Wrappers/WebSocketUpgradeHandler.cs index 57ded7b..e7ea051 100644 --- a/src/Jung.SimpleWebSocket/Wrappers/WebSocketUpgradeHandler.cs +++ b/src/Jung.SimpleWebSocket/Wrappers/WebSocketUpgradeHandler.cs @@ -105,7 +105,7 @@ private async Task SendWebSocketResponseHeaders(WebContext context, Cancellation private async Task SendWebSocketRejectResponse(WebContext context, CancellationToken cancellationToken) { var sb = new StringBuilder( - $"HTTP/1.1 409 Conflict\r\n"); + $"HTTP/1.1 {(int)context.StatusCode} {context.StatusDescription}\r\n"); AddHeaders(context, sb); CompleteHeaderSection(sb); AddBody(context, sb); @@ -299,9 +299,22 @@ internal IWebSocket CreateWebSocket(bool isServer, TimeSpan? keepAliveInterval = internal async Task RejectWebSocketAsync(WebContext response, CancellationToken cancellationToken) { + // This header is optional, but recommended to inform the client that the connection will be closed response.Headers.Add("Connection", "close"); - response.Headers.Add("Content-Type", "text/plain"); - response.Headers.Add("Content-Length", response.BodyContent.Length.ToString()); + + // If there is body content, ensure Content-Type and Content-Length headers are set + if (!string.IsNullOrEmpty(response.BodyContent)) + { + // Set default Content-Type if not already set + if (response.Headers["Content-Type"] == null) + { + response.Headers.Add("Content-Type", "text/plain"); + } + // Set Content-Length based on the body content length + response.Headers.Add("Content-Length", response.BodyContent.Length.ToString()); + } + + // Send the rejection response await SendWebSocketRejectResponse(response, cancellationToken).ConfigureAwait(false); } } \ No newline at end of file diff --git a/tests/Jung.SimpleWebSocket.IntegrationTests/Tests/DisplayEventsServerTest.cs b/tests/Jung.SimpleWebSocket.IntegrationTests/Tests/DisplayEventsServerTest.cs index 129ae9e..2ad1875 100644 --- a/tests/Jung.SimpleWebSocket.IntegrationTests/Tests/DisplayEventsServerTest.cs +++ b/tests/Jung.SimpleWebSocket.IntegrationTests/Tests/DisplayEventsServerTest.cs @@ -56,7 +56,7 @@ private void SimpleWebSocketServer_ClientConnected(object? sender, ClientConnect private void SimpleWebSocketServer_ClientDisconnected(object? sender, ClientDisconnectedArgs e) { - _logger.LogInformation("Client disconnected: {ClientId}", e.ClientId); + _logger.LogInformation("Client disconnected: {ClientId}", e.Client.Id); } private void SimpleWebSocketServer_MessageReceived(object? sender, ClientMessageReceivedArgs e) diff --git a/tests/Jung.SimpleWebSocket.IntegrationTests/Tests/SendMessagesLoopTest.cs b/tests/Jung.SimpleWebSocket.IntegrationTests/Tests/SendMessagesLoopTest.cs index 9bd26ba..1638852 100644 --- a/tests/Jung.SimpleWebSocket.IntegrationTests/Tests/SendMessagesLoopTest.cs +++ b/tests/Jung.SimpleWebSocket.IntegrationTests/Tests/SendMessagesLoopTest.cs @@ -15,7 +15,7 @@ internal override async Task RunAsync() var cancellationTokenSource = new CancellationTokenSource(); var token = cancellationTokenSource.Token; - using var client = new SimpleWebSocketClient("localhost", 8085, "", clientLogger); + using var client = new SimpleWebSocketClient("localhost", 8085, string.Empty, clientLogger); InitializeClientEvents(client); diff --git a/tests/Jung.SimpleWebSocket.UnitTests/SimpleWebSocketTest.cs b/tests/Jung.SimpleWebSocket.UnitTests/SimpleWebSocketTest.cs index 762f70d..4699925 100644 --- a/tests/Jung.SimpleWebSocket.UnitTests/SimpleWebSocketTest.cs +++ b/tests/Jung.SimpleWebSocket.UnitTests/SimpleWebSocketTest.cs @@ -167,7 +167,7 @@ public async Task TestClientServerConnection_ShouldSendAndReceiveHelloWorld() var IpAddress = (args.Client.RemoteEndPoint as IPEndPoint)?.Address; if (IpAddress == null) { - args.Handle = false; + args.AcceptRequest = false; return; } @@ -177,7 +177,7 @@ public async Task TestClientServerConnection_ShouldSendAndReceiveHelloWorld() { args.ResponseContext.StatusCode = HttpStatusCode.Forbidden; args.ResponseContext.BodyContent = "Connection only possible via local network."; - args.Handle = false; + args.AcceptRequest = false; } args.Client.Properties["test"] = "test"; }; From b79265ba7e6f6d5d840f0f2c7977d30b7695c631 Mon Sep 17 00:00:00 2001 From: Christoph Date: Wed, 15 Oct 2025 17:38:54 +0200 Subject: [PATCH 2/4] Complete xml summaries of the examples --- .../BasicUserHandlingClientExample/Program.cs | 9 ++++++++- .../BasicUserHandlingServerExample/Program.cs | 17 ++++++++++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/examples/BasicUserHandlingClientExample/Program.cs b/examples/BasicUserHandlingClientExample/Program.cs index 67eff18..66e3490 100644 --- a/examples/BasicUserHandlingClientExample/Program.cs +++ b/examples/BasicUserHandlingClientExample/Program.cs @@ -57,6 +57,14 @@ static void Main(string[] args) } } + /// + /// Handles the event triggered before a WebSocket upgrade request is sent, allowing customization of the + /// request. + /// + /// The source of the event, the WebSocket client instance. + /// The event arguments containing details about the upgrade request, including headers and other context. + /// The cancellation token of the client. + /// A completed task, as this method performs its operation synchronously. private static Task SimpleWebSocketClient_SendingUpgradeRequestAsync(object sender, SendingUpgradeRequestArgs e, CancellationToken cancellationToken) { // Add a custom header to the upgrade request @@ -64,7 +72,6 @@ private static Task SimpleWebSocketClient_SendingUpgradeRequestAsync(object send // Because this is a synchronous method, we return a completed task. return Task.CompletedTask; - } } } diff --git a/examples/BasicUserHandlingServerExample/Program.cs b/examples/BasicUserHandlingServerExample/Program.cs index 437f1ab..b30f4f7 100644 --- a/examples/BasicUserHandlingServerExample/Program.cs +++ b/examples/BasicUserHandlingServerExample/Program.cs @@ -49,7 +49,11 @@ static void Main(string[] args) // simpleWebSocketServer.ShutdownServer().Wait(); } - + /// + /// This event is triggered when a client successfully connects to the server. + /// + /// The server that the client connected to. + /// The event arguments containing the client ID. private static void SimpleWebSocketServer_ClientConnected(object? sender, ClientConnectedArgs e) { if (((SimpleWebSocketServer)sender!).GetClientById(e.ClientId) is WebSocketServerClient client) @@ -58,6 +62,11 @@ private static void SimpleWebSocketServer_ClientConnected(object? sender, Client } } + /// + /// This event is triggered when a client disconnects from the server. + /// + /// The server that the client disconnected from. + /// The event arguments containing the client and the disconnection details. private static void SimpleWebSocketServer_ClientDisconnected(object? sender, ClientDisconnectedArgs e) { // Remove the user from the connected users list @@ -66,6 +75,12 @@ private static void SimpleWebSocketServer_ClientDisconnected(object? sender, Cli Console.WriteLine($"User name {userName} disconnected."); } + + /// + /// This event is triggered when a text message is received from a client. + /// + /// The server that received the message. + /// The event arguments containing the client ID and the message. private static void SimpleWebSocketServer_MessageReceived(object? sender, ClientMessageReceivedArgs e) { if (((SimpleWebSocketServer)sender!).GetClientById(e.ClientId) is WebSocketServerClient client) From 81a56175e59e76c240fca10c9de345da836c65a9 Mon Sep 17 00:00:00 2001 From: Christoph Date: Wed, 15 Oct 2025 18:06:43 +0200 Subject: [PATCH 3/4] Standardize output of received binary messages --- examples/BasicServerExample/Program.cs | 2 +- examples/BasicUserHandlingServerExample/Program.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/BasicServerExample/Program.cs b/examples/BasicServerExample/Program.cs index fcd20e2..78d92c9 100644 --- a/examples/BasicServerExample/Program.cs +++ b/examples/BasicServerExample/Program.cs @@ -53,7 +53,7 @@ static void Main(string[] args) private static void SimpleWebSocketServer_BinaryMessageReceived(object? sender, ClientBinaryMessageReceivedArgs e) { // Convert the binary message to a hex string - string hex = BitConverter.ToString(e.Message).Replace("-", " "); + string hex = BitConverter.ToString(e.Message); Console.WriteLine($"Binary message received from {e.ClientId}: {hex}"); } diff --git a/examples/BasicUserHandlingServerExample/Program.cs b/examples/BasicUserHandlingServerExample/Program.cs index b30f4f7..7103c33 100644 --- a/examples/BasicUserHandlingServerExample/Program.cs +++ b/examples/BasicUserHandlingServerExample/Program.cs @@ -97,7 +97,7 @@ private static void SimpleWebSocketServer_MessageReceived(object? sender, Client private static void SimpleWebSocketServer_BinaryMessageReceived(object? sender, ClientBinaryMessageReceivedArgs e) { // Convert the binary message to a hex string - string hex = BitConverter.ToString(e.Message).Replace("-", " "); + string hex = BitConverter.ToString(e.Message); Console.WriteLine($"Binary message received from {e.ClientId}: {hex}"); } From 600f77d091f65d2894f4c46b7001087a6af11f6f Mon Sep 17 00:00:00 2001 From: Christoph Date: Wed, 15 Oct 2025 18:36:41 +0200 Subject: [PATCH 4/4] Fix false comments --- examples/BasicServerExample/Program.cs | 2 +- examples/BasicUserHandlingServerExample/Program.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/BasicServerExample/Program.cs b/examples/BasicServerExample/Program.cs index 78d92c9..c4f7934 100644 --- a/examples/BasicServerExample/Program.cs +++ b/examples/BasicServerExample/Program.cs @@ -18,7 +18,7 @@ static void Main(string[] args) // Create server options var serverOptions = new SimpleWebSocketServerOptions() { - // Set the server to listen on port 8080 and localhost + // Set the server to listen on port 8085 and localhost Port = 8085, LocalIpAddress = new System.Net.IPAddress([127, 0, 0, 1]) }; diff --git a/examples/BasicUserHandlingServerExample/Program.cs b/examples/BasicUserHandlingServerExample/Program.cs index 7103c33..2850237 100644 --- a/examples/BasicUserHandlingServerExample/Program.cs +++ b/examples/BasicUserHandlingServerExample/Program.cs @@ -22,7 +22,7 @@ static void Main(string[] args) // Create server options var serverOptions = new SimpleWebSocketServerOptions() { - // Set the server to listen on port 8080 and localhost + // Set the server to listen on port 8085 and localhost Port = 8085, LocalIpAddress = new System.Net.IPAddress([127, 0, 0, 1]) };