websocket

285 阅读3分钟

websocket(下文简称ws)

1.ws是什么:是一种在单个TCP连接上进行全双工通信的协议

tips:全双工指可以同时(瞬时)进行信号的双向传输(A→B且B→A)。指A→B的同时B→A,是瞬时同步的。

tips:单工就是在只允许甲方向乙方传送信息,而乙方不能向甲方传送

2.为什么要用ws:能够与服务器保持长久性连接,且允许服务端主动向客户端推送消息

解释:因为原本已有的http协议有个缺陷:通信只能由客户端发起。现在很多情况都会出现服务器有连续的状态变化,http协议是无法满足处理这情况。后来也出现解决方案轮询:

轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP请求,然后由服务器返回最新的数据给客户端的浏览器。

后面又基于轮询基础上有了Commet方案,采用全双工和长连接,但缺陷还是存在:需要反复发送请求和大量消耗服务器资源。

所以基于这种情况,HTML5定义了ws协议,它可以更好的节省服务器资源和带宽,并且能够更实时地进行通讯。

3.ws基本速用:

基本封装思路:创建ws对象,然后自定义方法对应到ws对象的方法上

<!DOCTYPE html>
<html lang="en">

<head>
   <meta charset="UTF-8">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <title>Document</title>
</head>

<body>
   <script language="javascript" type="text/javascript">
      var wsUri = "wss://echo.websocket.org/";
      var output;

      function init() {
         output = document.getElementById("output");
         testWebSocket();
      }

      function testWebSocket() {
         websocket = new WebSocket(wsUri);

         websocket.onopen = function (evt) {
            onOpen(evt)
         };

         websocket.onclose = function (evt) {
            onClose(evt)
         };

         websocket.onmessage = function (evt) {
            onMessage(evt)
         };

         websocket.onerror = function (evt) {
            onError(evt)
         };
      }

      function onOpen(evt) {
         writeToScreen("CONNECTED");
         doSend("WebSocket rocks");
      }

      function onClose(evt) {
         writeToScreen("DISCONNECTED");
      }

      function onMessage(evt) {
         writeToScreen('<span style = "color: blue;">RESPONSE: ' +
            evt.data + '</span>'); websocket.close();
      }

      function onError(evt) {
         writeToScreen('<span style = "color: red;">ERROR:</span> '
            + evt.data);
      }

      function doSend(message) {
         writeToScreen("SENT: " + message); websocket.send(message);
      }

      function writeToScreen(message) {
         var pre = document.createElement("p");
         pre.style.wordWrap = "break-word";
         pre.innerHTML = message;
         output.appendChild(pre);
      }

      window.addEventListener("load", init, false);
   </script>
   <h2>WebSocket Test</h2>
   <div id="output"></div>
</body>

</html>

remark:监听推送返回,可以在onmessage保存data到redux里,再在使用地方进行读取,函数组件可以在useEffect直接监听这个onmessage赋值的变量

