...one of the most highly
regarded and expertly designed C++ library projects in the
world.
— Herb Sutter and Andrei
Alexandrescu, C++
Coding Standards
Once a websocket session is established, messages can be sent unsolicited by either peer at any time. A message is made up of one or more messages frames. Each frame is prefixed with the size of the payload in bytes, followed by the data. A frame also contains a flag (called 'fin') indicating whether or not it is the last frame of the message. When a message is made up from only one frame, it is possible to know immediately what the size of the message will be. Otherwise, the total size of the message can only be determined once the last frame is received.
The boundaries between frames of a multi-frame message are not not considered part of the message. Intermediaries such as proxies which forward the websocket traffic are free to "reframe" (split frames and combine them) the message in arbitrary ways. These intermediaries include Beast, which can reframe messages automatically in some cases depending on the options set on the stream.
Caution | |
---|---|
An algorithm should never depend on the way that incoming or outgoing messages are split up into frames. |
Messages can be either text or binary. A message sent as text must contain consist of valid utf8, while a message sent as binary may contain arbitrary data. In addition to message frames, websocket provides control frames in the form of ping, pong, and close messages which have a small upper limit on their payload size. Depending on how a message is framed, control frames may have more opportunities to be sent in-between.
These stream members are used to write websocket messages:
Table 1.33. WebSocket Stream Write Operations
Function |
Description |
---|---|
Send a buffer sequence as a complete message. |
|
Send a buffer sequence as part of a message. |
This example shows how to send a buffer sequence as a complete message.
net::const_buffer b("Hello, world!", 13); // This sets all outgoing messages to be sent as text. // Text messages must contain valid utf8, this is checked // when reading but not when writing. ws.text(true); // Write the buffer as text ws.write(b);
The same message could be sent in two or more frames thusly.
Table 1.34. WebSocket Stream Read Operations
Function |
Description |
---|---|
Read a complete message into a DynamicBuffer. |
|
Read part of a message into a DynamicBuffer. |
|
Read part of a message into a MutableBufferSequence. |
After the WebSocket handshake is accomplished, callers may send and receive messages using the message oriented interface. This interface requires that all of the buffers representing the message are known ahead of time:
// This DynamicBuffer will hold the received message flat_buffer buffer; // Read a complete message into the buffer's input area ws.read(buffer); // Set text mode if the received message was also text, // otherwise binary mode will be set. ws.text(ws.got_text()); // Echo the received message back to the peer. If the received // message was in text mode, the echoed message will also be // in text mode, otherwise it will be in binary mode. ws.write(buffer.data()); // Discard all of the bytes stored in the dynamic buffer, // otherwise the next call to read will append to the existing // data instead of building a fresh message. buffer.consume(buffer.size());
Important | |
---|---|
|
Some use-cases make it impractical or impossible to buffer the entire message ahead of time:
For these cases, the partial data oriented interface may be used. This example reads and echoes a complete message using this interface:
// This DynamicBuffer will hold the received message multi_buffer buffer; // Read the next message in pieces do { // Append up to 512 bytes of the message into the buffer ws.read_some(buffer, 512); } while(! ws.is_message_done()); // At this point we have a complete message in the buffer, now echo it // The echoed message will be sent in binary mode if the received // message was in binary mode, otherwise we will send in text mode. ws.binary(ws.got_binary()); // This buffer adaptor allows us to iterate through buffer in pieces buffers_suffix<multi_buffer::const_buffers_type> cb{buffer.data()}; // Echo the received message in pieces. // This will cause the message to be broken up into multiple frames. for(;;) { if(buffer_bytes(cb) > 512) { // There are more than 512 bytes left to send, just // send the next 512 bytes. The value `false` informs // the stream that the message is not complete. ws.write_some(false, buffers_prefix(512, cb)); // This efficiently discards data from the adaptor by // simply ignoring it, but does not actually affect the // underlying dynamic buffer. cb.consume(512); } else { // Only 512 bytes or less remain, so write the whole // thing and inform the stream that this piece represents // the end of the message by passing `true`. ws.write_some(true, cb); break; } } // Discard all of the bytes stored in the dynamic buffer, // otherwise the next call to read will append to the existing // data instead of building a fresh message. buffer.consume(buffer.size());