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('\r\n'));
    })
}).listen(3000)

function getWebSocketKey(dataStr) {
    var match = dataStr.match(/Sec\-WebSocket\-Key:\s(.+)\r\n/);
    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(连接已断开)时,重新进行连接,连接成功给后端发送数据。

posted @ 2019-10-22 15:44  我会放电啪啪  阅读(529)  评论(0编辑  收藏  举报