Building a WebSocket Server with Fixed Header Protocol in SuperSocket 2.0
This article demonstrates how to create a WebSocket server using SuperSocket 2.0 with a fixed header protocol. The implementation leverages several key components including PackageInfo, PackageMapper, IAsyncCommand, WebSocketSession, and MiddlewareBase to construct a fully functional WebSocket server.
PackageInfo
The package information class serves as the fundamental data structure for messages exchanged between clients and the server. This follows the same pattern established in the base Socket server implementation.
PackageMapper
SuperSocket uses the PackageMapper to transform WebSocket packages from the WebSocketServer into application-specific package types that the business logic can work with directly. The Map method on WebSocketPackage enables conversion of raw bytes or string payloads into target PackageInfo objects.
The following implementation demonstrates mapping a binary WebSocket package containing a two-byte request type header followed by a two-byte length field and a variable-length body:
using System.Buffers;
using SuperSocket.WebSocket;
public class BinaryPackageMapper : IPackageMapper<WebSocketPackage, ApplicationPackage>
{
private const int FixedHeaderLength = 4;
private const int TypeFieldOffset = 0;
private const int LengthFieldOffset = 2;
public ApplicationPackage Map(WebSocketPackage package)
{
var buffer = new SequenceReader<byte>(package.Data);
buffer.TryReadBigEndian(out short requestCode);
buffer.TryReadBigEndian(out short payloadLength);
var payloadBody = package.Data.Slice(FixedHeaderLength).ToArray();
return new ApplicationPackage
{
CommandCode = requestCode,
Payload = payloadBody
};
}
}
The mapper reads the request type as a big-endian short from the first two bytes, reads the body length from bytes 2-3, and then extracts the remaining bytes as the message payload.
IAsyncCommand
Command handlers process incoming packages based on their type code. This pattern provides a clean separation between message parsing and business logic handling.
WebSocketSession
The WebSocketSession class represents an active connection to the WebSocket server. Unlike the standard AppSession, this class provides additional WebSocket-specific capabilities including direct methods for sending messages back to connected clients.
MiddlewareBase
The MiddlewareBase class implements the middleware pattern for WebSocketServer in .NET Core, serving a role analogous to SuperSocketService in traditional socket implementations. By injecting the session container through the UseInProcSessionContainer method, middleware can access the list of all currently connected sessions for broadcast messaging scenarios.
The following middleware implementation demonstrates perioidc message broadcasting to all connected WebSocket clients:
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using SuperSocket;
public class BroadcastMiddleware : MiddlewareBase
{
private ISessionContainer _sessionManager;
private Task _broadcastOperation;
private CancellationTokenSource _cancellation;
public override void Start(IServer server)
{
_sessionManager = server.GetSessionContainer();
_cancellation = new CancellationTokenSource();
_broadcastOperation = ExecuteBroadcastCycle(_cancellation.Token);
}
private async Task ExecuteBroadcastCycle(CancellationToken token)
{
while (!token.IsCancellationRequested)
{
var broadcastCount = await DispatchMessage();
var delayInterval = broadcastCount == 0
? TimeSpan.FromSeconds(5)
: TimeSpan.FromSeconds(2);
await Task.Delay(delayInterval, token);
}
}
private async ValueTask<int> DispatchMessage()
{
if (_sessionManager == null)
return 0;
var messagePayload = GenerateMessagePayload();
var deliveryCount = 0;
var activeSessions = _sessionManager.GetSessions<WebSocketConnection>();
foreach (var connection in activeSessions)
{
await connection.SendAsync(messagePayload);
deliveryCount++;
if (_cancellation.Token.IsCancellationRequested)
break;
}
return deliveryCount;
}
private string GenerateMessagePayload()
{
var identifier = string.Join("|", Enumerable.Range(0, 8)
.Select(_ => Guid.NewGuid().ToString("N").Substring(0, 8)));
return $"[{DateTime.UtcNow:HH:mm:ss}] {identifier}";
}
public override void Shutdown(IServer server)
{
_cancellation.Cancel();
_broadcastOperation.Wait();
foreach (var connection in _sessionManager.GetSessions<WebSocketConnection>())
{
connection.DisplayStatistics();
}
}
}
The middleware generates a unique identifier string and broadcasts it to all connected clients every few seconds. When no clients are connected, it reduces the broadcast frequency to conserve resources.
Complete Server Implementation
The following code demonstrates the complete WebSocket server setup including configuration, middleware registration, and command handler integration:
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using SuperSocket;
using SuperSocket.Command;
using SuperSocket.WebSocket.Server;
namespace WebSocketServer.Application
{
public class Startup
{
public static async Task Main()
{
var serverHost = WebSocketHostBuilder.Create()
.ConfigureWebSocketMessageHandler(async (session, package) =>
{
var timestamp = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss.fff");
Console.WriteLine($"[{timestamp}] Incoming message: {package.Message}");
var response = $"[{timestamp}] Server response: Echo - {package.Message}";
await session.SendAsync(response);
})
.ConfigureCommand<ApplicationPackage, BinaryPackageMapper>(options =>
{
options.AddCommand<EchoCommand>();
options.AddCommand<StatusCommand>();
})
.ConfigureSession<WebSocketConnection>()
.UseInProcSessionContainer()
.AddMiddleware<BroadcastMiddleware>()
.ConfigureAppConfiguration((context, configBuilder) =>
{
configBuilder.AddInMemoryCollection(new Dictionary<string, string>
{
{"server:name", "WebSocketBridge"},
{"server:endpoints:0:address", "Any"},
{"server:endpoints:0:port", "4040"}
});
})
.ConfigureLogging((context, logging) =>
{
logging.AddConsole();
logging.SetMinimumLevel(LogLevel.Information);
})
.Build();
await serverHost.RunAsync();
}
}
}
The server binds to port 4040 on all network interfaces, processes incoming WebSocket messages through registered command handlers, and utilizes middleware for background broadcast operations. The logging configuration ensures all activity is visible in the console output for debugging and monitoring purposes.