Utvecklingstips

Så skapar du en äkta realtidswebb med Node.Js och Socket.io i Windowsmiljö

Vad är realtidswebben?

Tänk er att komma in på en sajt som säljer flygresor och när du knappat klart alla dina val kring flygtider, antal byten, barnstolar etc så kommer du till sidan ”Tyvärr finns det inga resor som matchar din sökning”. Om någon precis då avbokar sin resa finns det längre ingen möjlighet att kommunicera med dig, du har ju redan hämtat din sida. Om denna resesajt hade varit en realtidswebb –hade du kunnat få upp ett meddelande på skärmen: ”Helt otroligt! Vi har precis fått en avbokning– vill du se resan?”. Man kan jämföra det med nummerpresentation i telefonsystem – en realtidswebb gör det möjligt att ringa tillbaka till kunden, inte bara ta emot samtal.

En förutsättning för att en webbplats på riktigt ska vara en realtidswebb är att alla delar den pratar med ska vara asynkrona, dvs att saker kan hända utan att det sker enligt ett schema. En asynkron bank kan t ex skicka över pengar direkt mellan konton men en synkron bank måste vänta till efter midnatt innan överföringen kommer över. Andra exempel på asynkrona system är SMS och telefonsamtal. Microsoft hade i sin reklamfilm för Microsoft.NET 2003 en bra vision för ett helt asynkront beställningssystem där kunden kunde välja färg på sin bil och direkt när färgvalet gjordes av säljaren på handdatorn såg man hur roboten i bilfabriken bytte färg i färgsprutan. 

Google Docs är ett annat bra exempel på en realtidswebb. När någon ändrar i ett stycke, till och med bara en enskild bokstav uppdateras direkt allas webbläsare som tittar på samma dokument.

Hybrid synkron/asynkrona tekniker

Genom åren har det förekommit många försök till realtidskommunikation mellan server och klient. De vanligaste teknikerna som använts är (i kronologisk ordning):

1.      Iframe med javascript-kommandon som skickas från servern

2.      Pollning med hjälp av XMLHttpRequest

3.      Långa requests med XMLHttpRequest med XML resultat

4.      Långa requests med JsonP

5.      Separat flashkomponent som pratar med servern via sockets

Alla dessa tekniker (förutom kanske nr 1) brukar sammanfattas med förkortningen AJAX som står för Asynchronous Javascript with XML. AJAX har varit och är fortfarande en av de viktigaste komponenterna av det som kallas Web 2.0. Den största förändringen med AJAX var att man kunde uppdatera delar av skärmen utan att behöva ladda om hela sidan vilket gjorde att en webbsida känns mer som ett program på datorn. Nästa utmaning har varit att kunna skicka information som händer på servern till klienten utan att kräva omladdning av sidan och då har ovanstående tekniker använts – vanligtvis genom en timer som frågar med jämna mellanrum om det har hänt något på servern och i så fall uppdateras sidan från klienten.

När webbplatser växer och får fler användare uppstår dock två problem med dessa tekniker vilket gör att man ofta får begränsa realtidsdelarna på webbplatsen för att inte hela tjänsten ska upplevas som långsam:

A.      För många requests till webbservern gör att servern blir överbelastad. Tänk er 2000 webbläsare som ställer tre frågor per sekund till servern om något har hänt, det blir för mycket för att hantera av en vanlig webbserver. Om det dessutom innebär att servern ska kontrollera i databasen om något har hänt blir det ännu jobbigare att hantera.

B.      Klienten går långsamt för att en av de fåtal connections som finns tillgängliga i webbläsaren blir upptagna (förut var det max två connections per domänadress men nu är det ökat till runt fem-tio beroende på webbläsare )

Vad är lösningen? Websockets?

I och med införandet av HTML5 har det kommit nya sätt att kommunicera mer effektivt mellan server och klient i och med att webbläsaren numera har inbyggt stöd för att prata med WebSockets vilket är den enklaste formen av kommunikation på internet. Websockets kan liknas med en inbyggd Telnet klient i webbläsaren. Med websockets blir det därför möjligt att öppna en kanal för att skicka kommandon både till server och klient med samma teknik.

