HTML5 WebSocket 协议

1. 概述

1.1 说明

WebSocket:是HTML5开始提供的一种在单个TCP连接上进行全双工通讯的协议。

WebSocket原理是使用JavaScript调用浏览器的API发出一个WebSocket请求至服务器,经过一次握手,和服务器建立了TCP通讯,因为它本质上是一个TCP连接,所以数据传输过程中稳定性强,数据传输量比较小。

1.2 属性值

objSocket.readyState:只读属性readyState表示WebSocket连接状态,状态值分别为0,1,2,3。

           0:表示连接尚未建立(connecting)。

           1:表示连接已建立,可以进行通信(open)。

           2:表示连接正在进行关闭(closing)。

           3:表示连接已经关闭或者连接不能打开(closed)。

objSocket.bufferedAmount:只读属性 bufferedAmount 已被 send() 放入正在队列中等待传输,但是还没有发出的 UTF-8 文本字节数。

1.3 事件

open:objSocket.onopen;连接建立时触发,即握手成功触发的事件。

message:objSocket.onmessage;客户端接收服务端数据时触发。

error:objSocket.onerror;通信发生错误时触发。

close:objSocket.onclose;连接关闭时触发。

1.4 方法

objSocket.send():使用连接发送数据。

objSocket.close():关闭连接。

 2. WebSocket实例

 2.1 webSocket使用

2.1.1 说明

WebSocket构造函数:用于创建一个WebSocket实例,执行后,客户端就会与服务端连接;var objSocket= new WebSocket('ws://localhost:8888/Demo'); 。

WebSocket.readyState:readyState属性返回实例对象的当前状态;if(objSocket.readyState ==WebSocket.CONNECTING){}; 。

WebSocket.onopen:用于制定连接成功后的回调函数; objSocket.onopen = function(evt) { console.log("Connection open ..."); objSocket.send("Hello WebSockets!"); }; 或者 objSocket.addEventListener('open', function (event) { objSocket.send('Hello Server!'); }); 

WebSocket.onclose:用于制定连接关闭后的回调函数; objSocket.onclose = function(event) { var code = event.code; var reason = event.reason; var wasClean = event.wasClean; }; 或者使用监听【objSocket.addEventListener('close',function(event){});】。

WebSocket.onmessage:用于指定收到服务器数据后的回调函数; objSocket.onmessage = function(event) { var data = event.data; }; 或者使用监听【objSocket.addEventListener('message',function(event){console.log(event.data)});】。其中服务器数据有可能是文本,也有可能是二进制数据,可使用(typeof event.data == String) 或 (event.data instanceof ArrayBuffer)进行判断数据类型。

WebSocket.onerror:用于指定报错时的回调函数; objSocket.onerror = function(event) {}; 或者 objSocket.addEventListener("error", function(event) {}); 。

var wsServer = 'ws://localhost:8888/Demo'; 
var objSocket= new WebSocket(wsServer); 
objSocket.onopen = function (evt) { onOpen(evt) }; 
objSocket.onclose = function (evt) { onClose(evt) }; 
objSocket.onmessage = function (evt) { onMessage(evt) }; 
objSocket.onerror = function (evt) { onError(evt) }; 
function onOpen(evt) { 
//可多个websocket访问(订阅),使用objSocket.send(订阅访问参数)进行发送 console.log(
"Connected to WebSocket server."); } function onClose(evt) { console.log("Disconnected"); } function onMessage(evt) {
//websocket返回数据信息处理(可处理open中send的多个订阅访问) console.log(
'Retrieved data from server: ' + evt.data); } function onError(evt) { console.log('Error occured: ' + evt.data); }

WebSocket.send():send方法用于向服务器发送数据,可发送文本/Blob/ArrayBuffer数据。在发送订阅时若使用到json对象参数,需进行JSON.stringify()格式化参数。

//******************发送文本****************************
objSocket.send("Hello WebSockets!");
//******************发送Blob数据************************
var file = document.querySelector('input[type="file"]').files[0];
objSocket.send(file);
//******************发送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];
}
objSocket.send(binary.buffer);

WebSocket.bufferedAmount:表示还有多少字节的二进制数据没有发送出去,可以用来判断发送是否结束。

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

 2.2 webSocket握手

2.2.1 说明

    建立一个websocket连接,客户端浏览器首先要向服务器发起一个HTTP请求,这个请求和通常的HTTP请求不同,包含了一些附加头信息,其中附加同信息"Upgrade:WebSocket"表明这是一个申请协议升级的HTTP请求,服务器端解析这些附加的头信息然后产生应答信息返回给客户端,客户端和服务端的WebSocket连接建立,双方通过这个连接通道自由的传递信息,并且这个连接会持续存在直到客户端或者服务器端的某一方主动的关闭连接 。

WebSocket使用ws或wss的统一资源标识符,类似https,其中wss表示在TLS之上的websocket。如:

ws://webapi.dev.example/wss/
wss://webapi.dev.example/wss/

websocket使用和HTTP相同的TCP端口,可以绕过大多数防火墙的限制。默认情况下,websocket协议使用80端口,运行在TLS之上时,默认使用443端口。

2.2.2 详解

 websocket握手请求示例如下:

客户端请求:

