Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    text
    copied!<p><em>Note: This is some explanation and pseudocode as to how to implement a very trivial server that can handle incoming and outcoming WebSocket messages as per the definitive framing format. It does not include the handshaking process. Furthermore, this answer has been made for educational purposes; it is not a full-featured implementation.</em></p> <p><a href="http://tools.ietf.org/html/rfc6455" rel="noreferrer">Specification (RFC 6455)</a></p> <hr> <h2>Sending messages</h2> <p>(In other words, server &rarr; browser)</p> <p>The frames you're sending need to be formatted according to the WebSocket framing format. For sending messages, this format is as follows:</p> <ul> <li>one byte which contains the type of data (and some additional info which is out of scope for a trivial server)</li> <li>one byte which contains the length</li> <li>either two or eight bytes if the length does not fit in the second byte (the second byte is then a code saying how many bytes are used for the length)</li> <li>the actual (raw) data</li> </ul> <p>The first byte will be <code>1000 0001</code> (or <code>129</code>) for a text frame.</p> <p>The second byte has its first bit set to <code>0</code> because we're not encoding the data (encoding from server to client is not mandatory).</p> <p>It is necessary to determine the length of the raw data so as to send the length bytes correctly:</p> <ul> <li>if <code>0 &lt;= length &lt;= 125</code>, you don't need additional bytes</li> <li>if <code>126 &lt;= length &lt;= 65535</code>, you need two additional bytes and the second byte is <code>126</code></li> <li>if <code>length &gt;= 65536</code>, you need eight additional bytes, and the second byte is <code>127</code></li> </ul> <p>The length has to be sliced into separate bytes, which means you'll need to bit-shift to the right (with an amount of eight bits), and then only retain the last eight bits by doing <code>AND 1111 1111</code> (which is <code>255</code>).</p> <p>After the length byte(s) comes the raw data.</p> <p>This leads to the following pseudocode:</p> <pre><code>bytesFormatted[0] = 129 indexStartRawData = -1 // it doesn't matter what value is // set here - it will be set now: if bytesRaw.length &lt;= 125 bytesFormatted[1] = bytesRaw.length indexStartRawData = 2 else if bytesRaw.length &gt;= 126 and bytesRaw.length &lt;= 65535 bytesFormatted[1] = 126 bytesFormatted[2] = ( bytesRaw.length &gt;&gt; 8 ) AND 255 bytesFormatted[3] = ( bytesRaw.length ) AND 255 indexStartRawData = 4 else bytesFormatted[1] = 127 bytesFormatted[2] = ( bytesRaw.length &gt;&gt; 56 ) AND 255 bytesFormatted[3] = ( bytesRaw.length &gt;&gt; 48 ) AND 255 bytesFormatted[4] = ( bytesRaw.length &gt;&gt; 40 ) AND 255 bytesFormatted[5] = ( bytesRaw.length &gt;&gt; 32 ) AND 255 bytesFormatted[6] = ( bytesRaw.length &gt;&gt; 24 ) AND 255 bytesFormatted[7] = ( bytesRaw.length &gt;&gt; 16 ) AND 255 bytesFormatted[8] = ( bytesRaw.length &gt;&gt; 8 ) AND 255 bytesFormatted[9] = ( bytesRaw.length ) AND 255 indexStartRawData = 10 // put raw data at the correct index bytesFormatted.put(bytesRaw, indexStartRawData) // now send bytesFormatted (e.g. write it to the socket stream) </code></pre> <hr> <h2>Receiving messages</h2> <p>(In other words, browser &rarr; server)</p> <p>The frames you obtain are in the following format:</p> <ul> <li>one byte which contains the type of data</li> <li>one byte which contains the length</li> <li>either two or eight additional bytes if the length did not fit in the second byte</li> <li>four bytes which are the masks (= decoding keys)</li> <li>the actual data</li> </ul> <p>The first byte usually does not matter - if you're just sending text you are only using the text type. It will be <code>1000 0001</code> (or <code>129</code>) in that case.</p> <p>The second byte and the additional two or eight bytes need some parsing, because you need to know how many bytes are used for the length (you need to know where the real data starts). The length itself is usually not necessary since you have the data already.</p> <p>The first bit of the second byte is always <code>1</code> which means the data is masked (= encoded). Messages from the client to the server are always masked. You need to remove that first bit by doing <code>secondByte AND 0111 1111</code>. There are two cases in which the resulting byte does not represent the length because it did not fit in the second byte:</p> <ul> <li>a second byte of <code>0111 1110</code>, or <code>126</code>, means the following two bytes are used for the length</li> <li>a second byte of <code>0111 1111</code>, or <code>127</code>, means the following eight bytes are used for the length</li> </ul> <p>The four mask bytes are used for decoding the actual data that has been sent. The algorithm for decoding is as follows:</p> <pre><code>decodedByte = encodedByte XOR masks[encodedByteIndex MOD 4] </code></pre> <p>where <code>encodedByte</code> is the original byte in the data, <code>encodedByteIndex</code> is the index (offset) of the byte counting from the first byte <em>of the real data</em>, which has index <code>0</code>. <code>masks</code> is an array containing of the four mask bytes. </p> <p>This leads to the following pseudocode for decoding:</p> <pre><code>secondByte = bytes[1] length = secondByte AND 127 // may not be the actual length in the two special cases indexFirstMask = 2 // if not a special case if length == 126 // if a special case, change indexFirstMask indexFirstMask = 4 else if length == 127 // ditto indexFirstMask = 10 masks = bytes.slice(indexFirstMask, 4) // four bytes starting from indexFirstMask indexFirstDataByte = indexFirstMask + 4 // four bytes further decoded = new array decoded.length = bytes.length - indexFirstDataByte // length of real data for i = indexFirstDataByte, j = 0; i &lt; bytes.length; i++, j++ decoded[j] = bytes[i] XOR masks[j MOD 4] // now use "decoded" to interpret the received data </code></pre>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload