websocket深入理解原理及拓展

websocket深入理解

1.websocket原理

websocket原理在之前的博客中生动形象的描述过了,这里大概介绍一下

百度对于websocket的解释是:WebSocket是一种在单个TCP连接上进行全双工通信的协议

也就是说websocket是基于TCP协议可以进行双向通信的一个工具

并且websocket在建立服务器和客户端直接的连接的时候只需要完成一次握手,即可实现双向通信,不像发送ajax协议需要实现三次握手四次挥手,并且websocket的实时性,和控制开销,更好的连接状态的保持都是优于ajax

websocket的诞生是人们为了解决实时推送技术,因为之前全是使用的ajax轮询,也就是使用ajax技术在特定的时间内发送http请求,然后服务器返回数据给客户端的浏览器,这样的效率其实很慢,因为ajax发送出去的http请求可能含有较长的头部,然而真正有效的数据可能仅仅是后面那一部分,所以浪费了服务器带宽,而且比较新一点的技术做轮询的是Comet,这种技术实现了双向通信,但是依然是含有较长的头部,浪费带宽

2.websocketAPI

补充

补充一点,websocket使用的是http的101状态码建立连接,如下图:

_blank

再补充一点,部分浏览器可能不支持websocket接口,你可以在浏览器中尝试实例,目前现代主流浏览器都支持websocket: Chrome, Mozilla, Opera 和 Safari

API
1.创建一个websocket实例
  // 打开一个 web socket
  var ws = new WebSocket("ws://后端提供的websocket地址");
2.开启websocket并发送数据给后端地址
               ws.onopen = function()
               {
                  // Web Socket 已连接上,使用 send() 方法发送数据
                  ws.send("发送数据");
                  alert("数据发送中...");
               };
                

onopen方法为打开websocket连接,其中ws为上面创建的websocket的实例

send方法为发送数据的方法,其中本次发送字符串"发送数据"内容

3.通过websocket接受后端发送过来的数据
               ws.onmessage = function (evt) 
               { 
                  var received_msg = evt.data;
                  alert("数据已接收...");
               };
                

之前也说过,websocket为双向通信,有发即有收

onmessage函数为接受数据,在函数中可传入evt行参来接受数据

4.websocket关闭函数

为什么要说websocket关闭函数?因为只有在触发websocket关闭的时候才会触发的函数(是不是有点废话了)

为什么这么说呢?关闭可能存在很多种情况,后端关闭,前端关闭,通信关闭都会触发

               ws.onclose = function()
               { 
                  // 关闭 websocket
                  alert("连接已关闭..."); 
               };
5.自测浏览器是否支持websocket

这个应该在开始说的,一般现代浏览器都支持websocket,在MDN上面也有兼容图,我就直接贴在这里了

websocket深入理解原理及拓展_第1张图片

当然你也可以用代码的形式来检验你的浏览器是否支持websocket

 		if ("WebSocket" in window)
            {
               alert("您的浏览器支持 WebSocket!");
            }else {
                alert("您的浏览器不支持 WebSocket!")
            }

当然,你也可以看出来,如果说浏览器支持websocket,即浏览器window对象中含有websocket对象

3.简单的websocket测试

你可以用以下代码来进行websocket的自测

可以打开调试工具来监听websocket

注意:此代码的自测网站会将你发送的websocket请求全部以websocket形式发送给你,所以,发送我写了循环9999次,所以接受也会有9999条数据打印到控制台

直接贴代码:


