De bibliotheek websockets, zonder asyncio · oefening
Een server in een paar regels
interop.py richtte de client van de bibliotheek op de server uit hoofdstuk 5. Deze les draait dat om: de server wordt die van de bibliotheek. In hoofdstuk 5 las serve_conversation serve_handshake de upgrade had beantwoord. Met de bibliotheek past het deel dat over echoën gaat in een handler van ongeveer zes regels.
Een handler en zijn verbinding
Een handler is een
connection.recv(timeout=...)wacht hooguit zoveel seconden op het volgende bericht en geeft het terug: eenstrvoor een tekstbericht,bytesvoor een binair bericht. Als er niets op tijd aankomt, werpt hetTimeoutErrorop.connection.send(message)stuurt een bericht: eenstrals tekst,bytesals binair.connection.close(code, reason)stuurt een close-frame met die code en reden, en voert de sluithandshake uit.connection.request.pathis het pad waar de client om vroeg, zoals/echo. Latere hoofdstukken gebruiken het; dit hoofdstuk niet.
De bibliotheek doet al het andere. Voor elke client die verbinding maakt, voert ze de
Drie clients maken tegelijk verbinding. Hoe vaak roept de bibliotheek echo_handler aan?
echo_handler(connection) is de echolus uit hoofdstuk 5 met het loodgieterswerk eruit. Zolang er minder dan MAX_MESSAGES berichten zijn geëchood, lees je er een met connection.recv(timeout=WAIT) en stuur je het meteen terug met connection.send. Door de server_reply met connection.close(1000, "message limit reached"). WAIT en MAX_MESSAGES staan al bovenaan echo_library.py: 2 seconden en 10 berichten.
Bedienen
run_echo(talk, *, serve=serve) bedient de handler voor één gesprek en geeft de talk, een functie die de client speelt. Het begint zo:
with serve(echo_handler, "127.0.0.1", 0) as server:
port = server.socket.getsockname()[1]
serve, uit websockets.sync.server, bindt en luistert, zoals socket.create_server, en geeft een serverobject terug. Geef echo_handler zelf mee, zonder hem aan te roepen: de bibliotheek roept hem aan, één keer per verbinding. Met poort 0 kiest het systeem een vrije poort, en server.socket is de luisterende getsockname()[1] leest de poort zoals je in Networks deed.
Er wordt nog niets bediend. server.serve_forever() accepteert verbindingen en start voor elk een handlerthread, en het keert pas terug als de server wordt afgesloten. Het heeft dus een eigen thread nodig: maak threading.Thread(target=server.serve_forever), waarbij je de talk(port) aanroept.
Als het with-blok eindigt, om welke reden ook, roept het serverobject shutdown() aan. Dat sluit de luisterende socket, sluit elke verbinding die nog open is met 1001, en laat serve_forever terugkeren. Join na het blok de thread met timeout=WAIT, en geef terug wat talk teruggaf.
De serve=serve is een naad (seam), zoals connect= in Networks with Python: run_echo gebruikt de serve van de bibliotheek tenzij een aanroeper een andere meegeeft, en Submit geeft een stand-in mee die noteert wat je code ermee doet.
Waardoor keert serve_forever terug?
Een gesprek met de bibliotheek gebruikt meer threads dan dat van jou. Bij een lokale telling draaide één gesprek met de server en de client van de bibliotheek in één proces zeven threads, en vijf met de
Wat er weg is
Zet echo_handler naast de server uit hoofdstuk 5. De handshake is weg: geen serve_handshake, geen check_upgrade, geen accept_value. Demaskeren is weg, en de pong ook: de bibliotheek beantwoordt elke ping zodra die aankomt, en recv geeft je alleen ooit berichten. De sluithandshake is ook weg: als de client een close stuurt, beantwoordt de bibliotheek die, het werk dat je close_frame en wait_for_close deden. Wat overblijft, is het deel dat over dit programma gaat: hoeveel berichten, wat er teruggestuurd wordt, wanneer er gesloten wordt en met welke code.
Welke van deze dingen doet je server uit hoofdstuk 5 die echo_handler niet meer doet?
Voordat je op Run drukt
Run drukt een blok van ongeveer tien regels naar standaardfout af dat op een crash lijkt. In deze les is dat te verwachten, en dit is waarom. De client uit hoofdstuk 5 stuurt zijn drie berichten en sluit daarna met 1000 en de reden done. Je handler zit weer in recv, wachtend op een vierde bericht, en recv werpt in plaats daarvan ConnectionClosedOK op. De echo_handler, en de bibliotheek logt hem. Het blok begint met de regel connection handler failed, gaat verder met een
websockets.exceptions.ConnectionClosedOK: received 1000 (OK) done; then sent 1000 (OK) done
Die laatste regel zegt dat de verbinding toch netjes sloot: de bibliotheek ontving de close van de client en stuurde dezelfde close terug. Submit accepteert in deze les een handler die deze exception laat ontsnappen, en les 4 vangt hem, zodat het blok verdwijnt.
Druk nu op Run. De sectie van les 2 draait de met de hand gebouwde client uit hoofdstuk 5 van de cursus tegen je bibliotheekserver. Een afgewerkte versie, gedraaid op de site, drukte af:
Lesson 2: your library server, the Chapter 5 client
{'echoes': ['hello', 'café ✓', 'third message'], 'code': 1000}
De zelfgebouwde client pingde en kreeg zijn pong, stuurde drie berichten, kreeg drie echo’s, en sloot met 1000; de bibliotheek beantwoordde elk frame. De latere secties zeggen nog “not written yet”. Druk daarna op Submit.
Als de threads je verbazen, vraag het Monty:
Waarom heeft serve_forever een eigen thread nodig als de bibliotheek al een thread voor elke handler start?
De server praat met een zelfgebouwde client. In de volgende les verhuist ook de clientkant naar de bibliotheek.
Opdracht
Schrijf twee echo_library.py, in deze volgorde. Submit noemt de eerste die nog NotImplementedError opwerpt.
echo_handler(connection): lees, zolang er minder danMAX_MESSAGESberichten zijn geëchood, er een metconnection.recv(timeout=WAIT)en stuur het ongewijzigd terug metconnection.send. Roep na deconnection.close(1000, "message limit reached")aan. Nog geentry: les 4 voegt die toe.run_echo(talk, *, serve=serve): lees inwith serve(echo_handler, "127.0.0.1", 0) as server:demet server.socket.getsockname()[1], start eenthreading.Thread(target=server.serve_forever), en roeptalk(port)aan. Join na hetwith-blok de thread mettimeout=WAITen geef terug wattalkteruggaf.
Druk op Run: de sectie van les 2 moet {'echoes': ['hello', 'café ✓', 'third message'], 'code': 1000} afdrukken, en het blok connection handler failed dat in de les wordt beschreven, verschijnt één keer op standaardfout.
Druk daarna op Submit. De controle opent nooit een echo_handler stand-in-verbindingen met berichten die ze je niet heeft laten zien, tekst en binair: ze verwacht dat elk ervan ongewijzigd wordt teruggestuurd, op volgorde, hooguit MAX_MESSAGES (ze probeert ook een kleinere MAX_MESSAGES), geen leesactie voorbij de limiet, een time-out van hooguit 2 seconden op elke recv, en een close met 1000. Een client die vroeg sluit, mag je handler in deze les laten opwerpen. Ze geeft run_echo een stand-in-serve en houdt de thread in de gaten die je start, zonder hem te draaien: serve aangeroepen met echo_handler, "127.0.0.1" en 0, een thread met als target serve_forever gestart vóór talk, talk aangeroepen met de poort, de server afgesloten na talk, ook als talk opwerpt, een join met een time-out na het afsluiten, en de talk teruggegeven. Houd import threading en schrijf threading.Thread(...). Zelf server.shutdown() aanroepen binnen het blok is prima, en een for-lus over range(MAX_MESSAGES) ook. Het importeren van echo_library.py mag niets afdrukken.