Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

你不得不懂的前端协议《三》浅谈Websocket #5

Open
Hjw52 opened this issue Oct 24, 2020 · 0 comments
Open

你不得不懂的前端协议《三》浅谈Websocket #5

Hjw52 opened this issue Oct 24, 2020 · 0 comments

Comments

@Hjw52
Copy link
Owner

Hjw52 commented Oct 24, 2020

Websocket浅谈

  • websocket特点

    • 双向通信技术
    • 没有同源限制
    • 数据分片,按帧传输(文本数据类型,二进制数据类型,控制帧类型)

websocket就服务端推送提供了另外一种解决方案,相比HTTP1.1,它优势就在于服务端推送。他本质上是在tcp协议上封装的另一种应用层协议(websocket协议)。因为他是基于tcp的,所以服务端推送自然不是什么难题。但是在实现上,他并不是直接连接一个tcp连接,然后在上面传输基于websocket协议的数据包。他涉及到一个协议升级(交换)的过程。

客户端request格式如下:

GET / HTTP/1.1
Host: localhost:8080
Origin: http://127.0.0.1:3000
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: w4v7O6xFTi36lq3RNcgctw==

服务端响应协议升级:

HTTP/1.1 101 Switching Protocols
Connection:Upgrade
Upgrade: websocket
Sec-WebSocket-Accept: Oy4NRAQ13jhfONC7bP8dTKb4PTU=

服务器给浏览器推送的时候,浏览器会发送ack,但是浏览器给服务器发送的时候,服务器貌似没有返回ack?

服务器(tcp)推送消息的时候把ack也带上了。而不是发送两个tcp包。这就是tcp的机制。tcp不会对每个包都发ack,他会累积确认(发ack),以减少网络的包,但是他也需要保证尽快地回复ack,否则就会导致客户端触发超时重传。tcp什么时候发送确认呢?比如需要发送数据的时候,或者超过一定时间没有收到数据包,或者累积的确认数量达到阈值等。

websocket连接持续多久?

在websocket连接上,一直不通信的话,websocket连接所维持的时间是依赖tcp实现的。因为我们发现tcp层会一直发送探测包。达到阈值之后,连接就会被断开。所以我们想维持websocket连接的话,需要自己去发送心跳包,比如ping,pong。

  • webcocket数据帧结构

image-20201015220950045

具体的含义如下:

  1. **FIN:**1位,表示当前数据帧是否为最后一帧。1代表最后帧,0代表还在传输。如果消息只由一帧构成,那么起始帧就是结束帧。
  2. **RSV1,RSV2,RSV3:**各1位,如果未定义拓展,那么都为0;如果定义了拓展则为非0值。如果接收的帧为非0值但拓展中没有该值的定义,那么连接关闭。
  3. **opcode:**4位,为PayloadData有效负荷,如果接收到未知的opcode,关闭连接。opcode主要有以下类型
    • 0x0:表示附加数据帧,延续帧
    • 0x1:表示文本数据帧
    • 0x2:表示二进制数据帧
    • 0x3-7:暂无定义,保留
    • 0x8:表示连接关闭
    • 0x9:表示ping
    • 0xA:表示pong,ping pong用于心跳连接,检测连接是否断开
    • 0xB-F:暂无定义,保留
  4. **MASK:**1位,用于标识PlayloadData是否经过掩码处理,如果是1,Masking-key域的数据即是掩码秘钥,用于解码PlayloadData。websocket的安全机制,从客户端向服务器传输的数据帧必须经过掩码处理,服务端若收到未经掩码处理的数据帧,则主动关闭连接(发送closed帧,状态码1002)。
  5. **MASKing-key:**客户端设置得32位的随机数,掩码算法主要是:
    • origin-i:原始数据的第i字节
    • transform-i:转换后的第i字节
    • a:选取的i mod 4 位的值(掩码key只有4个字节)
    • transform-i = origin-i XOR a 。两者异或得到掩码后的值。
  6. Playload data:x+y 位,包含拓展数据和应用数据。
  • 状态码

    当关闭一个已建立的连接时,端点可以使用预设的状态码来解释关闭原因,主要有以下状态码:

    状态码 描述
    1000 正常关闭
    1001 终端离开,可能服务端错误或客户端离开
    1002 协议错误
    1003 接收到不允许的数据类型(文本类型收到二进制数据)
    1004-1006 保留
    1007 接收到格式不符的数据(文本消息收到非utf-8)
    1008 收到不符数据,用于不适1003和1009场景
    1009 收到过大数据帧
    1011 客户端阻止完成请求
    1012 服务端重启
  • Node构建websocket

    模拟浏览器切换协议

    var WebSocket = function (url) {
     // 伪代码 解析ws://127.0.0.1:12010/updates 请求
     this.options = parseUrl(url);
     this.connect();
    };
    WebSocket.prototype.onopen = function () {
     // TODO
    };
    WebSocket.prototype.setSocket = function (socket) {
     this.socket = socket;
    };
    WebSocket.prototype.connect = function () {
     var this = that;
     var key = new Buffer(this.options.protocolVersion + '-' + Date.now()).toString('base64');
     var shasum = crypto.createHash('sha1'); 
     var expected = shasum.update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11').digest('base64');
     var options = {
     port: this.options.port, // 12010
     host: this.options.hostname, // 127.0.0.1
     headers: {
     'Connection': 'Upgrade',
     'Upgrade': 'websocket',
     'Sec-WebSocket-Version': this.options.protocolVersion,
     'Sec-WebSocket-Key': key
     }
     };
     var req = http.request(options);
     req.end();
     req.on('upgrade', function(res, socket, upgradeHead) {
     // 连接成功
     that.setSocket(socket);
     //触发open事件
     that.onopen();
     });
    }; 
    
    

    服务端

    var server = http.createServer(function (req, res) {
     res.writeHead(200, {'Content-Type': 'text/plain'});
     res.end('Hello World\n');
    });
    server.listen(12010);
    // 在收ڟupgrade请求ࢫLjߢኮ客户端ሎႹൎ换ၹᅱ
    server.on('upgrade', function (req, socket, upgradeHead) {
     var head = new Buffer(upgradeHead.length);
     upgradeHead.copy(head);
     var key = req.headers['sec-websocket-key'];
     var shasum = crypto.createHash('sha1');
     key = shasum.update(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").digest('base64');
     var headers = [
     'HTTP/1.1 101 Switching Protocols',
     'Upgrade: websocket',
     'Connection: Upgrade',
     'Sec-WebSocket-Accept: ' + key,
     'Sec-WebSocket-Protocol: ' + protocol
     ];
     // 发送数据
     socket.setNoDelay(true);
     socket.write(headers.concat('', '').join('\r\n'));
     // 建立服务器端WebSocket连接
     var websocket = new WebSocket();
     websocket.setSocket(socket);
    }); 
    

    客户端

    var socket = new WebSocket('ws://127.0.0.1:12010/updates');
    //握手完成后触发
    socket.onopen = function () {
     setInterval(function() {
     if (socket.bufferedAmount == 0)
     socket.send(getUpdateData());
     }, 50);
    };
    socket.onmessage = function (event) {
     // TODO:event.data
    }; 
    
  • 兼容性

websocket虽然功能强大,但不是所有的浏览器都能支持websocket,IE11以下的浏览器就无法使用websocket。这只能使用HTTP的轮询和长轮询(用户的扫码登录就是长轮询)来替代websocket了。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant