websocket接收消息再发送会发送两次

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了websocket接收消息再发送会发送两次相关的知识,希望对你有一定的参考价值。

你好,当两个方法有可能同时被执行,当A或者B方法遍历到某一个session并且调用sendMessage发送消息的时候,另外一个方法也正好也在使用相同的session发送另外一个消息(同一个session消息发送冲突了,也就是说同一个时刻,多个线程向一个socket session写数据冲突了),就会报上面的异常。

解决方法:

使用java 关键字 synchronized 给 session 加锁,保证同一时刻只能有一个线程执行当前 session 发送消息。

public void pushMessage(String userId, String message)
Session session = sessionPool.get(userId);
if (session != null && session.isOpen())
try
synchronized (session)
log.info("【websocket消息】 单点消息:" + message);
session.getBasicRemote().sendText(message);


catch (Exception e)
e.printStackTrace();


参考技术A §§ 1000
+1. axios的post方式发送的参数,后台只能用request.getParameter获取不到,而且发送的json参数,也接收不到
+2. axios的post发送消息,会把传递的参数多转义一次,所以需要在获取的时候转回,可用URLDecoder.decode
+3. elment-ui的上传文件钩子函数,在没有关闭文件上传的模态框的情况下,只能执行一次;
+4. websocket接收消息再发送会发送两次
参考技术B Websocket是一种可以在客户端和服务器之间双向通信的协议,它可以让客户端和服务器之间的通信更加及时、高效。Websocket的消息发送可以最少200字,最多500字,并且要求回答完整,不要出现重复的内容。为了礼貌,在回答您的问题时,我会把你改成您。

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

【中文标题】如何在服务器端发送和接收 WebSocket 消息?【英文标题】:How can I send and receive WebSocket messages on the server side? 【发布时间】:2011-12-28 20:34:54 【问题描述】:

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

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

框架如何在服务器→客户端和客户端→服务器方向上工作?

【问题讨论】:

【参考方案1】:

