Nota: Esta é uma explicação e pseudocódigo sobre como implementar um servidor muito trivial que pode lidar com mensagens WebSocket de entrada e saída de acordo com o formato de enquadramento definitivo. Não inclui o processo de handshaking. Além disso, essa resposta foi feita para fins educacionais; não é uma implementação completa.
Especificação (RFC 6455)
Enviando mensagens
(Em outras palavras, servidor → navegador)
Os frames que você está enviando precisam ser formatados de acordo com o formato de frame do WebSocket. Para enviar mensagens, este formato é o seguinte:
- um byte que contém o tipo de dados (e algumas informações adicionais que estão fora do escopo de um servidor comum)
- um byte que contém o comprimento
- dois ou oito bytes se o comprimento não couber no segundo byte (o segundo byte é um código que diz quantos bytes são usados para o comprimento)
- os dados reais (brutos)
O primeiro byte será 1000 0001
(ou 129
) para um quadro de texto.
O segundo byte tem seu primeiro bit definido como 0
porque não estamos codificando os dados (a codificação do servidor para o cliente não é obrigatória).
É necessário determinar o comprimento dos dados brutos para enviar os bytes de comprimento corretamente:
- se
0 <= length <= 125
você não precisa de bytes adicionais
- se
126 <= length <= 65535
, você precisa de dois bytes adicionais e o segundo byte é126
- se
length >= 65536
, você precisa de oito bytes adicionais, e o segundo byte é127
O comprimento deve ser dividido em bytes separados, o que significa que você precisará deslocar os bits para a direita (com uma quantidade de oito bits) e, então, reter apenas os últimos oito bits AND 1111 1111
(que é 255
).
Após o (s) byte (s) de comprimento, vêm os dados brutos.
Isso leva ao seguinte pseudocódigo:
bytesFormatted[0] = 129
indexStartRawData = -1 // it doesn't matter what value is
// set here - it will be set now:
if bytesRaw.length <= 125
bytesFormatted[1] = bytesRaw.length
indexStartRawData = 2
else if bytesRaw.length >= 126 and bytesRaw.length <= 65535
bytesFormatted[1] = 126
bytesFormatted[2] = ( bytesRaw.length >> 8 ) AND 255
bytesFormatted[3] = ( bytesRaw.length ) AND 255
indexStartRawData = 4
else
bytesFormatted[1] = 127
bytesFormatted[2] = ( bytesRaw.length >> 56 ) AND 255
bytesFormatted[3] = ( bytesRaw.length >> 48 ) AND 255
bytesFormatted[4] = ( bytesRaw.length >> 40 ) AND 255
bytesFormatted[5] = ( bytesRaw.length >> 32 ) AND 255
bytesFormatted[6] = ( bytesRaw.length >> 24 ) AND 255
bytesFormatted[7] = ( bytesRaw.length >> 16 ) AND 255
bytesFormatted[8] = ( bytesRaw.length >> 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)
Recebendo mensagens
(Em outras palavras, navegador → servidor)
Os quadros que você obtém estão no seguinte formato:
- um byte que contém o tipo de dados
- um byte que contém o comprimento
- dois ou oito bytes adicionais se o comprimento não couber no segundo byte
- quatro bytes que são as máscaras (= chaves de decodificação)
- os dados reais
O primeiro byte geralmente não importa - se você está apenas enviando texto, está usando apenas o tipo de texto. Será 1000 0001
(ou 129
) nesse caso.
O segundo byte e os dois ou oito bytes adicionais precisam de alguma análise, porque você precisa saber quantos bytes são usados para o comprimento (você precisa saber onde os dados reais começam). O comprimento em si geralmente não é necessário, pois você já tem os dados.
O primeiro bit do segundo byte é sempre, o 1
que significa que os dados são mascarados (= codificados). As mensagens do cliente para o servidor são sempre mascaradas. Você precisa remover esse primeiro bit fazendo secondByte AND 0111 1111
. Existem dois casos em que o byte resultante não representa o comprimento porque não cabia no segundo byte:
- um segundo byte de
0111 1110
, ou 126
, significa que os dois bytes seguintes são usados para o comprimento
- um segundo byte de
0111 1111
, ou 127
, significa que os oito bytes seguintes são usados para o comprimento
Os quatro bytes de máscara são usados para decodificar os dados reais que foram enviados. O algoritmo de decodificação é o seguinte:
decodedByte = encodedByte XOR masks[encodedByteIndex MOD 4]
onde encodedByte
é o byte original nos dados, encodedByteIndex
é o índice (deslocamento) do byte contando a partir do primeiro byte dos dados reais , que possui índice 0
. masks
é uma matriz contendo os quatro bytes da máscara.
Isso leva ao seguinte pseudocódigo para decodificação:
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 < bytes.length; i++, j++
decoded[j] = bytes[i] XOR masks[j MOD 4]
// now use "decoded" to interpret the received data
1000 0001
(129) para um quadro de texto? A especificação diz diz:%x1 denotes a text frame
. Portanto, deve ser0000 0001
(0x01
) ou?