The only thing you need in your Startup
is to add the UseWebsockets
middleware.
Then you can define your own middleware and filter connections if they are websocket
type like below:
Startup
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {
app.UseWebSockets();
app.UseMiddleware<SocketWare>();
}
Middleware
public class SocketWare {
private RequestDelegate next;
public SocketWare(RequestDelegate _next) {
this.next = _next;
}
public async Task Invoke(HttpContext context) {
if (!context.WebSockets.IsWebSocketRequest) {
return;
}
var socket=await context.WebSockets.AcceptWebSocketAsync();
await RunAsync(socket);
}
private async Task RunAsync(WebSocket socket) {
try {
var client = new ChatClient(socket, this.store,this.channelRegistry);
await client.RunAsync();
} catch (Exception ex) {
throw;
}
}
}
In my middleware i prefer to keep my business logic in a separate class that gets the Websocket
injected in it like below:
Client
public class ChatClient
{
private Task writeTask;
private Task readTask;
private WebSocket socket;
private CancellationTokenSource cts=new CancellationTokenSource();
ChatClient(WebSocket socket)
{
this.socket=socket;
}
public async Task RunAsync()
{
this.readTask=Task.Run(async ()=>await ReadLoopAsync(cts.Token),cts.Token);
this.writeTask=Task.Run(async()=>await WriteLoopAsync(cts.Token),cts.Token);
await Task.WhenAny(this.readTask,this.writeTask);
}
public async Task WriteLoopAsync()
{
Memory<byte> buffer=ArrayPool<byte>.Shared.Rent(1024);
try {
while (true) {
var result= await this.socket.ReceiveAsync(buffer,....);
var usefulBuffer=buffer.Slice(0,result.Count).ToArray();
var raw=Encoding.Utf8.GetString(usefulBuffer);
//deserialize it to whatever you need
//handle message as you please (store it somwhere whatever)
}
} catch (Exception ex) {
//socket error handling
//break loop or continue with go to
}
}
public async Task ReadLoopAsync()
{
try {
while (true) {
var data = await this.[someMessageProvider].GetMessageAsync() //read below !!!
var bytes = Encoding.UTF8.GetBytes(data);
//send the message on the websocket
await this.socket.SendAsync(data, WebSocketMessageType.Text, true, CancellationToken.None);
}
} catch (Exception ex) {
//do incorrect message/socket disconnect logic
}
}
}
Now regarding producing messages and consuming them. In your case you could define your producers as some Controller
routes like below .You would hit a route , produce a message and publish it to some message broker. I would use a Message Queue (RabbitMQ) or even a Redis Pub/Sub
as a message bus.
You would publish messages from your route
(s) and then consume them in your ReadLoopAsync
method from WebSocketClient
(look above).
Producing messages
public UpdateController:Controller
{
private IConnection
[HttpPost]
[someroute]
public void UpdateScoreboard(string someMessage)
{
this.connection.Publish("someChannel",someMessage);
}
[HttpPost]
[someotherroute]
public void DeletePlayer(string someOtherMessage)
{
this.connection.Publish("someChannel",someMessage);
}
}
Redis pub/sub
Check redis pub/sub here
Also check my repository
on github here in which i am
using exactly what you need (websockets, redis,pub sub)
RabbitMq
Another option as a message bus is to use RabbitMQ
, for more info regarding C# API
here
In Memory
You could also avoid using a third party and use some in memory data
structure like a BlockingCollection
.You could inject it as a
Singleton
service both in your Controller(s)
and your socket
Middleware(s)