0%

De bibliotheek websockets, zonder asyncio · eindproject

Eindproject: Project: de echoservice, opnieuw gebouwd

Je hebt een handler, een client en een manier om te lezen hoe een gesprek eindigde, elk apart geschreven en gecontroleerd. Wat je nog niet hebt, is een programma dat iemand kan draaien. In hoofdstuk 5 was dat programma loopback_demo.py met zijn : een begrensde server, clients na elkaar, een regel per client, en een exitstatus. Dit project bouwt hetzelfde programma op de bibliotheek, in echo_library.py, met nog drie .

Wat je bouwt

echo_session(url, texts, *, connect=connect) is echo_once dat ook meldt hoe het gesprek eindigde. Maak verbinding met dezelfde drie grenzen en proxy=None. Zet binnen het with-blok alles in één try: stuur elke tekst en lees de echo ervan, zoals eerder; sluit daarna met connection.close(1000, "done") en roep nog één keer connection.recv(timeout=WAIT) aan. Zodra de sluithandshake voorbij is, werpt die recv ConnectionClosed op, en except ConnectionClosed as exc: geeft {"echoes": echoes, "close": describe_close(exc)} terug.

De binnen de try zetten doet ertoe voor het andere einde. De server die je schreef, sluit als eerste als hij MAX_MESSAGES bereikt, en een server kan verdwijnen. Hoe dan ook werpt de volgende recv of send ConnectionClosed op, en dezelfde except geeft de echo’s die wel aankwamen terug met de close die ze beëindigde: 1000 en message limit reached voor een server op zijn limiet, 1006 voor een die verdween.

parse_args(argv) leest dezelfde drie opties als het project van hoofdstuk 5, met dezelfde grenzen, en weigert alles daarbuiten met parser.error, dat het programma stopt met status 2:

  • --clients, een int van 1 tot en met MAX_CLIENTS (5), standaard 2;

  • --messages, een int van 1 tot en met MAX_MESSAGES (10), standaard 3;

  • --deadline, een float boven 0 en hooguit 8 seconden, standaard 5.0.

main(argv=None, *, serve=serve, connect=connect, now=time.monotonic) zet het samen:

  1. lees de opties met parse_args(argv), en bereken wanneer de deadline valt: now() plus --deadline;

  2. bedien echo_handler op "127.0.0.1" 0 in een with-statement, druk de poort af, en start serve_forever op een thread, zoals in run_echo;

  3. voor elke client, genummerd vanaf 1: als now() de deadline heeft bereikt, druk dan af dat de client niet werd gestart en tel hem als mislukt; geef hem anders --messages teksten naar keuze en draai echo_session met de ws://127.0.0.1:PORT/echo en connect=connect;

  4. als echo_session OSError opwerpt, druk dan af dat de client faalde en tel hem als mislukt, en ga daarna door naar de volgende client; ConnectionRefusedError en TimeoutError zijn allebei soorten OSError;

  5. druk anders één regel voor de client af, bijvoorbeeld hoeveel echo’s hij kreeg en de , en tel hem alleen als geslaagd als hij elke echo kreeg en een close met 1000;

  6. join na het with-blok de thread met timeout=WAIT, en geef 0 terug als elke client slaagde, 1 als er een niet slaagde.

De formulering van je regels bepaal je zelf, en hoe je telt ook: een resultaten, een teller van geslaagde clients, of iets anders wat de juiste exitstatus geeft.

Weer naden

serve= en connect= zijn om dezelfde reden als connect= dat was in Networks with Python: zodat een aanroeper stand-ins kan meegeven. Submit geeft een serve mee waarvan de server noteert wat je ermee doet en niets bedient, en een connect die gescripte verbindingen uitdeelt: een die echoot, een waarvan de server na één echo sluit, een waarvan de server je close beantwoordt met 1001, een die een time-out krijgt, en een die geweigerd wordt. main moet zijn connect doorgeven aan echo_session; zonder connect=connect in die aanroep zou echo_session zijn standaardwaarde gebruiken, de echte connect van de bibliotheek.

now= is de geïnjecteerde klok uit Python in Practice. Submit geeft er een mee die voorbij de deadline springt zodra de eerste client verbinding heeft gemaakt, en verwacht dat er geen tweede client start.

Het afsluiten van de server heeft geen eigen code nodig. Omdat de server in een with-statement wordt gebruikt, roept het verlaten van het blok op elk pad shutdown() aan: na de laatste client, na een client die faalde, en zelfs als een die je code niet ving het blok zou verlaten.

echo_session werpt TimeoutError op voor de tweede client. Wordt de server nog steeds afgesloten?

Run

