zoukankan      html  css  js  c++  java
  • websocket1

    WebSocket

    最近项目中使用到了WebSocket,在这里总结一下使用的方法和遇到的问题

    首先介绍下什么是WebSocket

    WebSocket是一种网络传输层协议,可在单个TCP链接上进行全双工通信,
    位于OSI模型的应用层,WebSocket允许服务端向客户端主动推送数据。浏览器和我武器只需要完成一次握手,两者之间就可以创建持久性的连接,并进行双向数据传输。

    为什么要使用WebSocket(WebSocket与传统HTTP有什么优势)

    • 客户端与服务端只建立一个TCP连接,可以使用更少的连接
    • 服务器可以推送数据到客户端,比HTTP请求响应模式更灵活,更高效
    • 更轻量级的协议头,减少数据传送量

    WebSocket 握手

    客户端建立连接时,通过HTTP发起请求报文

    GET /chat HTTP/1.1
    Host: server.example.com
    Upgrade: websocket
    Connection: Upgrade
    Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
    Sec-WebSocket-Protocol: chat, superchat
    Sec-WebSocket-Version: 13
    

    与HTTP请求协议有区别的部分:

    Upgrade: websocket
    Connection: Upgrade
    

    这两个字段表示请求服务器端升级为WebSocket。

    Sec-WebSocket-Key 用安全校验:

    Sec-WebSocket-Key的值是随机生成的Base64编码的字符串。服务器端接收之后将其与字符串258EAFA5-E914-47DA-95CA-C5AB0DC85B11相连,行成dGhlIHNhbXBsZSBub25jZQ==258EAFA5-E914-47DA-95CA-C5AB0DC85B11,然后通过sha1安全散列算法计算出结果,在进行Base64编码,最后返回给客户端。

    Sec-WebSocket-Protocol: chat, superchat

    Sec-WebSocket-Version: 13

    上面两个字段指定子协议和版本号

    服务端处理完请求后,响应报文如下:

    HTTP/1.1 101 Switching Protocols
    Upgrade: websocket
    Connection: Upgrade
    Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
    Sec-WebSocket-Protocol: chat
    
    
    HTTP/1.1 101 Switching Protocols
    Upgrade: websocket
    Connection: Upgrade
    

    状态码101表示切换协议,更新应用层协议为WebSocket协议,并在当前的套接字上应用新的协议

    Sec-WebSocket-Accep:

    表示服务端基于Sec-WebSocket-Key生成的字符串

    Sec-WebSocket-Protocol:

    表示最终使用的协议

    客户端代码

    let ws = new WebSocket('ws://192.168.10.40:3000/')
          ws.onopen = mes=>{
            console.log(mes,'aaa')
      }
    

    服务端代码

    const net = require('net');
    const crypto = require('crypto');
    const wsGUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
    net.createServer(function(socket) {
        socket.on('data', function(buffer) {
            // data 是buffer需要转化
            const data = buffer.toString(),
                key = getWebSocketKey(data),
                acceptKey = crypto.createHash('sha1').update(key + wsGUID).digest('base64'),
                headers = [
                    'HTTP/1.1 101 Switching Protocols',
                    'Upgrade: websocket',
                    'Connection: Upgrade',
                    'Sec-WebSocket-Accept: ' + acceptKey
                    ];
            socket.write(headers.concat('','').join('
    '));
        })
    }).listen(3000)
    
    function getWebSocketKey(dataStr) {
        var match = dataStr.match(/Sec-WebSocket-Key:s(.+)
    /);
        if (match) {
            return match[1];
        }
    }
    

    客户端的API

    1.websocket 构造函数

    WebSocket 对象作为一个构造函数,用于新建 WebSocket 实例。

    var ws = new WebSocket('ws://localhost:8080');
    

    执行上面语句之后,客户端就会与服务器进行连接。下面这张图是实例对象的所有属性和方法清单

    2.webSocket.readyState

    readyState属性返回实例对象的当前状态,共有四种。

    console.log(ws.readyState) // 我们可以输入看当前是什么状态
    
    • CONNECTING:值为0,表示正在连接。
    • OPEN:值为1,表示连接成功,可以通信了。
    • CLOSING:值为2,表示连接正在关闭。
    • CLOSED:值为3,表示连接已经关闭,或者打开连接失败。

    3.webSocket.onopen

    实例对象的onopen属性,用于指定连接成功后的回调函数。

    ws.onopen = function () {
      ws.send('Hello Server!');
    }
    

    如果要指定多个回调函数,可以使用addEventListener方法。

    ws.addEventListener('open', function (event) {
      ws.send('Hello Server!');
    });
    

    4.webSocket.onclose

    实例对象的onclose属性,用于指定连接关闭后的回调函数。

    ws.onclose = function(event) {
      var code = event.code;
      var reason = event.reason;
      var wasClean = event.wasClean;
      // handle close event
    };
    

    5.webSocket.onmessage

    实例对象的onmessage属性,用于指定收到服务器数据后的回调函数

    ws.onmessage = function(event) {
      var data = event.data;
      // 处理数据
    };
    
    ws.addEventListener("message", function(event) {
      var data = event.data;
      // 处理数据
    });
    

    我们可以使用这个回调函数处理返回的数据相当于我们平时使用axios返回数据成功的then方法

    6.webSocket.send()

    实例对象的send()方法用于向服务器发送数据

    ws.send('your message');
    
    

    7.webSocket.bufferedAmount

    实例对象的bufferedAmount属性,表示还有多少字节的二进制数据没有发送出去。它可以用来判断发送是否结束。

    var data = new ArrayBuffer(10000000);
    socket.send(data);
    
    if (socket.bufferedAmount === 0) {
      // 发送完毕
    } else {
      // 发送还没结束
    }
    

    8.webSocket.onerror

    实例对象的onerror属性,用于指定报错时的回调函数。

    socket.onerror = function(event) {
      // handle error event
    };
    
    socket.addEventListener("error", function(event) {
      // handle error event
    });
    

    完整示例

    服务端

    import userInfoModel from '../model/userinfo';
    import addendanceInfoModel from '../model/addentance';
    import Websocket from 'ws';
    import * as http from 'http';
    const wss = new Websocket.Server({port: 3004});
    wss.on('connection',function(ws:Websocket,req:http.IncomingMessage) {
        ws.on('message',async function(){
            try{
                // 获取基本数据
                const userData = await userInfoModel.find();
                ws.send(JSON.stringify(userData));
                // 获取出勤数据
                const addendanceData = await addendanceInfoModel.find();
                ws.send(JSON.stringify(addendanceData))
            }catch(err){
                ws.close()
            }
        })
    })
    
    

    前端websocket

    // Ws 封装一系列websock方法
    // getInstance   获取当前类的实例
    // initConnect   初始化连接,返回promise,连接成功resolve,失败reject
    // send   发送数据,包括断开重新连接
    export default class Ws{
        constructor(){
            // 初始化连接
            this.initConnect() 
        }
        static getInstance() {
            if(!this.instance){
                this.instance = new Ws()
            }
            return this.instance;
        }   
        initConnect(){
            // 创建ws实例
            return new Promise((resolve,reject)=>{
                this.ws = new WebSocket('ws://192.168.10.40:3004/');
                this.ws.onmessage = event => this.message(JSON.parse(event.data));
                this.ws.onopen = event => resolve(event);
                this.ws.onclose = event => reject(event);
            })
        }
        async send(data){
            // 如果为连接断开就重新连接
            if(this.ws.readyState == 3 ){
                // 初始化连接
                await this.initConnect();
                // 连接成功发送消息
                data && this.ws.send(JSON.stringify(data))
            }else{
                // 发送消息
                data && this.ws.send(JSON.stringify(data))
            }
        }
        close() {
            if(this.ws.readyState == 3){
                return;
            } 
            this.ws.close();
        }
    }
    
    

    前端组件部分

    <script>
    import Ws from '@/assets/websocket.js';
    export default {
      name: 'HelloWorld',
      mounted() {
        this.ws = Ws.getInstance();
        this.ws.message = this.onmessage;
      },
      methods:{
        onmessage(data) {
          console.log(data,'数据')
        },
        OnClick() {
          this.ws.send({})
        }
      }
    }
    </script>
    
    

    websocket 遇到的坑

    如果前端发送数据到后端,后端响应时间过长,可能会超出服务器的读取时间,排查问题,最后发现nginx设置了超时代理

      proxy_read_timeout 60s;
      proxy_send_timeout 60s;
    

    proxy_read_timeout 60s:

    设置从后端读取超时,默认60s,他决定了nginx会等待多长时间获得请求的响应,超时nginx会关闭连接

    proxy_send_timeout 60s;

    设置发送请求给上游服务器的超时时间,如果60s内上游服务器没有收到任何响应,nginx关闭连接

    我们只需找运维同学帮我们把代理时间延长,就不会出现超时时间了

    我们在代码中也可以设置断开重连

        initConnect(){
            // 创建ws实例
            return new Promise((resolve,reject)=>{
                this.ws = new WebSocket('ws://192.168.10.40:3004/');
                this.ws.onmessage = event => this.message(JSON.parse(event.data));
                this.ws.onopen = event => resolve(event);
                this.ws.onclose = event => reject(event);
            })
        }
        async send(data){
            // 如果为连接断开就重新连接
            if(this.ws.readyState == 3 ){
                // 初始化连接
                await this.initConnect();
                // 连接成功发送消息
                data && this.ws.send(JSON.stringify(data))
            }else{
                // 发送消息
                data && this.ws.send(JSON.stringify(data))
            }
        }
    

    上面代码中,每次给后端发送数据,判断当前websocket连接状态,当状态是3(连接已断开)时,重新进行连接,连接成功给后端发送数据。

  • 相关阅读:
    CVE-2020-1938 Apache-Tomcat-Ajp 文件包含漏洞复现
    关于CVE-2020-0796
    如何伪造邮箱
    如何判断主机是否存在某系统漏洞
    DC3
    小记如何绕过受限的shell
    DC1
    1111. 有效括号的嵌套深度 看题两小时,做题10分钟系列,这是在考语文吧
    LeetCode 912. 排序数组
    LeetCode--算法,简单级别1
  • 原文地址:https://www.cnblogs.com/mengxiangji/p/11720197.html
Copyright © 2011-2022 走看看