Websockets
- Full duplex messaging
- No 6 connection limit
- Multi data-type support
- TCP socket upgrade: A standardized way to use one TCP socket through which messages can be sent from server to client and vice versa.
- WS protocol
Microsoft.AspNetCore.WebSockets
- Contains a managed implementation of the WebSocket protocol, along with server integration components.
Microsoft.AspNetCore.WebSocketsMicrosoft.AspNetCore.WebSockets.ProtocolMicrosoft.AspNetCore.WebSockets.ServerMicrosoft.AspNetCore.WebSockets.Test.WebSocketMiddlewareTestssrc 1- middleware src 2 and tests src 1
- Using WebSockets in ASP.NET Core blog 3 June, 2016
- Websockets in Asp.Net Core blog 4 July 2018
- Archived Implementation of the WebSocket protocol for aspnet 5, along with client and server integration components.
- asp net core api
- configure
app.UseWebSockets(new WebSocketOptions{ KeepAliveInterval = TimeSpan.FromSeconds(120), ReceiveBufferSize = 4*1024 }) - controller
var context = _httpContextAccessor.HttpContext; if (context.WebSockets.IsWebSocketRequest) { var ws = await context.WebSockets.AcceptWebSocketAsync(); await SendEvents(ws, params object[] ...) await ws.CloseAsync(WebSocketCloseStatus.NormalClosure, "done", CancellationToken.None); } else { context.Response.StatusCode = 400; } - configure
SendEvents — server-side async loop (C#)
private async Task SendEvents(WebSocket webSocket, int orderNo)
{
CheckResult result;
do
{
result = _orderChecker.GetUpdate(orderNo);
Thread.Sleep(2000);
if (!result.New) continue;
var jsonMessage = $"\"{result.Update}\"";
await webSocket.SendAsync(
buffer: new ArraySegment<byte>(
array: Encoding.ASCII.GetBytes(jsonMessage),
offset: 0,
count: jsonMessage.Length),
messageType: WebSocketMessageType.Text,
endOfMessage: true,
cancellationToken: CancellationToken.None);
} while (!result.Finished);
}
sequenceDiagram
participant S as Server (SendEvents)
participant C as Client (WebSocket)
loop until result.Finished
S->>S: _orderChecker.GetUpdate(orderNo)
S->>S: Thread.Sleep(2000)
alt result.New
S->>C: SendAsync(jsonMessage, Text)
end
end
- javascript — the client opens a WebSocket to receive updates, then POSTs via
fetchand pipes the response into the listener:
const listen = (id) => {
const socket = new WebSocket(`ws://localhost:60907/Coffee/${id}`);
socket.onmessage = event => {
const statusDiv = document.getElementById("status");
statusDiv.innerHTML = JSON.parse(event.data);
};
};
document.getElementById("submit").addEventListener("click", e => {
e.preventDefault();
const product = document.getElementById("product").value;
const size = document.getElementById("size").value;
fetch("/Coffee", {
method: "POST",
body: { product, size }
})
.then(response => response.text())
.then(text => listen(text));
});
sequenceDiagram
participant U as User (click)
participant B as Browser
participant API as Server (/Coffee POST)
participant WS as WebSocket (ws://.../Coffee/{id})
U->>B: submit click
B->>API: POST /Coffee {product, size}
API-->>B: orderId (text)
B->>WS: new WebSocket(ws://.../Coffee/{orderId})
loop server pushes updates
WS-->>B: onmessage(event.data)
B->>B: statusDiv.innerHTML = JSON.parse(event.data)
end
Browser
- The WebSocket 6 browser API.
- The WebSocket API is an advanced technology that makes it possible to open a two-way interactive communication session between the user’s browser and a server. With this API, you can send messages to a server and receive event-driven responses without having to poll the server for a reply.
- Bringing Sockets to the Web 7 2010
- desktop to web socket 8 2012
SignalR
Misc
- SuperWebSocket 9 A .NET server side implementation of WebSocket protocol. repo 10 SuperWebSocket is a .NET implementation of WebSocket server. supersocket 11 an extensible socket server framework, telnet example 12
- C# Websockets for all platforms using native bridges NVentimiglia/Websockets.PCL 13
- Building Real-time Web Apps with ASP .NET
WebAPIandWebSocketsblog article 14 July 17, 2012 Microsoft.AspNetCore.Http.WebSocketManagersrc 15Microsoft.AspNetCore.TestHost.WebSocketClientsrc 16
Radu Matei
Echo Sample
Official ASP.NET Core WebSocket echo sample 19.
wwwroot/index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
<style>
table { border: 0 }
.commslog-data { font-family: Consolas, Courier New, Courier, monospace; }
.commslog-server { background-color: red; color: white }
.commslog-client { background-color: green; color: white }
</style>
</head>
<body>
<h1>WebSocket Sample Application</h1>
<p id="stateLabel">Ready to connect...</p>
<div>
<label for="connectionUrl">WebSocket Server URL:</label>
<input id="connectionUrl" />
<button id="connectButton" type="submit">Connect</button>
</div>
<p></p>
<div>
<label for="sendMessage">Message to send:</label>
<input id="sendMessage" disabled />
<button id="sendButton" type="submit" disabled>Send</button>
<button id="closeButton" disabled>Close Socket</button>
</div>
<h2>Communication Log</h2>
<table style="width: 800px">
<thead><tr><td style="width: 100px">From</td><td style="width: 100px">To</td><td>Data</td></tr></thead>
<tbody id="commsLog"></tbody>
</table>
<script>
var connectionUrl = document.getElementById("connectionUrl");
var connectButton = document.getElementById("connectButton");
var stateLabel = document.getElementById("stateLabel");
var sendMessage = document.getElementById("sendMessage");
var sendButton = document.getElementById("sendButton");
var commsLog = document.getElementById("commsLog");
var closeButton = document.getElementById("closeButton");
var socket;
var scheme = document.location.protocol === "https:" ? "wss" : "ws";
var port = document.location.port ? (":" + document.location.port) : "";
connectionUrl.value = scheme + "://" + document.location.hostname + port + "/ws";
function updateState() {
function disable() { sendMessage.disabled = sendButton.disabled = closeButton.disabled = true; }
function enable() { sendMessage.disabled = sendButton.disabled = closeButton.disabled = false; }
connectionUrl.disabled = connectButton.disabled = true;
if (!socket) { disable(); return; }
switch (socket.readyState) {
case WebSocket.CLOSED: stateLabel.innerHTML = "Closed"; disable(); connectionUrl.disabled = connectButton.disabled = false; break;
case WebSocket.CLOSING: stateLabel.innerHTML = "Closing..."; disable(); break;
case WebSocket.CONNECTING:stateLabel.innerHTML = "Connecting...";disable(); break;
case WebSocket.OPEN: stateLabel.innerHTML = "Open"; enable(); break;
default: stateLabel.innerHTML = "Unknown: " + htmlEscape(socket.readyState); disable();
}
}
closeButton.onclick = function () { socket.close(1000, "Closing from client"); };
sendButton.onclick = function () {
var data = sendMessage.value;
socket.send(data);
commsLog.innerHTML += '<tr><td class="commslog-client">Client</td><td class="commslog-server">Server</td><td class="commslog-data">' + htmlEscape(data) + '</td></tr>';
};
connectButton.onclick = function () {
stateLabel.innerHTML = "Connecting";
socket = new WebSocket(connectionUrl.value);
socket.onopen = function (e) { updateState(); commsLog.innerHTML += '<tr><td colspan="3" class="commslog-data">Connection opened</td></tr>'; };
socket.onclose = function (e) { updateState(); commsLog.innerHTML += '<tr><td colspan="3" class="commslog-data">Connection closed. Code: ' + htmlEscape(e.code) + '. Reason: ' + htmlEscape(e.reason) + '</td></tr>'; };
socket.onerror = updateState;
socket.onmessage = function (e) { commsLog.innerHTML += '<tr><td class="commslog-server">Server</td><td class="commslog-client">Client</td><td class="commslog-data">' + htmlEscape(e.data) + '</td></tr>'; };
};
function htmlEscape(str) {
return str.toString().replace(/&/g,'&').replace(/"/g,'"').replace(/'/g,''').replace(/</g,'<').replace(/>/g,'>');
}
</script>
</body>
</html>
WebSocketController.cs
High-performance logging with LoggerMessage.Define and [LoggerMessage] source generator 20:
// High-perf structured logging via LoggerMessage.Define
internal static class HighPerfLog
{
private static readonly Action<ILogger, Exception?> s_ctor =
LoggerMessage.Define(LogLevel.Debug, new EventId(0, "WebSocketController ctor"),
"The WebSocket class allows applications to send and receive data after the WebSocket upgrade has completed.");
public static void Ctor(this ILogger logger) => s_ctor(logger, null!);
private static readonly Action<ILogger, WebSocketCloseStatus?, string?, TimeSpan, WebSocketState, string?, Exception?>
s_acceptWebSocketAsync = LoggerMessage.Define<WebSocketCloseStatus?, string?, TimeSpan, WebSocketState, string?>(
LogLevel.Information, new EventId(1, "AcceptWebSocketAsync"),
"[{CloseStatus}], Description: {CloseStatusDescription}, KeepAlive: {DefaultKeepAliveInterval}, State: {State}, SubProtocol: {SubProtocol}");
public static void AcceptWebSocketAsync(this ILogger logger, WebSocket ws) =>
s_acceptWebSocketAsync(logger, ws.CloseStatus, ws.CloseStatusDescription,
WebSocket.DefaultKeepAliveInterval, ws.State, ws.SubProtocol, null!);
}
// Source-generator approach ([LoggerMessage] attribute)
public static partial class HighPerfCodeGenLog
{
[LoggerMessage(EventId = 10, Level = LogLevel.Critical,
Message = "Closing web socket [{CloseStatus}], Description: {CloseStatusDescription}")]
public static partial void CompileTime(ILogger logger,
WebSocketCloseStatus? closeStatus, string? closeStatusDescription);
}
// Controller
public class WebSocketController : ControllerBase
{
readonly ILogger _logger;
public WebSocketController(ILoggerFactory loggerFactory)
{
_logger = loggerFactory.CreateLogger("WS");
_logger.Ctor();
}
[Route("/ws")]
public async Task Get()
{
if (HttpContext.WebSockets.IsWebSocketRequest)
{
using var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync();
_logger.AcceptWebSocketAsync(webSocket);
await Echo(webSocket);
}
else HttpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
}
async Task Echo(WebSocket webSocket)
{
var buffer = new byte[1024 * 4];
var receiveResult = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
while (!receiveResult.CloseStatus.HasValue)
{
await webSocket.SendAsync(new ArraySegment<byte>(buffer, 0, receiveResult.Count),
receiveResult.MessageType, receiveResult.EndOfMessage, CancellationToken.None);
receiveResult = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
}
await webSocket.CloseAsync(receiveResult.CloseStatus.Value,
receiveResult.CloseStatusDescription, CancellationToken.None);
HighPerfCodeGenLog.CompileTime(_logger, receiveResult.CloseStatus, receiveResult.CloseStatusDescription);
}
}