我修复了 Nitij 的 C# 实现中的 > 65535 消息长度问题。

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
    
        var lengthAsULong = Convert.ToUInt64(length);
        frame[1] = 127;
        frame[2] = (byte)((lengthAsULong >> 56) & 255);
        frame[3] = (byte)((lengthAsULong >> 48) & 255);
        frame[4] = (byte)((lengthAsULong >> 40) & 255);
        frame[5] = (byte)((lengthAsULong >> 32) & 255);
        frame[6] = (byte)((lengthAsULong >> 24) & 255);
        frame[7] = (byte)((lengthAsULong >> 16) & 255);
        frame[8] = (byte)((lengthAsULong >> 8) & 255);
        frame[9] = (byte)(lengthAsULong & 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;

【讨论】:

【参考方案2】:

更新了Haribabu Pasupathy 代码以处理 TCP 分段。在我的例子中,浏览器发送的大于 1024 字节的 websocket 数据包被拆分为 TCP 段,因此需要重新组装。

private static void processResponse(InputStream inputStream, OutputStream outputStream) throws IOException 
    int readPacketLength = 0;
    byte[] packet = new byte[1024];
    ByteArrayOutputStream packetStream = new ByteArrayOutputStream();

    while(true) 
        readPacketLength = inputStream.read(packet);

        if(readPacketLength != -1) 
            if ((packet[0] & (byte) 15) == (byte) 8)  // Disconnect packet
                outputStream.write(packet, 0, readPacketLength);
                // returning the same packet for client to terminate connection
                outputStream.flush();
                return;
            
            byte messageLengthByte = 0;
            int messageLength = 0;
            int maskIndex = 2;
            int messageStart = 0;
            //b[0] is always text in my case so no need to check;
            byte data = packet[1];
            byte op = (byte) 127; // 0111 111
            messageLengthByte = (byte) (data & op);

            int totalPacketLength = 0;
            if (messageLengthByte == (byte) 126 || messageLengthByte == (byte) 127) 
                if (messageLengthByte == (byte) 126) 
                    maskIndex = 4;
                    // if (messageLengthInt==(byte)126), then 16-bit length is stored in packet[2] and [3]
                    ByteBuffer messageLength16Bit = ByteBuffer.allocateDirect(4);
                    messageLength16Bit.order(ByteOrder.BIG_ENDIAN);
                    messageLength16Bit.put((byte) 0x00);
                    messageLength16Bit.put((byte) 0x00);
                    messageLength16Bit.put(packet, 2, 2);
                    messageLength16Bit.flip();
                    messageLength = messageLength16Bit.getInt();
                    totalPacketLength = messageLength + 8;
                 else 
                    maskIndex = 10;
                    // if (messageLengthInt==(byte)127), then 64-bit length is stored in bytes [2] to [9]. Using only 32-bit
                    ByteBuffer messageLength64Bit = ByteBuffer.allocateDirect(4);
                    messageLength64Bit.order(ByteOrder.BIG_ENDIAN);
                    messageLength64Bit.put(packet, 6, 4);
                    messageLength64Bit.flip();
                    messageLength = messageLength64Bit.getInt();
                    totalPacketLength = messageLength + 14;
                

                if (readPacketLength != totalPacketLength) 
                    packetStream.write(packet, 0, readPacketLength);

                    int lastPacketLength = 0;
                    while (readPacketLength < totalPacketLength) 
                        packet = new byte[1024];
                        readPacketLength += lastPacketLength = inputStream.read(packet);
                        packetStream.write(packet, 0, lastPacketLength);
                    
                    packet = packetStream.toByteArray();
                    packetStream.reset();
                
            
            else  // using message length from packet[1]
                messageLength = messageLengthByte;
            

            byte[] masks = new byte[4];
            int i=0; int j=0;
            for(i = maskIndex; i < (maskIndex+4); i++) 
                masks[j] = packet[i];
                j++;
            

            messageStart = maskIndex + 4;

            byte[] message = new byte[messageLength];
            for(i = messageStart, j = 0; i < readPacketLength; i++, j++)
                message[j] = (byte) (packet[i] ^ masks[j % 4]);
            
            System.out.println("Received message: " + new String(message));
            packet = new byte[1024];
        
    

【讨论】:

【参考方案3】:

感谢您的回答,如果有人感兴趣,我想在 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")

【讨论】:

【参考方案4】:

除了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 类 here 中实现了这个以及其他功能。

【讨论】:

【参考方案5】:

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;
    

【讨论】:

解码函数总是返回我的特定消息,并带有一个对我来说未定义的附录,如test�c=ܝX[,其中“test”是我的消息。另一部分是从哪里来的? 抱歉回复晚了。我创建了一个小型 C# 应用程序(控制台和 Web)来试用 Web 套接字。您可以从此处下载它们以查看其编码方式。链接:dropbox.com/s/gw8hjsov1u6f7c0/Web%20Sockets.rar?dl=0 这对我来说在大消息上失败了。我将长度 > 65535 代码替换为: var l = Convert.ToUInt64(length); var b = BitConverter.GetBytes(l); Array.Reverse(b, 0, b.Length); b.CopyTo(frame,2); ...这似乎有固定的东西。 干得好。只有一件事:在 DecodeMessage 上,我正在根据数据帧中包含的有效负载长度数据计算“解码”数组长度,因为“字节”数组长度可能不准确。 “bytes”数组长度取决于读取流的方式。 @Sean 你能告诉我你修复大消息问题的完整例子吗?我无法将该代码更改为您的示例。【参考方案6】:

C++ 实现(不是我做的)here。请注意,当您的字节数超过 65535 时,您需要使用 long 值进行移位,如 here 所示。

【讨论】:

【参考方案7】:

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();


【讨论】:

读取操作的合适缓冲区长度是多少? 不幸的是,它不起作用。我刚刚将无效广播(从服务器到客户端)复制到我的程序中。套接字连接成功,消息成功发送到浏览器,但浏览器没有收到任何消息。【参考方案8】:

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;

【讨论】:

【参考方案9】:

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))

