WebSocket协议与java实现

、webSocket协议研究:

研究源码发现有些域和方法的算法看不懂,不知道是什么含义。于是回来看看协议。网址: webSocket协议

该协议的帧结构: 

FIN:判断这一帧数据是不是这一消息的最后一帧。

RSV1、RSV2、RSV3: 必须是0,除非通信双方做了特别的协商

Opcode: 4 bits 占有4位,定义了PayLoad Data的含义  我理解是代表帧的类型

PayLoad length : 7 bits, 7+16 bits, or 7+64 bits  

  1. 第一种情况,如果是 000 0000 --- 111 1101 ,那么这7位代表的就是payload length;
  2. 第二种情况,如果是 111 1110 即126 ,那么它的后边2字节,共16位代表的无符号整数才是负载数据的长度;
  3. 第三种情况,如果时 111 1111 即 127 ,那么它的后边8个字节,共64位代表的无符号整数才是负载数据的长度。           
  4. 协议中原汁原味的描述: The length of the "Payload data", in bytes: if 0-125, that is the payload length. If 126, the following 2 bytes interpreted as a 16-bit unsigned integer are the payload length. If 127, the following 8 bytes interpreted as a 64-bit unsigned integer (the most significant bit MUST be 0) are the payload length. Multibyte length quantities are expressed in network byte order. Note that in all cases, the minimal number of bytes MUST be used to encode the length, for example, the length of a 124-byte-long string can't be encoded as the sequence 126, 0, 124. The payload length is the length of the "Extension data" + the length of the "Application data". The length of the "Extension data" may be zero, in which case the payload length is the length of the "Application data".

MASK: 有没有掩码的标志位

   这4个字节的数据如果存在到底是做什么用的呢?

Masking-key: 0 or 4 byteIt is used to mask the "Payload data" defined in the same section as frame-payload-data, which includes "Extension data" and "Application data".

 给负载数据戴面具,这是什么意思呢?

The masking does not affect the length of the "Payload data". To convert masked data into unmasked data, or vice versa, the following algorithm is applied. The same algorithm applies regardless of the direction of the translation, e.g., the same steps are applied to mask the data as to unmask the data.

戴面具的操作不能影响负载数据的长度,为了将被戴面具的数据转变成摘掉面具的数据,以下算法被应用。

这个算法的意思应该和我之前分享的加密算法之模糊算法 类似。八进制数 i 与 i%4 对应的“密码本”中对应的数取异或。

例如动态生成的“密码本”时  byte[0] = 12;   byte[1] = 74; byte[2] = 23; byte[3] = 200. 那么 取到的payload data 的byte[] pay = byte[payloadLength]中的数依次加密的算法就是按照以下的步骤进行的:

0%4 = 0;    加密的结果是 :pay[0]^12;

1%4 = 1;  加密的结果是: pay[1]^74;

2%4 = 2;    加密的结果是: pay[2]^23;

3%4 = 3;    加密的结果是:pay[3]^200; 

4%4 = 0;   加密的结果是: pay[4]^12;

5%4 = 1;    加密的结果是:pay[5]^74;

                 .

                 .

                 .

依次类推

Payload data:  (x+y) bytes The "Payload data" is defined as "Extension data" concatenated with "Application data".

负载数据由 扩展数据和应用数据串联组成。

Extension data:  x bytes

The "Extension data" is 0 bytes unless an extension has been negotiated. Any extension MUST specify the length of the "Extension data", or how that length may be calculated, and how the extension use MUST be negotiated during the opening handshake. If present, the "Extension data" is included in the total payload length.

Extension data :一般是0个字节,除非被特别协商;

Application data:  y bytes Arbitrary

"Application data", taking up the remainder of the frame after any "Extension data". The length of the "Application data" is equal to the payload length minus the length of the "Extension data".

Application data:  应用数据占据帧的剩余部分,位置在 "Extension data"之后。

整体感受一下:

ws-frame =

frame-fin ; 1 bit in length

frame-rsv1 ; 1 bit in length

frame-rsv2 ; 1 bit in length

frame-rsv3 ; 1 bit in length

frame-opcode ; 4 bits in length

frame-masked ; 1 bit in length

frame-payload-length ; either 7, 7+16, ;

or 7+64 bits in ;

length

[ frame-masking-key ] ; 32 bits in length

frame-payload-data ; n*8 bits in ;

length, where ; n >= 0

