0%

Die Bibliothek websockets, ohne asyncio · Übung

Als Client verbinden

Dein Bibliotheks-Server hat bisher nur mit dem handgebauten Client aus Kapitel 5 geredet. Diese Lektion schreibt die andere Seite mit der Bibliothek, echo_once(url, texts, *, connect=connect), und richtet dann diesen einen Client auf beide Server: deinen aus Lektion 2 und den Server aus Kapitel 5.

Eine URL statt einer Adresse

Der Client der Bibliothek nimmt eine , keinen Host und . Lies ws://127.0.0.1:50123/echo so, wie with Python dir beigebracht hat, eine URL zu zerlegen. Das Schema ws bedeutet über einfaches ; wss würde hinzufügen, das dieser Kurs nicht verwendet. 127.0.0.1 und 50123 sind Host und Port, mit denen verbunden wird, und /echo ist der Pfad.

Aus diesen Teilen baut die Bibliothek die Upgrade-Anfrage, die du in Kapitel 2 mit build_upgrade_request von Hand gebaut hast: eine GET-Anfragezeile mit dem Pfad als Ziel, einen Host-Header, einen neuen Sec-WebSocket-Key und Sec-WebSocket-Version: 13. Wenn die 101 ankommt, prüft sie den Sec-WebSocket-Accept- gegen ihren Schlüssel, die Aufgabe, die dein check_accept_response erledigt hat.

Welche Anfragezeile sendet die Bibliothek für ws://127.0.0.1:50123/echo?

Auf jeder Seite begrenzt

connect aus websockets.sync.client öffnet die TCP-Verbindung, führt den aus und gibt eine Verbindung mit denselben send, recv und close zurück, die dein Handler verwendet hat. Ein Client wartet an drei Stellen, und dieser Kurs begrenzt jede mit WAIT, 2 Sekunden:

  • open_timeout=WAIT begrenzt den Handshake. Der eigene Standardwert der Bibliothek ist 10 Sekunden.

  • recv(timeout=WAIT) begrenzt jedes Echo, wie in deinem Handler.

  • close_timeout=WAIT begrenzt den Schließ-Handshake. Sein Standardwert ist ebenfalls 10 Sekunden.

Eine Grenze verwandelt ein Warten in eine . Wenn ein Server die TCP-Verbindung annimmt, aber das Upgrade nie beantwortet, löst connect TimeoutError mit der Meldung timed out while waiting for handshake response aus, sobald open_timeout abläuft. In einem lokalen Test mit open_timeout=0.5 gegen einen Listener, der nie geantwortet hat, dauerte das ungefähr eine halbe Sekunde.

Ein weiteres gehört zu jedem connect: proxy=None. Standardmäßig liest die Bibliothek Proxy-Einstellungen aus der Umgebung, etwa die HTTP_PROXY, und folgt ihnen sogar für 127.0.0.1. In einem lokalen Test mit HTTP_PROXY auf einen Port gesetzt, an dem nichts lauschte, ging eine Verbindung, die für einen -Server gedacht war, stattdessen an diese Proxy-Adresse und scheiterte sofort; mit proxy=None hat dieselbe Verbindung den Server erreicht. Ob ein Rechner eine solche Variable setzt, ist nichts, wovon ein Loopback-Programm abhängen sollte, also übergibt jedes connect in diesem Kurs proxy=None.

Verwende connect(...) immer in einer with-Anweisung. Die Version, die du in Lektion 1 festgelegt hast, gibt eine DeprecationWarning aus, wenn connect auf andere Weise aufgerufen wird.

Warum übergibt jedes connect in diesem Kurs proxy=None?

Den Block verlassen

Das Gespräch eines Clients steht im with-Block:

with connect(url, open_timeout=WAIT, close_timeout=WAIT, proxy=None) as connection:
    connection.send("hello")

Wenn der Block endet, auf welchem Weg auch immer, sendet der Client einen Close- mit 1000 und einem leeren Grund und wartet dann, höchstens close_timeout, auf den antwortenden Close des Servers und darauf, dass der Server die TCP-Verbindung schließt. Das ist der Schließ-Handshake, den du in Kapitel 5 mit close_frame und wait_for_close geschrieben hast, jetzt für dich ausgeführt.

