封装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 ...
随机推荐
- 事务以及Spring的事务管理
一.什么是事务? 事务是逻辑上的一组操作,要么都执行,要么都不执行 二.事务的特性(ACID) 原子性: 事务是最小的执行单位,不允许分割.事务的原子性确保动作要么全部完成,要么完全不起作用: 一致性 ...
- JavaScript 预解析机制
首先我们来看一段代码: <script> console.log(a); var a = 10; </script> 此时运行结果为 为什么会显示undefined呢?这就 ...
- Vue组件传递数据
组件命名 1.字母全小写且必须包含一个连字符 my-componnect 2.使用 kebab-case(短横线分隔命名) 定义一个组件时,你也必须在引用这个自定义元素时使用 kebab-case,例 ...
- Web Scraper 高级用法——抓取属性信息 | 简易数据分析 16
这是简易数据分析系列的第 16 篇文章. 这期课程我们讲一个用的较少的 Web Scraper 功能--抓取属性信息. 网页在展示信息的时候,除了我们看到的内容,其实还有很多隐藏的信息.我们拿豆瓣电影 ...
- file_put_contents生成ansi文件
$line_body = array('张三','李四','王五'); $line_body = array_map(function ($element){return iconv('UTF-8', ...
- 前端进阶系列(三):HTML5新特性
HTML5 是对 HTML 标准的第五次修订.其主要的目标是将互联网语义化,以便更好地被人类和机器阅读,并同时提供更好地支持各种媒体的嵌入.HTML5 的语法是向后兼容的.现在国内普遍说的 H5 是包 ...
- PHP 深度理解preg_quote()函数
php手册上说,preg_quote()函数的作用是转义正则表达式字符.那么下面我们来深入了解下这个函数是怎么使用的: 说明:preg_quote()函数常和preg_replace()函数一起使用. ...
- Java中文件上传路径与路径修改相关问题(tomcat8.0+eclipse)
1.普通文件上传的路径: 通过getRealPath获取相关路径 String photoFolder =request.getServletContext().getRealPath("u ...
- Access Token 机制详解
我们在访问很多大公司的开放 api 的时候,都会发现这些 api 要求传递一个 access token 参数.这个参数是什么呢?需要去哪里获取这个 access token 呢? access to ...
- 日常破解--XCTF easy_apk
一.题目来源 来源:XCTF社区安卓题目easy_apk 二.破解思路 1.首先运行一下给的apk,发现就一个输入框和一个按钮,随便点击一下,发现弹出Toast验证失败.如下图所示: ...