ABONAMENTE VIDEO REDACȚIA
RO
EN
NOU
Numărul 144
Numărul 143 Numărul 142 Numărul 141 Numărul 140 Numărul 139 Numărul 138 Numărul 137 Numărul 136 Numărul 135 Numărul 134 Numărul 133 Numărul 132 Numărul 131 Numărul 130 Numărul 129 Numărul 128 Numărul 127 Numărul 126 Numărul 125 Numărul 124 Numărul 123 Numărul 122 Numărul 121 Numărul 120 Numărul 119 Numărul 118 Numărul 117 Numărul 116 Numărul 115 Numărul 114 Numărul 113 Numărul 112 Numărul 111 Numărul 110 Numărul 109 Numărul 108 Numărul 107 Numărul 106 Numărul 105 Numărul 104 Numărul 103 Numărul 102 Numărul 101 Numărul 100 Numărul 99 Numărul 98 Numărul 97 Numărul 96 Numărul 95 Numărul 94 Numărul 93 Numărul 92 Numărul 91 Numărul 90 Numărul 89 Numărul 88 Numărul 87 Numărul 86 Numărul 85 Numărul 84 Numărul 83 Numărul 82 Numărul 81 Numărul 80 Numărul 79 Numărul 78 Numărul 77 Numărul 76 Numărul 75 Numărul 74 Numărul 73 Numărul 72 Numărul 71 Numărul 70 Numărul 69 Numărul 68 Numărul 67 Numărul 66 Numărul 65 Numărul 64 Numărul 63 Numărul 62 Numărul 61 Numărul 60 Numărul 59 Numărul 58 Numărul 57 Numărul 56 Numărul 55 Numărul 54 Numărul 53 Numărul 52 Numărul 51 Numărul 50 Numărul 49 Numărul 48 Numărul 47 Numărul 46 Numărul 45 Numărul 44 Numărul 43 Numărul 42 Numărul 41 Numărul 40 Numărul 39 Numărul 38 Numărul 37 Numărul 36 Numărul 35 Numărul 34 Numărul 33 Numărul 32 Numărul 31 Numărul 30 Numărul 29 Numărul 28 Numărul 27 Numărul 26 Numărul 25 Numărul 24 Numărul 23 Numărul 22 Numărul 21 Numărul 20 Numărul 19 Numărul 18 Numărul 17 Numărul 16 Numărul 15 Numărul 14 Numărul 13 Numărul 12 Numărul 11 Numărul 10 Numărul 9 Numărul 8 Numărul 7 Numărul 6 Numărul 5 Numărul 4 Numărul 3 Numărul 2 Numărul 1
×
▼ LISTĂ EDIȚII ▼
Numărul 16
Abonament PDF

Aplicații real-time folosind SignalR

Radu Vunvulea
Solution Architect
@iQuest



PROGRAMARE

Trăim într-o lume dinamică, o lume în care datele zboară extrem de rapid. În această lume, aplicațiile web au devenit din ce în ce mai complexe. Zilele când aveam doar pagini web statice au trecut de mult, la fel și perioada în care Ajax și jQuery erau la putere.

Aplicații real-time

Într-o lume în care aplicațiile real time fac parte din viața noastră, avem nevoie de noi mecanisme pentru a putea face apeluri server2client. Aplicațiile web pentru monitorizare, jocurile online, aplicațiile bursiere sau cele în care edităm documente au nevoie de sisteme de acest fel, care să fie robuste și scalabile.

Web Sockets

HTML5 a adus cu el Web Sockets. O soluție perfectă care ne face viața mult mai ușoară. Acesta ne permite să ținem o conexiune deschisă între server și client prin care serverul poate să trimită date la clienți (chiar dacă aplicațiile rulează într-un browser). Un mecanism perfect pentru ceea ce avem nevoie.

Chiar dacă rata de adopție la HTML5 este bună, mai avem câțiva ani de așteptat până când vom avea o adopție de peste 90% la HTML5. Până la apariția WebSockets, pe piață au existat diferite soluții de genul Forever Frame, Server Send Events, Pooling sau SPDY. Până în acest moment niciuna dintre ele nu a fost adoptată în una limitată de către toate browser-ele.