Tyvärr fungerar inte Websockets rakt av i alla webbläsare, till exempel har Firefox avaktiverat sitt websockets-stöd per default i alla senare versioner. En komplett lista på vilka webbläsare som stöder Websockets (eller andra bra HTML5 finesser) finns på den utmärkta sajten caniuse.com. För att ändå börja använda WebSockets finns det självklart ramverk som hanterar lösningen med äldre klienter med hjälp av ovanstående tekniker (iframe, ajax pollning eller flashfallback). Det ramverk vi har utvärderat och kan rekommendera heter socket.io men det finns andra t ex now.js. Socket.IO är javascriptbaserat och är ett ramverk som installeras ovanpå servern node.js.

Vad är felet på .NET?

Både Socket.IO och now.js bygger på webbservern node.js och varför det är bra ska jag snart berätta, men först måste jag förklara lite av vår tidigare erfarenhet av utveckling för större webbplatser.  Vi har på Iteam gjort en hel del webbplatser med AJAX och många har haft en s.k. eventhubb som i princip skapar en .NET baserad meddelandekö som tillåter att man kan skicka meddelanden direkt från .NET till klienten via långa AJAX-anrop (enligt princip 3 ovan). I de projekt där vi ligger på IDG:s Topp 100 lista med tusentals samtidiga användare skapar denna eventhubb en onödigt stor belastning på servern eftersom alla anrop hamnar i .NET-kärnan och varje connection tar en tråd av processorn. Varje tråd tar dessutom c:a 2 mb i minne. Detta gör att man måste balansera längden på varje anrop och antalet anrop per minut på ett sätt som gör att realtidskänslan blir mindre påtaglig när många användare är inne. Det kostar helt enkelt för mycket att ha serverkraft att hålla alla dessa uppkopplingar igång på servern. Dvs, problemet är att IIS och .NET har för stor overhead per tråd och connection för att kunna hantera detta på ett effektivt sätt.

Ok, vi fattar men kom till sak (node.js)

Node.JS är en HTTP-server byggd för att köra Javascript kod (!) väldigt effektivt och väldigt lightweight. Servern är byggd på Googles Javascriptmotor V8 som också används i Chrome. Den stora fördelen med att använda node.js är att den inte är trådad vilket gör att den kan hantera extrema mängder samtidiga uppkopplingar utan att det tar mer processorkraft eller minne av servern. När nya anrop kommer till Node.JS läggs de till i en intern buffer i node.js och skapar ingen ny tråd och tar därför i princip bara några bytes per anrop. Detta gör att Node.JS är optimal för att hantera långa asynkrona anrop och lämpar sig därför som central hubb för realtidssystem.

Singeltrådad webbserver låter dåligt tycker jag?

Parallell har alltid varit bättre än seriell tycker jag så det var för mig en gåta hur detta med ”inga trådar” kunde göras om till något positivt?

T ex skulle den här koden göra att servern hänger sig och inga anrop går igenom:


while(new Date().getTime() < now + 1000) {
   // do nothing
}

Men då tänker man fortfarande synkront. Flödet genom en kod ska inte byggas på det sättet. Istället skriver man sin kod så att långa anrop returnerar sitt resultat asynkront vilket inte hänger servern alls och tar varken cpu eller minne av det väntande anropet:


callExternalServer(function(result){

           // ten seconds later?

           console.log(result);

});


Fördjupa dig på asynkron programmering med Node.Js här:

http://blog.mixu.net/2011/02/01/understanding-the-node-js-event-loop/

Vad är då Socket.IO?

Så, node.JS är alltså en webbserver baserad på Javascript, perfekt för asynkrona realtidssystem men den är bara en effektiv webbserver, men för att enkelt kunna kommunicera med den behöver vi ett ramverk. Socket.IO valde vi eftersom det löser precis den uppgift vi behövde:

1.      Inkluderar både ramverk på server och på klienten

2.      Hanterar bakåtkompatibiliteten med äldre webbläsare (med hjälp av flash, XmlHttpRequests, iframe etc)

3.      Klarar att gruppera lyssnare i grupper och skicka broadcasts till alla eller urval av grupperna

4.      Använder JSON som bärare

För att testa detta valde vi att börja med en volymkontroll som skulle uppdateras direkt när man ändrar på den på någon av webbläsarna i ett rum. På servern vill vi ta emot ett kommando ”volume” med data om vilket rum som volymen har ändrats i och alla andra i samma ”rum” ska få reda på att volymen har ändrats:

