如何在服务器端发送和接收WebSocket消息?

  • 如何根据协议使用WebSocket在服务器端发送和接收消息?

  • 当我从浏览器向服务器发送数据时,为什么在服务器上看起来随机的字节? 它以某种方式编码的数据?

  • 如何在服务器→客户端和客户端→服务器方向上进行组帧?

注意:这是一些解释和伪代码,如何实现一个非常平凡的服务器,可以处理传入和传出的WebSocket消息按照最终的帧格式。 它不包括握手过程。 此外,这个答案是为了教育目的。 这不是一个全功能的实现。

规范(RFC 6455)


发送消息

(换句话说,服务器→浏览器)

您发送的帧需要根据WebSocket成帧格式进行格式化。 发送消息的格式如下:

  • 一个包含数据类型的字节(以及一些超出服务器范围的附加信息)
  • 一个包含长度的字节
  • 如果长度不适合第二个字节,则为两个或八个字节(第二个字节则是表示长度使用了多少个字节的代码)
  • 实际(原始)数据

对于文本框,第一个字节将是1000 0001 (或129 )。

第二个字节的第一个位设置为0因为我们没有编码数据(从服务器到客户端的编码不是强制性的)。

有必要确定原始数据的长度,以便正确地发送长度字节:

  • 如果0 <= length <= 125 ,则不需要额外的字节
  • 如果126 <= length <= 65535 ,则需要两个附加字节,第二个字节是126
  • 如果length >= 65536 ,则需要8个附加字节,第二个字节是127

长度必须分成不同的字节,这意味着你需要向右移位(数量为8位),然后通过执行AND 1111 1111 (即255 )只保留最后8位, 。

在长度字节(s)到达原始数据之后。

这导致以下伪代码:

 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 (eg write it to the socket stream) 

接收消息

(换句话说,浏览器→服务器)

您获取的帧格式如下:

  • 一个包含数据类型的字节
  • 一个包含长度的字节
  • 如果长度不适合第二个字节,则可以是两个或八个附加字节
  • 作为掩码的四个字节(=解码键)
  • 实际的数据

第一个字节通常无关紧要 – 如果您只是发送文本,则只能使用文本类型。 在这种情况下将是1000 0001 (或129 )。

第二个字节和额外的两个或八个字节需要一些解析,因为你需要知道有多少字节用于这个长度(你需要知道真正的数据在哪里开始)。 由于您已经拥有数据,所以长度本身通常是不必要的。

第二个字节的第一位始终为1 ,表示数据被屏蔽(=编码)。 从客户端到服务器的消息始终被屏蔽。 您需要通过执行secondByte AND 0111 1111来删除第一个位。 有两种情况下结果字节不能代表长度,因为它不适合第二个字节:

  • 0111 1110126的第二个字节表示以下两个字节用于长度
  • 0111 1111127的第二个字节表示以下八个字节用于长度

四个掩码字节用于解码已发送的实际数据。 解码算法如下:

 decodedByte = encodedByte XOR masks[encodedByteIndex MOD 4] 

其中encodedByte是数据中的原始字节, encodedByteIndex是从实际数据的第一个字节开始计数的索引(偏移量),索引为0masks是包含四个掩码字节的数组。

这导致下面的解码伪码:

 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 

Java实现(如果有的话)

阅读:客户端到服务器

  int len = 0; byte[] b = new byte[buffLenth]; //rawIn is a Socket.getInputStream(); while(true){ len = rawIn.read(b); if(len!=-1){ byte rLength = 0; int rMaskIndex = 2; int rDataStart = 0; //b[0] is always text in my case so no need to check; byte data = b[1]; byte op = (byte) 127; rLength = (byte) (data & op); if(rLength==(byte)126) rMaskIndex=4; if(rLength==(byte)127) rMaskIndex=10; byte[] masks = new byte[4]; int j=0; int i=0; for(i=rMaskIndex;i<(rMaskIndex+4);i++){ masks[j] = b[i]; j++; } rDataStart = rMaskIndex + 4; int messLen = len - rDataStart; byte[] message = new byte[messLen]; for(i=rDataStart, j=0; i<len; i++, j++){ message[j] = (byte) (b[i] ^ masks[j % 4]); } parseMessage(new String(message)); //parseMessage(new String(b)); b = new byte[buffLenth]; } } 