De aceea au apărut diferite framework-uri care ne ajută în această zonă. Commet, Pusher, SockJSm Now.js sunt doar o parte din ele. Un framework care ne ajută să putem notifica clienții web este SignalR.

Ce este SignalR

SignalR este o librărie ce ne oferă o modalitate extrem de simplă pentru a putea avea o comunicare bidirecțională între client și server - real time. Aceasta este o soluție dedicată pentru cei care folosesc .NET. și rezolvă trei mari probleme pe care dezvoltatorii le întâmpinau:

  • Push de date de la server la client peste conexiuni HTTP/S,
  • Apeluri de tip RPC (Remote Procedure Call) peste conexiuni HTTP/S,
  • Compatibilitate cu browser-ele mai vechi.

Chiar dacă este susținut de Microsoft, acest framework este open source, putând fi găsit pe GitHub - fiind unul dintre cele mai urmărite proiecte de pe GitHub.

Unde putem să folosim SignalR

Acesta poate să ruleze pe sisteme care au ca backend Windows (.NET) și nu numai. Deoarece acesta poate să ruleze și pe Mono, putem să avem un sistem care rulează sub Linux și să folosească SignalR.

Clienții pe care îi putem avea sunt extrem de variați, începând de la browsere și terminând cu aplicații desktop, Silverlight, Windows Store, Windows Phone și IoS. Acesta poate să ruleze pe diferite browsere, chiar și pe cele pe care Web Sockets nu este suportat. Acest lucru este posibil datorită modului prin care SignalR comunică cu clienții.

Mecanisme de comunicare

Acesta suportă mai multe mecanisme de comunicare, iar în cazul în care observă că unul dintre ele nu este suportat de către browser (client) va face fallback automat la un alt mecanism. Metodele de comunicare suportate sunt:

  • Web Sockets,
  • Server Side Event,
  • Forever Frame,
  • Ajax long pooling.

Dacă un mecanism nu este suportat, se face fallback automat până când se ajunge la Ajax Long Pooling, care este suportat de toate browser-ele de azi. Datele sunt trimise la clienți în format JSON sau Plain Text. Trebuie ținut cont că această soluție nu este gândită să trimită date de dimensiuni mari - fișiere.

Notă: Singurul browser care nu suportă Server Side Event este Internet Explorer.

Base API

SignalR ne oferă două modalități de comunicare: Persistent Connection și Hubs.

Persistent Connection este foarte asemănătoare cu WebSockets, oferindu-ne o conexiune persistentă între client și server. Evenimentele și metodele disponibile în cazul Persistent Connection sunt aceleași pe care le avem la dispoziție și cu WebSockets - "Connect", "Disconnect", "Receive", "Error", "Send", "Broadcast".

Prin intermediul unei conexiuni de acest tip putem să facem broadcast la mesaje la toți clienții sau doar o parte din aceștia. Comparat cu WebSocket, avantajul folosirii Persistent Connection apare în momentul în care avem clienți care nu suportă WebSockets. Noi vom putea comunica cu ei, chiar dacă comunicarea se face prin Forever Frame sau Ajax. Modul în care se face comunicarea între client și server nu trebuie rezolvată de către dezvoltator.

Pentru a putea folosi un Persistent Connection este nevoie să extindem clasa "PersistentConnection" și să facem override la metodele de care noi avem nevoie. Două din cele mai importante metode din această clasă sunt "OnConnected" (care se apelează când un nou client se conectează) și "OnReceived" (apelată când un client trimite un mesaj). Mesajele se pot trimite la unul sau mai mulți clienți apelând metodele pe care proprietatea "Connection" ni le pune la dispoziție.

