zoukankan      html  css  js  c++  java
  • 浏览器与服务器长连接技术

    浏览器和服务器保持持久连接的手段。

    定时器

    最简单,使用setTimeout、setInterval或其他计时手段定期向服务器发送请求,此方法优点就是简单,缺点就是不灵活,容易造成大量没有意义的请求。

    长轮询

    浏览器向服务器发出一个请求,服务器收到请求并将这个请求挂起(pending),当服务器需要向浏览器发送数据了,就响应挂起的这个请求,浏览器收到响应之后立刻再发送一个请求,服务器再把它挂起,如此反复,即实现了最简单的长轮询机制,它不需要任何新的协议。
    适合B/S不频繁的通信,因为即便是很小的数据量,也要重新发送一个完整的http请求。
    浏览器端代码:

    function validHttpStatus(){
      return arguments[0] > 199 && arguments[0] < 300;
    }
    async function longPolling(){
      let response = await fetch("http://localhost:3000/getdata");
      if (!validHttpStatus(response.status)) {
        // 发生了错误,打印一下错误
        console.error(`${response.url}: ${response.statusText}`);
        setTimeout(() => { // 过一会再试
          longPolling();
        }, 1e3);
      }else{
        // 打印出服务器返回的数据
        let data = await response.text();
        console.info(data);
        // 立刻再次调用,保持连接一直处于打开状态
        longPolling();
      }
    }
    longPolling(); // 开始长轮询
    

    服务器端代码:

    // 使用了Koa
    function delay(seconds){
      return new Promise(ok=>setTimeout(ok, 1e3*seconds));
    }
    router.get('/getdata', async(ctx, next)=>{
      ctx.set('Access-Control-Allow-Origin', '*');
      ctx.set('Content-Type', 'text/plain; charset=utf-8');
      ctx.set("Cache-Control", "no-store"); // 禁用缓存
      await delay(Math.floor(Math.random()*10) + 1); // 模拟服务器突然向浏览器响应数据
      ctx.body = 'hi ' + (new Date);
      await next();
    });
    

    Server Sent Event

    规范文档: https://html.spec.whatwg.org/multipage/server-sent-events.html#the-eventsource-interface
    浏览器内建的EventSource构造函数能创建一个对应的实例,只有IE全系列不支持。
    支持SSE的服务器使用text/event-stream格式推送消息。
    约束:

    1. 只能由服务器向浏览器推送数据,浏览器不能主动向服务器发送数据
    2. 推送的数据只能是文本

    SSE使用的也是http协议,它可以自动重连,而websocket需要我们手动处理重连,对于单向的且数据量不多的情景可以使用SSE,没必要强行使用websocket。
    浏览器端代码:

    function start(){
      var eventSource = new EventSource('http://localhost:3000/getdata');
      eventSource.onmessage = function(e){ // 或addEventListener
        console.log('a new msg here:', e.data);
      };
      eventSource.addEventListener('goodbye', function(e){
        // 对于自定义事件,不能使用onxxxx,必须是addEventListener
        console.log('finial message:', e.data);
      });
      setTimeout(() => { // 一小时后自动关闭
        // 一旦一个EventSource实例被关闭,就无法再复用它了,必须再新建一个实例
        eventSource.close();
      }, 1e3*60*60);
    }
    // start(); // 启动
    

    服务器端代码:

    var http = require('http');
    var count = 0;
    http.createServer(function(req, res){
      res.setHeader('Access-Control-Allow-Origin', '*');
      if (req.url.includes('getdata')){
        if (count++ == 2){ // 2次之后不让浏览器继续连接了
          count = 0; // 重置
          res.statusCode = 204; // 规范约定了204是告诉浏览器不要重试了,服务器关闭连接了,204状态码本身表示无内容,No-Content
          res.end();
          return;
        }
        res.statusCode = 200;
        res.setHeader('Content-Type', 'text/event-stream; charset=utf-8');
        let id = setInterval(() => {
          // 每条消息以双LF分隔,每条消息还有event、retry和id字段
          // event: 表示本消息的事件名,浏览器需要对它进行addEventListener
          // retry: 告诉浏览器重试等待事件,单位毫秒,默认3000
          // id:本消息的ID,重试时浏览器会发送最后一个接收到的ID以告诉服务器从哪继续开始重传,就像TCP的ack确认号
          res.write(`data: hi ${new Date}
    
    `);
        }, (Math.floor(Math.random()*2) + 1)*1e3);
        setTimeout(() => {
          clearInterval(id);
          // 本次消息周期完成,然后浏览器将尝试自动重连
          res.end(`event: goodbye
    data: see next time
    
    `);
        }, 1e3*4);
      }else{
        res.end();
      }
    }).listen(3000);
    

    WebSocket

    是浏览器和服务器全双工通信的解决方案,通信不基于http(websocket握手还是采用http),而是使用自己的ws协议,以及TSL加密的wss协议。
    当浏览器请求建立websocket连接时,发送的http请求有2个重要字段:(不能使用XHR或fetch来模拟websocket的握手,因为JavaScript无法设置这些请求头)
    GET /getdata
    Connection: Upgrade // 表示浏览器需要改变(升级)协议
    Upgrade: websocket // 改变为websocket
    如果服务器支持websocket,就判断来源并同意是否升级,如果同意返回如下响应:
    101 Switching Protocols
    Connection: Upgrade
    Upgrade: websocket
    握手完成了,之后就是用ws的数据帧开始通信了。
    强大的websocket:

    1. 它没有同源策略的限制
    2. 浏览器对它支持很好,IE10开始也支持了
    3. 既可以发送文本也可以发送二进制
    4. 3个方法4个事件
      方法:
    • socket.send(data)
    • socket.close([code], [reason])
      事件:
    • open
    • message
    • error
    • close

    浏览器端代码:

    function start(){
      var id;
      let socket = new WebSocket('ws://localhost:3000/getdata'); // 注意是ws://
      socket.binaryType = 'arraybuffer'; // 默认是'blob',即把接收到的二进制当作blob,blob是有类型的二进制数据块,作为高层的二进制数据存在,可以直接供<a>、<img>等标签使用,而arraybuffer提供了细颗粒的二进制操作
      socket.onopen = function(e){
        console.log("opened");
        socket.send('hi'); // 发送文本
        // id = setInterval(() => {
        //   socket.send(new Uint8Array([1,2,3,4])); // 发送二进制,可以是ArrayBuffer或Blob
        // }, 2000);
      };
      socket.onmessage = function(e){
        console.log('a msg here:', e.data);
      };
      socket.onclose = function(e){
        console.log(`closed, code=${e.code}, reason=${e.reason}`);
        // clearInterval(id);
        // 如果返回的code是1006,表示对方被异常关闭,比如进程被杀死了,而这个状态码是无法通过代码设置的
      };
    }
    // start(); // 启动
    

    服务器端代码:

    const http = require('http');
    const ws = require('ws');
    
    const wsinstance = new ws.Server({noServer: true});
    
    http.createServer(function(req, res){
      // 只接受websocket
      if (!req.headers.upgrade || req.headers.upgrade.toLowerCase() != 'websocket') {
        res.end();
        return;
      }
      // Connection: keep-alive, Upgrade
      if (!/upgrade/i.test(req.headers.connection)) {
        res.end();
        return;
      }
      // 进行协议升级
      wsinstance.handleUpgrade(req, req.socket, Buffer.allocUnsafe(0), function(ws){
        ws.on('message', function(data){
          console.log('receive data from browser:', data);
          // ws.send(`now ${new Date}!`); // 发送文本
          ws.send(new Uint8Array([5,6,7,8])); // 发送二进制数据
          setTimeout(() => ws.close(1000, "Bye!"), 5000);
        })
      });
    }).listen(3000);
    
    版权所有,转载请标明出处。
  • 相关阅读:
    手写PE文件(不借助编译器,用十六进制数进行编写)
    浅谈代码段加密原理(防止静态分析)
    PE文件动态加载执行过程
    静态数据连接库(.lib)和动态连接库(.dll)
    关于普通断点/内存断点/硬件断点
    x64dbg零基础使用教程
    HOOK相关原理与例子
    socket,实现服务器和客户端对话
    解析PE文件
    python正则表达式---基于re模块
  • 原文地址:https://www.cnblogs.com/ryzz/p/14717582.html
Copyright © 2011-2022 走看看