Unterschiede
Hier werden die Unterschiede zwischen zwei Versionen der Seite angezeigt.
Beide Seiten, vorherige Überarbeitung Vorherige Überarbeitung Nächste Überarbeitung | Vorherige Überarbeitung | ||
talit:web:webapps:server [2025-04-10 05:19] – hof | talit:web:webapps:server [2025-04-13 10:59] (aktuell) – hof | ||
---|---|---|---|
Zeile 55: | Zeile 55: | ||
Mit dem Kommandozeilenaufruf `node app.js` wird der Node-Server gestartet. Auf der Adresse http:// | Mit dem Kommandozeilenaufruf `node app.js` wird der Node-Server gestartet. Auf der Adresse http:// | ||
- | Der Code bedeutet: Wann immer eine Anfrage | + | Der Code bedeutet: Wann immer eine _Request_ (_res_) |
- | Es ist auch möglich, Teile der im Request | + | Es ist auch möglich, Teile des im Request verlangten |
<code javascript> | <code javascript> | ||
Zeile 80: | Zeile 80: | ||
### Server-Side Vier-Gewinnt | ### Server-Side Vier-Gewinnt | ||
- | Erstelle eine Kopie deiner `connect4.js` Datei, um auf dem Server ein Spiel laufen zu lassen. | + | Erstelle eine Kopie deiner `connect4.js` Datei als `connect4_server.js`, um auf dem Server ein Spiel laufen zu lassen. |
Was muss sich ändern? | Was muss sich ändern? | ||
Zeile 345: | Zeile 345: | ||
} | } | ||
</ | </ | ||
- | |||
### Polling | ### Polling | ||
Zeile 377: | Zeile 376: | ||
</ | </ | ||
+ | ### Long-Polling | ||
+ | Mit der obigen Lösung fragt jeder Client zweimal pro Sekunde nach einem Update. Polling hat zwei Nachteile: | ||
+ | * es wird relative viel Traffic erzeugt, insbesondere, | ||
+ | * trotzdem ist die Responsivität tief - im Mittel wartet jeder Client die Hälfte des eingestellten Delays, also 250 Millisekunden. Das ist für Vier-Gewinnt zwar ausreichend tief, aber nicht genug für Spiele mit Interaktivität. | ||
+ | |||
+ | Um das zu beheben gibt es zwei Lösungsmöglichkeiten: | ||
+ | - **Long-Polling**: | ||
+ | - **Web-Sockets**: | ||
+ | |||
+ | Hier werden wir Long-Polling anschauen. Als erstes benötigen wir eine Datenstruktur, | ||
+ | <code javascript app.js> | ||
+ | /** All long-poll requests by gameid. For each gameid, a list of | ||
+ | * [response, userid] entries is stored. | ||
+ | let longpolls = {}; | ||
+ | </ | ||
+ | |||
+ | Dazu definieren wir eine neue Route `/< | ||
+ | |||
+ | <code javascript app.js> | ||
+ | /** Serve the game state of any valid game id, but block until | ||
+ | * the client is no longer blocked (long-polling). */ | ||
+ | app.get('/: | ||
+ | const userid = getUserId(req, | ||
+ | const game = games[parseInt(req.params[' | ||
+ | if (game == undefined) { | ||
+ | res.status(404).json(" | ||
+ | } else { | ||
+ | if (isWaiting(game, | ||
+ | console.log(`Game ${game.id} directly joined by ${userid}`) | ||
+ | join(game, userid) | ||
+ | res.json(toJson(game, | ||
+ | res.end() | ||
+ | return | ||
+ | } | ||
+ | // Check if userid needs to wait, then either: | ||
+ | // - stash the [response, userid] pair for later | ||
+ | // - instantly return actionable state | ||
+ | if (shouldBlockRequest(game, | ||
+ | console.log(`Stash long-poll request for ${game.id} by ${userid}`) | ||
+ | longpolls[game.id] ??= [] | ||
+ | longpolls[game.id].push([res, | ||
+ | // do not end here but keep request hanging. | ||
+ | } else { | ||
+ | // User can act on the state, no point in waiting. | ||
+ | res.json(toJson(game, | ||
+ | res.end() | ||
+ | } | ||
+ | } | ||
+ | }) | ||
+ | </ | ||
+ | |||
+ | Im der server-side Game-Logik implementieren wir eine neue Funktion, um zu wissen, ob ein Request blockiert werden soll oder nicht - genau dann, wenn der Zustand `" | ||
+ | |||
+ | <code javascript connect4_server.js> | ||
+ | export function shouldBlockRequest(game, | ||
+ | return game.state == " | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Nun benötigen wir noch die Logik, um die Long-Poll-Requests zu beantworten, | ||
+ | |||
+ | <code javascript app.js> | ||
+ | /** Sends any pending long-polling responses after a state change. */ | ||
+ | function sendLongPollResponses(game) { | ||
+ | let polls = longpolls[game.id] | ||
+ | delete longpolls[game.id] | ||
+ | if (polls) { | ||
+ | for (let response of polls) { | ||
+ | // a response object is a list with the actual HTTP response and the userid. | ||
+ | let res = response[0] | ||
+ | let userid = response[1] | ||
+ | console.log(`End long-poll request for ${game.id} by ${userid}`) | ||
+ | res.json(toJson(game, | ||
+ | res.end() | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Was bleibt zu tun? Der Client soll nicht mehr warten, sondern bei Bedarf das `longpoll` API benützen: | ||
+ | |||
+ | <code javascript connect4_client.js> | ||
+ | async function handleFetch(grid, | ||
+ | let response = await fetch(url); | ||
+ | if (response.ok) { | ||
+ | game = await response.json(); | ||
+ | // Update the HTML view. | ||
+ | updateHtml(grid, | ||
+ | // Update game status area to reflect winner / tie | ||
+ | updateStatus(status, | ||
+ | } | ||
+ | if (!response.ok) { | ||
+ | await wait(500) // wait a little if there is connection trouble | ||
+ | } | ||
+ | if (!response.ok || game.state == " | ||
+ | await handleFetch(grid, | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Nun haben wir viel weniger Datenverkehr, | ||
### Hinweise | ### Hinweise |