封装WebSocket(建立链接、主动关闭)
一、前言
近期项目里需做一个在线聊天功能,就想要在对话的时候建立socket链接。又因为聊天只是其中一个部分,在它外面还有一些全局的消息通知需要接收,因此也需要建立socket链接。在该项目里不仅一处用到了socket,就想着封装一个socket的,可以在项目里调用。
之前也用过一次websocket,但那次是直接用的socke.io,我也忘了这次为啥没有继续使用,对这个也一知半解,似懂非懂,先一点一点记起来。具体是介绍和解释就不写了,主要写几个帮助理解的部分。
二、HTML5 WebSocket
WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。
2.1、创建WebSocket 实例
WebSocket 对象作为一个构造函数,用于新建 WebSocket 实例。
var Socket = new WebSocket(url,[protocol]);
以上代码用于创建 WebSocket 对象,其中的第一个参数 url, 即为指定连接的 URL。第二个参数 protocol 是可选的,指定了可接受的子协议。一般来说,大多没有具体要求的,就只用写url即可。
2.2 websocket属性
例如:var ws = new WebSocket('ws://1xx.xxx.xxx.xxx:8080/ws');
打印出创建的socket对象,内容如下,这些也正是wensocket的属性。
binaryType: "blob" //返回websocket连接所传输二进制数据的类型,如果传输的是Blob类型的数据,则为"blob",如果传输的是Arraybuffer类型的数据,则为"arraybuffer"
bufferedAmount: 0 //为只读属性,用于返回已经被send()方法放入队列中但还没有被发送到网络中的数据的字节数。
extensions: ""
onclose: ƒ () //连接关闭时触发
onerror: ƒ () //通信发生错误时触发
onmessage: ƒ (e) //客户端接收服务端数据时触发,e为接受的数据对象
onopen: ƒ () //连接建立时触发
protocol: "" //用于返回服务器端选中的子协议的名字;这是一个在创建websocket对象时,在参数中指定的字符串。
protocols
readyState: 1 //返回值为当前websocket的链接状态
url: "ws://1xx.xxx.xxx.xxx:8080/ws" //返回值为当构造函数创建WebSocket实例对象时URL的绝对路径。
所有属性讲解清单:https://developer.mozilla.org/en-US/docs/Web/API/WebSocket
2.3 websocket属性之readyState
readyState返回当前websocket的链接状态,共有4种。可根据具体项目的需求来利用此状态,写对应的需求。
CONNECTING:值为0,表示正在连接。
OPEN: 值为1,表示连接成功,可以通信了。
CLOSING: 值为2,表示连接正在关闭。
CLOSED: 值为3,表示连接已经关闭,或者打开连接失败。
2.4 websocket的方法
假定我们使用了上述代码创建的websocket对象,下面两个方法可以在任意socket事件中调用,根据自己项目需求而定。
ws.send() :使用连接发送数据,可以发送你想要发送的各种类型数据,如Blob对象、ArrayBuffer 对象、基本或复杂的数据类型等;
ws.send('消息');
//发送对象需要格式转换,接受数据同理
ws.send(JSON.stringify(data));
ws.close() : 关闭连接,用户可以主动调取此方法,来关闭连接。
三、封装websocket
可在项目中定义一个socket.js文件,在需要建立socket的页面引入此js文件,即可在一个项目中创建多个socket连接。
var webSocket = null;
var globalCallback = null;//定义外部接收数据的回调函数 //初始化websocket
function initWebSocket(url) {
if ("WebSocket" in window) {
webSocket = new WebSocket(url);//创建socket对象
console.log(webSocket)
} else {
alert("该浏览器不支持websocket!");
}
//打开
webSocket.onopen = function() {
webSocketOpen();
};
//收信
webSocket.onmessage = function(e) {
webSocketOnMessage(e);
};
//关闭
webSocket.onclose = function() {
webSocketClose();
};
//连接发生错误的回调方法
webSocket.onerror = function() {
console.log("WebSocket连接发生错误");
};
} //连接socket建立时触发
function webSocketOpen() {
if (e === "LOGIN") {
const data = {
type: "CONNECT",
token: sessionStorage.getItem("token") || ""
};
sendSock(data, function() {});
}
console.log("WebSocket连接成功");
} //客户端接收服务端数据时触发,e为接受的数据对象
function webSocketOnMessage(e) {
const data = JSON.parse(e.data);//根据自己的需要对接收到的数据进行格式化
globalCallback(data);//将data传给在外定义的接收数据的函数,至关重要。
/*在此函数中还可以继续根据项目需求来写其他东西。 比如我的项目里需要根据接收的数据来判断用户登录是否失效了,此时需要关闭此连接,跳转到登录页去。*/
} //发送数据
function webSocketSend(data) {
webSocket.send(JSON.stringify(data));//在这里根据自己的需要转换数据格式
} //关闭socket
function webSocketClose() {
//因为我建立了多个socket,所以我需要知道我关闭的是哪一个socket,就做了一些判断。
if (
webSocket.readyState === 1 &&
webSocket.url === "ws://1xx.xx.xx.xxx:8088/ws"
) {
webSocket.close();//这句话是关键,之前我忘了写,一直没有真正的关闭socket
console.log("对话连接已关闭");
}
} //在其他需要socket地方调用的函数,用来发送数据及接受数据
function sendSock(agentData, callback) {
globalCallback = callback;//此callback为在其他地方调用时定义的接收socket数据的函数,此关重要。
//下面的判断主要是考虑到socket连接可能中断或者其他的因素,可以重新发送此条消息。
switch (webSocket.readyState) {
//CONNECTING:值为0,表示正在连接。
case webSocket.CONNECTING:
setTimeout(function() {
webSocketSend(agentData, callback);
}, 1000);
break;
//OPEN:值为1,表示连接成功,可以通信了。
case webSocket.OPEN:
webSocketSend(agentData);
break;
//CLOSING:值为2,表示连接正在关闭。
case webSocket.CLOSING:
setTimeout(function() {
webSocketSend(agentData, callback);
}, 1000);
break;
//CLOSED:值为3,表示连接已经关闭,或者打开连接失败。
case webSocket.CLOSED:
// do something
break;
default:
// this never happens
break;
}
} //将初始化socket函数、发送(接收)数据的函数、关闭连接的函数export出去
export default {
initWebSocket,
webSocketClose,
sendSock
};
四、使用此socketJs文件(主要在vue项目中使用封装的socket)
因为我项目里涉及多出页面需要使用socket,所以我将此文件在main.js中,将其定义在原型上,使其在每个 Vue 的实例中均可使用。不用单独在每个页面都引入一次。
1、部分main.js代码
import socketApi from "./tool/socket";//找到封装的socket.js文件 Vue.prototype.socketApi = socketApi;//将其挂在原型上,这样 $socketApi就在所有的 Vue 实例中可用了。
2、某一vue页面
<template>
<div>在此页面使用封装的socket</div>
</template> <script>
export default {
name: "Message",
data() {
return {
wsUrl: " ws://172.16.10.140:8088/ws",//定义socket连接地址
wsType: "CONNECT"
};
},
methods: {
// 接收socket回调函数返回数据的方法
getConfigResult(res) {
console.log(res);//服务端返回的数据
},
websocketSend(data) {
//data为要发送的数据,this.getConfigResult为回调函数,用于在此页面接收socket返回的数据。
//至关重要!我一开始没写这个,就蒙了,咋才能到拿到回来的数据呢。
this.socketApi.sendSock(data, this.getConfigResult);
},
},
beforeRouteLeave(to, from, next) {
//在离开此页面的时候主动关闭socket
this.socketApi.webSocketClose();
next();
}, created() {
//建立socket连接
this.socketApi.initWebSocket(this.wsUrl);
//data为和后端商量好的数据格式
const data = {
type:this.wsType,
msg: "说的话",
};
this.websocketSend(data);
}
};
</script> <style lang="scss" scoped>
</style>
五、遇到的问题
1、websocket Failed to execute 'send' on 'WebSocket': Still in CONNECTING state
一开始我只是初始化了socket,并没有发送消息过去。于是websocket 实例化后(我以为的建立成功了),就立马发送数据,就报了这个错误,说“正在连接”。其实我以为的建立成功是我看到了我在连接成功后的回调函数里打印的一句话:“WebSocket连接成功”,到底是什么正在连接还是连接成功我也不知道,所以需要在发送数据前,利用socket.readyState先判断此时的连接状态。
function sendSock(agentData, callback) {
globalCallback = callback;//此callback为在其他地方调用时定义的接收socket数据的函数,此关重要。 //此处先判断socket连接状态 switch (webSocket.readyState) {
//CONNECTING:值为0,表示正在连接。
case webSocket.CONNECTING:
setTimeout(function() {
webSocketSend(agentData, callback);
}, 1000);
break;
//OPEN:值为1,表示连接成功,可以通信了。
case webSocket.OPEN:
webSocketSend(agentData);
break;
//CLOSING:值为2,表示连接正在关闭。
case webSocket.CLOSING:
setTimeout(function() {
webSocketSend(agentData, callback);
}, 1000);
break;
//CLOSED:值为3,表示连接已经关闭,或者打开连接失败。
case webSocket.CLOSED:
// do something
break;
default:
// this never happens
break;
}
}
2、后端要求在建立连接的时候先发送一条数据,用于确定当前连接的状态
在此项目里,因为建立的socket需要有不同的连接用途,所以后端要在建立连接的时候给它发消息,确定建立socket,等此条消息发送完以后,再发送一条数据确定开始对话的状态。当时我在疑惑,我都socket还没建立,怎么给你发消息呢,我在什么时候发给你呢?
解决办法是在open函数里,发送一条消息过去。
function webSocketOpen() {
//在此次定义好需要传过去的数据,先发送一个数据过去,data为与后端协议的数据类型
const data = {
type: "CONNECT",
token: sessionStorage.getItem("token") || ""
};
sendSock(data, function() {});//调用发送数据的函数
console.log("WebSocket连接成功");
}
六、其他未考虑的地方
1、因为socket连接也会因为各种因素中断,所以有人写了“保活”:保活的原理-->心跳,前端每隔一段时间发送一段约定好的message给后端,后端收到后返回一段约定好的message给前端,如果多久没收到前端就调用重连方法进行重连。详见:https://www.jianshu.com/p/a4eacaf8de17
2、关于socket.io使用介绍,https://www.cnblogs.com/dreamsqin/p/12018866.html
封装WebSocket(建立链接、主动关闭)的更多相关文章
- PreparedStatement实现表数据的增删改 & 封装数据库链接和关闭操作
PreparedStatement实现表数据的增删改 PreparedStatementUpdateTest package com.aff.PreparedStatement; import jav ...
- [Openfire]使用WebSocket建立Openfire的客户端
近日工作闲暇之余,对IM系统产生了兴趣,转而研究了IM的内容.找了半天,知道比较流行的是Openfire的系统,Openfire有许多平台实现,由于我是做Web的,所以当然是希望寻找Web的实现.Op ...
- C#如何使用VS2010与SQL2008建立链接及初步调用(转)
关于VS2010与SQL2008建立链接及初步调用问题,网上参考的资料很多,我写这个博客,并非是做重复工作,也不是做搬运工.本文将以一种初学者的角度,去完成从数据库建立,到VS2010与SQL中的数据 ...
- spring boot 集成 websocket 实现消息主动推送
spring boot 集成 websocket 实现消息主动 前言 http协议是无状态协议,每次请求都不知道前面发生了什么,而且只可以由浏览器端请求服务器端,而不能由服务器去主动通知浏览器端,是单 ...
- 主动关闭 tcp fin-wait-2 time-wait 定时器
后面整理相关信息 //后面整理相关信息 /* * This function implements the receiving procedure of RFC 793 for * all state ...
- SqlServer2012 数据库的同步之SQL JOB + 建立链接服务器
文章参考百度过的文章,现在忘了具体哪篇,感谢其分享,这里根据自己的操作和遇到的问题整理一下. 需求:在两个不同的SQL SERVER 2012的服务器之间进行数据访问和更新.我们需 ...
- 关于TCP主动关闭连接中的wait_timeout
首先我们先来回顾一下tcp关闭连接的过程: 假设A和B连接状态为EST,A需要主动关闭: A发送FIN给B,并将状态更改为FIN_WAIT1, B接收到FIN将状态更改为CLOSE_WAIT,并回复A ...
- 测试Websocket建立通信,使用protobuf格式交换数据
接到一个应用测试,应用实现主要使用websocket保持长链接,使用protobuf格式交换数据,用途为发送消息,需要我们测试评估性能,初步评估需要测试长链接数.峰值消息数以及长期运行稳定性 整体需求 ...
- TCP/IP协议的建立连接与关闭连接过程
一.建立连接(三次握手) 第一次握手:建立连接时,客户端发送SYN(seq=x)包到服务器,并进入SYN_SENT状态,等待服务器的确认.SYN:同步序列编号(Synchronize Sequence ...
随机推荐
- ResultMap和ResultType到底有什么区别?
转载请标明出处:https://www.cnblogs.com/Dreamice/ 首先,SQL语句执行后返回的结果可以使用 Map 存储,也可以使用 POJO 存储. 一.使用Map存储结果集 下面 ...
- 《Java 8实战》读书笔记系列——第三部分:高效Java 8编程(四):使用新的日期时间API
https://www.lilu.org.cn/https://www.lilu.org.cn/ 第十二章:新的日期时间API 在Java 8之前,我们常用的日期时间API是java.util.Dat ...
- 最近做的一个Spring Boot小项目,欢迎大家访问 http://39.97.115.152/
最近做的一个Spring Boot小项目,欢迎大家访问 http://39.97.115.152/,帮忙找找bug,网站里有源码地址 网站说明 甲壳虫社区(Beetle Community) 一个开源 ...
- Java2变量和运算符
课后作业:[必做题] 1√AB互换 已知a,b均是整型变量,写出将a,b两个变量中的值互换的程序.(知识点:变量和运算符综合应用) [必做题] package com.two; public clas ...
- linux入门系列17--邮件系统之Postfix和Dovecot
前文演示了通过Samba和NFS实现文件共享,本篇演示使用Postfix和Dovecot在局域网实现电子邮件收发系统. 电子邮件系统是我们日常生活和工作中非常重要的一个网络服务,在windows下收发 ...
- localstorage二次封装-模块模式
var db = function () { // 本地存储前缀,减少命名冲突 var prefix = 'ydb'; return { setPrefix: function (_prefix) { ...
- 7-11 jmu-python-分段函数&数学函数 (15 分)
本题要求计算下列分段函数f(x)的值(x为从键盘输入的一个任意实数): 输入格式: 直接输入一个实数x 输出格式: 在一行中按“f(x)=result”的格式输出,其中x与result都保留三位小数. ...
- 一步步打造自己的纯CSS单标签图标库
图标作为网页设计中的一部分,其在凸显网页重要元素特性,视觉交互.引导以及网页装饰等充当的角色作用举足轻重.由于图标普遍具有尺寸小的特点,在项目实践时不宜将每个图标作为单个图片元素进行加载,这会增加Ht ...
- jinja2的url_for 和数据块
1.静态文件引入:{{ url_for('static', filename='文件路径') }} 2.定义路由:{{ url_for('模块名.视图名',变量=参数) }} 3.定义数据块: ...
- python入门到放弃-基本数据类型之tuple元组
#概述 元组俗称不可变的列表,又称只读列表,是python的基本数据类型之一, 用()小括号表示,里面使用,逗号隔开 元组里面可以放任何的数据类型的数据,查询可以,循环可以,但是就是不能修改 #先来看 ...