0%

Een gesprek op loopback · eindproject

Eindproject: Project: de zelfgebouwde echoservice

Tot nu toe draaide main één gesprek per keer via run_once, de bedrading die het hoofdstuk meeleverde. In dit project schrijf je de bedrading zelf en maak je van loopback_demo.py een commando: python loopback_demo.py --clients 2 --messages 3 start je server op 127.0.0.1, bedient de clients na elkaar binnen een berichtenlimiet en een deadline, laat elk ervan sluiten met 1000, drukt een regel af per client en per serverresultaat, en eindigt met exitcode 0. Het is het laatste stuk van de cursus dat met niets anders dan de standaardbibliotheek is gebouwd.

Wat je bouwt

Drie in loopback_demo.py.

serve_clients(listener, results, *, max_clients=2, max_messages=MAX_MESSAGES, deadline=5.0, now=time.monotonic) is de van de server, en die draait op een thread:

  1. Geef de listener een time-out met listener.settimeout(WAIT).

  2. Bereken stop_at = now() + deadline, één keer, vóór de lus.

  3. Accepteer zolang er minder dan max_clients resultaten zijn en now() < stop_at. Als accept TimeoutError opwerpt, kwam er op tijd geen client: geef de resultaten tot nu toe terug.

  4. Geef elke geaccepteerde zijn eigen settimeout(WAIT), sluit hem met with, en voeg serve_connection(conn, max_messages=max_messages) toe aan results.

  5. Geef results terug.

De thread kan geen returnwaarde aan main teruggeven, dus voegt hij toe aan de die main meegaf, hetzelfde idee als de outcome in run_once.

parse_args(argv) leest drie opties met argparse: --clients, een int van 1 tot en met 5, standaard 2; --messages, een int van 1 tot en met 10, standaard 3; --deadline, een float boven 0 en hooguit 8, standaard 5.0. Een buiten de grenzen gaat naar parser.error(...), dat de gebruiksregel en de melding naar standaardfout schrijft en eindigt met status 2.

main(argv=None) zet het samen:

  1. Parse de opties.

  2. Open de listener met socket.create_server(("127.0.0.1", 0)) in een with-blok, lees de met getsockname()[1], en druk listening on 127.0.0.1:PORT af met de echte poort.

  3. Start serve_clients op een threading.Thread, met de listener en een resultatenlijst als args, en max_clients, max_messages en deadline als kwargs. De limiet van de server is MAX_MESSAGES, niet --messages: de grens hoort bij de server, niet bij wat de zegt dat clients zullen sturen. --messages bepaalt hoeveel teksten elke client stuurt.

  4. Maak voor elke client, in de hoofdthread, verbinding met socket.create_connection(("127.0.0.1", port), timeout=WAIT) in een with-blok, draai client_connection met teksten zoals "client 1, message 2", en druk client N: E echoes, closed with C af. Als de client OSError of opwerpt, druk dan af dat hij faalde en ga door naar de volgende.

  5. Join de serverthread met timeout=WAIT, binnen het with-blok van de listener, zodat de listener niet gesloten wordt onder een thread die misschien nog accepteert.

  6. Druk server: client N sent M messages, closed with C af voor elk resultaat.

  7. Geef 0 terug als elke client met 1000 sloot en de thread stopte, anders 1. De laatste regel van het bestand, raise SystemExit(main()), maakt daar de exitstatus van het commando van.

Tijd als parameter

serve_clients roept time.monotonic() nooit zelf aan. Het roept now() aan, waarvan de standaardwaarde time.monotonic is, de geïnjecteerde klok uit Python in Practice. Een echte run krijgt de echte klok. Een controle kan een functie meegeven die 0.0 en daarna 6.0 teruggeeft, en zien of de deadline de lus stopt zonder zes seconden te wachten.

De deadline wordt tussen clients gecontroleerd, niet tijdens een gesprek. Een gesprek dat bezig is, heeft al eigen grenzen: een time-out van 2 seconden op elke leesactie en een limiet op berichten. De deadline beslist alleen of er nog een client wordt geaccepteerd.

De deadline verstrijkt terwijl de tweede client nog aan het praten is. Wat doet serve_clients?

Draai het

Deze les heeft een . Draai, met je drie functies geschreven, het commando met de standaardwaarden:

python loopback_demo.py

Het drukt dit af, met een poortnummer waar PORT staat:

listening on 127.0.0.1:PORT
client 1: 3 echoes, closed with 1000
client 2: 3 echoes, closed with 1000
server: client 1 sent 3 messages, closed with 1000
server: client 2 sent 3 messages, closed with 1000

Probeer de grootste run die de opties toestaan, vijf clients met elk tien berichten; het drukt voor elk van de vijf een clientregel en een serverregel af, allemaal gesloten met 1000:

python loopback_demo.py --clients 5 --messages 10