Druk op Run. De laatste sectie van library_demo.py roept je main aan met --clients 2 --messages 2 en drukt af wat die teruggeeft. Een afgewerkte versie, gedraaid op de site, drukte dit af, waarbij PORT staat voor de poort die het systeem koos:

Lesson 6: main with two clients
listening on 127.0.0.1:PORT
client 1: 2 echoes, closed with 1000
client 2: 2 echoes, closed with 1000
0

Jouw regels luiden zoals je ze schreef; de 0 is de exitstatus die main teruggaf, en die moet 0 zijn. Standaardfout moet leeg zijn, zoals in les 4. Op je eigen computer, met de bibliotheek geïnstalleerd vanuit je requirements.txt, draait python echo_library.py --clients 3 hetzelfde programma vanuit een , via de laatste twee regels van het bestand.

Druk daarna op Submit. Voordat het de drie nieuwe functies controleert, controleert het de eerdere opnieuw: de handler, run_echo, echo_once en describe_close.

Als iets in main je verbaast, vraag het Monty:

Waarom geeft mijn main connect=connect mee aan echo_session als echo_session connect al als standaardwaarde heeft?

Nog steeds één tegelijk

Kijk wat main met de server van de bibliotheek doet: één client, dan de volgende. De server zou graag meerdere clients tegelijk bedienen, elk op een eigen handlerthread, maar dit programma vraagt hem dat nooit. Er is ook niets wat clients kunnen delen: elk praat alleen met zijn eigen echo. Hoofdstuk 7 verandert beide. Het bedient meerdere clients tegelijk en geeft ze iets gemeenschappelijks, het scorebord. Een scorebord dat netjes moet stoppen terwijl kijkers nog verbonden zijn, heeft meer nodig dan de manier waarop dit programma tussen clients stopt, bijvoorbeeld open verbindingen sluiten met 1001 als zijn deadline komt; het eindproject in hoofdstuk 10 regelt dat.

Opdracht

Schrijf drie in echo_library.py, in deze volgorde. Submit noemt de eerste die nog NotImplementedError opwerpt.

  1. echo_session(url, texts, *, connect=connect): maak verbinding zoals echo_once doet, met open_timeout=WAIT, close_timeout=WAIT en proxy=None, in een with-statement. Stuur binnen één try elke tekst en lees de echo ervan met recv(timeout=WAIT), en roep daarna connection.close(1000, "done") en nog één keer connection.recv(timeout=WAIT) aan. Geef in except ConnectionClosed as exc: {"echoes": echoes, "close": describe_close(exc)} terug.

  2. parse_args(argv): --clients (int, 1 tot en met MAX_CLIENTS, standaard 2), --messages (int, 1 tot en met MAX_MESSAGES, standaard 3) en --deadline (float, boven 0 en hooguit 8, standaard 5.0), met parser.error voor een buiten de grenzen.

  3. main(argv=None, *, serve=serve, connect=connect, now=time.monotonic): lees de opties; stel de deadline in vanuit now(); bedien echo_handler op "127.0.0.1" 0 in een with-statement en start serve_forever op een threading.Thread; draai één echo_session per client, na elkaar, met --messages teksten en connect=connect, en start geen client meer zodra now() de deadline heeft bereikt; tel een client die OSError opwerpt, minder echo’s krijgt dan hij stuurde, of een andere close dan 1000 krijgt, als mislukt; druk één regel per client af; join na het blok de thread met timeout=WAIT; geef 0 terug als elke client slaagde en anders 1.

Druk op Run: de sectie van les 6 moet eindigen met 0, na één regel per client.

Druk daarna op Submit. De controle opent nooit een en start geen thread. Ze controleert eerst echo_handler, run_echo, echo_once en describe_close opnieuw, zoals les 2 tot en met 4 deden. Ze draait echo_session tegen stand-in-verbindingen: een echoserver (ze verwacht close(1000, "done") en {"echoes": [...], "close": {"code": 1000, "reason": "done"}}), een server die na één echo sluit, en een server die verdwijnt (1006), met dezelfde grenzen op connect en recv als in les 3. Ze draait parse_args op de standaardwaarden, de grootste toegestane waarden en waarden net voorbij elke grens. Ze draait main met een stand-in-serve, een stand-in-connect en een registrerende threading: serve(echo_handler, "127.0.0.1", 0), een thread op serve_forever gestart vóór de eerste client, één verbinding per client naar de poort die de server meldde, elk --messages teksten, het afsluiten na de clients en een join met een time-out na het afsluiten, en 0 voor twee goede clients. Ze verwacht 1, met de server toch afgesloten, voor een client met één echo van de twee, een close beantwoord met 1001, een geweigerde verbinding en een recv met een time-out; geen tweede client zodra haar klok voorbij de deadline gaat; en status 2 voor --clients 9. Je afgedrukte formulering wordt niet gecontroleerd.