jsramverk.me

jsramverk.me / Realtid

Realtid

Skriven av: Emil Folino. Uppdaterad: 2019-08-14

Vi tittar på hur vi kan använda websockets för realtidsprogrammering på webben.

Läsa

Läs igenom nedanstående dokumentation översiktligt och spara de för referens under arbetet.

  1. websocket modulen ws är en websocket server som använder rena (native) WebSockets.

  2. MDN WebSockets API ger dig material för klientsidan samt bakomliggande information om hur man bygger en server från grunden.

  3. socket.io är en introduktion till en modul som implementerar websockets (och närliggande tekniker) för realtid i klient och server.

Material och tekniker

HTML5 Websockets skapar nya möjligheter där webbläsaren kan ha en konstant uppkoppling mot en server där meddelanden både kan skickas och tas emot med minimal overhead. Möjligheten öppnar sig för att bättre bygga webbapplikationer som agerar i realtid.

Det finns en W3C standard för The Websocket API, det ger grunden till vad utvecklare av webbläsare och liknande behöver förhålla sig till.

Om man vill se detaljer om Websocket protokollet så finns en RFC 6455 som beskriver protokollet och hur klienten och servern kopplar upp sig, sköter kommunikationen och stänger ned kopplingen.

För att se guider med exempelkod för både klient och servrar implementerade i olika språk så vänder vi oss till MDN WebSocket API.

Vi kommer denna vecka främst använda socket.io för realtidskommunikation. Till grund för detta paketet finns grund implementationen av websocket. I artikeln Realtidsprogrammering med paketet ws beskrivs hur man kan använda paketet ws för realtidskommunikation. Läs igenom artikeln för ytterligare information om websockets och se exempel på hur man kan implementera realtidsprogrammering utan socket.io.

socket.io

socket.io är ett paket för “real-time, bidirectional and event-based communication” och underlättar processen för att skapa och driftsätta tjänster med realtidskommunikation. Vi ska i följande avsnitt bekanta oss med både server och klient implementationer av en chatt med websocket kommunikation.

Exempelkod för både server och klienter finns under /socket i GitHub-repot emilfolino/jsramverk.

Server

socket.io server API dokumentationen är en bra start för att bekanta sig med vilket API socket.io servern exponerar.

Vi vill skapa en socket.io server i express kontext och därför börjar vi med att skapa en katalog och ett npm projekt. I detta projektet installerar vi express och socket.io.

$mkdir socket-server
$cd socket-server
$npm init --yes
$npm install --save express socket.io
$touch app.js

Vi instantierar sedan app objektet i express och kopplar socket.io till app objektet. Än så länge ser vi till att bara

// app.js
const express = require('express');
const app = express();

const server = require('http').createServer(app);
const io = require('socket.io')(server);


io.on('connection', function () {
    console.info("User connected");
});

server.listen(3000);

Starta servern med kommandot node app.js och sen tar vi en titt på hur vi med hjälp av en klient kan koppla oss mot servern.

Klient

socket.io klient API dokumentationen är en bra start för att bekanta sig med vilket API socket.io klienten exponerar.

Vi skapar en ny katalog på samma nivå som socker-server katalogen och installerar socket.io-client npm-paketet. Vi installerar även webpack för att kunna packa ihop filerna.

$mkdir socket-client
$cd socket-client
$npm init --yes
$npm install --save socket.io-client
$npm install --save-dev webpack webpack-cli
$touch client.js index.html webpack.config.js

Som det sista skapar vi tre filer. I index.html skapar vi grunden för webbplats med följande HTML. Observera att vi inkluderar JavaScript filen dist/bundle.js, som skapas med hjälp av webpack.

<!doctype html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <title>Websocket chatt</title>
</head>
<body>
    <h1>Websocket chatt</h1>

    <script type="text/javascript" src="dist/bundle.js"></script>
</body>
</html>

Webpack konfigurationen är enkel och det enda vi gör i den är att specificera entry och output.

module.exports = {
    entry: "./client.js",
    output: {
        filename: "bundle.js"
    }
};

I client.js importerar vi socket.io-client och skapar en socket instans, som är kopplat mot localhost på port 3000. Vi använder sedan två stycken socket.io-client event connect och disconnect för att än så länge skriva ut status för förbindelsen till servern till konsollen.

import io from 'socket.io-client';

const socket = io('http://localhost:3000');

socket.on('connect', function() {
    console.info("Connected");
});

socket.on('disconnect', function() {
    console.info("Disconnected");
});

Om vi bundlar ihop JavaScript filen med hjälp av npm-skriptet "start": "webpack -d" och öppnar index.html i en webbläsare ska vi i konsollen nu kunna se Connected. Om du inte ser detta, kolla så att servern ligger och snurrar.

Men ett meddelande i konsollen kommer vi inte så långt med. Så låt oss skapa möjlighet för att kunna skicka ett meddelande till servern. Vi skapar ett input fält och ett element där vi senare kan lägga till meddelanden som har skickats till servern och tillbaka ut till klienten.

<!doctype html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <title>Websocket chatt</title>

    <link rel="stylesheet" href="style.css" />
</head>
<body>
    <h1>Websocket chatt</h1>

    <h2>Messages:</h2>
    <div id="all-messages" class="all-messages"></div>

    <p><strong>Write new message:</strong></p>
    <input id="new-message" class="new-message" value=""/>


    <script type="text/javascript" src="dist/bundle.js"></script>
