0%

Frames, Byte für Byte · Übung

Einen Frame aus einem Stream dekodieren

encode_frame verwandelt eine Nachricht in Bytes. Die empfangende Seite braucht das Gegenteil: Bytes von der Verbindung nehmen und die Nachricht zurückbekommen. Das Problem hast du in Networks with Python, Kapitel 6, gelöst. liefert einen Stream, keine Pakete, also kann ein am Stück ankommen, an beliebiger Stelle in Stücke geschnitten oder an den Anfang des nächsten Frames geklebt. Ein Decoder, der annimmt, dass ein recv einen Frame enthält, funktioniert auf einem ruhigen Testrechner und scheitert in einem ausgelasteten Netzwerk.

Die Antwort aus Networks gilt weiterhin: Frag die Verbindung nie “was hast du?”, sondern “gib mir genau n Bytes”. Ein Frame sagt dir Schritt für Schritt, wie viele Bytes du als Nächstes anfordern musst.

read_exact als Parameter

decode_frame(read_exact, *, max_size=MAX_SIZE) bekommt seine Bytes nur über read_exact, dieselbe Art von , die read_length nimmt. Es sieht nie einen . Das macht es leicht, mit einem Stellvertreter zu testen, und das Kapitel liefert einen in fakes.py. Öffne die Datei und lies ihre beiden Funktionen einmal:

  • pieces(data, size) schneidet data in eine von Stücken mit höchstens size Bytes, so wie eine ausgelastete Verbindung sie liefern könnte.

  • reader_over(chunks) gibt eine Funktion read_exact(n) zurück, die der Reihe nach Stücke aus der Liste nimmt, sie in einem Puffer sammelt, bis sie n Bytes hat, genau diese zurückgibt und den Rest für den nächsten Aufruf behält. Wenn die Stücke vorher ausgehen, löst sie ConnectionError aus, wie es eine geschlossene Verbindung tun würde.

Also ist fakes.reader_over(fakes.pieces(frame, 1)) ein read_exact über eine Verbindung, die frame ein Byte nach dem anderen liefert. Genau das übergibt frames_demo.py deinem Decoder in seinem Abschnitt zu Lektion 5. Die Ein-Byte-Stücke beweisen nur, dass dein Decoder genaue Anzahlen anfordert und nie annimmt, dass mehr angekommen ist; die Stücke zu diesen Anzahlen zu sammeln ist die Aufgabe des Lesers, derselbe Empfangspuffer, den du in Networks geschrieben hast.

Der Reihe nach

Ein Decoder liest einen Frame in der Reihenfolge, in der sein Header ihn beschreibt:

  1. read_exact(2), die zwei Bytes, die jeder Frame hat und die parse_header_bytes liest;

  2. read_length(...), das je nach 7-Bit-Länge 0, 2 oder 8 weitere Bytes liest;

  3. read_exact(length), die Payload.

Jeder Schritt braucht den vorigen, um zu wissen, wie viel er anfordern soll, also ist die Reihenfolge fest. Mehr als einen Schritt auf einmal anzufordern, etwa 14 Bytes, um den längsten Header abzudecken, würde bei einem 7 Byte langen Frame wie dem “Hello” des RFC ewig warten oder den Anfang des nächsten Frames verschlucken. Genau einen Frame zu lesen bedeutet auch, dass ein zweiter Frame direkt dahinter unversehrt im Puffer bleibt, für den nächsten Aufruf.

decode_frame hat die ersten beiden Bytes 82 7e gelesen. Was fordert es als Nächstes an?

Vor dem Lesen ablehnen

max_size ist die größte Payload, die dieser Decoder akzeptiert, standardmäßig MAX_SIZE, eine Million Bytes. Networks with Python, Kapitel 8, hat dieselbe Art von Grenze für seine Frames gesetzt, aus demselben Grund. Die 64-Bit-Länge lässt eine Gegenseite eine Payload von Milliarden Bytes ankündigen, und ein Programm, das versuchte, sie zu lesen, würde auf Bytes warten, die vielleicht nie kommen, und mehr Speicher belegen, als es hat.

Der Header sagt bereits, wie groß die Payload ist. Vergleiche die Länge also mit max_size, sobald read_length zurückkehrt, und löse aus, bevor du read_exact um ein einziges Payload-Byte bittest. Nach dem Eintreffen der Payload zu prüfen ist zu spät: Das Warten und der Speicher sind dann schon verbraucht.

