Eindproject: een live scorebord · eindproject
Eindproject: Maak de begrensde server af
Sinds hoofdstuk 7 draaide de server alleen binnen pl_ws.py, dat hem uit je scoreboard_server.py, serve_scoreboard en main.
Lees de meegeleverde delen
Drie meegeleverde stukken doen het meeste leidingwerk; lees ze eerst.
server_parser() declareert vijf opties met hun standaardwaarden: --port 8765, --max-clients 5, --max-updates 10, --deadline 5 seconden en --token-file token.txt. Hun bereik controleren is het werk van main.
make_room_check(board, max_clients) geeft een process_response-hook terug. De process_request-hook uit hoofdstuk 9 draait voordat de bibliotheek heeft gecontroleerd of het verzoek überhaupt een geldige upgrade is, dus als de ruimte daar werd geteld, zou een misvormd verzoek dat met 400 wordt beantwoord, of een scorebijhouder die met 401 wordt geweigerd, voor altijd een plek innemen. process_response draait later en ziet het antwoord dat de bibliotheek op het punt staat te sturen. De ruimtecontrole telt alleen een 101, maakt van een 101 voor één verbinding te veel een 503 Service Unavailable, en laat elk ander antwoord door. De meegeleverde handle_connection geeft de plek terug in zijn finally. Op een lokale computer, met ruimte voor twee, kregen drie kijkers 101, 101 en 503, terwijl een 401 en een 400 geen plek innamen. Er blijft één gaatje open: een client die binnen zijn eigen
--max-clients is hoeveel verbindingen er tegelijk open mogen zijn, geen totaal: elke opdracht score opent één verbinding, dus een totaal zou een quiz na een handvol scores beëindigen. De verbinding van een scorebijhouder neemt ook een plek in, dus vijf kijkers laten geen ruimte over voor een score.
open_server neemt nu port en max_clients aan en geeft de ruimtecontrole aan serve mee als process_response.
Er zijn vijf verbindingen open en --max-clients is 5. Een zesde kijker maakt verbinding. Wat krijgt hij?
Twee manieren om te stoppen
Een run eindigt bij de eerste van twee grenzen: de laatste update, of de deadline. serve_scoreboard wacht op beide met één aanroep, board["finished"].wait(timeout=deadline). Net als in hoofdstuk 8 geeft Event.wait True terug als het event gezet werd en False als de time-out eerst verliep. Het wachten is dus ook de klok: serve_scoreboard leest zelf geen tijd.
True betekent dat elke kijker de laatste snapshot is gestuurd en is gesloten met 1000, dus server.shutdown(code=1000, reason="the quiz is over") sluit wat er nog over is, zoals een scorebijhouder. False betekent dat de deadline eerst verstreek: server.shutdown(code=1001, reason="the scoreboard is closing"), waarbij 1001 “going away” betekent. De signatuur van de bibliotheek is shutdown(close_connections=True, code=1001, reason=""), en shutdown() is with-blok op de weg naar buiten doet niets meer.
shutdown() wacht ook totdat elke handlerthread is teruggekeerd, dus wat de weg naar buiten begrenst, is dat elke handler bij zijn volgende stille controle, binnen een halve seconde, zijn gesloten verbinding opmerkt, niet de time-out die je aan join geeft. Geef join toch een time-out: wacht nooit zonder grens.
Eén eerlijke send zodra de buffers vol zijn (hoofdstuk 8, les 1). Hij stuurt de laatste snapshot nooit en verlaat het register nooit, dus finished blijft gewist, en de deadline sluit iedereen met 1001, hoewel de quiz af was.
board["finished"].wait(timeout=8.0) geeft False terug. Wat doet je server daarna?
Noem eerst het adres
Met --port 0 kiest het systeem een vrije serve_scoreboard leest hem uit server.socket.getsockname()[1] en drukt listening on 127.0.0.1:PORT af, met de echte poort, als eerste regel, voordat hij wacht. Druk hem af met flush=True: als standard output een pipe is en geen flush=True stuurt de regel meteen. De standaardpoort, 8765, is voor de run van les 5 op je eigen computer.
Op volgorde opent serve_scoreboard de server met open_server(board, token, port=port, max_clients=max_clients, serve=serve) in een with-blok, drukt het adres af, start één threading.Thread met server.serve_forever als target, wacht op finished, sluit af met 1000 of 1001, joint de thread met een time-out zoals WAIT, en geeft terug wat het wachten teruggaf.
Weiger een slechte opdrachtregel voordat er iets bindt
main(argv=None, *, serve=serve) parset argv met server_parser() en controleert elke --port van 0 tot en met 65535, --max-clients van 1 tot en met MOST_CLIENTS, --max-updates van 1 tot en met MOST_UPDATES, en --deadline boven 0 en hoogstens LONGEST_DEADLINE seconden. Voor een waarde buiten haar bereik drukt parser.error(message) de gebruiksaanwijzing en je melding af naar standard error en stopt hij met exitstatus 2, wat in elk programma van deze cursus betekent dat de opdrachtregel geweigerd is.
Een onleesbaar of leeg tokenbestand is een ander soort mislukking: de opdrachtregel was in orde, maar het programma kan zijn werk niet doen. Lees het met read_token; druk bij een OSError of een leeg token één regel af naar standard error en geef 1 terug, voordat er iets wordt geserveerd. Bouw daarna het bord met make_board(TEAMS, max_updates=...), roep serve_scoreboard aan, druk één afsluitende regel af, en geef 0 terug. Beide eindes geven 0 terug: de deadline is een van de twee manieren waarop een run hoort te eindigen.
Iemand draait python scoreboard_server.py --deadline 0. Wat gebeurt er?
Je opdracht
Schrijf serve_scoreboard en main. Draai daarna het scenario dat je main twee keer aanroept:
python pl_ws.py server-main
Een afgemaakte versie, op de site uitgevoerd, drukte dit af, waarbij PORT staat voor het poortnummer dat het systeem koos:
scenario server-main: your scoreboard_server.main() runs a quiz of two updates, then a quiz that stops at its deadline
your main() is called with --port 0 --max-updates 2 --deadline 4
viewer A joined; its first message was a snapshot with version 0
viewer B joined; its first message was a snapshot with version 0
the scorekeeper sent 3 points for Owls; its reply was a snapshot with version 1
the scorekeeper sent 2 points for Foxes; its reply was a snapshot with version 2
viewer A received versions [0, 1, 2], then the server closed it with 1000 'the quiz is over'
viewer B received versions [0, 1, 2], then the server closed it with 1000 'the quiz is over'
your server | listening on 127.0.0.1:PORT
your server | the quiz is over at version 2; every viewer was closed with 1000
your main() returned 0
your main() is called with --port 0 --max-updates 2 --deadline 1
viewer A joined; its first message was a snapshot with version 0
viewer B joined; its first message was a snapshot with version 0
the scorekeeper sent 4 points for Herons; its reply was a snapshot with version 1
viewer A received versions [0, 1], then the server closed it with 1001 'the scoreboard is closing'
viewer B received versions [0, 1], then the server closed it with 1001 'the scoreboard is closing'
your server | listening on 127.0.0.1:PORT
your server | the deadline passed at version 1; every connection was closed with 1001
your main() returned 0
De regels die gemarkeerd zijn met your server zijn wat je programma afdrukte, verzameld en na elke run getoond zodat ze zich niet mengen met de regels van de kijkers; je afsluitende regel mag het anders zeggen. Probeer daarna een geweigerde opdrachtregel, en vraag de shell om de exitstatus:
python scoreboard_server.py --max-clients 9
echo $?
Het drukt de gebruiksaanwijzing en je melding af, en echo $? drukt 2 af. De server is nu een programma, maar aan de andere kant is er nog niets: les 3 schrijft de client.
Opdracht
Schrijf in scoreboard_server.py twee
serve_scoreboard(board, token, *, port=0, max_clients=MOST_CLIENTS, deadline=DEADLINE, serve=serve): druk in eenwith-blok opopen_server(board, token, port=port, max_clients=max_clients, serve=serve)listening on 127.0.0.1:PORTaf (de echte, uit server.socket.getsockname()[1]) metflush=Trueals eerste regel, voordat er iets wacht; start éénthreading.Threadwaarvan de targetserver.serve_foreverdraait; roepboard["finished"].wait(timeout=deadline)aan; als datTrueteruggeeft, roep jeserver.shutdown(code=1000, reason=...)aan, andersserver.shutdown(code=1001, reason=...); join de thread met een time-out zoalsWAIT; geef terug wat het wachten teruggaf.main(argv=None, *, serve=serve): parseargvmetserver_parser(); weiger--portbuiten 0 tot en met 65535,--max-clientsbuiten 1 tot en metMOST_CLIENTS,--max-updatesbuiten 1 tot en metMOST_UPDATES, en een--deadlinevan 0 of minder of bovenLONGEST_DEADLINE, elk metparser.error(...)(exitstatus 2) voordat er iets wordt geserveerd; lees het token metread_token(args.token_file), en druk bij eenOSErrorof een leeg token één regel af naar standard error en geef 1 terug; bouw het bord metmake_board(TEAMS, max_updates=args.max_updates); roepserve_scoreboard(board, token, port=..., max_clients=..., deadline=..., serve=serve)aan; druk één afsluitende regel af; geef 0 terug.
Draai python pl_ws.py server-main en python scoreboard_server.py --max-clients 9, en daarna echo $?. Klik daarna op Check my work. Die draait de controles van les 1 opnieuw. Ze roept serve_scoreboard aan met een stand-in-serve waarvan de server serve_forever, shutdown en zijn scoreboard_server.threading vervangen door stand-ins waarvan de thread vastlegt wat hij kreeg en niets draait: de server moet 127.0.0.1 en de opgegeven poort binden, met de keepalivewaarden en beide hooks; de eerste regel moet listening on 127.0.0.1: en de poort van de stand-in zijn, afgedrukt en geflusht vóór het wachten; er moet precies één thread worden gestart, waarvan de target serve_forever bereikt als de controle hem daarna aanroept; het wachten moet de deadline krijgen (een finished gezet is en 1001 als dat niet zo is, met True of False als join moet een time-out krijgen. Ze roept main aan met serve_scoreboard vervangen door een recorder en een eigen tokenbestand: goede opties moeten serve_scoreboard bereiken met het token gestript; zes opties buiten hun bereik moeten eindigen met status 2 voordat er iets wordt geserveerd; een ontbrekend en een leeg tokenbestand moeten 1 teruggeven met één regel op standard error. Ze controleert ook de meegeleverde ruimtecontrole. Elke afsluitende regel is prima, en een afgedrukte regel met return 2 in plaats van parser.error ook.