Eén server, veel clients · oefening
Broadcast elke verandering
Het register vindt elke verbonden kijker, en het bord verandert nog steeds nooit. Deze les schrijft de verandering: apply_score telt de punten van een score op bij het bord, en broadcast stuurt het nieuwe bord naar elke kijker.
Eén verandering, één houding
apply_score(board, message) ontvangt een score-envelop die parse_message al heeft gecontroleerd, dus het team is een with board["lock"]:-blok doet hij vier dingen, in volgorde:
controleer het team: als
message["team"]geen sleutel vanboard["scores"]is, werp danValueError("unknown team")op voordat er iets verandert;tel de punten op bij de score van dat team;
tel 1 op bij
board["version"];bouw de nieuwe snapshot met
snapshot_message(board), geef hem aanbroadcast(board, snapshot)en geef hem terug.
De check_message, omdat alleen het bord weet welke teams meespelen.
Alle vier stappen delen één houding, om de reden uit les 3. Het binnenkomen neemt dezelfde lock, dus een kijker komt binnen ofwel vóór de verandering, en is dan geregistreerd om haar te ontvangen, ofwel erna, en dan bevat zijn snapshot haar al.
Eén keer coderen, vaak sturen
broadcast(board, snapshot) wordt door apply_score aangeroepen terwijl de lock wordt vastgehouden, dus hij mag de lock niet zelf nemen: een thread die een lock neemt die hij al vasthoudt, wacht eeuwig op zichzelf. Hij codeert de snapshot één keer met scoreboard_messages.encode_message, en stuurt daarna diezelfde tekst naar elke kijker in board["viewers"]. Elke kijker krijgt dezelfde bytes, dus er is geen reden om ze voor elke kijker opnieuw te coderen. Het coderen controleert ook de envelop, één keer, voordat er iets naar wie dan ook wordt verstuurd.
Een kijker die weg is
Een kijker kan op elk moment verdwijnen: een dichtgeklapte laptop, een weggevallen netwerk. Het register weet dat pas als iets de verbinding probeert te gebruiken. Dan werpt send ConnectionClosed op, die je in hoofdstuk 6 tegenkwam. Eén verdwenen kijker mag de anderen hun update niet kosten, dus vang de
De verdwenen kijker moet ook het register verlaten. De voor de hand liggende regel, hem binnen de for-lus erdoorheen loopt:
Verzamel dus de id’s van mislukte kijkers tijdens de lus in een .items() door het register loopt, krijg je elke id en zijn verbinding samen:
failed = []
for viewer_id, viewer in board["viewers"].items():
try:
viewer.send(text)
except ConnectionClosed:
failed.append(viewer_id)
Elke verbinding opzoeken met board["viewers"][viewer_id] werkt net zo goed. Als de eigen handler van die kijker later zijn finally-blok bereikt, vindt zijn remove_viewer de id niet meer, en pop met een standaardwaarde maakt dat onschadelijk:
Wat drukt dit af?
d = {"a": 1}
print(d.pop("b", None), d.pop("a", None), d)
In broadcast kwamen de id’s een moment eerder uit het register, dus daar is een gewone pop(viewer_id) prima.
Wat gebeurt er met de handler van de verdwenen kijker? Zijn thread zit nog in watch_until_closed. Zijn volgende recv werpt ook ConnectionClosed op, de handler vertrekt via except en finally, en remove_viewer vindt niets om te verwijderen. Twee threads proberen allebei de kijker te verwijderen, en welke als tweede komt, doet niets.
Er zijn drie kijkers geregistreerd en de send van de tweede werpt ConnectionClosedError op. Wat doet broadcast?
Volgorde gratis, voorlopig
Elke send naar een kijker gebeurt nu terwijl een of andere thread de lock van het bord vasthoudt: de snapshot bij binnenkomst binnen add_viewer, en elke update binnen apply_score. Er houdt maar één thread tegelijk de lock vast, dus de sends naar één kijker gebeuren na elkaar, in de volgorde waarin het bord veranderde. Een kijker kan nooit versie 7 toegestuurd krijgen vóór versie 6.
Waarom kan een kijker in de server van dit hoofdstuk nooit versie 7 ontvangen vóór versie 6?
Die volgorde heeft een prijs. Zolang de send naar één kijker bezig is, wordt de lock vastgehouden, dus elke andere kijker, de volgende score en elk nieuw binnenkomen wachten erop. Sturen onder een lock is een bewuste vereenvoudiging voor dit hoofdstuk, en zo hoort een drukke server niet te werken. Hoofdstuk 8 meet wat het kost en haalt het weg.
Uitvoeren
Klik op Run. De sectie van les 4 registreert drie kijkers, laat de send van B mislukken en verwerkt één score. Een afgemaakte versie, op de site uitgevoerd, drukte af:
Lesson 4: one score, three viewers, one of them gone
viewer A received versions [0, 1]
viewer B received versions [0]
viewer C received versions [0, 1]
registry after the update: ['A', 'C']
Elke kijker ontving zijn snapshot bij binnenkomst, versie 0. A en
Binnenkomen, register en broadcast werken nu allemaal, en alleen de demo en Submit hebben ze geprobeerd. In de volgende les schrijf je eigen tests die elk ervan bewijzen.
Opdracht
Schrijf in scoreboard_server.py twee NotImplementedError opwerpt.
broadcast(board, snapshot): de aanroeper houdt de lock van het bord al vast, dus neem hier geen lock. Codeer de snapshot één keer metscoreboard_messages.encode_message, stuur de tekst naar elke verbinding inboard["viewers"], en vangConnectionClosedrond elke send, waarbij je de id van die kijker aan eentoevoegt. Verwijder na de elke id uit de lijst uit board["viewers"].apply_score(board, message): werp binnen éénwith board["lock"]:-blokValueError("unknown team")op alsmessage["team"]niet op het bord staat, voordat je iets verandert; tel andersmessage["points"]op bij de score van dat team, tel 1 op bijboard["version"], maak de nieuwe snapshot metsnapshot_message(board), roepbroadcast(board, snapshot)aan en geef de snapshot terug.
Klik op Run en lees de sectie van les 4, en klik daarna op Submit. De controle houdt de lock van het bord in de gaten en legt elke schrijfactie naar de versie en de scores van het bord vast. Ze registreert vier stand-in-kijkers, van wie de tweede weg is, en verwerkt een score die je nog niet hebt gezien: ze verwacht de nieuwe snapshot terug, dat elk van de andere drie kijkers hem precies één keer ontvangt, en dat het register die drie in hun volgorde houdt. Twee scores achter elkaar moeten elke kijker bereiken als versie 1 en 2. Een score voor Bats moet broadcast, los aangeroepen terwijl de lock wordt vastgehouden, moet elke kijker bereiken die niet weg is. Itereren over board["viewers"].copy(), Exception vangen in plaats van ConnectionClosed, en per kijker één keer coderen worden allemaal geaccepteerd.