Die Bibliothek websockets, ohne asyncio · Abschlussprojekt
Abschlussprojekt: Projekt: Der Echo-Dienst, neu gebaut
Du hast einen Handler, einen Client und eine Möglichkeit zu lesen, wie ein Gespräch geendet hat, jedes für sich geschrieben und geprüft. Was du noch nicht hast, ist ein Programm, das ein Mensch ausführen könnte. In Kapitel 5 war dieses Programm loopback_demo.py mit seiner echo_library.py, mit drei weiteren
Was du baust
echo_session(url, texts, *, connect=connect) ist echo_once, das zusätzlich meldet, wie das Gespräch geendet hat. Verbinde dich mit denselben drei Grenzen und proxy=None. Setze im with-Block alles in ein einziges try: Sende jeden Text und lies sein Echo, wie zuvor; schließe dann mit connection.close(1000, "done") und rufe noch einmal connection.recv(timeout=WAIT) auf. Sobald der Schließ-recv ConnectionClosed aus, und except ConnectionClosed as exc: gibt {"echoes": echoes, "close": describe_close(exc)} zurück.
Die try zu setzen ist für das andere Ende wichtig. Der Server, den du geschrieben hast, schließt zuerst, wenn er MAX_MESSAGES erreicht, und ein Server kann verschwinden. In beiden Fällen löst das nächste recv oder send ConnectionClosed aus, und dasselbe except gibt die Echos, die angekommen sind, mit dem Close zurück, der sie beendet hat: 1000 und message limit reached für einen Server an seiner Grenze, 1006 für einen, der verschwunden ist.
parse_args(argv) liest dieselben drei Optionen wie das Projekt aus Kapitel 5, mit denselben Grenzen, und lehnt alles außerhalb davon mit parser.error ab, was das Programm mit dem Status 2 stoppt:
--clients, einintvon 1 bisMAX_CLIENTS(5), Standardwert 2;--messages, einintvon 1 bisMAX_MESSAGES(10), Standardwert 3;--deadline, einfloatüber 0 und höchstens 8 Sekunden, Standardwert 5.0.
main(argv=None, *, serve=serve, connect=connect, now=time.monotonic) setzt alles zusammen:
Lies die Optionen mit
parse_args(argv)und berechne, wann die Frist abläuft:now()plus--deadline;bediene
echo_handlerauf"127.0.0.1"0 in einer with-Anweisung, gib den Port aus und starteserve_foreverin einem Thread, wie inrun_echo;für jeden Client, nummeriert ab 1: Wenn
now()die Frist erreicht hat, gib aus, dass der Client nicht gestartet wurde, und zähle ihn als gescheitert; gib ihm andernfalls--messagesTexte deiner Wahl und führeecho_sessionmit derws://127.0.0.1:PORT/echoundconnect=connectaus;wenn
echo_sessionOSErrorauslöst, gib aus, dass der Client gescheitert ist, zähle ihn als gescheitert und mach mit dem nächsten Client weiter;ConnectionRefusedErrorundTimeoutErrorsind beide Arten vonOSError;gib andernfalls eine Zeile für den Client aus, etwa wie viele Echos er bekommen hat und den
, und zähle ihn nur dann als Erfolg, wenn er jedes Echo und einen Close mit 1000 bekommen hat; führe nach dem
with-Block den join des Threads mittimeout=WAITaus und gib 0 zurück, wenn jeder Client erfolgreich war, 1, wenn einer es nicht war.
Der Wortlaut deiner Zeilen ist dir überlassen, ebenso wie du zählst: eine
Wieder Nahtstellen
serve= und connect= sind aus demselben Grund connect= es in Networks with Python war: damit ein Aufrufer Stellvertreter übergeben kann. Submit übergibt ein serve, dessen Server aufzeichnet, was du damit machst, und nichts bedient, und ein connect, das vorgegebene Verbindungen ausgibt: eine, die Echos sendet, eine, deren Server nach einem Echo schließt, eine, deren Server deinen Close mit 1001 beantwortet, eine, die in ein Timeout läuft, und eine, die abgelehnt wird. main muss sein connect an echo_session weitergeben; ohne connect=connect in diesem Aufruf würde echo_session seinen Standardwert verwenden, das echte connect der Bibliothek.
now= ist die injizierte Uhr aus Python in der Praxis. Submit übergibt eine, die über die Frist springt, sobald sich der erste Client verbunden hat, und erwartet, dass kein zweiter Client startet.
Das Herunterfahren des Servers braucht keinen eigenen Code. Weil der Server in einer with-Anweisung verwendet wird, ruft das Verlassen des Blocks auf jedem Weg shutdown() auf: nach dem letzten Client, nach einem Client, der gescheitert ist, und sogar, wenn eine
echo_session löst für den zweiten Client TimeoutError aus. Fährt der Server trotzdem herunter?
Run
Klicke auf Run. Der letzte Abschnitt von library_demo.py ruft dein main mit --clients 2 --messages 2 auf und gibt aus, was es zurückgibt. Eine fertige Version, auf der Website ausgeführt, hat Folgendes ausgegeben, wobei PORT für den Port steht, den das System gewählt hat:
Lesson 6: main with two clients
listening on 127.0.0.1:PORT
client 1: 2 echoes, closed with 1000
client 2: 2 echoes, closed with 1000
0
Deine Zeilen lauten so, wie du sie geschrieben hast; die 0 ist der Exit-Code, den main zurückgegeben hat, und er sollte 0 sein. Die Standardfehlerausgabe sollte leer sein, wie in Lektion 4. Auf deinem eigenen Computer, mit der Bibliothek aus deiner requirements.txt installiert, führt python echo_library.py --clients 3 dasselbe Programm aus einem
Klicke dann auf Submit. Bevor es die drei neuen Funktionen prüft, prüft es die früheren erneut: den Handler, run_echo, echo_once und describe_close.
Wenn dich etwas an main verwirrt, frag Monty:
Warum übergibt mein main connect=connect an echo_session, wenn echo_session connect schon als Standardwert hat?
Immer noch einer nach dem anderen
Sieh dir an, was main mit dem Server der Bibliothek macht: ein Client, dann der nächste. Der Server würde problemlos mehrere Clients gleichzeitig bedienen, jeden in seinem eigenen Handler-Thread, aber dieses Programm verlangt das nie. Es gibt auch nichts, was Clients teilen könnten: Jeder redet nur mit seinem eigenen Echo. Kapitel 7 ändert beides. Es bedient mehrere Clients gleichzeitig und gibt ihnen etwas Gemeinsames, die Anzeigetafel. Eine Anzeigetafel, die sauber anhalten muss, während noch Zuschauer verbunden sind, braucht mehr als die Art dieses Programms, zwischen Clients anzuhalten, etwa offene Verbindungen mit 1001 zu schließen, wenn ihre Frist kommt; das Abschlussprojekt in Kapitel 10 kümmert sich darum.
Aufgabe
Schreibe drei echo_library.py, in dieser Reihenfolge. Submit nennt die erste, die noch NotImplementedError auslöst.
echo_session(url, texts, *, connect=connect): Verbinde dich wieecho_once, mitopen_timeout=WAIT,close_timeout=WAITundproxy=None, in einerwith-Anweisung. Sende in einem einzigentryjeden Text und lies sein Echo mitrecv(timeout=WAIT), rufe dannconnection.close(1000, "done")und noch einmalconnection.recv(timeout=WAIT)auf. Gib inexcept ConnectionClosed as exc:{"echoes": echoes, "close": describe_close(exc)}zurück.parse_args(argv):--clients(int, 1 bisMAX_CLIENTS, Standardwert 2),--messages(int, 1 bisMAX_MESSAGES, Standardwert 3) und--deadline(float, über 0 und höchstens 8, Standardwert 5.0), mitparser.errorfür einenaußerhalb seiner Grenzen. main(argv=None, *, serve=serve, connect=connect, now=time.monotonic): Lies die Optionen; setze die Frist anhand vonnow(); bedieneecho_handlerauf"127.0.0.1"0 in einer with-Anweisung und starteserve_foreverin einemthreading.Thread; führe pro Client einecho_sessionaus, einen nach dem anderen, mit--messagesTexten undconnect=connect, und starte keinen Client mehr, sobaldnow()die Frist erreicht hat; zähle einen Client, derOSErrorauslöst, weniger Echos bekommt, als er gesendet hat, oder einen anderen Close als 1000 bekommt, als gescheitert; gib pro Client eine Zeile aus; führe nach dem Block den join des Threads mittimeout=WAITaus; gib 0 zurück, wenn jeder Client erfolgreich war, und sonst 1.
Klicke auf Run: Der Abschnitt zu Lektion 6 sollte nach einer Zeile pro Client mit 0 enden.
Klicke dann auf Submit. Die Prüfung öffnet nie einen echo_handler, run_echo, echo_once und describe_close, wie es die Lektionen 2 bis 4 getan haben. Sie lässt echo_session gegen Stellvertreter-Verbindungen laufen: einen Echo-Server (sie erwartet close(1000, "done") und {"echoes": [...], "close": {"code": 1000, "reason": "done"}}), einen Server, der nach einem Echo schließt, und einen Server, der verschwindet (1006), mit denselben Grenzen für connect und recv wie in Lektion 3. Sie lässt parse_args mit den Standardwerten, den größten erlaubten Werten und Werten knapp jenseits jeder Grenze laufen. Sie lässt main mit einem Stellvertreter-serve, einem Stellvertreter-connect und einem aufzeichnenden threading-serve(echo_handler, "127.0.0.1", 0), ein Thread auf serve_forever, gestartet vor dem ersten Client, eine Verbindung pro Client zu dem Port, den der Server gemeldet hat, je --messages Texte, das Herunterfahren nach den Clients und ein join mit Timeout nach dem Herunterfahren, und 0 für zwei gute Clients. Sie erwartet 1, bei trotzdem heruntergefahrenem Server, für einen Client mit einem von zwei Echos, einen mit 1001 beantworteten Close, eine abgelehnte Verbindung und ein recv, das in ein Timeout läuft; keinen zweiten Client, sobald ihre Uhr die Frist überschreitet; und den Status 2 für --clients 9. Dein ausgegebener Wortlaut wird nicht geprüft.