websocket学习(二)

版权声明: https://blog.csdn.net/weixin_33768153/article/details/84237611

websocket客户端,服务端通信的最小单位是帧,由1个或者多个帧组成一个条完成的消息(message)

  • 发送端,将消息切割成多个帧,并发送给接收端
  • 接收端,接受消息帧,并将关联的症组装成完整的消息

websocket数据帧格式

单位是比特,比如FIN,RSV1各占一个bit,opcode占据四个bit。

 0                   1                   2                   3
  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 +-+-+-+-+-------+-+-------------+-------------------------------+
 |F|R|R|R| opcode|M| Payload len |    Extended payload length    |
 |I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
 |N|V|V|V|       |S|             |   (if payload len==126/127)   |
 | |1|2|3|       |K|             |                               |
 +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
 |     Extended payload length continued, if payload len == 127  |
 + - - - - - - - - - - - - - - - +-------------------------------+
 |                               |Masking-key, if MASK set to 1  |
 +-------------------------------+-------------------------------+
 | Masking-key (continued)       |          Payload Data         |
 +-------------------------------- - - - - - - - - - - - - - - - +
 :                     Payload Data continued ...                :
 + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
 |                     Payload Data continued ...                |
 +---------------------------------------------------------------+
  • FIN: 一个bit,如果是1,表这是message的最后一个分片(fragment),如果是0,表示不是message的最后一个分片。
  • RSV1,RSV2,RSV3:各占一个bit,一般情况写全部为0.当客户端,服务端协商采用Websocket扩展是,这三个标志可以非0,且值的含义可以自由扩展进行定义。如果出现非0值,且并没有采用Websocket扩展,链接出错
  • opcode:4个bit,操作码。opcode的值决定了应该如何解析后续的数据(data payload)。 如果操作代码是不认识的,那么接收端应该断开链接
    • %x0:表示一个延续帧。当Opcode为0时,表示本次数据传输采用了数据分片,当前收到的数据帧为其中一个数据分片。
    • %x1:表示这是一个文本帧(frame)
    • %x2:表示这是一个二进制帧(frame)
    • %x3-7:保留的操作代码,用于后续定义的非控制帧。
    • %x8:表示连接断开。
    • %x9:表示这是一个ping操作。
    • %xA:表示这是一个pong操作。
    • %xB-F:保留的操作代码,用于后续定义的控制帧。
  • Mask:一个bit,表示是否要对数据载荷进行掩码操作
    • 客户端像服务端发送数据时,需要对数据进行掩码操作,从服务端像客户端发送数据是,不需要对数据进行掩码操作,如果服务端接收到的数据没有进行掩码操作,服务器需要断开连接。
    • 如果Mask是1,那么在Masking-key中会顶一个一个掩码键(masking key)。并用这个掩码键来对数据进行反掩码。所有的客户端发送到服务端的数据正,mask都是1
  • Payload length: 数据载荷的长度。单位是字节。为7为,或者7+16为,或7+64为。
    • Payload length=x为0~125:数据的长度为x字节。
    • Payload length=x为126:后续2个字节代表一个16位的无符号整数,该无符号整数的值为数据的长度。
    • Payload length=x为127:后续8个字节代表一个64位的无符号整数(最高位为0),该无符号整数的值为数据的长度。
    • 如果payload length占用了多个字节的话,payload length的二进制表达采用网络序(big endian,重要的位在前)
  • Masking-key: 或4字节(32位) 所有从客户端传送到服务端的数据帧,数据载荷都进行了掩码操作,Mask为1,且携带了4字节的Masking-key。如果Mask为0,则没有Masking-key。载荷数据的长度,不包括mask key的长度
  • Payload data:(x+y) 字节
    • 载荷数据:包括了扩展数据、应用数据。其中,扩展数据x字节,应用数据y字节。
    • 扩展数据:如果没有协商使用扩展的话,扩展数据数据为0字节。所有的扩展都必须声明扩展数据的长度,或者可以如何计算出扩展数据的长度。此外,扩展如何使用必须在握手阶段就协商好。如果扩展数据存在,那么载荷数据长度必须将扩展数据的长度包含在内。
    • 应用数据:任意的应用数据,在扩展数据之后(如果存在扩展数据),占据了数据帧剩余的位置。载荷数据长度 减去 扩展数据长度,就得到应用数据的长度。

掩码算法

掩码键(Masking-key)是由客户端挑选出来的32位的随机数。掩码操作不会影响数据载荷的长度。掩码、反掩码操作都采用如下算法:

  • 对索引i模以4得到j,因为掩码一共就是四个字节
  • 对原来的索引进行异或对应的掩码字节
  • 异或就是两个数的二进制形式,按位对比,相同取0,不同取1
function unmask(buffer, mask) {
      const length = buffer.length;
      for (let i = 0; i < length; i++) {
          buffer[i] ^= mask[i & 3];
      }
  }