public class FooConnection:PersistentConnection 
{
 protected override Task OnConnected(IRequest request, string connectionId)
  {
   return Connection.Broadcast("We have a new 
    user: " + request.QueryString["nickname"]);
  }
  protected override Task OnReceived(IRequest request, string connectionId, string data)
  {
    return Connection.Broadcast(request.QueryString["nickname"] + ":" + data);
  }
}

Pe partea de client, odată ce clientul obține o referință la connection, poate să facă override la metode precum "received" sau "error". Este foarte important de reținut că nu este de ajuns acest lucru. Odată ce am făcut override la ele, este necesar să deschidem o conexiune cu serverul, apelând metoda "start".

var connection = $.connection("/echo", "name=" + nickname, true);;
connection.received(function (data) {
	...
});
connection.error(function (err) {
	...
});
connection.start(function () {
	...
});

Hub-urile pot să fie privite ca o abstractizare a Persistent Connection. API-ul care ne este pus la dispoziție prin hub-uri este mult mai simplu și mai ușor de folosit. Această abstractizare ne aduce o funcționalitate care nu este disponibilă dacă folosim Persistent Connection - RPC (Remote Procedure Calls). Da, ați auzit bine, hub-urile ne permit să facem acest lucru extrem de ușor.

Putem foarte ușor să apelăm metode client de pe server (chiar dacă sunt definite în JavaScript) sau clientul poate la fel de bine să apeleze metode care sunt pe server. Cred că acest lucru este unul din cele mai mari avantaje pe care hub-urile le aduc.

Pe server, dacă dorim să adăugăm un nou hub este nevoie să extindem clasa Hub. De această dată nu mai avem nevoie să facem override la nici o metodă. Trebuie doar să ne definim metodele pe care dorim să le expunem.

La hub-uri este foarte important să adăugăm atributul "HubName" pe clasa care definește hub-ul nostru. Acesta va specifica ce nume de clienți hub trebuie să folosească pentru a apela hub-ul nostru. Pentru a apela metodele pe care clientul le expune este nevoie să ne folosim de proprietatea "Client" care ne pune la dispoziție diferite metode să apelăm metodele expuse de client. Putem să facem atât apeluri la un anumit client cât și apeluri de tip broadcast. În aceste cazuri datele de tip dynamic ne sunt de mare ajutor.

[HubName("footballScore")]
public class ScoreHub : Hub
{
    public void Start(string matchId, string team1Name, string team2Name)
    {
        DateTime when = DateTime.Now;
        Clients.All.matchStart(matchId, team1Name, team2Name, when.ToShortTimeString());
    }

    public void Stop(string matchId)
    {
        DateTime when = DateTime.Now;
        Clients.All.matchEnded(matchId, when.ToShortTimeString());
    }<

    public void NewScore(string matchId, string team1Score, string team2Score, string playerName)
    {
        Clients.All.goal(matchId, team1Score, team2Score, playerName);
    }        
}

Fiecare client se identifică unic printr-un connection token. Managementul acestuia este făcut în întregime de server și semnat cu o semnătură digitală. Connection token există până la finalul conexiunii și este format din connection id și username. Dacă username-ul există doar în cazul în care clientul este autentificat, connection id-ul există din primul moment când o conexiune este stabilită între client și server.

Pe partea de client, lucrurile se simplifică. Odată ce avem o referința la hub-ul nostru, putem să definim metodele client pe care serverul le poate apela sau să scriem cod care apelează metodele expuse de către server. Înainte să începem să trimitem date sau să fim apelați este nevoie să apelăm $.connection.hub.start().

// server2client example
var footballScore = $.connection.footballScore;
$.extend(footballScore.client, {
    matchStart: function (matchId, team1Name, team2Name, when) {
	...
    },
    
    matchEnded: function (matchId, when) {
	...
    },
    
    goal: function (matchId, team1Score, team2Score, playerName) {
	...
    },
});
// client2Server example
var footballScore = $.connection.footballScore;
$.connection.hub.start()
    .done(function() {
        $("#Start").click(function() {
            footballScore.server.start($("#TeamId").val(), $("#Team1Name").val(), $("#Team2Name").val());
        });
        $("#Stop").click(function () {
            footballScore.server.stop($("#TeamId").val());
        });
        
        $("#Goal").click(function () {
            footballScore.server.newScore($("#TeamIdScore").val(), $("#Team1Score").val(), $("#Team2Score").val(), $("#Player").val());
        });
    });

De remarcat este faptul că dacă avem mai multe mai multe hub-uri, pe care serverul le expune, comunicare între clienți și server se va face pe câte un connection pentru fiecare client. Un client va folosi aceeași conexiune pentru a comunica cu două sau mai multe hub-uri de pe același server. Acest lucru este făcut deoarece se încearcă limitarea numărului de conexiune deschise între server și clienți. Folosirea unei singure conexiuni nu are nici o repercusiune din punct de vedere a performanței.

Hubs vs Persistent Connection

O întrebare destul de firească care poate să apară în acest moment este:

Când să folosesc un hub și când să folosesc persistent connection?

Răspunsul este destul de simplu. Hubs se recomandă să fie folosit în momentul în care avem nevoie de RPC. Pentru cazurile în care formatul mesajului trebuie specificat sau vrem să folosim un model de tipul messaging and dispatching atunci Persistent Connection este soluția recomandată. Totodată în cazul în care integrăm într-o aplicație deja existentă SignalR, atunci se recomandă să folosim Persistent Connection, migrarea spre SignalR fiind mult mai ușoară.

Securitate

Apelurile de tip CSRF (Cross-Site Request Forgery) sunt evitate prin următoarele mecanisme:

