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?0001
, como afirma no cabeçalho dessa parte das especificações: "Opcode: 4 bits". O primeiro byte consiste em FIN, RSV1-3 e opcode. FIN é1
, RSV1-3 são os três0
e o opcode é0001
que soma1000 0001
para o primeiro byte. Veja também a arte na especificação que mostra como os bytes são divididos nas diferentes partes.Implementação Java (se houver)
Leitura: Cliente para Servidor
Escrita: Servidor para Cliente
fonte
Implementação de JavaScript:
fonte
2^31 - 1
.Implementação C #
Navegador -> Servidor
Servidor -> Navegador
fonte
test�c=ܝX[
em que "teste" é minha mensagem. De onde vem a outra parte?Resposta de pimvdb implementada em python:
Um exemplo de uso:
fonte
Além da função de codificação de quadro PHP, aqui segue uma função de decodificação:
Implementei essa e também outras funções em uma classe WebSocket PHP fácil de usar aqui .
fonte
Implementação de PHP:
fonte
Obrigado pela resposta, eu gostaria de adicionar a versão Python de hfern (acima) para incluir a função Sending se alguém estiver interessado.
Uso para leitura:
Uso para escrita:
fonte
Implementação em Go
Parte da codificação (servidor -> navegador)
Parte da decodificação (navegador -> servidor)
fonte
Clojure, a função de decodificação assume que o quadro é enviado como mapa de
{:data byte-array-buffer :size int-size-of-buffer}
, porque o tamanho real pode não ser o mesmo tamanho da matriz de bytes, dependendo do tamanho do bloco de seu fluxo de entrada.Código postado aqui: https://gist.github.com/viperscape/8918565
fonte
Implementação C ++ (não por mim) aqui . Observe que quando seus bytes estão acima de 65535, você precisa mudar com um valor longo, conforme mostrado aqui .
fonte