一、前言

  近期项目里需做一个在线聊天功能,就想要在对话的时候建立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(建立链接、主动关闭)的更多相关文章

  1. PreparedStatement实现表数据的增删改 & 封装数据库链接和关闭操作

    PreparedStatement实现表数据的增删改 PreparedStatementUpdateTest package com.aff.PreparedStatement; import jav ...

  2. [Openfire]使用WebSocket建立Openfire的客户端

    近日工作闲暇之余,对IM系统产生了兴趣,转而研究了IM的内容.找了半天,知道比较流行的是Openfire的系统,Openfire有许多平台实现,由于我是做Web的,所以当然是希望寻找Web的实现.Op ...

  3. C#如何使用VS2010与SQL2008建立链接及初步调用(转)

    关于VS2010与SQL2008建立链接及初步调用问题,网上参考的资料很多,我写这个博客,并非是做重复工作,也不是做搬运工.本文将以一种初学者的角度,去完成从数据库建立,到VS2010与SQL中的数据 ...

  4. spring boot 集成 websocket 实现消息主动推送

    spring boot 集成 websocket 实现消息主动 前言 http协议是无状态协议,每次请求都不知道前面发生了什么,而且只可以由浏览器端请求服务器端,而不能由服务器去主动通知浏览器端,是单 ...

  5. 主动关闭 tcp fin-wait-2 time-wait 定时器

    后面整理相关信息 //后面整理相关信息 /* * This function implements the receiving procedure of RFC 793 for * all state ...

  6. SqlServer2012 数据库的同步之SQL JOB + 建立链接服务器

         文章参考百度过的文章,现在忘了具体哪篇,感谢其分享,这里根据自己的操作和遇到的问题整理一下.      需求:在两个不同的SQL SERVER 2012的服务器之间进行数据访问和更新.我们需 ...

  7. 关于TCP主动关闭连接中的wait_timeout

    首先我们先来回顾一下tcp关闭连接的过程: 假设A和B连接状态为EST,A需要主动关闭: A发送FIN给B,并将状态更改为FIN_WAIT1, B接收到FIN将状态更改为CLOSE_WAIT,并回复A ...

  8. 测试Websocket建立通信,使用protobuf格式交换数据

    接到一个应用测试,应用实现主要使用websocket保持长链接,使用protobuf格式交换数据,用途为发送消息,需要我们测试评估性能,初步评估需要测试长链接数.峰值消息数以及长期运行稳定性 整体需求 ...

  9. TCP/IP协议的建立连接与关闭连接过程

    一.建立连接(三次握手) 第一次握手:建立连接时,客户端发送SYN(seq=x)包到服务器,并进入SYN_SENT状态,等待服务器的确认.SYN:同步序列编号(Synchronize Sequence ...

随机推荐

  1. iOS开发黑科技之runtime

    iOS 开发之黑科技-runtime runtime其实就是oc底层的一套C语音的API 调用方法的本质就是发消息, 1.动态交换两个方法的实现(特别是交换系统自动的方法) 2.动态添加对象的成员变量 ...

  2. songCMS 3.15 cookie SQLINJ

    ./code/profile.php ... $db = new db(); $SQL = "SELECT * FROM `{$dbprefix}user` WHERE `ID` = {$_ ...

  3. 编写一个可复用的SpringBoot应用运维脚本

    前提 作为Java开发者,很多场景下会使用SpringBoot开发Web应用,目前微服务主流SpringCloud全家桶也是基于SpringBoot搭建的.SpringBoot应用部署到服务器上,需要 ...

  4. MySQL学习之路 一 : MySQL 5.7.19 源码安装

    MySQL 5.7.19 源码安装 查看系统: # cat /etc/redhat-release CentOS Linux release 7.3.1611 (Core) 安装依赖包 # yum - ...

  5. 7-31 jmu-分段函数l (20 分)

    本题目要求计算以下分段函数的值(x为从键盘输入的一个任意实数): 如果输入非数字,则输出“Input Error!” 输入格式: 在一行中输入一个实数x. 输出格式: 在一行中按”y=result”的 ...

  6. 【WPF学习】第五十六章 基于帧的动画

    除基于属性的动画系统外,WPF提供了一种创建基于帧的动画的方法,这种方法只使用代码.需要做的全部工作是响应静态的CompositionTarge.Rendering事件,触发该事件是为了给每帧获取内容 ...

  7. C/C++ 手动开O2

    手动O2比赛不能用,平时玩玩就好 #pragma GCC optimize (2) #pragma G++ optimize (2)

  8. # 记一次Tomcat升级——幽灵猫

    一.背景 近日Tomcat被爆出幽灵猫漏洞,覆盖的版本从6-9全部中招,因此升级是当前能做的手段,那么就来说一下SpringBoot内置的Tomcat如何升级. 二.升级过程 1 下载对应的版本 To ...

  9. Simple: 一个支持中文和拼音搜索的 sqlite fts5插件

    之前的工作关系,需要在手机上支持中文和拼音搜索.由于手机上存储数据一般都是用 sqlite,所以是基于 sqlite3 fts5 来实现.这段时间再次入门 c++,所以想用 c++ 实现一下,一来用于 ...

  10. mongoose .find().limit()返回undefined

    当我们使用mongoose的find方法查询数据库的同时,可以使用limit方法来指定返回数据的条数. limit方法基本语法如下所示: >db.COLLECTION_NAME.find().l ...