  • Apelurile de tip cross domain sunt dezactivate by default,
  • Connection token-ul este verificat la fiecare apel,
  • Connection token este pus în query string,
  • Niciodată connection token nu este persistat pe client.

Din cauza ultimului punct menționat, dacă avem doua tab-uri deschise vom avea două connection token-uri diferite și automat doi clienți separați. Cu puțin custom code putem să trecem peste această limitare.

Performanță

Performanțele pe care SignalR le are sunt bune. Putem să avem peste 450.000 de mesaje manipulate de către un singur server, iar numărul de conexiuni pe o singură mașină pe care le putem avea este de 15.000-20.000. Numărul de conexiuni este limitat de un singur factor - numărul de porturi pe care le avem disponibile.

Scalabilitate

Scalabilitatea într-un astfel de sistem nu este foarte ușoară. Acest lucru se datorează problemei pe care SignalR o rezolvă. Deoarece face handling la mesaje, dacă scalăm cu încă o instanță și pune un load balancer în față la server, un apel de tip broadcast nu o să ajungă la toți clienți.

Pentru a putea rezolva această problemă trebuie să folosim un mecanism prin care un mesaj poate să fie trimis la toate nodurile din cluster. Acest lucru se face destul de ușor prin intermediul a trei soluții aplicabile în acest moment:

  • Windows Azure Service Bus,
  • Redis,
  • SqlServer.

Folosirea unui astfel de serviciu este simplă, singurul lucru pe care trebuie să îl facem este să specificăm string-ul de conexiune. De exemplu integrarea cu Service Bus și sincronizarea între noduri se reduce la o singura linie de cod:

GlobalHost.DependencyResolver.UseServiceBus(sbConnectStrion, "codecampcluj");

Totodată nimic nu ne oprește să extindem modul în care nodurile se pot sincroniza, doar că este nevoie să scriem noi modalitatea de sincronizare. Dacă folosim mai multe mașini cu SignalR care se sincronizează între ele este bine de știu că latența în cazul unui broadcast crește ușor. Acest lucru se întâmplă din cauza că un mesaj odată ce ajunge la server trebuie să fie trimis și la restul serverelor.

O fermă formată din noduri cu SignalR poate să fie folosită cu succes dacă dorim să facem broadcast la mesaje, iar o latență de câteva milisecunde nu ne afectează. Aceasta nu este recomandă pentru comunicări de tip client2client sau high-frequency realtime deoarece latența poate să fie destul de mare, nefiind cea mai bună soluție de care noi avem nevoie.

Concluzie

Am văzut că SignalR este un framework care ne ajută să avem aplicații web care pot să comunice în ambele sensuri, fiind perfect pentru aplicații bursiere sau aplicații de monitorizare. Numărul de mesaje pe care un server cu SignalR le poate procesa este extrem de mare fiind o soluție ideală când avem nevoie să facem față la zeci de milioane de mesaje pe oră.

Vă invit să incercați să folosiți SignalR și să vedeți cât de simplu este.

NUMĂRUL 143 - Software Craftsmanship

Sponsori

  • Accenture
  • BT Code Crafters
  • Accesa
  • Bosch
  • Betfair
  • MHP
  • BoatyardX
  • .msg systems
  • P3 group
  • Ing Hubs
  • Cognizant Softvision
  • Colors in projects