<html>
   <head>
   <meta charset="utf-8">
   <title>websocket测试title>
        <div id="message">div>
      <script type="text/javascript">
         function WebSocketTest()
         {
            if ("WebSocket" in window)
            {
               alert("您的浏览器支持 WebSocket!");
               
               // 打开一个 web socket
               var ws = new WebSocket("ws://echo.websocket.org");
                
               ws.onopen = function()
               {
                  // Web Socket 已连接上,使用 send() 方法发送数据
                  for (let index = 0; index < 9999; index++) {
                    ws.send("发送数据"+index);
                      
                  }
                  alert("数据发送中...");
               };
                
               ws.onmessage = function (evt) 
               { 
                  var received_msg = evt.data;
                //   document.getElementById('message').innerHTML += received_msg + '
';
console.log(evt); // alert("数据已接收..."); }; ws.onclose = function() { // 关闭 websocket alert("连接已关闭..."); }; } else { // 浏览器不支持 WebSocket alert("您的浏览器不支持 WebSocket!"); } }
script> head> <body> <div id="sse"> <a href="javascript:WebSocketTest()">运行 WebSocketa> div> body> html>

4.websocket进阶

简单的websocket已经完成,但是在实际工作中,我们使用websocket需要考虑到多方面应用

以下,借用公司大佬写的websocket封装的文件,来做详细讲解

1.与后台数据约定,加密传输

发送数据前,需要在websocket中传入后台指定的参数并且加密传输

例如:

//websocket
const tio = {}
tio.ws = {};

/**
 * {
	 ws_protocol wss or ws ,
	 ip,
	 port,
	 paramStr,加在ws url后面的请求参数,形如:name=张三&id=12
	 heartbeatTimeout,心跳时间 单位:毫秒
	 reconnInterval,重连间隔时间 单位:毫秒
	 binaryType 'blob' or 'arraybuffer';//arraybuffer是字节
	 handler 处理器
 }
 */
tio.ws = function({
	ws_protocol = 'ws', // 发送协议
	ip = '192.168.1.10', // ip
	port = '9326', // 端口号
	heartbeatTimeout = 5000, // 心跳时间
	reconnInterval = 1000, // 重连间隔
	binaryType = 'blob', // 数据格式
	paramStr, // 携带传过来的参数
	handler
}) {
....
}

在此函数中,给定了函数默认值,并且paramStr为前端携带过来的参数

前端vue页面也封装了一个函数:

    initIM() {
      var that = this;
      var nonce = randomNum(1, 10000),
        signTimestamp = new Date().getTime(); // 创建随机数字字符串
      var signature = sha1("XXXX" + nonce + signTimestamp); // 加密算法
      that.tiows = new that.$tio.ws({
        paramStr: {
          appKey: "XXXX",
          nonce: nonce,
          signTimestamp: signTimestamp,
          signature: signature,
          userId: that.loginUser.id,
          groups: "",
          identity: "service"
        },
        handler: {
          onopen: function(event, ws) {},
          onclose: function(event, ws) {},
          onerror: function(event, ws) {},
          ping: function(ws) {
            ws.send(JSON.stringify({ chatType: "heartbeat" })); // 心跳开始发送
          },
          onmessage: function(data, ws) {
            that.loading = false;
            console.log("接收消息", data);
            // 成功接收数据后,给表格注入数据
              // 将接收到的数据存储至store中,实现数据共享
            that.$store.commit('common/updataMymsg',data.data)
            for (let i = 0; i < that.tableData.length; i++) {
              that.tableData[i].address = data.data;
              
            }
          }
        }
      });
      that.tiows.connect();
    },

与后端约定的数据(这里我用XXXX代替,不是appkey)与signTimestamp和nonce后进行加密,然后传输给后台通过验证,后台再将验证完的数据处理再返回新的数据给我

2.传输数据之前,需要将数据进行处理

因为我们是通过websocket形式,以url地址将数据发送过去,即在封装的websocket方法中需要将其方法进行处理

	this.ip = ip 
	this.port = port 
	this.url = ws_protocol + '://' + ip + ':' + port // 拼接url字符串
	this.binaryType = binaryType

	if (paramStr) {
		this.url = addUrlParam(this.url, paramStr)
		this.reconnUrl = this.url + "&"
	} else {
		this.reconnUrl = this.url + "?"
	}
	this.reconnUrl += "reconnect=true";

	
	var addUrlParam = function(url = '', params = {}) {
	if (url && JSON.stringify(params) != "{}") {
		var paramArray = [];
		Object.keys(params).forEach(function(key) {
			var param = key + '=' + params[key]
			paramArray.push(param)
		});
		var url2 = encodeURI(url + '?' + paramArray.join('&'));
		var enurl = encodeURI(url2);
		return enurl
	} else {
		return url;
	}
}

上面代码执行操作了后将数据从url上面扒下来提取成为键值对的形式

3.设置websocket心跳,确保数据的实时性

为了确保数据的实时性和准确性,需要在websocket上面增加心跳

确保websocket在一定的时间内没收到信息再重新发送请求

this.handler = handler
	this.heartbeatTimeout = heartbeatTimeout
	this.reconnInterval = reconnInterval

	this.lastInteractionTime = function() {
		if (arguments.length == 1) {
			this.lastInteractionTimeValue = arguments[0]
		}
		return this.lastInteractionTimeValue
	}

	this.heartbeatSendInterval = heartbeatTimeout / 2

上面的代码规定了心跳发送时间,而真正的发送心跳的时间将在websocket发送数据时执行

4.使用websock发送数据
this.connect = function(isReconnect) {
		var _url = this.url;
		if (isReconnect) {
			_url = this.reconnUrl;
		}
		try {
			var ws = new WebSocket(_url);
			this.ws = ws
		} catch (err) {
			console.log("错误", err)
		}

		ws.binaryType = this.binaryType; // 'arraybuffer'; // 'blob' or 'arraybuffer';//arraybuffer是字节
		var self = this
		ws.onopen = function(event) {
			self.handler.onopen.call(self.handler, event, ws)
			self.lastInteractionTime(new Date().getTime())
			self.pingIntervalId = setInterval(function() {
				self.ping(self)
			}, self.heartbeatSendInterval) // 开启ws并设置心跳时间
		}
		ws.onmessage = function(event) {
			if (event.data) {
				var data = JSON.parse(event.data)
				console.log(data);
				if (data.code != 200) {
					self.handler.onmessage.call(self.handler, data, ws)
				}
				if (data.code == 401) {
					clearInterval(self.pingIntervalId)
				}
			}
			self.lastInteractionTime(new Date().getTime())
		}
		ws.onclose = function(event) {
			clearInterval(self.pingIntervalId) // clear send heartbeat task

			try {
				self.handler.onclose.call(self.handler, event, ws)
			} catch (error) {}

			//self.reconn(event)
		}
		ws.onerror = function(event) {
			clearInterval(self.pingIntervalId)
			self.handler.onerror.call(self.handler, event, ws)
		}

		return ws
	}

	this.reconn = function(event) {
		var self = this
		setTimeout(function() {
			var ws = self.connect(true)
			self.ws = ws
		}, self.reconnInterval)
	}

	this.ping = function() {
		var iv = new Date().getTime() - this.lastInteractionTime(); // 已经多久没发消息了
		// 单位:秒
		if ((this.heartbeatSendInterval + iv) >= this.heartbeatTimeout) {
			this.handler.ping(this.ws)
		}
	};

	this.send = function(data) {
		this.ws.send(data);
	};
}

这样,封装的一个websocket的方法就完成了,确保了数据传输的安全性和稳定性,虽然官方文档介绍websocket很粗略,但是数据传输确实需要考虑到很多方面

欢迎关注我的微信公众号一起学习前端知识

5.参考文档

  • HTML5websocket|菜鸟教程

  • MDN|websocketAPI

  • websockt自测网站|websocket.org

你可能感兴趣的:(前端技巧)