Hello Everyone,
Hope you are doing well. Today we will delve into one of the communication protocols.
WebSocket provides a way to create a persistent, full-duplex communication channel over a single TCP connection, allowing data to flow in both directions simultaneously. This makes it especially useful for real-time, interactive applications such as live chat systems, gaming, and real-time financial data services.
In this article, we'll explore how to create a real-time chat application using ASP.NET Core for the server-side logic and static HTML for the client-side interface. This combination provides a powerful and efficient method for implementing bi-directional communication between a client and a server.
WebSockets provide a way to open interactive communication sessions between the user's browser and a server. With WebSockets, you can send messages to a server and receive event-driven responses without having to poll the server for a reply.
Overview
Here’s what we’ll cover,
- Setting up the Server: Using ASP.NET Core to handle WebSocket requests and manage connections.
- Client-Side Implementation: Creating a simple HTML interface to send and receive messages.
- Server-Client Interaction: How messages are exchanged and displayed in real-time.
- Testing an application: Demonstrate the working application.
- Advantage Vs Disadvantage: WebSockets provide a unique capability for real-time, full-duplex communication on the web, which can be essential for certain types of applications. However, like any technology, they come with both advantages and disadvantages.
Server-Side Setup with ASP.NET Core
Implementation Details
- ASP.NET Core Setup: Begin by setting up an ASP.NET Core project with the necessary services and middleware for hosting a WebSocket server.
- WebSocket Management: Handle WebSocket requests, establish connections, and manage ongoing communications.
Code Explanation
- Initialize Core Services
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
var app = builder.Build();
- Middleware Configuration
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseWebSockets();
// Dictionary to store WebSocket connections
ConcurrentDictionary<string, WebSocket> webSockets = new ConcurrentDictionary<string, WebSocket>();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
- Request Handling
app.Use(async (context, next) =>
{
if (context.WebSockets.IsWebSocketRequest)
{
WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync();
var connectionId = Guid.NewGuid().ToString();
webSockets.TryAdd(connectionId, webSocket);
await HandleWebSocketCommunication(context, webSocket, connectionId);
webSockets.TryRemove(connectionId, out var _);
}
else
{
await next.Invoke();
}
});
app.Run();
- WebSocket Communication Handling
async Task HandleWebSocketCommunication(HttpContext context, WebSocket webSocket, string connectionId)
{
var buffer = new byte[1024 * 4];
WebSocketReceiveResult result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
while (!result.CloseStatus.HasValue)
{
var receivedMessage = Encoding.UTF8.GetString(buffer, 0, result.Count);
await Task.Delay(3000); // Simulate processing delay
var timestamp = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture);
var responseMessage = $"[{timestamp}] {connectionId}: {receivedMessage}";
var responseBuffer = Encoding.UTF8.GetBytes(responseMessage);
await webSocket.SendAsync(
new ArraySegment<byte>(responseBuffer, 0, responseBuffer.Length),
WebSocketMessageType.Text,
true,
CancellationToken.None
);
result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
}
await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None);
}
This function handles the lifecycle of a WebSocket connection including receiving data from the client, processing this data (potentially adding a timestamp), and sending a response back.
Client-Side Implementation
The client-side of the application uses basic HTML and JavaScript to establish WebSocket connections and interact with the server.
HTML Setup
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebSocket Chat Example</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 10px;
}
#messageArea {
height: 300px;
width: 300px;
overflow-y: scroll;
border: 1px solid #ccc;
padding: 5px;
}
#inputArea {
margin-top: 10px;
}
</style>
</head>
<body>
<h2>WebSocket Chat Example</h2>
<div id="messageArea"></div>
<div id="inputArea">
<input type="text" id="messageInput" placeholder="Type a message...">
<button onclick="sendMessage();">Send</button>
</div>
<script>
let websocket;
function connectWebSocket() {
const protocolPrefix = (window.location.protocol === 'https:') ? 'wss:' : 'ws:';
const port = window.location.port ? ':' + window.location.port : '';
const wsUrl = `${protocolPrefix}//${window.location.hostname}${port}/ws`;
websocket = new WebSocket(wsUrl);
websocket.onopen = function () {
displayMessage('[System] Connected to WebSocket server.');
};
websocket.onmessage = function (evt) {
const timestamp = formatTimestamp(new Date());
displayMessage(`[${timestamp}] ${evt.data}`);
};
websocket.onerror = function (evt) {
displayMessage('[System] WebSocket error: ' + evt.data);
};
websocket.onclose = function () {
displayMessage('[System] WebSocket connection closed.');
};
}
function sendMessage() {
const messageInput = document.getElementById('messageInput');
const message = messageInput.value;
const timestamp = formatTimestamp(new Date());
displayMessage(`[${timestamp}] You: ${message}`);
websocket.send(message);
messageInput.value = '';
}
function displayMessage(message) {
const messageArea = document.getElementById('messageArea');
messageArea.innerHTML += message + '<br>';
messageArea.scrollTop = messageArea.scrollHeight;
}
function formatTimestamp(date) {
return date;
}
window.onload = connectWebSocket;
</script>
</body>
</html>
Establishing a WebSocket Connection
- URL Construction
- WebSockets use
ws://
for unencrypted connections or wss://
for encrypted connections (similar to http://
and https://
).
- The URL can also include a specific port and endpoint, typically decided by the server's configuration.
- Initialization
- In the client's code (JavaScript), a WebSocket object is instantiated using the constructed URL. This is where the attempt to connect to the WebSocket server begins.
- Initial Handshake
- The client sends a WebSocket handshake request using standard HTTP upgrade headers that include details necessary to initiate the WebSocket protocol.
- The server, upon recognizing the upgrade request to WebSocket, responds accordingly if it supports WebSockets, hence upgrading the HTTP connection to a WebSocket connection.
- Connection Establishment
- Once the handshake is successful, an open connection is established, triggering the
open
event in the client’s WebSocket instance.
- The server and client can now exchange data freely in a bidirectional manner, firing
message
events for incoming data.
- Handlers
- Custom event handlers (
onopen
, onmessage
, onerror
, onclose
) are implemented to appropriately manage various phases of the connection lifecycle such as opening, message receipt, errors, and closing of the connection.
- Communication
- The client and server can now send data back and forth as often as needed with minimal overhead.
- Closure
- Either party can close the WebSocket connection by sending a close message, which is automatically negotiated between the client and the server.
Testing an application
Run the Project in Visual Studio and open the index.html.It will give you an interactive chat box. when user enter the message, the server will respond after 3 second with Guid. For each session, it has a different guide as we can see in the below example. This example illustrates the real-time duplex communication between client and server.
Advantage Vs Disadvantage
Advantages |
Disadvantages |
Real-Time Communication |
Complexity in Scalability |
Allows for live, instant data exchange |
Managing large-scale deployments can be complex due to persistent connections |
Reduced Overhead |
Limited Browser Support |
No need for an HTTP header on each message |
Older browsers may not support WebSockets |
Full Duplex Communication |
Security Concerns |
Simultaneous bi-directional messaging |
Needs careful configuration to secure WebSocket connections, especially over wss:// |
Efficient Use of Resources |
Compatibility with Proxies and Firewalls |
Persistent connection reduces the need for repeated handshakes and connection teardowns |
Some proxies and firewalls may not properly support WebSockets |
Fallback Mechanisms |
Not RESTful |
Can degrade to HTTP polling methods if necessary |
Does not conform to REST architecture, which can complicate API design and integration |
Persistent Connection |
Risk of Resource Exhaustion |
Keeps the connection open, avoiding the continuous open/close in HTTP |
Maintaining a large number of open connections can exhaust server resources |
Conclusion
WebSockets offer a highly efficient and real-time communication method for modern web applications, eliminating the lag and overhead associated with traditional HTTP requests. By establishing a persistent, two-way interactive channel between the client and the server, WebSockets enable immediate data exchanges ideal for applications requiring rapid updates such as live chat systems, gaming, or financial trading platforms. Implementing WebSockets enhances user experience due to smoother interactions and quicker responsiveness, emphasizing the importance of understanding and utilizing this technology to its full potential in creating responsive and dynamic web applications.