Was passiert, wenn der Block with connect(...) endet?

Deine Aufgabe

Schreibe echo_once(url, texts, *, connect=connect) in echo_library.py. Öffne die Verbindung mit connect(url, open_timeout=WAIT, close_timeout=WAIT, proxy=None) in einer with-Anweisung. Sende jeden Text und lies sein Echo mit connection.recv(timeout=WAIT), wobei du das Echo an eine anhängst. Gib die Liste nach dem Block zurück, damit die Verbindung bis dahin geschlossen ist. Erst alle Texte zu senden und danach die Echos zu lesen ergibt hier dieselbe Liste; ein Senden, dann ein Lesen hält jedes Echo neben dem Text, auf den es antwortet. connect=connect ist dieselbe Art von Nahtstelle wie serve=serve: Submit übergibt einen Stellvertreter.

Bevor du auf Run klickst

Run gibt diesmal zwei der Blöcke aus Lektion 2 aus, jeder beginnt mit connection handler failed, einer für jedes Gespräch, das dein Handler aus Lektion 2 bedient. Der erste kommt wie zuvor aus dem Abschnitt zu Lektion 2. Der zweite kommt aus dem ersten Abschnitt zu Lektion 3: Dein Client verlässt seinen with-Block, während dein Handler in recv wartet, also trifft der Handler wieder auf einen Close, diesmal ohne Grund. Dieser Block endet mit:

websockets.exceptions.ConnectionClosedOK: received 1000 (OK); then sent 1000 (OK)

Beide sind zu erwarten, Submit akzeptiert sie weiterhin, und Lektion 4 entfernt sie.

Klicke auf Run. Nach dem Abschnitt zu Lektion 2 hat eine fertige Version, auf der Website ausgeführt, Folgendes ausgegeben:

Lesson 3: your library client, your library server
['hello', 'café ✓', 'third message']
Lesson 3: your library client, the Chapter 5 server
{'client': ['hello', 'café ✓', 'third message'], 'Chapter 5 server': [{'messages': 3, 'code': 1000}]}

Der erste Abschnitt ist die Bibliothek auf beiden Enden. Der zweite ist noch einmal die Interop-Prüfung aus Lektion 1, diesmal mit deinem eigenen Client: Der Server aus Kapitel 5 meldet drei Nachrichten und einen Close mit 1000. Klicke dann auf Submit.

Beide Gespräche enden auf der Serverseite mit einem , obwohl nichts schiefgegangen ist. Die nächste Lektion liest, wie eine Verbindung geendet hat, und lässt den Handler still enden.

Aufgabe

Schreibe echo_once(url, texts, *, connect=connect) in echo_library.py:

  1. Öffne die Verbindung mit connect(url, open_timeout=WAIT, close_timeout=WAIT, proxy=None) in einer with-Anweisung;

  2. sende jeden Text mit connection.send und lies sein Echo mit connection.recv(timeout=WAIT), wobei du das Echo an eine anhängst;

  3. gib die Liste zurück.

Klicke auf Run: Die beiden Abschnitte zu Lektion 3 sollten ['hello', 'café ✓', 'third message'] ausgeben und dieselben Echos mit dem Bericht des Servers aus Kapitel 5 [{'messages': 3, 'code': 1000}]. Zwei Blöcke connection handler failed erscheinen auf der Standardfehlerausgabe, wie die Lektion beschreibt.

Klicke dann auf Submit. Die Prüfung öffnet nie einen . Sie übergibt echo_once ein Stellvertreter-connect und zeichnet auf, wie es aufgerufen wird: die url unverändert weitergegeben, open_timeout und close_timeout jeweils gesetzt und höchstens 2 Sekunden, und proxy=None. Die Stellvertreter-Verbindungen beantworten Texte, die du nicht gesehen hast, und eine davon beantwortet jeden Text in Großbuchstaben, also muss die Liste, die du zurückgibst, enthalten, was recv zurückgegeben hat, in Reihenfolge. Jedes recv braucht ein Timeout von höchstens 2 Sekunden, ein recv pro Text, und die Verbindung muss in einer with-Anweisung verwendet werden. Alle Texte zu senden, bevor alle Echos gelesen werden, ist in Ordnung.