一、前言

  近期项目里需做一个在线聊天功能,就想要在对话的时候建立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. Java版飞机订票系统

    关注微信公众号:Worldhello 回复 飞机订票系统 可获得系统源代码并可加群讨论交流 数据结构课程设计题目:          [飞机订票系统]            通过此系统可以实现如下功能 ...

  2. 网络地址转换NAT与端口地址转换PAT

    网络地址转换NAT与端口地址转换PAT 一.网络地址转换 NAT (Network Address Translation) 1.1.网络地址转换简介 需要在专用网(内网)连接到因特网的路由器上安装 ...

  3. prometheus服务发现机制

    一. Prometheus与服务发现 1.1 目前支持的服务发现方式 二. 案例 2.1 基于文件的服务发现 2.2 基于Consul的服务发现 三.本地测试 3.1 基于文件的服务发现 1.测试环境 ...

  4. Python 中的集合 --set

    前言 在Python中,我们用[]来表示列表list,用()来表示元组tuple,那{}呢?{}不光可用来定义字典dict,还可以用来表示集合set. 集合 set 集合(set)是一个无序的不重复元 ...

  5. React Native 在 Airbnb(译文)

    在Android,iOS,Web和跨平台框架的横向对比中,React Native本身是一个相对较新且快速开发移动的平台.两年后,我们可以肯定地说React Native在很多方面都是革命性的.这是移 ...

  6. LaTex公式符号

    下面这个网站是我认为比较齐全的网站 http://www.mohu.org/info/symbols/symbols.htm

  7. disruptor 多生产者多消费者实战 四

    一.创建event类 Order public class Order { private String id; private String name; private double price; ...

  8. RestTemplate 负载均衡原理

    RestTemplate 是通过拦截器改变请求的URI的方式来指定服务器的,此处将通过一个自定义LoadBalanced的方式来进行说明 1.导入jar包 <parent> <gro ...

  9. form里面文件上传并预览

    其实form里面是不能嵌套form的,如果form里面有图片上传和其他input框,我们希望上传图片并预览图片,然后将其他input框填写完毕,再提交整个表单的话,有两种方式! 方式一:点击上传按钮的 ...

  10. 1构建个人博客--使用Hugo快速成型

    概述 人在武汉,病毒肆虐. 隔离久了,有点闷,闲余时间找点事情做. 建个博客吧, 内容不重要,写不写也不那么要紧,目前水平也写不出什么有深度的东西. 但是这个姿势一定要优美, 过程一定要折腾. OK, ...