基本概念没问题了。

二、源码分析

websocket帧的定义:


package org.eclipse.paho.client.mqttv3.internal.websocket;

import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.security.SecureRandom;

public class WebSocketFrame {
    public static final int frameLengthOverhead = 6;
    //代表帧的类型 比如是closed帧或者ping帧
    private byte opcode;
    //标记位,如果是1,表示这是最后一帧
    private boolean fin;
    // 负载数据
    private byte[] payload;
    //关闭标志位
    private boolean closeFlag = false;

    public byte getOpcode() {
        return this.opcode;
    }

    public boolean isFin() {
        return this.fin;
    }

    public byte[] getPayload() {
        return this.payload;
    }

    public boolean isCloseFlag() {
        return this.closeFlag;
    }

    // 构造器1 传入的参数由opcode , 最后一帧标志位, 负载数据
    public WebSocketFrame(byte opcode, boolean fin, byte[] payload) {
        this.opcode = opcode;
        this.fin = fin;
        if (payload != null) {
            this.payload = (byte[])payload.clone();
        }

    }

    // 构造器2  传入的参数是一个原始帧
    public WebSocketFrame(byte[] rawFrame) {
        // 原始帧数据是字节数组格式,直接放进ByteBuffer中
        ByteBuffer buffer = ByteBuffer.wrap(rawFrame);
        // 获取第一个字节
        byte b = buffer.get();
        // 调用子函数,设置 Fin和opcode
        this.setFinAndOpCode(b);
        // 获取第二个字节
        b = buffer.get();
        // 取masked标志位
        boolean masked = (b & 128) != 0;
        // 取负载数据的长度
        int payloadLength = (byte)(127 & b);
        // 表示负载数据长度的字节数 0 2 8 三种情况
        int byteCount = 0;
        if (payloadLength == 127) {
            // 当payloadLength 是 127 ,就用 一个64位无符号整数表示payloadLength
            byteCount = 8;
        } else if (payloadLength == 126) {
            // 当payloadLength 是126, 就用 一个 16位无符号整数表示payloadLength
            byteCount = 2;
        }

        // while循环,
        while(true) {
            // 先减去1 ,再判断
            --byteCount;
            // 当 payloadLength 不是16位或者64位无符号整数表示时
            if (byteCount <= 0) {
                // 声明一个掩码数组 ,实际就是加密
                byte[] maskingKey = null;
                // 加密的情况
                if (masked) {
                    maskingKey = new byte[4];
                    // 从buffer中拿到这个密码数组
                    buffer.get(maskingKey, 0, 4);
                }
                
                // 用来装负载数据的
                this.payload = new byte[payloadLength];
                // 获取 负载数据
                buffer.get(this.payload, 0, payloadLength);
                //如果数据被加密
                if (masked) {
                    // 对数据进行解密 按字节进行
                    for(int i = 0; i < this.payload.length; ++i) {
                        byte[] var10000 = this.payload;
                        var10000[i] ^= maskingKey[i % 4];
                    }
                }

                return;
            }

            b = buffer.get();
            // 其余两种情况时,取负载数据的长度
            payloadLength |= (b & 255) << 8 * byteCount;
        }
    }

    private void setFinAndOpCode(byte incomingByte) {
        this.fin = (incomingByte & 128) != 0;
        this.opcode = (byte)(incomingByte & 15);
    }

    // 构造器3 接受一个输入流
    public WebSocketFrame(InputStream input) throws IOException {
        byte firstByte = (byte)input.read();
        this.setFinAndOpCode(firstByte);
        if (this.opcode != 2) {
            if (this.opcode == 8) {
                this.closeFlag = true;
            } else {
                throw new IOException("Invalid Frame: Opcode: " + this.opcode);
            }
        } else {
            byte maskLengthByte = (byte)input.read();
            boolean masked = (maskLengthByte & 128) != 0;
            int payloadLength = (byte)(127 & maskLengthByte);
            int byteCount = 0;
            if (payloadLength == 127) {
                byteCount = 8;
            } else if (payloadLength == 126) {
                byteCount = 2;
            }

            if (byteCount > 0) {
                payloadLength = 0;
            }

            while(true) {
                --byteCount;
                if (byteCount < 0) {
                    byte[] maskingKey = null;
                    if (masked) {
                        maskingKey = new byte[4];
                        input.read(maskingKey, 0, 4);
                    }

                    this.payload = new byte[payloadLength];
                    int offsetIndex = 0;
                    int tempLength = payloadLength;

                    int bytesRead;
                    for(boolean var10 = false; offsetIndex != payloadLength; tempLength -= bytesRead) {
                        bytesRead = input.read(this.payload, offsetIndex, tempLength);
                        offsetIndex += bytesRead;
                    }

                    if (masked) {
                        for(int i = 0; i < this.payload.length; ++i) {
                            byte[] var10000 = this.payload;
                            var10000[i] ^= maskingKey[i % 4];
                        }
                    }

                    return;
                }

                maskLengthByte = (byte)input.read();
                payloadLength |= (maskLengthByte & 255) << 8 * byteCount;
            }
        }
    }