Daarna een waarde buiten de grenzen:

python loopback_demo.py --clients 6
echo $?
usage: loopback_demo.py [-h] [--clients CLIENTS] [--messages MESSAGES]
                        [--deadline DEADLINE]
loopback_demo.py: error: --clients must be 1 to 5

Dat gaat naar standaardfout, en echo $? drukt de exitstatus af, 2. Je tests uit les 4 slagen nog steeds:

python -m pytest -q

Als de standaardrun eindigt met beide clients gesloten met 1000, druk je op Check my work. Het draait de controles van les 1 tot en met 3 opnieuw op je afgewerkte bestand. Het geeft serve_clients een stand-in-listener met wachtende clients en een klok die het zelf bestuurt, en controleert de clientlimiet, de deadline, de time-out op accept, beide time-outs, dat elke geaccepteerde socket gesloten wordt, en dat een client die verdwijnt de volgende niet tegenhoudt. Het roept parse_args aan met waarden binnen en buiten de grenzen. main opent een echte listener, een thread en clients, dus de controle leest in plaats daarvan de code ervan: het adres ("127.0.0.1", 0), een Thread, en een time-out op elke join en op create_connection. Je afgedrukte regels worden niet gecontroleerd, dus lees ze zelf.

Waar de code uit bestaat

Tel wat deze dienst kostte. In de versies van de cursus van de drie bestanden komen ws_handshake.py, ws_frames.py en loopback_demo.py op ongeveer 620 regels. Het deel dat er een echoservice van maakt, elk bericht terugsturen, is een paar regels van server_reply. Bijna al het andere is loodgieterswerk dat elk -programma nodig heeft en dat geen ervan wil schrijven: de , de lengtevormen, maskeren met een nieuwe sleutel, beantwoorden, de sluithandshake in beide richtingen, foutcodes, en de threads en time-outs die alles begrensd houden.

Dat is het punt van het één keer gebouwd hebben. Hoofdstuk 6 geeft al dat loodgieterswerk aan de bibliotheek websockets en bouwt deze dienst opnieuw in een paar regels, te beginnen door de eigen client van de bibliotheek te richten op de versie van de cursus van de server die je net hebt afgemaakt.

Opdracht

Schrijf drie in loopback_demo.py:

  1. serve_clients(listener, results, *, max_clients=2, max_messages=MAX_MESSAGES, deadline=5.0, now=time.monotonic): roep listener.settimeout(WAIT) aan, bereken stop_at = now() + deadline één keer, en accepteer zolang len(results) < max_clients en now() < stop_at. Geef elke geaccepteerde settimeout(WAIT), sluit hem met with, en voeg serve_connection(conn, max_messages=max_messages) toe aan results. Als accept TimeoutError opwerpt, geef je de resultaten tot nu toe terug. Roep de klok alleen aan via now.

  2. parse_args(argv): --clients (int, 1 tot en met 5, standaard 2), --messages (int, 1 tot en met 10, standaard 3) en --deadline (float, boven 0 en hooguit 8, standaard 5.0) met argparse; weiger een buiten de grenzen met parser.error(...).

  3. main(argv=None): parse de opties; bind socket.create_server(("127.0.0.1", 0)) in een with-blok en druk listening on 127.0.0.1:PORT af; draai serve_clients op een threading.Thread met max_clients=args.clients, max_messages=MAX_MESSAGES en deadline=args.deadline; maak vanuit de hoofdthread voor elke client verbinding met socket.create_connection(..., timeout=WAIT) en draai client_connection met --messages teksten, en druk één regel per client af; join de thread met timeout=WAIT voordat de listener sluit; druk één regel per serverresultaat af; geef alleen 0 terug als elke client met 1000 sloot en de thread stopte.

In de moet python loopback_demo.py twee clientregels en twee serverregels afdrukken, allemaal gesloten met 1000, en moet python loopback_demo.py --clients 6 eindigen met status 2.

Druk daarna op Check my work. Het herhaalt de controles van les 1 tot en met 3 op je afgewerkte bestand. Het geeft serve_clients een stand-in-listener met gescripte clients en een klok die het zelf bestuurt: drie wachtende clients met max_clients=2, een klok die na de eerste client voorbij de deadline gaat, een listener waarvan accept een time-out krijgt, een client die verdwijnt vóór de volgende, en max_messages=1; het controleert de time-outs op de listener en op elke geaccepteerde socket, en dat elke geaccepteerde socket gesloten wordt. Het roept parse_args aan met de standaardwaarden, de grootste toegestane waarden, en waarden net buiten elke grens. Het leest de code van main en de functies die het gebruikt op het adres ("127.0.0.1", 0), een Thread, en een time-out op elke join en op create_connection, en faalt op "0.0.0.0" waar dan ook in het bestand. Het draait main niet, leest je terminal niet, en controleert je afgedrukte regels niet.