WebSocket使用心得

504 阅读4分钟

一. 为什么要使用WebSocket?

  • 对于我们第一次接触WebSocket的同学,其实大家都有一个疑问🤔️,我们已经有了HTTP,为什么还要使用WebSocket,它的优势是什么?其实很简单,HTTP只能由客户端发送请求到服务器端,是单向的。而WebSocket是双向的,服务器可以主动向客户端推送信息。

二. WebSocket是什么?

  • WebSocket是HTML5出的东西,它就是一个新的协议,它和HTTP协议基本没有关系,只是为了兼容现有浏览器的握手规范而已,也就是说它是HTTP协议上的一种补充,咱们可以通过这样一张图理解。

三. 那WebSocket是什么样的协议,具体有什么优点?

  • 如果你想知道WebSocket有怎样的优点,首先咱们得了解HTTP有哪些的痛点,你说是不是这个理儿?
  • 那我们就先来说说HTTP请求
    • HTTP的生命周期通过Request来界定,也就是一个Request,一个Response,那么在HTTP1.0中,这次HTTP请求就结束了。
    • 在HTTP1.1中进行了改进,使得有一个keep-alive,也就是说,在一个HTTP连接中,可以发送多个Request,接收多个Response。
    • 但是请记住 Request = Response ,在HTTP中永远是这样,也就是说一个request只能有一个response。而且这个response也是被动的,不能主动发起。
    • 还有一点,HTTP是无状态的协议,通俗的说就是,服务器因为每天要和太多的人打交道了,比较健忘,你一走,他就把你的事全忘光了。你再来还得告诉它你是谁?
  • 那我们用人话,再说的简单一点
    • HTTP是单向,只能由客户端发送请求到服务器端。但是WebSocket是双向的,可以由服务器直接推送消息到客户端。
    • HTTP只能是一个请求,对应一个响应。但是WebSocket可以是一个请求,对应多个响应
    • HTTP是无状态的协议,每次建立TCP连接,经历过一个request和一个response之后,就会断开连接。再次发送请求还需要重新建立TCP连接,再次告诉服务器,你是谁。但是Websocket只需要一次HTTP握手,所以说整个通讯过程是建立在一次连接/状态中,也就避免了HTTP的无状态性,服务端会一直知道你的信息,直到你关闭请求。
    • WebSocket天生自带跨域,天然加密。

四. 说了这么多好的地方,WebSocket是如何实现握手的呢?

  • 让我们先来看看WebSocket的请求头
    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==
    
  • 重点请求首部意义如下:
    • Connection: Upgrade:表示要升级协议
    • Upgrade: WebSocket:告诉服务器,虽然我一开始给你发http,但是后续咱们升级成WebSocket吧,如何?
    • Sec-WebSocket-Version: 13:表示WebSocket的版本。如果服务端不支持该版本,需要返回一个Sec-WebSocket-Versionheader,里面包含服务端支持的版本号。
    • Sec-WebSocket-Key:验证你是不是WebSocket,直接甩给你一个WebSocket才懂的一句话,如果你正确回复了,那就说明你确实是WebSocket服务器,如果你回不过来,说明你是装的。
  • 然后服务器会返回下列东西,表示已经接受到请求, 成功建立WebSocket啦!
    HTTP/1.1 101 Switching Protocols
    Upgrade: websocket
    Connection: Upgrade
    Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
    Sec-WebSocket-Protocol: chat
    

五. 撸一下代码,感受一下😜

  • client
    <!DOCTYPE html>
    <html lang="en" dir="ltr">
      <head>
        <meta charset="utf-8">
        <title></title>
        <script>
        let ws = new WebSocket('ws://localhost:8080/');
    
        ws.onopen = function (){
          alert('连接已建立');
        };
        ws.onmessage=function (){};
        ws.onclose=function (){};
        ws.onerror=function (){};
        </script>
      </head>
      <body>
    
      </body>
    </html>
    
  • server
    const net=require('net');
    const crypto=require('crypto');
    
    function parseHeader(str){
        // http协议所有的换行都是\r\n,希望把空行滤掉
        let arr = str.split('\r\n').filter(line => line);
        arr.shift();
        
        let headers = {};
        arr.forEach(line => {
        let [name, value] = line.split(':');
    
        name = name.replace(/^\s+|\s+$/g, '').toLowerCase();
        value = value.replace(/^\s+|\s+$/g, '');
    
        headers[name] = value;
      });
    
      return headers;
    }
    
    let server = net.createServer(sock => {
      sock.once('data', buffer => {
        let str = buffer.toString();
        let headers = parseHeader(str);
    
        if (headers['upgrade'] != 'websocket') {
          console.log('no upgrade');
          sock.end();
        } else if (headers['sec-websocket-version']!='13'){
          console.log('no 13');
          sock.end();
        } else {
          let key = headers['sec-websocket-key'];
          let uuid = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
          let hash =c rypto.createHash('sha1');
    
          hash.update(key+uuid);
          let key2 = hash.digest('base64');
    
          sock.write(`HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection:upgrade\r\nSec-Websocket-Accept:${key2}\r\n\r\n`);
        }
      });
    
      sock.on('end', ()=>{
    
      });
    });
    server.listen(8080);
    
    

六. socket.io

  • 通过io来实现server与client的连接,兼容多个浏览器。
  • client
    <!DOCTYPE html>
    <html lang="en" dir="ltr">
      <head>
        <meta charset="utf-8">
        <title></title>
        <script src="http://localhost:8080/socket.io/socket.io.js" charset="utf-8"></script>
        <script>
        let sock = io.connect('ws://localhost:8080/');
    
        sock.on('timer', time => {
          console.log(time);
        });
        </script>
      </head>
      <body>
    
      </body>
    </html>
    
  • server
    const http = require('http');
    const io = require('socket.io');
    
    // 1.建立普通http
    let server = http.createServer((req, res) => {});
    server.listen(8080);
    
    // 2.建立ws
    let wsServer = io.listen(server);
    wsServer.on('connection', sock => {
      // sock.emit('name', 数据)
      // sock.on('name', function (数据) { });
    
      /*sock.on('aaa', function (a, b) {
        console.log(a, b, a+b);
      });*/
    
      setInterval(function (){
        sock.emit('timer', new Date().getTime());
      }, 1000);
    });
    

七. 总结

  • 如果你的应用需要提供多个用户相互交流,并且服务器端的数据经常由变动,真的可以考虑一下用websocket来实现。