    // 编码函数 
    public byte[] encodeFrame() {
        int length = this.payload.length + 6;
        if (this.payload.length > 65535) {
            length += 8;
        } else if (this.payload.length >= 126) {
            length += 2;
        }

        ByteBuffer buffer = ByteBuffer.allocate(length);
        appendFinAndOpCode(buffer, this.opcode, this.fin);
        byte[] mask = generateMaskingKey();
        appendLengthAndMask(buffer, this.payload.length, mask);

        // 这里时重点 比较经典
        for(int i = 0; i < this.payload.length; ++i) {
            byte[] var10001 = this.payload;
            buffer.put(var10001[i] ^= mask[i % 4]);
        }

        buffer.flip();
        return buffer.array();
    }

    public static void appendLengthAndMask(ByteBuffer buffer, int length, byte[] mask) {
        if (mask != null) {
            appendLength(buffer, length, true);
            buffer.put(mask);
        } else {
            appendLength(buffer, length, false);
        }

    }

    private static void appendLength(ByteBuffer buffer, int length, boolean masked) {
        if (length < 0) {
            throw new IllegalArgumentException("Length cannot be negative");
        } else {
            byte b = masked ? -128 : 0;
            if (length > 65535) {
                buffer.put((byte)(b | 127));
                buffer.put((byte)0);
                buffer.put((byte)0);
                buffer.put((byte)0);
                buffer.put((byte)0);
                buffer.put((byte)(length >> 24 & 255));
                buffer.put((byte)(length >> 16 & 255));
                buffer.put((byte)(length >> 8 & 255));
                buffer.put((byte)(length & 255));
            } else if (length >= 126) {
                buffer.put((byte)(b | 126));
                buffer.put((byte)(length >> 8));
                buffer.put((byte)(length & 255));
            } else {
                buffer.put((byte)(b | length));
            }

        }
    }
    
    //拼接fin 和 opcode
    public static void appendFinAndOpCode(ByteBuffer buffer, byte opcode, boolean fin) {
        byte b = 0;
        if (fin) {
            b = (byte)(b | 128);
        }

        b = (byte)(b | opcode & 15);
        buffer.put(b);
    }

    //生在 masking key
    public static byte[] generateMaskingKey() {
        SecureRandom secureRandomGenerator = new SecureRandom();
        int a = secureRandomGenerator.nextInt(255);
        int b = secureRandomGenerator.nextInt(255);
        int c = secureRandomGenerator.nextInt(255);
        int d = secureRandomGenerator.nextInt(255);
        return new byte[]{(byte)a, (byte)b, (byte)c, (byte)d};
    }
}

三、小总结

1、按位异或的好处是什么呢?

加密的时候按位取异或,解密的时候再次按位取异或就得到了没有加密的数。

2、发现的优秀博客:这个讲的比较好

掩码的作用并不是为了防止数据泄密,而是为了防止早期版本的协议中存在的代理缓存污染攻击(proxy cache poisoning attacks)问题。这里要注意的是从客户端向服务端发送数据时,需要对数据进行掩码操作;从服务端向客户端发送数据时,不需要对数据进行掩码操作。

如果服务端接收到的数据没有进行过掩码操作,服务端需要断开连接。如果Mask是1,那么在Masking-key中会定义一个掩码键(masking key),并用这个掩码键来对数据载荷进行反掩码。

所有客户端发送到服务端的数据帧,Mask都是1。

掩码算法:按位做循环异或运算,先对该位的索引取模来获得 Masking-key 中对应的值 x,然后对该位与 x 做异或,从而得到真实的 byte 数据。

发布了85 篇原创文章 · 获赞 21 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/weixin_41670928/article/details/103613008