zoukankan      html  css  js  c++  java
  • WebSocket协议探究(一)

    一 复习和目标

    1 复习

    • 上一节使用wireshark抓包分析了WebSocket流量
    • 包含连接的建立:HTTP协议升级WebSocket协议
    • 使用建立完成的WebSocket协议发送数据

    2 目标

    • 协议对比

    • 初始握手和计算响应键值

    • 消息格式

    • 关闭握手

    注:WebSocket服务器使用《HTML5 WebSocket权威指南》3.4节中使用nodejs实现,WebSocket客户端使用Chrome浏览器实现。

    二 协议对比

    特性 TCP HTTP WebSocket
    寻址 IP地址和端口 URL URL
    并发传输 全双工 半双工 全双工
    内容 字节流 MIME消息 文本和二进制消息
    消息定界
    连接定向

    注:

    • TCP传送字节流,消息定界由高层协议来表现。
    • WebSocket中,多字节的消息作为整体、按照顺序到达。.

    三 初始握手

    1 HTTP请求升级协议和协议升级成功响应

    • HTTP请求
    GET ws://localhost:9999/echo HTTP/1.1
    Host: localhost:9999
    Connection: Upgrade
    Upgrade: websocket
    Sec-WebSocket-Version: 13
    Sec-WebSocket-Key: UjxPJpGjxC4JH5+0znrYBg==
    Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
    
    • HTTP响应
    HTTP/1.1 101 Web Socket Protocol Handshake
    Upgrade: WebSocket
    Connection: Upgrade
    sec-websocket-accept: NTeDlW+9/P48+pMOtotMmM1m/J0=
    

    注:响应不带Sec-WebSocket-Extensions代表该服务器不支持请求中的拓展

    2 计算响应键值

    (1)概述

    响应中的sec-websocket-accept等于base64(sha1(请求中的Sec-WebSocket-Key + 258EAFA5-E914-47DA-95CA-C5AB0DC85B11))

    (2)nodejs版本实现

    var KEY_SUFFIX = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
    
    function(key){
        var sha1 = crypto.createHash('sha1');
        sha1.update(key+KEY_SUFFIX,'ascii');
        return sha1.digest('base64');
    }
    

    (3)java版本

    public class MessageDigestUtils {
        
        private final static String KEY_SUFFIX = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
    
        public static String generateFinalKey(String key) {
            String seckey = key.trim() + KEY_SUFFIX;
            MessageDigest sha1;
            try {
                sha1 = MessageDigest.getInstance( "SHA1" );
            } catch ( NoSuchAlgorithmException e ) {
                throw new IllegalStateException( e );
            }
            return Base64.getEncoder().encodeToString(sha1.digest(seckey.getBytes()));
        }
    }
    

    (4)其他首部

    首部字段 描述
    Sec-WebSocket-Key 用于初始握手,避免跨协议攻击。
    Sec-WebSocket-Accept 用于初始握手,服务器确认WebSocket协议。
    Sec-WebSocket-Extensions 用于初始握手,服务器确认客户端的拓展。
    Sec-WebSocket-Protocol 用于初始握手,服务器子协议选择。
    Sec-WebSocket-Version 用于初始握手,对于RFC 6455对应为13。

    四 消息格式

    1 帧和消息

    • 帧:最小的通信单位,包含可变长度的帧首部和净荷部分,净荷可能包含完整或部分应用消息。
    • 消息:一系列帧,与应用消息对等。

    2 帧格式

    • FIN:表示当前帧是否为消息的最后一帧;可能一条消息就只有一帧。
    • 操作码(4位):表示被传输帧的类型
      • 1:文本
      • 2:二进制
      • 8:关闭连接
      • 9:呼叫,ping
      • 10:回应,pong
    • 掩码位:净荷是否有掩码(只适用客户端发送给服务器的消息)
    • 净荷长度:
      • 0~125:表示长度
      • 126:接下来2个字节的16位无符号整数才是该帧的长度
      • 127:接下来8个字节的64位无符号整数才是该帧的长度,高位必须为0。
    • 掩码键:包含32位,用于给净荷加掩护
    • 净荷包含应用数据,如果客户端和服务器在建立连接时协商过,也可以包含自定义的扩展数据。

    注:WebSocket的队首阻塞:如果一个大消息被分成多个WebSocket 帧,就会阻塞其他消息的帧。

    3 数据抓包

    3.1 基础信息

    • 客户端:
      • IP:192.168.1.10
      • Port:3263
    • 服务器:
      • IP:192.168.1.10
      • Port:9999

    注:如果使用localhost,wireshark无法抓包,因为流量走的时loop back接口。

    # 管理员执行route add 本机IP地址 mask 掩码 网关IP地址
    route add 192.168.1.10 mask 255.255.255.255 192.168.1.1
    

    3.2 客户端 -> 服务器(长度小于125的小包)

    (1)wireshark抓包
    WebSocket
    	1... .... = Fin: True
    	.000 .... = Reserved: 0x0
    	.... 0001 = Opcode: Text (1)
    	1... .... = Mask: True
    	.000 0101 = Payload length: 5
    	# [Extended Payload length (16 bits): 40200] 如果length超过125
    	Masking-Key: 0b4b5535
    	Masked payload
    		63 2e 39 59 64
    
    (2)掩码解析:nodejs
    // maskBytes为0b4b5535  data为632e395964
    // 结果为:68656c6c6f ==> hello
    var unmask = function (maskBytes, data) {
        var payload = new Buffer(data.length);
        for (var i = 0; i < data.length; i++) {
            payload[i] = maskBytes[i % 4] ^ data[i];
        }
        return payload;
    }
    
    (3)掩码解析:java
     public static String unmask(byte[] maskBytes,byte[] data){
         byte[] payload = new byte[data.length];
         for (int i = 0; i < data.length; i++) {
             payload[i] = (byte)(maskBytes[i % 4] ^ data[i]);
         }
         return new String(payload);
     }
    
    (4)数据解析:nodejs
    WebSocketConnection.prototype._processBuffer = function () {
        var buf = this.buffer;
    
        if (buf.length < 2) return;
    
        var b1 = buf.readUInt8(0);
        var fin = b1 & 0x80;
        var opcode = b1 & 0x0f;
        
        var b2 = buf.readUInt8(1);
        var mask = b2 & 0x80;
        var length = b2 & 0x7f;
        var idx = 2; // 索引
    
        if (length > 125) {
            if (buf.length < 8) return;
    
            if (length == 126) {
                length = buf.readUInt16BE(2);
                idx += 2;
            } else if (length == 127) {
                var highBits = buf.readUInt32BE(2); 
                if (highBits != 0)  this.close(1009, "");// 高位必须为0
                length = buf.readUInt32BE(6);
                idx += 8;
            }
        }
    
        // 4个字节的掩码
        if (buf.length < idx + 4 + length) {
            return;
        }
    
        maskBytes = buf.slice(idx, idx + 4);
        idx += 4;
        
        var payload = buf.slice(idx, idx + length);
        payload = unmask(maskBytes, payload);
        
        this._handleFrame(opcode, payload);
        this.buffer = buf.slice(idx + length); // buffer置空
        
        return true;
    }
    

    注:java版本的数据解析太麻烦,后期考虑补上。

    3.3 服务器 -> 客户端 (长度小于125的小包)

    • 服务器发给客户端不需要掩码,直接发送即可。
    WebSocket
    	1... .... = Fin: True
    	.000 .... = Reserved: 0x0
    	.... 0001 = Opcode: Text (1)
    	0... .... = Mask: False
    	.000 0101 = Payload length: 5
    	Payload
    		hello
    

    五 关闭握手

    1 关闭握手异常代号

    代号 描述 使用场景
    1000 正常关闭 会话正常完成时
    1001 离开 应用离开且不期望后续连接的尝试而关闭连接时
    1002 协议错误 因协议错误而关闭连接时
    1003 不可接受的数据类型 非二进制或文本类型时
    1007 无效数据 文本格式错误,如编码错误
    1008 消息违反政策 当应用程序由于其他代号不包含的原因时
    1009 消息过大 当接收的消息太大,应用程序无法处理时(帧的载荷最大为64字节)
    1010 需要拓展
    1011 意外情况

    2 其他代号

    代号 描述 使用情况
    0~999 禁止
    1000~2999 保留
    3000~3999 需要注册 用于程序库、框架和应用程序
    4000~4999 私有 应用程序自由使用

    3 抓包分析

    (1)客户端发起关闭

    WebSocket
    	1... .... = Fin: True
    	.000 .... = Reserved: 0x0
    	.... 1000 = Opcode: Connection Close (8)
    	1... .... = Mask: True
    	.000 0000 = Payload length: 0
    	Masking-Key: 461086e0
    

    (2)服务器响应关闭

    WebSocket
    	1... .... = Fin: True
    	.000 .... = Reserved: 0x0
    	.... 1000 = Opcode: Connection Close (8)
    	0... .... = Mask: False
    	.000 0000 = Payload length: 0
    

    参考:

    • 《Web性能权威指南》
    • 《HTML5 WebSocket权威指南》
    • RFC 6455
  • 相关阅读:
    学习进度条05
    构建之法阅读笔记03
    子数组和最大值算法
    学习进度条04
    学习进度条03
    定制小学四则运算
    单元测试示例
    构建之法阅读笔记02
    学习进度条02
    decimal扩展方法(转换为字符串,去掉末尾的0)
  • 原文地址:https://www.cnblogs.com/linzhanfly/p/10099389.html
Copyright © 2011-2022 走看看