GET / HTTP/1.1
Upgrade: websocket  说明:必须设置为Websocket,表示希望升级到Websocket协议
Connection: Upgrade  说明:必须设置为Upgrade,表示客户端希望升级连接
Host: example.com
Origin: http://example.com  说明:此项为可选,表示在浏览器中发起此websocket连接所在的页面,只包含协议和主机名称。
Sec-WebSocket-Key: sN9cRrP/n9NdMgdcy2VJFQ==  说明:随机的字符串,服务器会用这些数据来构造一个SHA-1的信息摘要。
                              把"Sec-WebSocket-Key"加上一个特殊字符串"258EAFA5-E914-47DA-95CA-C5AB0DC85B11",然后计算SHA-1摘要,之后进行BASE-64编码,将结果作为"Sec-WebSocket-Accept"头的值返回给客户端,
                              如此操作,可以尽量避免普通HTTP请求被误认为Websocket协议。 Sec-WebSocket-Version: 13 说明:标志支持的websocket版本

服务器回应:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: fFBooB7FAkLlXgRSz0BT3v4hq5s=
Sec-WebSocket-Location: ws://example.com/

 2.3 webSocket 心跳

2.3.1 说明

     在使用websocket过程中,可能会出现网络断开的情况(如信号不好或者网络临时关闭等),此时websocket连接已经断开,而浏览器不会执行websocket的onclose方法,我们无法知道连接是否已经断开,也就无法进行重新连接操作。故为了解决此问题,需使用websocket心跳进行检测websocket连接。

2.3.2 实例(vue文件)

  依赖模块为 websocket ,页面引入使用  let W3CWebSocket = require('websocket').w3cwebsocket ;

 export default {
       data(){
           return{
               objWS: null,//WebSocket实例
               wsUrl: "wss://xxx.xxx.xx",//WebSocket地址
               lockReconnect: false,//避免WebSocket重复连接
               heartJson: JSON.stringify({"method":"server.ping","params":[], "id":1}),//WebSocket心跳订阅
               heartCheck: this.socketHeartCheck()//心跳检测对象
           }
       },
        mounted: function() {
            this.$nextTick(() => {
                this.socketDisconnect();
                this.createWebSocket(this.$data.wsUrl);
            });
        },
        methods:{
            //  region WebSocket相关基础配置(创建/心跳/关闭等方法集合)
            /**
             * WebSocket连接断开
             **/
            socketDisconnect:function() {
                let self = this;
                if (window.onbeforeunload) {
                    window.onbeforeunload = function() {
                        self.$data.objWS.close();
                        self.$data.socketSubscribe =false;
                    };
                }

                if (window.onunload) {
                    window.onunload = function() {
                        self.$data.objWS.close();
                        self.$data.socketSubscribe =false;
                    };
                }
            },
            /**
             * WebSocket心跳检测
             **/
            socketHeartCheck:function(){
                let rootThis = this;
                let socketHeart = {
                    timeout: 10000,
                    timeoutObj: null,
                    serverTimeoutObj: null,//多个订阅时此对象相关需删除
                    reset: function () {
                        clearTimeout(this.timeoutObj);
                        clearTimeout(this.serverTimeoutObj);
                        return this;
                    },
                    start: function () {
                        let self = this;
                        this.timeoutObj = setTimeout(function () {
                            rootThis.$data.objWS.send(rootThis.$data.heartJson);
                            self.serverTimeoutObj = setTimeout(function () {
                                rootThis.$data.objWS.close();
                            }, self.timeout)
                        },self.timeout)
                    }
                };
                return socketHeart;
            },
            /**
             * 创建WebSocket
             */
            createWebSocket:function (url) {
                this.$data.objWS = new WebSocket(url);
                this.handleWebSocket();
            },
            /**
             * WebSocket相关连接操作
             **/
            handleWebSocket: function(){
                let self =this;
                self.$data.objWS.onopen = function (evt) {
                    self.$data.heartCheck.reset().start();
                    self.socketOpenSubscribe(evt);
                };
                self.$data.objWS.onclose = function (evt) {
                    self.socketClose(evt);
                };
                self.$data.objWS.onmessage = function (evt) {
                    self.socketMessage(evt);
                };
                self.$data.objWS.onerror = function (evt) {
                    self.socketError(evt)
                };
            },
            /**
             *关闭WebSocket连接
             * @param evt
             */
            socketClose:function (evt) {
                this.socketReConnect(this.$data.wsUrl);
            },
            /**
             * WebSocket连接错误
             * @param evt
             */
            socketError:function (evt) {
                this.socketReConnect(this.$data.wsUrl);
            },
            /**
             * WebSocket重新连接
             * @param url
             */
            socketReConnect:function (url) {
                let self = this;
                if (self.$data.lockReconnect) return;
                self.$data.lockReconnect = true;
                setTimeout(function () {     //没连接上会一直重连,设置延迟避免请求过多
                    self.createWebSocket(url);
                    self.$data.lockReconnect = false;
                }, 2000);
            },
            /**
             * WebSocket订阅
             * @param evt
             */
            socketOpenSubscribe:function (evt) {
                //使用send发送WebSocket访问订阅
            },
            /**
             * WebSocket服务端返回信息
             * @param evt
             */
            socketMessage:function (evt) {
                //evt.data获取数据信息
            }
        },
        beforeDestroy: function() {
            this.socketDisconnect();
        }
    }
原文地址:https://www.cnblogs.com/ajuan/p/9914447.html