På klientenhar vi en HTML5 volymkontroll (type=”range”). Observera också att klientfilerna för socket.io länkas från node.js och i vårt fall är node installerat på localhost på port 8080. Vi anger även vilken ordning vi vill använda transporterna med websockets först.


   <script type="text/javascript" src="http://localhost:8080/socket.io/socket.io.js"></script>

   <input type="range" id="volume"onchange="volume(this.value)"/>

   <script type="text/javascript">

       var socket = io.connect('http://localhost:8080', { rememberTransport:false, transports: ['websocket','xhr-polling', 'jsonp-polling'] });

       var room = 'TestVolume';

        socket.emit('checkin', { room: room, user:'TestUser'});

        socket.on('onVolumeChange',function (data) {

// someone changed volume in this room, let’s update the volume meter

document.getElementById('volume').value = data.volume;

        });

        function volume(value) {

// send this new volume value to all listeners in this room

            socket.emit('volume', { room: room, volume: value });

       }

   </script>

Och på servern (sparas somindex.js och körs med node.exe index.js):


var io = require('socket.io').listen(8080, { transports: ["websocket", "flashsocket", "xhr-polling"] });

io.set('log level', 2);// reduce logging

io.sockets.on('connection',function (socket) {

   // join to room and save the room name

    socket.on('checkin',function (data) {

            socket.join(data.room);// join a room

    });

    socket.on('volume',function (data) {

// broadcast back this message to all listeners except the sender (which would prevent an endless loop)

            socket.broadcast.to(data.room).emit('onVolumeChange', data);

   });

}

Resultatet blir såhär: (använd Chrome eller Safari för att se volymkontrollerna)

Testa själva på http://tekniken.nu/examples/socketio-range.htm 

och öppna därefter ytterligare ett fönster http://tekniken.nu/examples/socketio-range.htm

Hur kör man nu detta i en Windowsmiljö?

Node.JS är från början byggt för Linux och länge behövde man installera Node i en Cygwin emulator för att kunna köra detta på en windowsmaskin. Numera är dock node portat till Windows och enklast laddar man ner det via NuGet:

http://nuget.org/List/Packages/nodejs

Innan du kan ladda ner Node.JS via nuget måste du först initiera ett lokalt maskin-knutet bibliotek för NuGet:

Kör i Package Manager Console:

Initialize-Chocolatey

Därefter:

Install-Package nodejs

Nu har du NodeJs lokalt på din maskin, antagligen i mappen C:\NuGet\lib\nodejs.0.5.3\tools\node.exe

För att köra socket.io behöver du nu ladda ner socket.io från socket.io:

https://github.com/learnboost/socket.io/

Eftersom NPM inte finns för Windows får du packa upp zipfilen och installera den manuellt i ditt projekt. Du placerar alltså socket.io på server-sidan och packar upp zip-filen i en mapp som heternode_modules:

Om nu allt är uppsatt korrekt ska du kunna skriva:

C:\NuGet\lib\nodejs.0.5.3\tools\node.exe index.js

För att detta ska bli driftsäkert behöver du installera node att köras som en service. Det gör du enklast genom att använda det fånigt namngivna programmet NSSM (Non Sucking Service Manager):

nssm install NodeExample C:\NuGet\lib\nodejs.0.5.3\tools\node.exe d:\path_to_your_project\index.js

Nu kan du starta din första node tjänst

NET START NodeExample

Och kom ihåg att du behöver starta om servicen om du ändrar i koden. Och se till att din server som kör node.js står utanför brandväggen om du vill kunna tillåta riktigt långa connections och minska belastningen på din brandvägg ;)

Alternativt kan du köra node i IIS:

Det finns även sätt att installera Node att köras som en del av IIS med IISNode(däremot är jag inte säker på att det är en bra idé att blanda in IIS av de anledningar som jag beskrev ovan men något konkret test av detta är inte genomfört än). Att utveckla med hjälp av IISNode är helt klart en fördel eftersom den startar om sig själv så fort någon av filerna ändras i projektet samt att den hanterar loggning.

Vi byggde resten av vår kod i .NET för så skoj är inte Javascript när det väl kommer till unittest, objektorienterad programmering osv, men för själva eventhanteringen sköts den nu med detta ramverk vilket gör att ASP.NET blir helt avlastad från dessa anrop och vi kan slösa på realtidsanrop utan att det (nästan inte) kostar något på servern. Enligt flera bloggar klarar en vanligt utrustad server med node.js av flera tiotusentals samtidiga connections vilket är enormt i förhållande till motsvarande lösning byggd med IhttpAsyncHandler (.NET).

Läs även IBMs genomgång av NodeJs:

http://www.ibm.com/developerworks/opensource/library/os-nodejs/

 
Christian Landgren

2011-09-05 kl. 17:12

Web Analytics