const socket = {
    host: '',
    errorCount: 0,
    keepaliveObj: null,
    webSocket: null,
    retryConnect: null,
    // 创建websocket连接
    connectSocket(domain) {
        const pThis = this;
        if (pThis.keepaliveObj !== null) {
            clearInterval(pThis.keepaliveObj);
        }
        const webSocket = new WebSocket(domain);
        window.webSocket = webSocket;
        pThis.webSocket = webSocket;
        /* 建立链接 */
        webSocket.onopen = evt => {
            console.log('webSocket连接成功');
            // 保持链接  心态机制
            clearInterval(pThis.keepaliveObj);
            pThis.keepaliveObj = setInterval(() => {
                pThis.webSocket.send('');
            }, 30000);
        };
        /* 链接关闭 */
        webSocket.onclose = evt => {
            console.log('webSocket连接关闭');
            // 重连
            pThis.connectSocket(domain);
        };
        /* 接收服务器推送消息 */
        webSocket.onmessage = evt => {
            const data = JSON.parse(evt.data);
            // 保持链接
            if (data.kind === 'heartbeat' && data.code === 0) {
                console.log('success');
            }
            // console.log(data)
        };
        /* 连接发生错误时 */
        webSocket.onerror = (evt, e) => {
            console.log(evt);
            // pThis.connectSocket()
        };
    },
    // 初始化weosocket
    connectWebSocket(domain) {
        const pThis = this;

        if (pThis.keepaliveObj !== null) {
            clearInterval(pThis.keepaliveObj);
        }

        if (domain) {
            this.host = domain;
        }

        pThis.webSocket = new WebSocket(pThis.host);
        // 保存一份信息在window窗口
        window.webSocket = pThis.webSocket;

        // websocket 事件
        pThis.webSocket.onopen = pThis.webSocketOnOpen;
        pThis.webSocket.onmessage = pThis.webSocketOnMessage;
        pThis.webSocket.onclose = pThis.webSocketClose;
        pThis.webSocket.onerror = pThis.webSocketOnError;

        if (pThis.retryConnect != null) {
            clearInterval(pThis.retryConnect);
        }
    },
    // 链接成功
    webSocketOnOpen() {
        clearInterval(socket.retryConnect);
        socket.webSocketSend('{"kind": "heartbeat", "body": {}}');
        // 保持链接
        clearInterval(socket.keepaliveObj);
        socket.keepaliveObj = setInterval(() => {
            socket.webSocketSend('');
        }, 30000);
    },
    // 数据接收
    webSocketOnMessage(evt) {
        let data = {};
        try {
            data = JSON.parse(evt.data);
            // 数据过滤
            if (data.kind === 'heartbeat') { }

            if (data.code !== 0) { }

        } catch (error) {
            console.log(data, 'data error OnMessage');
            console.log(`webSocketOnMessage parse error:${error}`);
        }
        //处理,可以丢到redux,也可以直接返回,主要能让每个调用的地方能接受到返回的消息体
        return data
    },
    // 数据发送
    webSocketSend(agentData) {
        const {
            webSocket
        } = socket;
        console.log(webSocket)
        // 0: 对应常量CONNECTING(numeric value 0),
        // 正在建立连接连接, 还没有完成。 The connection has not yet been established.
        // 1: 对应常量OPEN(numeric value 1),
        // 连接成功建立, 可以进行通信。 The WebSocket connection is established and communication is possible.
        // 2: 对应常量CLOSING(numeric value 2)
        // 连接正在进行关闭握手, 即将关闭。 The connection is going through the closing handshake.
        // 3: 对应常量CLOSED(numeric value 3)
        // 连接已经关闭或者根本没有建立。 The connection has been closed or could not be opened.
        if (webSocket.readyState === 1) {
            socket.webSocket.send(agentData);
        } else {
            console.log('state', webSocket.readyState)
            // socket.connectWebSocket();
            // socket.webSocketSend(agentData);
        }
    },
    // 关闭
    webSocketClose(evt) {
        console.log(`connection closed (${evt.code})`);
        // 错误次数少于10时重连
        if (socket.errorCount <= 10) {
            socket.connectWebSocket();
            socket.errorCount++;
        } else {
            // 1分钟连一次
            socket.retryConnect = setInterval(() => {
                socket.connectWebSocket();
            }, 60000);
        }
    },
    // 错误
    webSocketOnError(evt, e) {
        console.log(`connection closed (${evt.code})`);
    },
};

export default socket;


4.ws使用可能遇到问题

ws关闭状态码表:大概归结网络错误、长时间连接和服务器发生错误(由于数据量过大,遭受攻击等) 移动端:js在手机熄屏后会中断,在唤醒之后js会继续执行。所以设置在js中的定时发送心跳包的功能在手机熄屏后就没法执行了。熄屏时间过长,这个时候链接就会被服务端强制断开,并且大部分手机在熄屏时,websocket连接就已经断开了。方法如下:

document.addEventListener('visibilitychange', function () {
    if (document.visibilityState == 'hidden') {
        that.hiddenTime = new Date().getTime()	//记录页面隐藏时间
    } else {
        let visibleTime = new Date().getTime();
        if ((visibleTime - that.hiddenTime) / 1000 > 10) {	//页面再次可见的时间-隐藏时间>10S,重连
            typeof that.ws.close == 'function' && that.ws.close();    //先主动关闭连接
            setTimeout(function () {
                that.openSocket()    //打开连接,使用的vuejs,这是websocket的连接方法
            }, 3000);    //3S后重连
        } else {
            //非重连处理
        }
    }
});

总结:大概思路都是在ws断开方法里设置定时重连机制