Warum vergleicht decode_frame die Länge mit max_size, bevor es die Payload liest?

Ein maskierter Frame

Noch eine Ablehnung. Abschnitt 5.7 des RFC zeigt “Hello” so, wie ein Client es senden würde, maskiert:

81 85 37 fa 21 3d 7f 9f 4d 51 58

Das zweite Byte, 85, hat gesetztes MASK, und die vier Bytes danach sind der . Ein Server maskiert seine Frames nie, also weiß ein Client, der einen maskierten Frame empfängt, dass etwas nicht stimmt, und lehnt ihn ab. Der Decoder dieses Kapitels ist die Seite des Clients, und er kann noch nicht demaskieren, also löst er ValueError für jeden Frame aus, dessen Feld "masked" gleich True ist. Kapitel 4 lehrt das Maskieren und gibt dem Decoder auch die Seite des Servers.

Deine Aufgabe

Schreibe in ws_frames.py die Funktion decode_frame(read_exact, *, max_size=MAX_SIZE):

  1. Lies 2 Bytes und übergib sie an parse_header_bytes, das bei reservierten Bits und ValueError auslöst;

  2. löse ValueError aus, wenn der Frame maskiert ist;

  3. hole die Payload-Länge mit read_length;

  4. löse ValueError aus, wenn die Länge über max_size liegt, bevor du irgendeine Payload liest;

  5. lies die Payload und gib {"fin": ..., "opcode": ..., "payload": ...} zurück, mit der Payload als bytes.

Fange ConnectionError nicht ab. Wenn die Gegenseite mitten in einem Frame schließt, muss der Aufrufer davon erfahren.

Klicke auf Run. Der Abschnitt zu Lektion 5 dekodiert das “Hello” des RFC, das deinem Decoder ein Byte nach dem anderen zugeführt wird:

Lesson 5: Hello decoded one byte at a time
{'fin': True, 'opcode': 1, 'payload': b'Hello'}

Klicke dann auf Submit. Die Prüfung gibt deinem Decoder ein Stellvertreter-read_exact, das jede angeforderte Anzahl aufzeichnet. Sie dekodiert die Frames des RFC, ein Fragment, binäre Frames mit 256 und 65536 Bytes und Payloads, die du nicht gesehen hast, und zwei direkt hintereinander gesendete Frames; sie prüft, dass ein zu großer Frame abgelehnt wird, bevor seine Payload angefordert wird, dass ein maskierter Frame, ein reservierter Opcode und eine nicht minimale Länge ValueError auslösen und dass ein Schließen mitten in einem Frame den Aufrufer als ConnectionError erreicht.

Der Lern-Codec ist für unmaskierte Frames fertig. Das Projekt, das dieses Kapitel abschließt, schreibt die Tests, die ihn an die Bytes des RFC binden.

Aufgabe

Schreibe decode_frame(read_exact, *, max_size=MAX_SIZE) in ws_frames.py. Sie liest einen unmaskierten über read_exact und gibt {"fin": ..., "opcode": ..., "payload": ...} zurück:

  1. Lies 2 Bytes und übergib sie an parse_header_bytes;

  2. löse aus, wenn der Frame maskiert ist;

  3. hole die Payload-Länge mit read_length;

  4. löse ValueError aus, wenn die Länge über max_size liegt, bevor du irgendein Payload-Byte anforderst;

  5. lies genau die Payload und gib sie als bytes zusammen mit fin und dem zurück.

Lass einen ConnectionError aus read_exact den Aufrufer erreichen.

Klicke auf Run: Der Abschnitt zu Lektion 5 sollte {'fin': True, 'opcode': 1, 'payload': b'Hello'} ausgeben. Klicke dann auf Submit. Die Prüfung übergibt decode_frame ein Stellvertreter-read_exact, das jede angeforderte Anzahl aufzeichnet. Sie erwartet, dass die Text- und -Frames des RFC, ein Fragment, ein leerer Close-Frame, binäre Frames mit 256 und 65536 Bytes und zwei Frames hintereinander dekodiert werden, mit den Anforderungen 2, dann den Längenbytes, dann der Payload, und dass nichts über den Frame hinaus gelesen wird. Sie erwartet ValueError für einen Frame über max_size ohne Anforderung seiner Payload, einen maskierten Frame, einen reservierten Opcode und eine nicht minimale Länge, und ConnectionError, wenn die Bytes mitten in einem Frame aufhören. Dein darf zusätzliche Schlüssel enthalten.