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 loopback_demo.py.
serve_clients(listener, results, *, max_clients=2, max_messages=MAX_MESSAGES, deadline=5.0, now=time.monotonic) is de
Geef de listener een time-out met
listener.settimeout(WAIT).Bereken
stop_at = now() + deadline, één keer, vóór de lus.Accepteer zolang er minder dan
max_clientsresultaten zijn ennow() < stop_at. AlsacceptTimeoutErroropwerpt, kwam er op tijd geen client: geef de resultaten tot nu toe terug.Geef elke geaccepteerde
zijn eigen settimeout(WAIT), sluit hem metwith, en voegserve_connection(conn, max_messages=max_messages)toe aanresults.Geef
resultsterug.
De thread kan geen returnwaarde aan main teruggeven, dus voegt hij toe aan de 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 parser.error(...), dat de gebruiksregel en de melding naar standaardfout schrijft en eindigt met status 2.
main(argv=None) zet het samen:
Parse de opties.
Open de listener met
socket.create_server(("127.0.0.1", 0))in eenwith-blok, lees demet getsockname()[1], en druklistening on 127.0.0.1:PORTaf met de echte poort.Start
serve_clientsop eenthreading.Thread, met de listener en een resultatenlijst alsargs, enmax_clients,max_messagesendeadlinealskwargs. De limiet van de server isMAX_MESSAGES, niet--messages: de grens hoort bij de server, niet bij wat dezegt dat clients zullen sturen. --messagesbepaalt hoeveel teksten elke client stuurt.Maak voor elke client, in de hoofdthread, verbinding met
socket.create_connection(("127.0.0.1", port), timeout=WAIT)in eenwith-blok, draaiclient_connectionmet teksten zoals"client 1, message 2", en drukclient N: E echoes, closed with Caf. Als de clientOSErrorofopwerpt, druk dan af dat hij faalde en ga door naar de volgende. Join de serverthread met
timeout=WAIT, binnen hetwith-blok van de listener, zodat de listener niet gesloten wordt onder een thread die misschien nog accepteert.Druk
server: client N sent M messages, closed with Caf voor elk resultaat.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
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
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 loopback_demo.py:
serve_clients(listener, results, *, max_clients=2, max_messages=MAX_MESSAGES, deadline=5.0, now=time.monotonic): roeplistener.settimeout(WAIT)aan, berekenstop_at = now() + deadlineéén keer, en accepteer zolanglen(results) < max_clientsennow() < stop_at. Geef elke geaccepteerdesettimeout(WAIT), sluit hem metwith, en voegserve_connection(conn, max_messages=max_messages)toe aanresults. AlsacceptTimeoutErroropwerpt, geef je de resultaten tot nu toe terug. Roep de klok alleen aan vianow.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) metargparse; weiger eenbuiten de grenzen met parser.error(...).main(argv=None): parse de opties; bindsocket.create_server(("127.0.0.1", 0))in eenwith-blok en druklistening on 127.0.0.1:PORTaf; draaiserve_clientsop eenthreading.Threadmetmax_clients=args.clients,max_messages=MAX_MESSAGESendeadline=args.deadline; maak vanuit de hoofdthread voor elke client verbinding metsocket.create_connection(..., timeout=WAIT)en draaiclient_connectionmet--messagesteksten, en druk één regel per client af; join de thread mettimeout=WAITvoordat 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 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.