【讨论】:

【参考方案10】:

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)

【讨论】:

【参考方案11】:

注意:这是一些关于如何实现一个非常简单的服务器的解释和伪代码,该服务器可以根据最终的帧格式处理传入和传出的 WebSocket 消息。它不包括握手过程。此外,此答案是出于教育目的;它不是一个功能齐全的实现。

Specification (RFC 6455)


发送消息

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

您发送的帧需要根据 WebSocket 帧格式进行格式化。对于发送消息,这种格式如下:

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

文本框的第一个字节是1000 0001(或129)。

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

需要确定原始数据的长度才能正确发送长度字节:

如果0 &lt;= length &lt;= 125,则不需要额外的字节 如果126 &lt;= length &lt;= 65535,你需要两个额外的字节,第二个字节是126 如果length &gt;= 65536,则需要额外8个字节,第二个字节为127

长度必须被分割成单独的字节,这意味着你需要向右移位(8位),然后通过AND 1111 1111只保留最后8位(这是255)。

在长度字节之后是原始数据。

这导致以下伪代码:

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)

接收消息

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

您获得的帧格式如下:

包含数据类型的一个字节 一个字节包含长度 如果长度不适合第二个字节,则增加两个或八个字节 四个字节是掩码(= 解码密钥) 实际数据

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

第二个字节和额外的两个或八个字节需要一些解析,因为您需要知道长度使用了多少字节(您需要知道实际数据从哪里开始)。长度本身通常不是必需的,因为您已经有了数据。

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

第二个字节0111 1110,或126,表示后面两个字节用于长度 第二个字节0111 1111,或127,表示后面8个字节用于长度

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

decodedByte = encodedByte XOR masks[encodedByteIndex MOD 4]

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

这导致以下用于解码的伪代码:

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) 用于文本框架?规范说:%x1 denotes a text frame。所以应该是0000 00010x01),还是? @Dennis:帧操作码是0001,因为它在规范部分的标题中声明:“操作码:4 位”。第一个字节由 FIN、RSV1-3 和操作码组成。 FIN 是1,RSV1-3 都是三个0,操作码是0001,第一个字节加起来是1000 0001。另请参阅规范中的插图,其中显示了字节如何在不同部分中拆分。 在 Server->Client 模型中有几行读起来像 'bytesFormatted[2] = ( bytesRaw.length >> 56 ) AND 255' - 你介意为我分解一下吗? AND 对我来说似乎是一个逻辑运算符,所以我不能指望在 C# 中简单地在它后面加上一个数字会对我做任何事情。同样,我不确定您的标记中的“>>”应该表示什么 - 但是它确实转移到了 C#... 不管这对我意味着什么... :P 如果有人可以为我解决这个问题,我很乐意发布我的 C# 实现作为答案。 @Neevek:他们的意思是掩码字节本身需要是不可预测的。如果它们是恒定的,那么它们没有多大意义。基本上,当恶意用户拥有一段数据时,他应该无法在没有掩码的情况下对其进行解码。如果掩码 position 不可预测,那么真正的服务器解码有点困难:)【参考方案12】:

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;

【讨论】:

可能值得注意的是,JavaScript 实际上并不支持使用大于2^31 - 1 的数字进行移位。【参考方案13】:

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', '!']

【讨论】:

我尝试在我的脚本中使用您的代码,但没有成功。你能帮忙吗? ***.com/questions/43748377/…

以上是关于websocket接收消息再发送会发送两次的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 websocket websocketpp 发送和接收消息?

udp didReceiveData 接收两次

如何在 Web 套接字中从服务器向客户端发送消息

如何在接收响应时通过 websocket 发送请求

我想问一下神通广大的各位网友,为啥我前端使用websocket通信的时候后台可以接收到我客户端发送的消息?

Websocket4Net 只接收第一条消息的回复