</body>
</html>

Vi lägger även till lite CSS för att få chatten till att se lite trevligare ut.

@import url('https://fonts.googleapis.com/css?family=Roboto+Mono&display=swap');

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font-family: 'Roboto Mono', monospace;
    font-size: 1.8rem;
    margin: 2rem auto;
    width: 60%;
}

.all-messages {
    width: 100%;
    height: 30vh;
    border: 1px solid #ccc;
    border-radius: 4px;
    margin-bottom: 2rem;
}

.all-messages p:nth-child(2n) {
    background-color: #ccc;
}

.new-message {
    width: 100%;
    height: 5vh;
    border: 1px solid #ccc;
    border-radius: 4px;
    margin-bottom: 2rem;
    font-size: 1.8rem;
}

Vi kopplar sedan ett event till händelsen keyup för input fältet. När vi trycker på enter i det fältet vill vi att meddelandet som just nu är skrivit i fältet skickas till servern. Vi vill bara att meddelandet ska skickas när vi är uppkopplade mot servern och därför lägger vi event hanteraren inuti vårt connect anrop. Notera att vi ger det data vi skickar ett namn 'chat message' och skickar med värdet från fältet.

const newMessage = document.getElementById("new-message");

...

socket.on('connect', function() {
    newMessage.addEventListener("keyup", function (event) {
        if (event.code === "Enter") {
            socket.emit('chat message', event.target.value);
            event.target.value = "";
        }
    });
});

Tillbaka till servern

På servern använder vi sedan eventet socket.on('chat message') för att ta emot meddelandet från klienten. För enkelhetens skull skickar vi sedan tillbaka meddelandet till samtliga uppkopplade klienter, inklusive den klienten som skickade meddelandet, med hjälp av emit funktionen. Nedan syns koden för servern i sin helhet.

const express = require('express');
const app = express();

const server = require('http').createServer(app);
const io = require('socket.io')(server);

io.on('connection', function (socket) {
    console.info("User connected");

    socket.on('chat message', function (message) {
        io.emit('chat message', message);
    });
});

server.listen(3000);

Visa nya meddelanden i klienten

En chatt är inte till mycket nytta om vi inte ser vad de andra klienter skriver. Så varje gång vi får ett meddelande från servern vill vi skriva ut detta. Vi gör detta med eventet socket.on('chat message') där meddelandet är skickat med som data till callbacken för eventet. Vi lägger sedan till meddelandet i elementet som visar upp alla meddelanden. Nedan syns koden för klienten i sin helhet.

import io from 'socket.io-client';

const newMessage = document.getElementById("new-message");
const allMessages = document.getElementById("all-messages");

const socket = io('http://localhost:3000');

socket.on('connect', function() {
    socket.on('chat message', function (message) {
        let addedMessage = document.createElement("p");

        addedMessage.textContent = message;

        allMessages.appendChild(addedMessage);
    });

    newMessage.addEventListener("keyup", function (event) {
        if (event.code === "Enter") {
            socket.emit('chat message', event.target.value);
            event.target.value = "";
        }
    });
});

socket.on('disconnect', function() {
    console.info("Disconnected");
});

Exempelprogram

I kursrepot finns ett exempel simulate-prices som använder sig av socket.io för att visualisera simulerade priser på kakor. Detta är ett annat exempel på hur man kan använda realtidsprogrammering för annat än det klassiska chatt exemplet.

I exempelprogrammet skapar vi både en server och en klient för att kommunicera över websockets. Servern broadcaster sedan priserna för de olika kakorna var 5:e sekund och klienterna kan sedan fånga upp priserna. I filen stock.js används en Wiener-process för att simulera priserna på kakorna. En Wiener-process är det närmaste vi kommer att kunna simulera aktiekurser matematisk.

För att visualisera priserna används en graf modul kallad Rickshaw. Graferna visar realtidsdata med hjälp att rita ut en SVG bild.

Titta igenom exemplet och se hur socket.io kan användas för annat än en chatt. Titta speciellt noga på hur servern och klienterna kommunicerar med hjälp av JSON data. Detta kan vara mycket användbart i veckans inlämningsuppgift.

Kravspecifikation

  1. Skapa en klient och en server för chatt.

  2. Integrera klienten i ditt valda ramverk som en del av din me-sida. Kolla om det finns paket eller integrationer i ditt ramverk som stödjer websockets eller socket.io.

  3. Gör ett medvetet val om chatt backend ska ligga som egen driftsatt server med egen domän eller som en del av me-api:t.

  4. När man kopplar upp sig så identifierar man sig med ett nick, ett smeknamn.

  5. Flera klienter kan koppla sig till chatten. När någon kopplar upp sig i chatten skicka ett meddelande om detta till alla uppkopplade klienter.

  6. När någon skriver något ser alla andra det. Man ser nicket och tiden för meddelandet tillsammans med meddelandet.

  7. Committa, tagga och pusha relevanta repon samt driftsätta på din server.

  8. Länka till GitHub repon och driftsatta klienten i din inlämning på Canvas.

Skriva

Vi fortsätter iterativt med att förbättra vårt akademiska skrivande. Använd den återkopplingen du fick på förra veckans text och förbättra din forskningsfråga och metod.

Gå tillbaka till skrivguiden och titta under metod för bra tips.

Lämna in texten som PDF bilaga till din inlämning på Canvas.