Maskers en controleframes · oefening
Ping, pong en andere controleframes
Je codec leest en schrijft nu gemaskeerde en niet-gemaskeerde decode_frame precies zoals een tekstbericht. Deze drie zijn anders: het zijn controleframes, die de verbinding zelf beheren in plaats van de berichten van de applicatie te dragen, en RFC 6455 houdt ze aan strengere regels.
De bovenste helft van de opcodetabel
Hoofdstuk 3 verdeelde de zestien opcodes in twee groepen. De onderste helft, 0x0 tot en met 0x7, is voor data: vervolg, tekst, binair, en vijf gereserveerde 0x8 tot en met 0xF, is voor controle:
| Opcode | Frame |
|---|---|
0x8 | close |
0x9 | ping |
0xA | pong |
0xB tot en met 0xF | gereserveerd voor toekomstige controleframes |
Elke opcode in de bovenste helft heeft de bit 0x8 gezet, en geen enkele in de onderste helft, dus “is dit een controleframe?” is één vergelijking: de opcode is 8 of meer. parse_header_bytes weigert de gereserveerde opcodes al, dus in de praktijk is een controleframe een close, een ping of een pong.
Drie regels voor controleframes
RFC 6455 sectie 5.5 geeft controleframes drie regels die dataframes niet hebben:
Hooguit 125 bytes payload. Een controleframe gebruikt dus altijd de lengte van 7 bits; de markeringen 126 en 127 verschijnen er nooit in.
Nooit gefragmenteerd. Een controleframe heeft altijd FIN gezet. Les 5 laat zien wat fragmenten zijn; een controleframe is er nooit een.
Toegestaan tussen fragmenten. Een controleframe mag midden in een bericht aankomen dat over meerdere frames is verdeeld. Les 5 laat zien waarom dat ertoe doet.
De eerste twee kun je alleen uit de eerste twee bytes controleren. Byte 0 bevat FIN en de opcode, en byte 1 bevat de lengte van 7 bits, die alleen boven 125 ligt als het een van de markeringen is. Dus kan decode_frame een controleframe dat een van beide regels breekt direct na parse_header_bytes weigeren, voordat het nog iets leest. Net als bij de groottelimiet in hoofdstuk 3 betekent weigeren op basis van de header dat een peer je programma niet kan laten wachten op, of laten vasthouden, een payload die het toch weigert. De encoder volgt dezelfde regels, zodat je eigen code nooit per ongeluk zo’n frame kan bouwen.
Een frame begint met 89 7e 00 c8. Wat doet de decoder?
Ping en pong
Een ping vraagt de andere kant “ben je er nog?”. De andere kant antwoordt met een pong die precies dezelfde payload draagt, de applicatiedata van de ping (secties 5.5.2 en 5.5.3). De RFC zegt dat de ontvanger moet antwoorden “zo snel als praktisch mogelijk is”; ze belooft geen bepaalde tijd. Sectie 5.7 drukt een ping af met “Hello” en de gemaskeerde pong die een client terugstuurt met de sleutel 37 fa 21 3d, de eerste en de laatste regel hieronder. De middelste regel staat niet in de RFC: het is de niet-gemaskeerde pong die een server zou sturen, berekend met dezelfde regels:
89 05 48 65 6c 6c 6f
8a 05 48 65 6c 6c 6f
8a 85 37 fa 21 3d 7f 9f 4d 51 58
Beide kanten mogen een ping sturen, en de payload kiest de afzender, vaak leeg. De RFC staat ook een pong toe waar niemand om vroeg, een ongevraagde pong, die werkt als een eenrichtings-“ik ben er nog” en geen antwoord nodig heeft. Hoofdstuk 8 gebruikt pings om een peer op te merken die stil is gevallen; hier beantwoord je ze alleen.
Je client ontvangt een pong waar hij nooit om vroeg. Wat moet hij doen?
Wie de pong maskeert
Een pong is een frame zoals elk ander, dus de richtingsregel geldt ervoor. De pong van een client is gemaskeerd, zoals elk clientframe, en de pong van een server niet. Daarom neemt reply_to_ping(frame, *, mask_key=None) een mask_key aan en geeft die door aan encode_frame: een server roept het aan zonder sleutel, en een client met een nieuwe. Het frame is de decode_frame teruggeeft, dus de payload van de pong is frame["payload"], en een frame waarvan de opcode geen ping is, heeft niets om te beantwoorden.
Wat drukt dit af?
print(0x89 & 0x0F, (0x8A & 0x0F) >= 8)
Je opdracht
In ws_frames.py:
Schrijf
is_control(opcode):Truevoor een opcode van 8 of meer,Falsedaaronder.Schrijf
reply_to_ping(frame, *, mask_key=None): werpop tenzij frame["opcode"]OP_PINGis, en geef daarnaencode_frame(OP_PONG, frame["payload"], mask_key=mask_key)terug.Werp in
decode_frame, direct na de richtingsregel,ValueErrorop voor een controleframe waarvan de lengte van 7 bits groter is dan 125 of waarvan de FIN-bit uit staat. De controle moet komen voordat de payload gelezen wordt.Werp in
encode_frameValueErrorop voor een controleframe met meer dan 125 bytes payload of metfin=False.
Dataframes houden hun oude regels: een tekstframe van 126 bytes, of een met FIN uit, codeert en decodeert nog steeds.
Druk op Run. De sectie van les 3 decodeert de ping van de RFC zoals een client hem leest, en beantwoordt hem daarna zoals een client moet, met je reply_to_ping en de sleutel van de RFC. Het resultaat is de gemaskeerde pong van de RFC:
Lesson 3: the client's pong for the server's ping
8a 85 37 fa 21 3d 7f 9f 4d 51 58
Lesson 4: close 1000 and back
not written yet
Druk daarna op Submit. De controle roept is_control aan voor alle zestien opcodes. Ze decodeert pings en pongs van maximaal 125 bytes, gemaskeerd en niet gemaskeerd, in één keer afgeleverd, één byte en twee bytes per keer, en verwacht ValueError voor een ping of pong die meer aankondigt, en voor een ping of close met FIN uit, zonder dat de payload gelezen wordt. Ze vraagt encode_frame om controleframes die de regels breken, beantwoordt pings die je nog niet hebt gezien met en zonder sleutel, en verwacht ValueError van reply_to_ping voor een pong, een tekstframe en een close. Het close-frame is het enige controleframe waarvan de payload iets betekent; de volgende les leest het.
Opdracht
In ws_frames.py:
Schrijf
is_control(opcode):Truevoor eenvan 8 of meer (close, , pong en de gereserveerde controle-opcodes), Falsedaaronder.Schrijf
reply_to_ping(frame, *, mask_key=None):frameis eenzoals decode_framehem teruggeeft. Werpop tenzij de opcode ervan OP_PINGis; geef andersencode_frame(OP_PONG, frame["payload"], mask_key=mask_key)terug.Werp in
decode_frameValueErrorop voor een controleframe waarvan de lengte van 7 bits groter is dan 125 of waarvan de FIN-bit uit staat, voordat je de payload leest.Werp in
encode_frameValueErrorop voor een controleframe met meer dan 125 payloadbytes offin=False.
Druk op Run: de sectie van les 3 moet 8a 85 37 fa 21 3d 7f 9f 4d 51 58 afdrukken. Druk daarna op Submit. De controle roept is_control aan voor elke opcode van 0 tot en met 15; decodeert pings en pongs van maximaal 125 bytes, gemaskeerd en niet gemaskeerd, in één keer, één byte en twee bytes per keer; verwacht ValueError zonder dat de payload gelezen wordt voor een ping of pong die meer dan 125 bytes aankondigt en een ping of close met FIN uit; verwacht dat tekstframes van 126 bytes of met FIN uit blijven werken; verwacht ValueError van encode_frame voor een ping of pong van 126 bytes en een close met fin=False; en beantwoordt de ping van de RFC, een lege ping en een onbekende ping van 125 bytes met en zonder sleutel, en verwacht ValueError voor een pong, een tekstframe of een close.