0%

Frames, byte voor byte · oefening

Een frame uit een stroom decoderen

encode_frame zet een bericht om in bytes. De ontvangende kant heeft het omgekeerde nodig: bytes van de verbinding nemen en het bericht terugkrijgen. Het probleem is er een dat je in Networks with Python hoofdstuk 6 oploste. levert een stroom, geen pakketjes, dus een kan heel aankomen, op elke plek in stukken geknipt, of vastgeplakt aan het begin van het volgende frame. Een decoder die aanneemt dat één recv één frame bevat, werkt op een rustige testmachine en faalt op een druk netwerk.

Het antwoord uit Networks geldt nog steeds: vraag de verbinding nooit “wat heb je?”, vraag haar “geef me precies n bytes”. Een frame vertelt je, stap voor stap, om hoeveel bytes je daarna moet vragen.

read_exact als parameter

decode_frame(read_exact, *, max_size=MAX_SIZE) krijgt zijn bytes alleen via read_exact, dezelfde soort die read_length aanneemt. Het ziet nooit een . Daardoor is het makkelijk te testen met een stand-in, en het hoofdstuk levert er een in fakes.py. Open het en lees de twee functies één keer:

  • pieces(data, size) knipt data in een stukken van hooguit size bytes, zoals een drukke verbinding ze zou kunnen afleveren.

  • reader_over(chunks) geeft een functie read_exact(n) terug die stukken op volgorde uit de lijst neemt, ze in een buffer houdt totdat hij n bytes heeft, precies die teruggeeft, en de rest bewaart voor de volgende aanroep. Als de stukken eerder op zijn, werpt hij ConnectionError op, zoals een gesloten verbinding zou doen.

Dus fakes.reader_over(fakes.pieces(frame, 1)) is een read_exact over een verbinding die frame één byte per keer aflevert. Dat is wat frames_demo.py je decoder geeft in zijn sectie voor les 5. De stukken van één byte bewijzen alleen dat je decoder om exacte aantallen vraagt en nooit aanneemt dat er meer is aangekomen; de stukken tot die aantallen verzamelen is de taak van de reader, dezelfde ontvangstbuffer die je in Networks schreef.

Op volgorde

Een decoder leest een frame in de volgorde waarin de header het beschrijft:

  1. read_exact(2), de twee bytes die elk frame heeft, die parse_header_bytes leest;

  2. read_length(...), dat nog 0, 2 of 8 bytes leest, afhankelijk van de lengte van 7 bits;

  3. read_exact(length), de payload.

Elke stap heeft de vorige nodig om te weten om hoeveel hij moet vragen, dus de volgorde ligt vast. Om meer dan één stap tegelijk vragen, zoals 14 bytes om de langste header te dekken, zou eeuwig wachten op een frame van 7 bytes zoals het “Hello” van de RFC, of het begin van het volgende frame opslokken. Precies één frame lezen betekent ook dat een tweede frame er vlak achter intact in de buffer blijft, voor de volgende aanroep.

decode_frame heeft de eerste twee bytes 82 7e gelezen. Waar vraagt het daarna om?

Weigeren vóór het lezen

max_size is de grootste payload die deze decoder accepteert, standaard MAX_SIZE, een miljoen bytes. Networks with Python hoofdstuk 8 legde dezelfde soort limiet op zijn frames, om dezelfde reden. Met de lengte van 64 bits kan een peer een payload van miljarden bytes aankondigen, en een programma dat die probeerde te lezen, zou wachten op bytes die misschien nooit komen en meer geheugen vasthouden dan het heeft.

De header zegt al hoe groot de payload is. Vergelijk de lengte dus met max_size zodra read_length iets teruggeeft, en werp op voordat je read_exact om ook maar één payloadbyte vraagt. Controleren nadat de payload is aangekomen, is te laat: het wachten en het geheugen zijn dan al besteed.

Waarom vergelijkt decode_frame de lengte met max_size voordat het de payload leest?

Een gemaskeerd frame

Nog één weigering. Sectie 5.7 van de RFC toont “Hello” zoals een client het zou sturen, gemaskeerd:

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

De tweede byte, 85, heeft MASK gezet, en de vier bytes erna zijn de . Een server maskeert zijn frames nooit, dus een client die een gemaskeerd frame ontvangt, weet dat er iets mis is en weigert het. De decoder van dit hoofdstuk is de kant van de client, en hij kan nog niet demaskeren, dus hij werpt ValueError op voor elk frame waarvan het veld "masked" True is. Hoofdstuk 4 leert maskeren en geeft de decoder ook de kant van de server.

Je opdracht

Schrijf in ws_frames.py decode_frame(read_exact, *, max_size=MAX_SIZE):

  1. lees 2 bytes en geef ze aan parse_header_bytes, dat ValueError opwerpt voor gereserveerde bits en ;

  2. werp ValueError op als het frame gemaskeerd is;

  3. haal de payloadlengte op met read_length;

  4. werp ValueError op als de lengte groter is dan max_size, voordat je enige payload leest;

  5. lees de payload en geef {"fin": ..., "opcode": ..., "payload": ...} terug, met de payload als bytes.

Vang ConnectionError niet. Als de peer midden in een frame sluit, moet de aanroeper dat weten.

Druk op Run. De sectie van les 5 decodeert het “Hello” van de RFC, dat één byte per keer aan je decoder wordt gegeven:

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

Druk daarna op Submit. De controle geeft je decoder een stand-in-read_exact die elk aantal noteert waarom gevraagd wordt. Ze decodeert de frames van de RFC, een fragment, binaire frames van 256 en 65536 bytes met payloads die je nog niet hebt gezien, en twee frames die direct na elkaar worden gestuurd; ze controleert dat een te groot frame wordt geweigerd voordat om de payload gevraagd wordt, dat een gemaskeerd frame, een gereserveerde opcode en een niet-minimale lengte ValueError opwerpen, en dat een sluiting midden in een frame de aanroeper bereikt als ConnectionError.

De leercodec is af voor niet-gemaskeerde frames. Het project dat dit hoofdstuk afsluit, schrijft de tests die hem aan de bytes van de RFC houden.

Opdracht

Schrijf decode_frame(read_exact, *, max_size=MAX_SIZE) in ws_frames.py. Het leest één niet-gemaskeerd via read_exact en geeft {"fin": ..., "opcode": ..., "payload": ...} terug:

  1. lees 2 bytes en geef ze aan parse_header_bytes;

  2. werp op als het frame gemaskeerd is;

  3. haal de payloadlengte op met read_length;

  4. werp ValueError op als de lengte groter is dan max_size, voordat je om enige payloadbyte vraagt;

  5. lees precies de payload, en geef hem terug als bytes met fin en de .

Laat een ConnectionError uit read_exact de aanroeper bereiken.

Druk op Run: de sectie van les 5 moet {'fin': True, 'opcode': 1, 'payload': b'Hello'} afdrukken. Druk daarna op Submit. De controle geeft decode_frame een stand-in-read_exact die elk aantal noteert waarom gevraagd wordt. Ze verwacht dat de tekst- en pingframes van de RFC, een fragment, een leeg close-frame, binaire frames van 256 en 65536 bytes, en twee frames achter elkaar worden gedecodeerd, met de verzoeken 2, dan de lengtebytes, dan de payload, en niets gelezen voorbij het frame. Ze verwacht ValueError voor een frame groter dan max_size zonder verzoek om zijn payload, een gemaskeerd frame, een gereserveerde opcode en een niet-minimale lengte, en ConnectionError als de bytes midden in een frame ophouden. Je mag extra sleutels bevatten.