Využití WebSocket protokolu pomocí SignalR
14.11.2020Slovníček
WebSocket | Aplikační protokol, umožňující obousměrnou komunikaci mezi serverem a webovým prohlížečem. Standardizován byl v roce 2011 a implementován napříč prohlížeči byl zhruba kolem roku 2013. |
Fallback | Záložní řešení, které se použije pokud primární řešení z nějakého důvodu selže. |
Server-sent event | Technologie umožňující posílat data ze serveru klientovi, implementována do prohlížečů byla zhruba kolem roku 2011 (s výjimkou IE). |
Long Polling | Technika, kdy klient pošle serveru HTTP požadavek a pokud server nemá k dispozici nová data, tak na požadavek neodpoví a nechá ho timeoutovat. Klient po timeoutu nebo odpovědi od serveru posílá nový požadavek. |
P2P | Peer-to-peer je komunikace mezi dvěma počítači (napřímo bez serveru jako prostředníka). |
SignalR Hub | API na backendu, které se používá pro WebSocket komunikaci. |
Obsah
Popis řešení
Představa je taková, že webový server poskytne html stránku, která umožní uživatelům kreslit do <canvas> html tagu, pomocí javascriptu. Javascript ale veškeré souřadnice pohybu myši bude zároveň posílat na server, který souřadnice předá dalším připojeným uživatelům zavoláním javascriptové funkce, kterou každý uživatel bude mít staženou ve svém prohlížeči
Implementace
Jako webový framework jsem zvolil ASP.NET Core 3, jehož je SignalR součástí.
Backend
Ve Startup.cs zaregistrujeme middleware a nadefinujeme adresu k Hub API
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddSignalR();
}
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
endpoints.MapHub<DrawingHub>("/drawinghub");
});
nyní aplikace ví, že má veškeré požadavky na adresu \drawinghub přeposílat třídě DrawingHub, kterou ale musíme nejdříve vytvořit. Vytvoříme si tedy novou třídu s velice jednoduchou API funkcí DrawLine pro přeposílání souřadnic
using Microsoft.AspNetCore.SignalR;
using System.Threading.Tasks;
namespace SharedDrawing.Hubs
{
public class DrawingHub : Hub
{
public async Task DrawLine(int x1, int y1, int x2, int y2)
{
await Clients.Others.SendAsync("DrawLine", x1, y1, x2, y2);
}
}
}
Frontend
Pro kreslení si vypůjčím kód z mozilla stránek, který jen trochu upravím. Uživatel musí mít také stáhnutého SignalR klienta, v našem případě javascriptovou knihovnu, která bude zajišťovat komunikaci se serverem. HTML, CSS a odkazy na JS scripty vložíme do index.html.
<style>
canvas {
border: 1px solid black;
width: 365px;
height: 200px;
}
</style>
<p>Shared drawing</p>
<canvas id="myPics" width="365" height="200"></canvas>
@section Scripts {
<script src="https://cdnjs.cloudflare.com/ajax/libs/aspnet-signalr/1.1.4/signalr.min.js"></script>
<script src="~/js/drawingHub.js"></script>
}
Vytvoříme také drawingHub.js, na který se odkazujeme. Obsahuje mouse eventy, ke kterým přidáme SignalR funkce na připojení k serveru connection.start(), na poslouchání příchozích příkazů connection.on() a do drawLine funkce přidáme volání Hub API connection.invoke(). Jedná se o tři klíčové funkce pro komunikaci s API. DrawLine funkce je rozšířená o parametr callSignalR, který je nastavený na false, v případě kdy se funkce volá z příchozího volání serveru. Parametr řídí přeposílání souřadnic zpátky na server, aby nedošlo k vytvoření nekonečné smyčky.
/* Drawing events
* https://developer.mozilla.org/en-US/docs/Web/API/Element/mousemove_event
**********************************/
// When true, moving the mouse draws on the canvas
let isDrawing = false;
let x = 0;
let y = 0;
const myPics = document.getElementById("myPics");
const context = myPics.getContext("2d");
// event.offsetX, event.offsetY gives the (x,y) offset from the edge of the canvas.
// Add the event listeners for mousedown, mousemove, and mouseup
myPics.addEventListener("mousedown", e => {
x = e.offsetX;
y = e.offsetY;
isDrawing = true;
});
myPics.addEventListener("mousemove", e => {
if (isDrawing === true) {
drawLine(context, x, y, e.offsetX, e.offsetY);
x = e.offsetX;
y = e.offsetY;
}
});
window.addEventListener("mouseup", e => {
if (isDrawing === true) {
drawLine(context, x, y, e.offsetX, e.offsetY);
x = 0;
y = 0;
isDrawing = false;
}
});
/* SignalR
**********************************/
// initialize connection
var connection = new signalR.HubConnectionBuilder().withUrl("/drawinghub").build();
// connect to the server
connection
.start()
.then(function () {
console.log("connected to the drawing hub");
})
.catch(function (error) {
return console.error(error.toString());
});
// listen to remote procedure calls (RPC)
connection.on("DrawLine", function (x1, y1, x2, y2) {
console.log("DrawLine", x1, y1, x2, y2);
drawLine(context, x1, y1, x2, y2, callSignalR = false);
});
// function is called via mousemove and mouseup events
function drawLine(context, x1, y1, x2, y2, callSignalR = true) {
context.beginPath();
context.strokeStyle = "black";
context.lineWidth = 1;
context.moveTo(x1, y1);
context.lineTo(x2, y2);
context.stroke();
context.closePath();
// check if we want to call server, to prevent looping
if (callSignalR) {
// call DrawLine action on the server.
connection.invoke("DrawLine", x1, y1, x2, y2)
.catch(function (err) {
return console.error(err.toString());
});
}
}
Výsledek
Nyní máme vše připravené a můžeme aplikaci vyzkoušet. Aplikaci spustíme přímo z visual studia (IIS Express), otevřeme dvě okna prohlížeče a můžeme z obou stran kreslit :-) Připojit se může i více uživatelů, protože server nám souřadnice přeposílá na všechny připojené klienty.