Die Bibliothek websockets, ohne asyncio · Übung
Lesen, wie eine Verbindung geendet hat
Der Handler aus Lektion 2 sendet korrekte Echos, und jedes Gespräch, das er bedient, endet trotzdem mit einem recv hat ConnectionClosedOK ausgelöst, weil der Client zuerst geschlossen hat, nichts in echo_handler hat die connection handler failed protokolliert. Die Verbindung selbst wurde sauber geschlossen, wie die letzte Zeile des Blocks gesagt hat. Ein Client, der schließt, ist das normale Ende eines Gesprächs, also sollte der Handler seine
OK und Error
Sobald eine Verbindung geschlossen ist, löst jedes spätere recv und send darauf eine Ausnahme aus websockets.exceptions aus. Welche, hängt davon ab, wie die Verbindung geendet hat:
ConnectionClosedOK, wenn in jede Richtung ein Close-ging und beide 1000, 1001 oder gar keinen Code trugen (ein leerer Close, der als 1005 gemeldet wird); ConnectionClosedErrorin jedem anderen Fall: ein Close mit einem anderen Code, etwa 1008, oder eine Verbindung, die ganz ohne Close-Frame endete.
Diese Aufteilung ist die eigene Entscheidung der Bibliothek darüber, was als normales Ende gilt, keine Regel aus RFC 6455. Beide Klassen sind Arten von ConnectionClosed, dem Namen, den echo_library.py bereits importiert. Eine except-Klausel, die eine Klasse nennt, fängt auch jede Ausnahme ab, die eine Art dieser Klasse ist, also fängt except ConnectionClosed: beide ab.
Der Server schließt mit 1008. Was löst das nächste recv des Clients aus?
Was angekommen ist
Jede dieser Ausnahmen trägt die Close-Frames. Nenne die Ausnahme in der except-Zeile, um sie zu lesen:
try:
connection.recv(timeout=WAIT)
except ConnectionClosed as exc:
print(exc.rcvd)
exc.rcvd ist der Close-Frame, der von der Gegenseite angekommen ist, mit .code und .reason, und exc.sent ist der, den diese Seite gesendet hat. Beide können None sein. Wenn exc.rcvd is None gilt, ist kein Close-Frame angekommen: Die describe_close tut dasselbe.
Also prüft describe_close(exc) zuerst exc.rcvd is None und gibt dann {"code": 1006, "reason": ""} zurück. Andernfalls gibt es Code und Grund von exc.rcvd in derselben Art von is None für die Prüfung, wie bei jedem Test auf None.
Älterer Code liest exc.code. Das funktioniert in der Version, die du festgelegt hast, noch, gibt aber eine DeprecationWarning aus, die auf rcvd.code verweist, also lies den Close-Frame über rcvd.
exc.rcvd ist None. Was ist passiert?
Auch send absichern
recv ist nicht der einzige Aufruf, der die Verbindung geschlossen vorfinden kann. Der Client kann schließen, nachdem dein recv zurückgekehrt ist und bevor dein send läuft; dann löst send ConnectionClosedOK aus. Also deckt das try beide Zeilen des Schleifenrumpfs ab:
try:
message = connection.recv(timeout=WAIT)
connection.send(message)
except ConnectionClosed:
return
Das andere Ende ist ein Client, der nichts sagt. Wenn innerhalb von WAIT keine Nachricht ankommt, löst recv Pythons eingebautes TimeoutError aus. Ein Client, der 2 Sekunden lang nichts gesendet hat, wartet auf nichts von diesem Server, also schließe ihn selbst, mit 1000 und einem Grund wie "no message arrived in time", und kehre zurück. Gib dem try eine zweite Klausel, except TimeoutError:, die das tut.
Warum deckt der try-Block sowohl send als auch recv ab?
Deine Aufgabe
In echo_library.py:
Schreibe
describe_close(exc):{"code": 1006, "reason": ""}, wennexc.rcvd is Nonegilt, und sonst Code und Grund ausexc.rcvd;stelle
echo_handlerfertig: Setze in der Schleiferecvundsendin eintry; kehre beiConnectionClosedzurück; rufe beiTimeoutErrorconnection.close(1000, "no message arrived in time")auf und kehre zurück. Die Grenze und der abschließende Close nachMAX_MESSAGESbleiben, wie sie sind.
Run
Klicke auf Run. Die Standardfehlerausgabe bleibt diesmal leer: kein Block connection handler failed. Drei neue Abschnitte verbinden sich mit dem Client der Bibliothek, lesen, bis die Verbindung geschlossen ist, und geben den Klassennamen der Ausnahme aus und das, was dein describe_close daraus gemacht hat. Eine fertige Version, auf der Website ausgeführt, hat Folgendes ausgegeben:
Lesson 4: you close
ConnectionClosedOK, {'code': 1000, 'reason': 'done'}
Lesson 4: the server closes with 1008
ConnectionClosedError, {'code': 1008, 'reason': 'this path is closed'}
Lesson 4: the server vanishes
ConnectionClosedError, {'code': 1006, 'reason': ''}
Im ersten schließt der Client mit 1000 und done, und der Server antwortet mit demselben Close, also tragen beide Frames 1000: OK. Im zweiten schließt der Handler eines Bibliotheks-Servers sofort mit 1008, also ist das Ende ein Error, und rcvd enthält Code und Grund des Servers. Im dritten beantwortet ein Server, der von Hand aus serve_handshake aus Kapitel 2 gebaut ist, das Upgrade mit einer 101 und schließt dann die TCP-Verbindung ohne Close-Frame: ein Error, bei dem rcvd auf None gesetzt ist, und deine ending und read_the_ending in library_demo.py, um zu sehen, wie jeder Fall erzeugt wird. Klicke dann auf Submit.
Dein Handler besteht aus einer Handvoll Zeilen, und dein Client aus ein paar mehr. Die nächste Lektion legt sie neben die Funktionen, die du in den Kapiteln 2 bis 5 geschrieben hast, und nennt, was die Bibliothek tut, das diese nie getan haben.
Aufgabe
In echo_library.py:
Schreibe
describe_close(exc). Wennexc.rcvd is Nonegilt, gib{"code": 1006, "reason": ""}zurück. Andernfalls gib{"code": exc.rcvd.code, "reason": exc.rcvd.reason}zurück.Stelle
echo_handlerfertig. Setze in derdas recvund dassendin eintry. Kehre beiConnectionClosedzurück. Rufe beiTimeoutErrorconnection.close(1000, "no message arrived in time")auf und kehre zurück. Behalte die Grenze und den Close mit 1000 nachMAX_MESSAGESNachrichten.
Klicke auf Run: Es erscheint kein Block connection handler failed, und die drei Abschnitte zu Lektion 4 geben ConnectionClosedOK, {'code': 1000, 'reason': 'done'}, ConnectionClosedError, {'code': 1008, 'reason': 'this path is closed'} und ConnectionClosedError, {'code': 1006, 'reason': ''} aus.
Klicke dann auf Submit. Die Prüfung baut die describe_close: OK- und Error-Enden mit Codes und Gründen, die du nicht gesehen hast, einen Close-exc.code statt exc.rcvd.code zu lesen scheitert, weil die Prüfung die Deprecation-Warnung der Bibliothek in einen Fehler verwandelt. Sie gibt echo_handler Stellvertreter-Verbindungen: einen Client, der nach zwei Nachrichten schließt, eine Verbindung, die ohne Close-Frame endet, ein send, das die Verbindung geschlossen vorfindet, einen Client, der nichts rechtzeitig sendet (ein Close mit 1000 wird erwartet und kein weiteres recv), und zehn Nachrichten, gefolgt von weiteren. Jeder Fall muss damit enden, dass der Handler zurückkehrt, nicht damit, dass er eine Ausnahme auslöst. Zwei except-Klauseln, eine für ConnectionClosedOK und eine für ConnectionClosedError, sind statt except ConnectionClosed: in Ordnung.