About
In this code snippet, we will learn how to use WebSockets in C#.
WebSockets allow you to establish a bi-directional(full-duplex) communication channel over HTTP between two devices. This type of connection is long-lived and suitable for real-time data such as video streams, commands or inputs for a multiplayer video game.
In this post, I will use a simple console application example. If you are using ASP.NET the process is slightly different when it comes to receiving the initial HTTP connection. You see the documentation here.
Note: If you are going to be using this in production, consider adding a cancelation token to the request tasks so you can cancel/time out any requests taking too much time. Also, consider adding some error handling with a try catch in case the connection is interrupted.
Let’s see how to use WebSockets in the code examples below.
WebSocket Server Code:
using System.Net; using System.Net.WebSockets; using System.Text; namespace CSWebsockets { internal class Program { static async Task Main(string[] args) { await RunServer(); } public static async Task RunServer() { //Define the URL for the WebSocket server. string serverUrl = "http://localhost:8080/ws/"; //Create and start a new HttpListener on the specified URL. HttpListener listener = new HttpListener(); listener.Prefixes.Add(serverUrl); listener.Start(); Console.WriteLine($"WebSocket Server started on {serverUrl}"); //Keep listening for incoming connections forever/until the server is stopped. while (true) { HttpListenerContext context = await listener.GetContextAsync(); //Handle each connection asynchronously. The _ is just a temporary variable that will get discarded. _ = Task.Run(() => HandleWebSocketConnection(context)); } } private static async Task HandleWebSocketConnection(HttpListenerContext context) { //As this is a WebSocket server we'll check if the incoming request is a WebSocket request. //If not we'll, return a 400 Bad Request response and close the connection. if (!context.Request.IsWebSocketRequest) { context.Response.StatusCode = 400; context.Response.Close(); return; } //Accept the WebSocket connection and get the WebSocket from it. HttpListenerWebSocketContext wsContext = await context.AcceptWebSocketAsync(null); WebSocket webSocket = wsContext.WebSocket; Console.WriteLine("WebSocket connection established."); //Create a buffer to store the received message from the WebSocket into it. var receiveBuffer = new byte[1024]; WebSocketReceiveResult result = await webSocket.ReceiveAsync(new ArraySegment<byte>(receiveBuffer), CancellationToken.None); //Keep receiving messages from the WebSocket until the connection is closed. while (!result.CloseStatus.HasValue) { //Convert the received message to a string and display it to the server console. string receivedMessage = Encoding.UTF8.GetString(receiveBuffer, 0, result.Count); Console.WriteLine($"Received: {receivedMessage}"); //Send back the received message. byte[] sendMessage = Encoding.UTF8.GetBytes($"Server echo: {receivedMessage}"); await webSocket.SendAsync(new ArraySegment<byte>(sendMessage), WebSocketMessageType.Text, true, CancellationToken.None); //Setup the receive task to receive the next message from the WebSocket. result = await webSocket.ReceiveAsync(new ArraySegment<byte>(receiveBuffer), CancellationToken.None); } //Close the WebSocket connection after we are done. await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None); Console.WriteLine("WebSocket connection closed"); } } }
WebSocket Client Code:
using System.Net.WebSockets; using System.Text; namespace CSWebsocketClient { internal class Program { static async Task Main(string[] args) { await WebSocketDemo(); } public static async Task WebSocketDemo() { //Create a new WebSocket client and connect to the server. MyWebSocketClient myClient = new MyWebSocketClient("ws://localhost:8080/ws/"); await myClient.ConnectToServer(); await myClient.Send("Hello from the client!"); string response = await myClient.Receive(); Console.WriteLine(response); } class MyWebSocketClient { public MyWebSocketClient(string serverUrl) { this.serverURL = serverUrl; this.client = new ClientWebSocket(); } //Destructor method that gets called when the object is destroyed. ~MyWebSocketClient() { //Close the WebSocket if still open. client.CloseAsync(WebSocketCloseStatus.NormalClosure, "Client closed", CancellationToken.None).GetAwaiter().GetResult(); } private string serverURL { get; set; } private ClientWebSocket client { get; set; } private byte[] receiveBuffer { get; set; } = new byte[1024]; public async Task ConnectToServer() { try { //Try to connect to the server. Console.WriteLine($"Started WebSocket client and trying to connect to {serverURL} ..."); await client.ConnectAsync(new Uri(serverURL), CancellationToken.None); Console.WriteLine("Connected to the WebSocket server."); } catch (Exception ex) { Console.WriteLine($"WebSocket connection attempt to {serverURL} failed."); } } public async Task Send(string message) { //Convert the message to a byte array. byte[] bytes = Encoding.UTF8.GetBytes(message); //Create a byte array segment from the byte array. ArraySegment<byte> byteArraySegment = new ArraySegment<byte>(bytes); //Send the message to the server. await client.SendAsync(byteArraySegment, WebSocketMessageType.Text, true, CancellationToken.None); } public async Task<string> Receive() { //Receive a message from the server. WebSocketReceiveResult result = await client.ReceiveAsync(new ArraySegment<byte>(receiveBuffer), CancellationToken.None); //Convert the received message to a string. string receivedMessage = Encoding.UTF8.GetString(receiveBuffer, 0, result.Count); return receivedMessage; } } } }
I added the two console projects(client and server) into a single Visual Studio solution. You can start both at the same time by:
1. Right-clicking on your solution and selecting Properties.
2. Then select Startup Project, there tick the Multiple startup projects option.
3. Finally, set the Action for both of your projects to Start.
Result
WebSocket Client Code:
using System.Net.WebSockets; using System.Text; namespace CSWebsocketClient { internal class Program { static async Task Main(string[] args) { await RunWebSocketMessagingClient(); } public static async Task RunWebSocketMessagingClient() { //Set the URL of the WebSocket server. string ServerURL = "ws://localhost:8080/ws/"; //Create a buffer for later to store the received message from the server. byte[] receiveBuffer = new byte[1024]; //Create a new WebSocket client and connect to the server. ClientWebSocket client = new ClientWebSocket(); await client.ConnectAsync(new Uri(ServerURL), CancellationToken.None); Console.WriteLine($"WebSocket client connect to the server at {ServerURL}."); //Initialize the tasks for user input and server output. Task<string> inputTask = Task.FromResult<string>(string.Empty); Task<WebSocketReceiveResult> outputTask = Task.FromResult<WebSocketReceiveResult>(null); //Run the messaging client in a loop until the user types "exit" or keep: // 1. checking for user input and sending it to the server // 2. checking for a message from the server and display it to the client while (true) { //Check if the user has typed a new message. if (inputTask.IsCompleted) { //Skip it the first time when null with no task yet. if (inputTask.Result is not null) { //Get the user input from the completed task. string input = inputTask.Result; //If the user types "exit", break the loop and exit the program else send the message to the server. if (input == "exit") { break; } else if(!string.IsNullOrEmpty(input)) { byte[] bytes = Encoding.UTF8.GetBytes(input); ArraySegment<byte> byteArraySegment = new ArraySegment<byte>(bytes); await client.SendAsync(byteArraySegment, WebSocketMessageType.Text, true, CancellationToken.None); } } //Create a new task to read the next user input. inputTask = Task.Run(() => Console.ReadLine()); } //Wait for a short time before checking for a new message from the server. Task.Delay(100).Wait(); if (outputTask.IsCompleted) { //Skip it the first time when null with no task yet. if (outputTask.Result is not null) { //Get the message from the server and display it to the client. string receivedMessage = Encoding.UTF8.GetString(receiveBuffer, 0, outputTask.Result.Count); Console.WriteLine(receivedMessage); } //Create a new task to read the next message from the server. outputTask = client.ReceiveAsync(new ArraySegment<byte>(receiveBuffer), CancellationToken.None); } } } } }