栗子

这里使用tcp实现聊天室功能

  • 新建目录,新建index.html, server.js

  • 客户端:index.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>Document</title>
    </head>
    <body>
        <div>hello webscoket</div>
        <script>
            // 创建一个socket对象,相当于买了一部手机
            // socket 插座
            let socket = new WebSocket('ws://localhost:9999')
            // 如果服务器链接成功,会触发此事件
            socket.onopen = function () {
                console.log('链接已经建立好了')
                socket.send(('你好'))
            }
            // 如果客户端接收到消息,会触发这个事件
            socket.onmessage = (e)  => {
                console.log(e.data)
            }
        </script>
    </body>
    </html>
    
  • 服务端:server.js

    let net = require('net')
    let code = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
    let crypto = require('crypto')
    
    let express = require('express')
    let app = express()
    app.use(express.static(__dirname))
    app.listen(8080)
    let server = net.createServer(( socket ) => {
        // 监听客户端发来的信息
        socket.once('data', data => {
            data = data.toString();
            // data 内容大概如下
            /*  请求头
                Request Method: GET
                Connection: Upgrade // 请求升级协议
                Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
                Sec-WebSocket-Key: BdHBU6Vo/0OMycVshRE3iQ== 
                Sec-WebSocket-Version: 13 // 升级的版本
                Upgrade: websocket // 升级的类型
    
                */
            let lines = data.split('\r\n')
            let headers = lines.slice(1, -2)
            // 处理字符串为一个对象 获取请求头
            headers = headers.reduce((headers, line) => {
                let [ key, val ] = line.split(': ')
                headers[key] = val
                return headers
            }, {});
            // console.log('headers', headers) // 打印出来的header如下
            /* 
            { Host: 'localhost:9999',
                Connection: 'Upgrade',
                Pragma: 'no-cache',
                'Cache-Control': 'no-cache',
                'User-Agent':
                'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36',
                Upgrade: 'websocket',
                Origin: 'http://localhost:8080',
                'Sec-WebSocket-Version': '13',
                'Accept-Encoding': 'gzip, deflate, br',
                'Accept-Language': 'zh,zh-CN;q=0.9,en;q=0.8,en-AU;q=0.7',
                Cookie:
                'visit=10; visit.sig=VVIZcIDkunpRwQDicJGbWKrQhTE; hello=bf5b7610-d783-11e8-aa65-354f8f3423d3',
                'Sec-WebSocket-Key': 'qmuFW2gfEl4prcOwCqg7tA==',
                'Sec-WebSocket-Extensions': 'permessage-deflate; client_max_window_bits' }
             */
            // 判断是否升级协议
            // 如果Upgrade的知识websocket的话,则需要升级协议
            if (headers.Upgrade === 'websocket') {
                let key = headers['Sec-WebSocket-Key']
                let accept = crypto.createHash('sha1').update(key + code).digest('base64')
                // 处理响应头
                let response = [
                    "HTTP/1.1 101 Switching Protocols",
                    "Upgrade: websocket",
                    "Connection: Upgrade",
                    `Sec-WebSocket-Accept: ${accept}`,
                    '\r\n'
                ].join('\r\n')
               
                socket.write(response)
                // 切换协议成功,开始监听客户端发来的消息
                socket.on('data', function (bufs)  {
                    let FIN = (bufs[0] & 0b10000000) === 0b10000000 // 是否最后一帧
                    let opcode = bufs[0] & 0b00001111 // 操作码 1文本,
                    let isMasked = (bufs[1] & 0b100000000) === 0b100000000 // 对方是否掩码
                    let payloadLength = bufs[1] & 0b01111111;//取得负载数据的长度
                    let mask = bufs.slice(2, 6);//掩码 4个字节
                    let payload = bufs.slice(6);//负载数据
                    // console.log('FIN', FIN) // true
                    // console.log('opcode', opcode) // 1
                    // console.log(FIN, opcode, isMasked, payloadLength, mask, payload) 
                    // true 1 false 6 <Buffer 04 6e 75 fe> <Buffer e0 d3 d5 1b a1 d3>
    
                    for (let i = 0; i < payload.length; i++) {
                        payload[i] ^= mask[i % 4] // 为运算 相同是0, 不想同是1
                    }
    
                    // 处理返回值
                    // console.log(FIN, opcode, isMasked, payloadLength, mask, payload.toString()) 
                    let response = Buffer.alloc( 2 + payload.length )
                    response[0] = 0b10000001
                    response[1] = payload.length
                    // 将payload buf对象从下标2开始拷贝到response中
                    payload.copy(response, 2)
                    socket.write(response)
                })
            }
        })
    })
    server.listen(9999)
    

    总结

    其实我也不是很懂,就是勉勉强强知道丝丝,还要学习呀。

    嗯么么,附上完成代码

猜你喜欢

转载自blog.csdn.net/weixin_33768153/article/details/84237611
今日推荐