写作:服务器到客户端

 public void brodcast(String mess) throws IOException{ byte[] rawData = mess.getBytes(); int frameCount = 0; byte[] frame = new byte[10]; frame[0] = (byte) 129; if(rawData.length <= 125){ frame[1] = (byte) rawData.length; frameCount = 2; }else if(rawData.length >= 126 && rawData.length <= 65535){ frame[1] = (byte) 126; int len = rawData.length; frame[2] = (byte)((len >> 8 ) & (byte)255); frame[3] = (byte)(len & (byte)255); frameCount = 4; }else{ frame[1] = (byte) 127; int len = rawData.length; frame[2] = (byte)((len >> 56 ) & (byte)255); frame[3] = (byte)((len >> 48 ) & (byte)255); frame[4] = (byte)((len >> 40 ) & (byte)255); frame[5] = (byte)((len >> 32 ) & (byte)255); frame[6] = (byte)((len >> 24 ) & (byte)255); frame[7] = (byte)((len >> 16 ) & (byte)255); frame[8] = (byte)((len >> 8 ) & (byte)255); frame[9] = (byte)(len & (byte)255); frameCount = 10; } int bLength = frameCount + rawData.length; byte[] reply = new byte[bLength]; int bLim = 0; for(int i=0; i<frameCount;i++){ reply[bLim] = frame[i]; bLim++; } for(int i=0; i<rawData.length;i++){ reply[bLim] = rawData[i]; bLim++; } out.write(reply); out.flush(); } 

JavaScript实现:

 function encodeWebSocket(bytesRaw){ var bytesFormatted = new Array(); bytesFormatted[0] = 129; if (bytesRaw.length <= 125) { bytesFormatted[1] = bytesRaw.length; } else if (bytesRaw.length >= 126 && bytesRaw.length <= 65535) { bytesFormatted[1] = 126; bytesFormatted[2] = ( bytesRaw.length >> 8 ) & 255; bytesFormatted[3] = ( bytesRaw.length ) & 255; } else { bytesFormatted[1] = 127; bytesFormatted[2] = ( bytesRaw.length >> 56 ) & 255; bytesFormatted[3] = ( bytesRaw.length >> 48 ) & 255; bytesFormatted[4] = ( bytesRaw.length >> 40 ) & 255; bytesFormatted[5] = ( bytesRaw.length >> 32 ) & 255; bytesFormatted[6] = ( bytesRaw.length >> 24 ) & 255; bytesFormatted[7] = ( bytesRaw.length >> 16 ) & 255; bytesFormatted[8] = ( bytesRaw.length >> 8 ) & 255; bytesFormatted[9] = ( bytesRaw.length ) & 255; } for (var i = 0; i < bytesRaw.length; i++){ bytesFormatted.push(bytesRaw.charCodeAt(i)); } return bytesFormatted; } function decodeWebSocket (data){ var datalength = data[1] & 127; var indexFirstMask = 2; if (datalength == 126) { indexFirstMask = 4; } else if (datalength == 127) { indexFirstMask = 10; } var masks = data.slice(indexFirstMask,indexFirstMask + 4); var i = indexFirstMask + 4; var index = 0; var output = ""; while (i < data.length) { output += String.fromCharCode(data[i++] ^ masks[index++ % 4]); } return output; } 

C#实现

浏览器 – >服务器

  private String DecodeMessage(Byte[] bytes) { String incomingData = String.Empty; Byte secondByte = bytes[1]; Int32 dataLength = secondByte & 127; Int32 indexFirstMask = 2; if (dataLength == 126) indexFirstMask = 4; else if (dataLength == 127) indexFirstMask = 10; IEnumerable<Byte> keys = bytes.Skip(indexFirstMask).Take(4); Int32 indexFirstDataByte = indexFirstMask + 4; Byte[] decoded = new Byte[bytes.Length - indexFirstDataByte]; for (Int32 i = indexFirstDataByte, j = 0; i < bytes.Length; i++, j++) { decoded[j] = (Byte)(bytes[i] ^ keys.ElementAt(j % 4)); } return incomingData = Encoding.UTF8.GetString(decoded, 0, decoded.Length); } 

