Eén server, veel clients · eindproject
Eindproject: Project: scorebord v1
Kijkers kunnen binnenkomen, zich registreren en elke verandering ontvangen, en je tests bewijzen het. Wat ontbreekt, is de verbinding die iets verandert: die van de scorebijhouder, op /score. handle_connection stuurt die al naar scorekeeper_handler, de laatste stub in scoreboard_server.py. Als je die schrijft, is scorebord v1 compleet, en daarna draai je de hele server met meerdere clients tegelijk.
De handler van de scorebijhouder
scorekeeper_handler(connection, board) is een begrensde MAX_SCORE_MESSAGES (10) berichten, elk met connection.recv(timeout=WAIT), en voor elk ervan:
parset hij het met
scoreboard_messages.parse_message;controleert hij dat het
type"score"is: een snapshot of een error is een geldige envelop, maar niet iets wat een scorebijhouder mag sturen;verwerkt hij het met
apply_score(board, message), die het naar elke kijker broadcast en de nieuwe snapshot teruggeeft;beantwoordt hij de scorebijhouder met die snapshot, gecodeerd met
scoreboard_messages.encode_message, zodat die het bord ziet dat zijn score opleverde.
Drie andere eindes, die elk de verbinding sluiten en terugkeren:
recvwerptTimeoutErrorop: er kwam 2 seconden geen score; sluit met 1000 en een reden zoals"no score arrived in time";een van de stappen 1 tot en met 3 werpt
op (bytes, tekst die geen is, een kapotte envelop, het verkeerde type, of een team dat niet meespeelt): sluit met 1008 en een reden zoals "that was not a valid score", en verwerk niets;het tiende bericht is beantwoord: sluit met 1000 en
"message limit reached".
Tel de berichten die je leest, niet de scores die je verwerkt: een bericht dat wordt geweigerd, beëindigt de lus toch, en een limiet op het lezen is wat voorkomt dat één verbinding eeuwig een thread bezet houdt. Een while-lus met een teller, zoals in de echohandler, of een for-lus over range(MAX_SCORE_MESSAGES) werken allebei. Sluit met 1000 nadat de lus is geëindigd, erbuiten.
Voor stap 2 kun je zelf ValueError opwerpen als het type verkeerd is, zodat één except ValueError: elk ongeldig bericht afhandelt. En zet de hele lus in een try waarvan de except ConnectionClosed: terugkeert: als de scorebijhouder op enig moment sluit, of verdwijnt, moet de handler stil eindigen, zoals die van de kijker.
Hoofdstuk 9 beantwoordt een fout met een error-envelop en houdt de verbinding open. Voorlopig beëindigt een fout het gesprek. Het antwoord gaat met opzet als laatste: de scorebijhouder hoort pas over versie 1 als die naar elke kijker is verstuurd, en de run hieronder laat je dat zien.
De scorebijhouder stuurt {"type": "score", "team": "Owls", "points": 11}. Hoe beëindigt de server van dit hoofdstuk de verbinding?
Maak kennis met pl_ws.py
Check my work herhaalt eerst de controles van les 1 tot en met 4, en test daarna je handler met stand-ins, zoals de eerdere lessen deden. Om het echte werk te zien, heb je een server en meerdere clients nodig die tegelijk over echte pl_ws.py mee, een programma dat voor deze cursus is geschreven en elke client speelt. Het is geen tool die je ergens anders vindt. Het start je server vanuit make_handler op
Draai het zonder scenario om ze op te sommen; dat van hoofdstuk 7 is quiz-round:
python pl_ws.py quiz-round
Kijkers A, B en
scenario quiz-round: viewers A, B and C join in turn, then a scorekeeper scores
server options: the library's defaults with close_timeout=1.0 (no open_server in this chapter yet)
viewer A joined; its first message was a snapshot with version 0
viewer B joined; its first message was a snapshot with version 0
viewer C joined; its first message was a snapshot with version 0
the registry holds 3 viewers
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
before the reply with version 1 reached the scorekeeper, the server had sent that version to 3 of 3 viewers
before the reply with version 2 reached the scorekeeper, the server had sent that version to 3 of 3 viewers
viewer B left after version 2; the registry holds 2 viewers
the scorekeeper sent 5 points for Herons; its reply was a snapshot with version 3
the server shut down
viewer A received versions [0, 1, 2, 3], then the server closed it with 1001
viewer B received versions [0, 1, 2], then it closed the connection itself
viewer C received versions [0, 1, 2, 3], then the server closed it with 1001
Lees het van boven af. Het eerste bericht van elke kijker was een snapshot. Het register groeide tot drie, en kromp tot twee toen B vertrok, via het finally-blok uit les 3. De versies van elke kijker gaan alleen omhoog. Aan het eind sloot het afsluiten van de server de overgebleven kijkers met 1001, en zo eindigt de handler van een kijker als niets anders hem beëindigt.
pl_ws.py meldt volgorde en aantallen, nooit tijden, omdat de threads van meerdere clients niet op elke machine in dezelfde volgorde klaar zijn. De volgorde die het wel meldt, is het interessante deel: voordat elk antwoord de scorebijhouder bereikte, had de server die versie al naar alle drie de kijkers gestuurd.
Waarom komt in het transcript elk antwoord aan de scorebijhouder na het exemplaar van de update van elke kijker?
Je kunt ook python scoreboard_demo.py draaien, waarvan de sectie van les 6 twee keer scoort en daarna 11 punten stuurt, en je tests uit les 5, die nog steeds moeten slagen:
python -m pytest -q
Als quiz-round tot het eind draait, klik je op Check my work.
Waar het transcript op hint
Drie kijkers op loopback, allemaal in één proces op één machine, antwoorden meteen, dus niemand merkt het wachten. Maar elk antwoord wacht op de send van elke kijker, en zolang één send bezig is, wordt de lock van het bord vastgehouden, dus de volgende score en elk nieuw binnenkomen wachten ook. Eén kijker op een trage verbinding zou de scorebijhouder, en alle anderen, op hem laten wachten. Hoofdstuk 8 meet dat, en geeft daarna elke kijker zijn eigen verzender.
Opdracht
Schrijf in scoreboard_server.py scorekeeper_handler(connection, board):
lees hoogstens
MAX_SCORE_MESSAGESberichten, elk metconnection.recv(timeout=WAIT);roep voor elk ervan
scoreboard_messages.parse_messageaan, controleer dat het type"score"is, roepapply_score(board, message)aan, en stuur de snapshot die die teruggeeft, gecodeerd metscoreboard_messages.encode_message;sluit bij
TimeoutErrormet 1000 en een reden, en keer terug;sluit bij
uit het parsen, de typecontrole of apply_scoremet 1008 en een reden, en keer terug;sluit na het laatste toegestane bericht met 1000 en een reden;
keer bij
ConnectionClosed, waar dan ook, terug zonder een exception op te werpen.
Draai python pl_ws.py quiz-round in de pl_ws.py niet en leest je terminal niet. Ze herhaalt eerst de controles van les 1 tot en met 4, en draait daarna je handler op stand-in-verbindingen, met twee stand-in-kijkers geregistreerd. Twee scores moeten worden beantwoord met versie 1 en 2, waarbij elk antwoord wordt verstuurd nadat beide kijkers die versie is gestuurd, en er mag niets gesloten worden totdat de scorebijhouder sluit. Na één geldige score moet elk van 11 punten, een team met de naam Bats, "not json", een binair error-envelop geen antwoord krijgen, een close met 1008, geen verdere leesactie, en het bord op versie 1 laten. Een scorebijhouder die niet op tijd iets stuurt, moet worden gesloten met 1000. Twaalf wachtende scores moeten tien antwoorden en een close met 1000 krijgen, waarbij de elfde nooit wordt gelezen. Een verbinding die eindigt, of een antwoord dat haar gesloten aantreft, moet de handler stil laten eindigen. Elke recv heeft een time-out nodig boven 0 en van hoogstens 2 seconden. Een for-range(MAX_SCORE_MESSAGES) is net zo goed als een teller, en de redenteksten kies je zelf.