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 數據。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章