Ws 最佳实践

954 阅读2分钟

简介

ws 是 websocket 的简称

WebSocket 协议在2008年诞生,2011年成为国际标准。所有浏览器都已经支持了。

特点

它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。

Http或https 都是一种单项交流,由客户端主动发起,通过一系列的校验流程,最终完成消息的传送

还有一些比较好的优点:

  1. 建立在 TCP 协议之上,服务器端的实现比较容易。

  2. 与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。

  3. 没有同源限制,客户端可以与任意服务器通信。

Ws 有两种类型,类似 http 和 https ,特点是加密的区分

地址分别对应

ws://example.com:80/some/path

wss://example.com:443/some/path

例子

我们来先看一个简单的例子


const ws = new WebSocket('wss://echo.websocket.org');

ws.onopen = function(evt) {
  console.log('Connection open ...');
  ws.send('Hello WebSockets!');
};

ws.onmessage = function(evt) {
  console.log(`Received Message: ${evt.data}`);
  ws.close();
};

ws.onerror = function(evt) {
  console.log('evt: Connection error');
};

ws.onclose = function(evt) {
  console.log('Connection closed.');
};

ws.send(msg);

Ws 需要通过 new Websocket() 产生一个实例,并且在这个过程中需要把要连接的地址写入实例中,执行语句之后,客户端就会与服务器进行连接。实例对象的所有属性和方法清单,可以参照这里

api简单介绍

Ws 中监听实例的当前状态 需要用readyState, 共有四种

  • CONNECTING:值为0,表示正在连接。

  • OPEN:值为1,表示连接成功,可以通信了。

  • CLOSING:值为2,表示连接正在关闭。

  • CLOSED:值为3,表示连接已经关闭,或者打开连接失败。

// 可以是这样
switch (ws.readyState) {
  case WebSocket.CONNECTING: // do something
    break;
  case WebSocket.OPEN: // do something
    break;
  case WebSocket.CLOSING: // do something
    break;
  case WebSocket.CLOSED: // do something
    break;
  default:
    // this never happens
    break;
}


// 也可以这样 
if(ws.readyState === 1) {};
if(ws.readyState === 2) {};
if(ws.readyState === 3) {};

Ws 存在5个基础的api 回调

onopen onmessage onerror onclose send

Onopen 初始连接成功后 触发,只会触发一次

Onmessage 则是在连接成功后 结束之前 所有的信息推送的回调,具体涉及数据逻辑 这个阶段进行处理

Onclose 断开连接时 触发,同样只会触发一次 注意 onmessage 阶段 接收到的消息有可能是 string 也可能是二进制(blob对象或Arraybuffer对象)。


ws.onmessage = function(event){
  if(typeof event.data === String) {
    console.log("Received data string");
  }

  if(event.data instanceof ArrayBuffer){
    var buffer = event.data;
    console.log("Received arraybuffer");
  }
}

这个接受的信息类型,可以与第三方或者服务端沟通清楚,最终我们在处理数据的时候统一一种,便于数据的处理

ws.send() 用于向服务端发送数据 可以是文本 也可以是二进制数据

ws.send('your message');

// Sending canvas ImageData as ArrayBuffer
var img = canvas_context.getImageData(0, 0, 400, 320);
var binary = new Uint8Array(img.data.length);
for (var i = 0; i < img.data.length; i++) {
  binary[i] = img.data[i];
}
ws.send(binary.buffer);

当我们发送二进制的数据的时候,是可以监听到 消息剩余多少没有发送,用到ws.bufferedAmount,bufferedAmount 为 0 时 代表着消息发送完毕


var data = new ArrayBuffer(10000000);
ws.send(data);

// console.log(ws.bufferedAmount)
if (ws.bufferedAmount === 0) {
  // 发送完毕
} else {
  // 发送还没结束
} 

下面举一个处理数据的例子

const handleMessage = async (elinkCno: string, ws: WebSocket, evt: MessageEvent) => {
  const { data } = evt;
  // console.log('===========data========', data);
  Reflect.ownKeys(eLinkStateObj).forEach(v => {
    eLinkStateObj[v].forEach((m: string) => {
      if (data.includes(m)) eLinkObj.state = v;
    });
  });
  // console.log('数组处理后的state', eLinkObj.state);
  if (eLinkObj.state === 'NOTINFORCE') {
    doSend(ws, `AgentCallbackLogin|agent-login|${elinkCno}|${elinkCno}`);
  }
  
  if (data === 'doLoginOK') doSend(ws, `AgentStatus|${elinkCno}`);
  if (data === 'HeartBeat') doSend(ws, 'HeartBeat');
  
  if (eLinkObj.state === 'OFFLINE' && data.includes('QTdoLoginOff')) return;
  
};

const doSend = (
  websocket: WebSocket,
  msg: string | ArrayBuffer | SharedArrayBuffer | Blob | ArrayBufferView,
) => {
  if (!websocket) return;
  // 当readyState不为1时不发送消息
  if (websocket.readyState !== 1) {
    // console.log('WebSocket is already in CLOSING or CLOSED state');
  } else {
    websocket.send(msg);
  }
};

Onerror 处理报错时的回调函数。这个函数使用的频率很少,除非有指定的错误,来单独处理,一般会在函数里面通过sentry来捕获发生的错误

ws.onerror = (event) => {
  // handle error event
};

整体流程

一般我们在和服务端连接过程中 有这么几个步骤

  1. 建立连接
  2. 连接成功,登录
  3. 登录成功,确认登录后的账户,设定登录状态
  4. 心跳开始, 并且在整个连接过程中一直互通
  5. 消息接收,数据处理
  6. 连接关闭

连接关闭可能是手动,也可能是被动,一般被动的情况下,会有服务端持续发送的消息提醒,如果消息在一定的次数内没有回应,服务端则会自动关闭当前管道的连接。