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. 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 read_length nimmt. Es sieht nie einen fakes.py. Öffne die Datei und lies ihre beiden Funktionen einmal:
pieces(data, size)schneidetdatain einevon Stücken mit höchstens sizeBytes, so wie eine ausgelastete Verbindung sie liefern könnte.reader_over(chunks)gibt eine Funktionread_exact(n)zurück, die der Reihe nach Stücke aus der Liste nimmt, sie in einem Puffer sammelt, bis sienBytes hat, genau diese zurückgibt und den Rest für den nächsten Aufruf behält. Wenn die Stücke vorher ausgehen, löst sieConnectionErroraus, 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:
read_exact(2), die zwei Bytes, die jeder Frame hat und dieparse_header_bytesliest;read_length(...), das je nach 7-Bit-Länge 0, 2 oder 8 weitere Bytes liest;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 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 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):
Lies 2 Bytes und übergib sie an
parse_header_bytes, das bei reservierten Bits undValueErrorauslöst;löse
ValueErroraus, wenn der Frame maskiert ist;hole die Payload-Länge mit
read_length;löse
ValueErroraus, wenn die Länge übermax_sizeliegt, bevor du irgendeine Payload liest;lies die Payload und gib
{"fin": ..., "opcode": ..., "payload": ...}zurück, mit der Payload alsbytes.
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 read_exact und gibt {"fin": ..., "opcode": ..., "payload": ...} zurück:
Lies 2 Bytes und übergib sie an
parse_header_bytes;löse
aus, wenn der Frame maskiert ist; hole die Payload-Länge mit
read_length;löse
ValueErroraus, wenn die Länge übermax_sizeliegt, bevor du irgendein Payload-Byte anforderst;lies genau die Payload und gib sie als
byteszusammen mitfinund demzurü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 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