服务器 – >浏览器

  private static Byte[] EncodeMessageToSend(String message) { Byte[] response; Byte[] bytesRaw = Encoding.UTF8.GetBytes(message); Byte[] frame = new Byte[10]; Int32 indexStartRawData = -1; Int32 length = bytesRaw.Length; frame[0] = (Byte)129; if (length <= 125) { frame[1] = (Byte)length; indexStartRawData = 2; } else if (length >= 126 && length <= 65535) { frame[1] = (Byte)126; frame[2] = (Byte)((length >> 8) & 255); frame[3] = (Byte)(length & 255); indexStartRawData = 4; } else { frame[1] = (Byte)127; frame[2] = (Byte)((length >> 56) & 255); frame[3] = (Byte)((length >> 48) & 255); frame[4] = (Byte)((length >> 40) & 255); frame[5] = (Byte)((length >> 32) & 255); frame[6] = (Byte)((length >> 24) & 255); frame[7] = (Byte)((length >> 16) & 255); frame[8] = (Byte)((length >> 8) & 255); frame[9] = (Byte)(length & 255); indexStartRawData = 10; } response = new Byte[indexStartRawData + length]; Int32 i, reponseIdx = 0; //Add the frame bytes to the reponse for (i = 0; i < indexStartRawData; i++) { response[reponseIdx] = frame[i]; reponseIdx++; } //Add the data bytes to the response for (i = 0; i < length; i++) { response[reponseIdx] = bytesRaw[i]; reponseIdx++; } return response; } 

pimvdb的答案在Python中实现:

 def DecodedCharArrayFromByteStreamIn(stringStreamIn): #turn string values into opererable numeric byte values byteArray = [ord(character) for character in stringStreamIn] datalength = byteArray[1] & 127 indexFirstMask = 2 if datalength == 126: indexFirstMask = 4 elif datalength == 127: indexFirstMask = 10 masks = [m for m in byteArray[indexFirstMask : indexFirstMask+4]] indexFirstDataByte = indexFirstMask + 4 decodedChars = [] i = indexFirstDataByte j = 0 while i < len(byteArray): decodedChars.append( chr(byteArray[i] ^ masks[j % 4]) ) i += 1 j += 1 return decodedChars 

用法示例:

 fromclient = '\x81\x8c\xff\xb8\xbd\xbd\xb7\xdd\xd1\xd1\x90\x98\xea\xd2\x8d\xd4\xd9\x9c' # this looks like "?ŒOÇ¿¢gÓ ç\Ð=«ož" in unicode, received by server print DecodedCharArrayFromByteStreamIn(fromclient) # ['H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd', '!'] 

除了PHP框架编码功能之外,下面还有一个解码功能:

 function Decode($M){ $M = array_map("ord", str_split($M)); $L = $M[1] AND 127; if ($L == 126) $iFM = 4; else if ($L == 127) $iFM = 10; else $iFM = 2; $Masks = array_slice($M, $iFM, 4); $Out = ""; for ($i = $iFM + 4, $j = 0; $i < count($M); $i++, $j++ ) { $Out .= chr($M[$i] ^ $Masks[$j % 4]); } return $Out; } 

我已经在这里实现了这个和其他的功能在一个易于使用的WebSocket PHP类。

PHP实现:

 function encode($message) { $length = strlen($message); $bytesHeader = []; $bytesHeader[0] = 129; // 0x1 text frame (FIN + opcode) if ($length <= 125) { $bytesHeader[1] = $length; } else if ($length >= 126 && $length <= 65535) { $bytesHeader[1] = 126; $bytesHeader[2] = ( $length >> 8 ) & 255; $bytesHeader[3] = ( $length ) & 255; } else { $bytesHeader[1] = 127; $bytesHeader[2] = ( $length >> 56 ) & 255; $bytesHeader[3] = ( $length >> 48 ) & 255; $bytesHeader[4] = ( $length >> 40 ) & 255; $bytesHeader[5] = ( $length >> 32 ) & 255; $bytesHeader[6] = ( $length >> 24 ) & 255; $bytesHeader[7] = ( $length >> 16 ) & 255; $bytesHeader[8] = ( $length >> 8 ) & 255; $bytesHeader[9] = ( $length ) & 255; } $str = implode(array_map("chr", $bytesHeader)) . $message; return $str; } 

在Go中执行

编码部分(服务器 – >浏览器)

 func encode (message string) (result []byte) { rawBytes := []byte(message) var idxData int length := byte(len(rawBytes)) if len(rawBytes) <= 125 { //one byte to store data length result = make([]byte, len(rawBytes) + 2) result[1] = length idxData = 2 } else if len(rawBytes) >= 126 && len(rawBytes) <= 65535 { //two bytes to store data length result = make([]byte, len(rawBytes) + 4) result[1] = 126 //extra storage needed result[2] = ( length >> 8 ) & 255 result[3] = ( length ) & 255 idxData = 4 } else { result = make([]byte, len(rawBytes) + 10) result[1] = 127 result[2] = ( length >> 56 ) & 255 result[3] = ( length >> 48 ) & 255 result[4] = ( length >> 40 ) & 255 result[5] = ( length >> 32 ) & 255 result[6] = ( length >> 24 ) & 255 result[7] = ( length >> 16 ) & 255 result[8] = ( length >> 8 ) & 255 result[9] = ( length ) & 255 idxData = 10 } result[0] = 129 //only text is supported // put raw data at the correct index for i, b := range rawBytes { result[idxData + i] = b } return } 

解码部分(浏览器 – >服务器)

 func decode (rawBytes []byte) string { var idxMask int if rawBytes[1] == 126 { idxMask = 4 } else if rawBytes[1] == 127 { idxMask = 10 } else { idxMask = 2 } masks := rawBytes[idxMask:idxMask + 4] data := rawBytes[idxMask + 4:len(rawBytes)] decoded := make([]byte, len(rawBytes) - idxMask + 4) for i, b := range data { decoded[i] = b ^ masks[i % 4] } return string(decoded) } 

Clojure认为,解码函数假定帧是作为{:data byte-array-buffer :size int-size-of-buffer}映射发送的,因为根据块的大小,实际大小可能与字节数组的大小不一样你的输入流。

代码发布在这里: https : //gist.github.com/viperscape/8918565

 (defn ws-decode [frame] "decodes websocket frame" (let [data (:data frame) dlen (bit-and (second data) 127) mstart (if (== dlen 127) 10 (if (== dlen 126) 4 2)) mask (drop 2 (take (+ mstart 4) data)) msg (make-array Byte/TYPE (- (:size frame) (+ mstart 4)))] (loop [i (+ mstart 4), j 0] (aset-byte msg j (byte (bit-xor (nth data i) (nth mask (mod j 4))))) (if (< i (dec(:size frame))) (recur (inc i) (inc j)))) msg)) (defn ws-encode [data] "takes in bytes, return websocket frame" (let [len (count data) blen (if (> len 65535) 10 (if (> len 125) 4 2)) buf (make-array Byte/TYPE (+ len blen)) _ (aset-byte buf 0 -127) ;;(bit-or (unchecked-byte 0x80) (unchecked-byte 0x1) _ (if (= 2 blen) (aset-byte buf 1 len) ;;mask 0, len (do (dorun(map #(aset-byte buf %1 (unchecked-byte (bit-and (bit-shift-right len (*(- %2 2) 8)) 255))) (range 2 blen) (into ()(range 2 blen)))) (aset-byte buf 1 (if (> blen 4) 127 126)))) _ (System/arraycopy data 0 buf blen len)] buf)) 

谢谢你的回答,我想添加到hfern (上面)的Python版本,包括发送功能,如果任何人有兴趣。

 def DecodedWebsockRecieve(stringStreamIn): byteArray = stringStreamIn datalength = byteArray[1] & 127 indexFirstMask = 2 if datalength == 126: indexFirstMask = 4 elif datalength == 127: indexFirstMask = 10 masks = [m for m in byteArray[indexFirstMask : indexFirstMask+4]] indexFirstDataByte = indexFirstMask + 4 decodedChars = [] i = indexFirstDataByte j = 0 while i < len(byteArray): decodedChars.append( chr(byteArray[i] ^ masks[j % 4]) ) i += 1 j += 1 return ''.join(decodedChars) def EncodeWebSockSend(socket,data): bytesFormatted = [] bytesFormatted.append(129) bytesRaw = data.encode() bytesLength = len(bytesRaw) if bytesLength <= 125 : bytesFormatted.append(bytesLength) elif bytesLength >= 126 and bytesLength <= 65535 : bytesFormatted.append(126) bytesFormatted.append( ( bytesLength >> 8 ) & 255 ) bytesFormatted.append( bytesLength & 255 ) else : bytesFormatted.append( 127 ) bytesFormatted.append( ( bytesLength >> 56 ) & 255 ) bytesFormatted.append( ( bytesLength >> 48 ) & 255 ) bytesFormatted.append( ( bytesLength >> 40 ) & 255 ) bytesFormatted.append( ( bytesLength >> 32 ) & 255 ) bytesFormatted.append( ( bytesLength >> 24 ) & 255 ) bytesFormatted.append( ( bytesLength >> 16 ) & 255 ) bytesFormatted.append( ( bytesLength >> 8 ) & 255 ) bytesFormatted.append( bytesLength & 255 ) bytesFormatted = bytes(bytesFormatted) bytesFormatted = bytesFormatted + bytesRaw socket.send(bytesFormatted) 

用法阅读:

 bufSize = 1024 read = DecodedWebsockRecieve(socket.recv(bufSize)) 

用法写作:

 EncodeWebSockSend(sock,"hellooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo") 

C ++实现(不是由我) 在这里 。 请注意,当您的字节超过65535时,您需要按照此处所示移动一个长整型值。