1、为什么使用websocket

前端和后端的交互模式最常见的就是前端发数据请求,从后端拿到数据后展示到页面中。如果前端不做操作,后端不能主动向前端推送数据,这恰恰就是http协议的缺陷。但在我们平常开发中,常遇到客户端需要实时获取服务端信息,做到客户端与服务端互通有无,通过http协议实现(轮询)存在一定延时性,且会造成资源的很大浪费,websocket却能完美实现。恰巧最近有项目需求,就做了一定研究,特此记录。

2、简介

websocket是一种在单个TCP连接上进行全双工通信的协议,该协议兼容我们常用的浏览器。例如Chrome、 Firefox、IE等。它可以使客户端和服务端双向数据传输更加简单快捷,并且在TCP连接进行一次握手后,就可以持久性连接,同时允许服务端对客户端推送数据。外加传统模式的协议一般HTTP请求可能会包含较长的头部,但真正有效的可能只有小部分,从而就占用了很多资源和带宽。因此WebSocket协议不仅可以实时通讯,支持扩展;也可以压缩节省服务器资源和带宽。 WS协议和WSS协议两个均是WebSocket协议的SCHEM,两者一个是非安全的,一个是安全的。也是统一的资源标志符。就好比HTTP协议和HTTPS协议的差别。非安全的没有证书,安全的需要SSL证书。(SSL是Netscape所研发,用来保障网络中数据传输的安全性,主要是运用数据加密的技术,能够避免数据在传输过程被不被窃取或者监听。)其中WSS表示在TLS之上的WebSocket。WS一般默认是80端口,而WSS默认是443端口,大多数网站用的就是80和433端口。

3、特点

(1)建立在 TCP 协议之上,服务器端的实现比较容易。

(2)与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。

(3)数据格式比较轻量,性能开销小,通信高效。

(4)可以发送文本,也可以发送二进制数据。

(5)没有同源限制,客户端可以与任意服务器通信。

(6)协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。

4、与http的关系

(1)相同点

都是一样基于TCP的,都是可靠性传输协议;都是应用层协议。

(2)不同点

WebSocket是双向通信协议,模拟Socket协议,可以双向发送或接受信息,HTTP是单向的;WebSocket是需要浏览器和服务器握手进行建立连接的,而http是浏览器发起向服务器的连接,服务器预先并不知道这个连接。

5、代码实现

(1)服务端


@ServerEndpoint("/websocket/sendMessage")
public class WebSocketUtil {private static Logger logger = Logger.getLogger(WebSocketUtil.class);/*** 静态变量,用来记录当前在线连接数。*/private static volatile int onlineCount = 0;/*** concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。*/private static CopyOnWriteArraySet<WebSocketUtil> webSocketSet = new CopyOnWriteArraySet<WebSocketUtil>();/*** 与某个客户端的连接会话,需要通过它来给客户端发送数据*/private Session session;/*** 用户编码*/private String userCode;/*** 连接建立成功调用的方法** @param session 可选的参数。session为与某个客户端的连接会话,需要通过它来给客户端发送数据*/@OnOpenpublic void onOpen(Session session) {String userCode = (String) session.getQueryString();this.session = session;this.userCode = userCode;//加入set中webSocketSet.add(this);//在线数加1addOnlineCount();logger.info("有新连接加入" + userCode + "!当前在线客户端数为" + getOnlineCount());}/*** 连接关闭调用的方法*/@OnClosepublic void onClose() {// 从set中删除webSocketSet.remove(this);// 在线数减1subOnlineCount();logger.info("有一连接关闭!当前在线客户端数为" + getOnlineCount());}/*** 接收消息消息** @param txt     接受服务端消息体内容,必填字段(msg:消息内容,type:消息类型),选填字段(to:需发送的用户,默认像所有人发送)* @param session*/@OnMessagepublic void onMessage(String txt, Session session) {// 向客户端发送消息JSONObject jsonTo = JSONObject.fromObject(txt);String to = jsonTo.containsKey("to") ? jsonTo.getString("to") : "All";String content = jsonTo.containsKey("content") ? jsonTo.getString("content") : "";String type = jsonTo.containsKey("type") ? jsonTo.getString("type") : null;WebSocketMsgVo vo = new WebSocketMsgVo();vo.setContent(content);vo.setTo(to);vo.setType(type);sendMessage(vo);if(!"testLink".equals(type)){// 非测试消息打印logger.info("onMessage: " + txt);}}/*** 发生错误时调用** @param session* @param error*/@OnErrorpublic void onError(Session session, Throwable error) {logger.error("发生错误", error);}/*** 发送消息到客户端(** @param message 消息内容*/public static void sendMessage(WebSocketMsgVo message) {if (StringUtil.isNotBlank(message.getType())) {// 必须指定发送消息的类型JSONObject json = JSONObject.fromObject(message);if (("ALL").equals(message.getTo())) {// 群发for (WebSocketUtil item : webSocketSet) {item.session.getAsyncRemote().sendText(json.toString());}} else {// 发给指定用户for (WebSocketUtil item : webSocketSet) {if (StringUtil.isNotBlank(item.userCode) && item.userCode.equals(message.getTo())) {item.session.getAsyncRemote().sendText(json.toString());}}}}}public static synchronized int getOnlineCount() {return onlineCount;}public static synchronized void addOnlineCount() {onlineCount++;}public static synchronized void subOnlineCount() {onlineCount--;}}

2、客户端

data () {return {websocketUrl: 'ws://localhost:8090/portal-admin/websocket/sendMessage',// websocket请求地址socket: ""}
},
mounted () {// 初始化this.websocketInit()
},
methods: {// websocket初始化websocketInit () {if(typeof(WebSocket) === "undefined"){this.$message.warning('您的浏览器不支持socket')}else{// 实例化socketthis.socket = new WebSocket(this.websocketUrl + '?80512179')// 监听socket连接this.socket.onopen = this.websocketOpen// 监听socket错误信息this.socket.onerror = this.websocketError// 监听socket消息this.socket.onmessage = this.websocketGetMessage}},websocketOpen: function () {console.log("socket连接成功")},websocketError: function () {console.log("连接错误")},websocketGetMessage: function (msg) {// 执行具体业务逻辑var json = JSON.parse(msg.data)if(json.type == 'portalMsg'){this.getUnReadNum()}},// 向服务端发送消息websocketSend: function (params) {this.socket.send(JSON.stringify(params), '80512179')},websocketClose: function () {console.log("socket已经关闭")}
},
destroyed () {// 销毁监听this.socket.onclose = this.websocketClose()
}

到此,一种基于Tomcat的实现方式就完成了,需要注意的是,该实现要求tomcat的版本为tomcat7.x,本文示例使用的是tomcat8。当然服务端实现方式肯定不止当前这一种,比如基于spring的实现,其中的实现请读者自行尝试。

6、问题解决

在尝试使用过程中也遇到各种各样的问题,但这也正是开发的乐趣,享受发现问题与解决问题的过程,其中主要问题点如下:

  1. 问题一,连接中断

目前我们的项目大多数是负载均衡,通过nginx做负载,如此就存在一个巨大的隐患。因为nginx一般都存在一个超时时间,一定时间内没有交互就会断开连接。

为此我们引入心跳包,维持连接的有效性,首先定义心跳数据代码:

var heartCheck = { timeout: 60000,//60s timeoutObj: null, reset: function(){ clearInterval(this.timeoutObj); this.start();}, start: function(){ this.timeoutObj = setInterval(function(){ if(websocket.readyState==1){ websocket.send("HeartBeat");} }, this.timeout) }
};   

其次,在 websocket onopen 事件上执行 heartCheck.start(),表示连接成功后开始发送心跳包(每隔 60s 发送一次);在 onmessage 事件执行 heartCheck.reset() ,收到数据时,重置发送心跳定时程序。

websocketOpen: function () {console.log("socket连接成功")heartCheck.start();
},
websocketGetMessage: function (msg) {heartCheck.reset();// 执行具体业务逻辑var json = JSON.parse(msg.data)if(json.type == 'portalMsg'){this.getUnReadNum()}
},

最后,关闭重连。一般情况下,如网络故障,服务器故障等发生时(一般情况 onclose 事件的 evnt.code=1006),故障时我们都会设置重连。但一些特殊情况下我们可以指定其中 evnt.code = 4500 返回的代码,来判断不需要,不需要重连。

websocketClose: function () {console.log("socket已经关闭")if (evnt.code != 4500) { //4500为指定不需重连的编码reconnect();//重连 }
}

(2)问题2

WebSocket connection to 'ws://angelapi.bluemoon.com.cn/portal-admin/websocket/sendMessage' failed: Error during WebSocket handshake: Unexpected response code: 200

翻译后就是 WebSocket握手过程中出错:意外响应代码:200

主要原因就是过滤器,因为项目的配置文件与代码是分开部署的,测试环境没有配置过滤url,导致websocket的请求会被拦截,而不会到达websocket的链接处,就会报上面的错。添加配置后使得URL不被拦截,问题就就解决了。

(3)问题3

Mixed Content: The page at '*****' was loaded over HTTPS, but attempted to connect to the insecure WebSocket endpoint 'ws://*****'. This request has been blocked; this endpoint must be available over WSS.

遇到这个问题不得不说一下 ws与wss:

WebSocket可以使用 ws 或 wss 来作为统一资源标志符,类似于 HTTP 或 HTTPS。其中 ,wss 表示在 TLS 之上的 WebSocket,相当于 HTTPS。默认情况下,WebSocket的 ws 协议基于Http的 80 端口;当运行在TLS之上时,wss 协议默认是基于Http的 443 端口。说白了,wss 就是 ws 基于 SSL 的安全传输,与 HTTPS 一样样的道理。所以,如果你的网站是 HTTPS 协议的,那你就不能使用 ws:// 了,浏览器会 block 掉连接,和 HTTPS 下不允许 HTTP 请求一样。

遇到这个问题只需在前端建立连接的时候针对http与https多做一步处理即可:

(4)问题4

WebSocket connection to ‘wss://{ip}:{port}/‘ failed: Error in connection establishment: net::ERR_SSL_PROTOCOL_ERROR

在Http的情况下,客户端用ip+port的形式来连接服务端,是不会出现什么问题。但是在更改成Https后,若还是以这种方式连接服务端,浏览器就会报 SSL 协议错误,这很明显就是证书的问题。如果这时候还用 IP + 端口号 的方式连接 WebSocket ,根本就没有证书存在的(即使我们在Nginx配置了SSL证书,但这种方式其实是不会走Nginx代理的)。所以建议我们还是域名访问。

(5)问题5

WebSocket connection to ‘wss://{域名}/‘ failed: Error during WebSocket handshake: Unexpected response code: 400

看到这个错误信息后,确定这是服务端返回的400响应。既然可以请求到服务端,就说明客户端这边是没有问题的,那么问题最可能出在客户端和服务端之间。由于中间层使用了Nginx做转发,所以导致服务端无法知道这是一个合法的WebSocket请求。于是立刻请求了IDC同事的帮助,做了处理后问题得到解决。自己也查找了网上资料,表示在Nginx配置文件加入以下配置,并能解决这个问题,不过没有实践过

server {

location / {

proxy_pass http://localhost:{port};

proxy_http_version 1.1;

proxy_set_header Upgrade $http_upgrade;

proxy_set_header Connection "upgrade";

}

}

(6)问题6

其实到此websocket单机部署已经成功了,但因为我们一般生产都是多节点多服务器部署,如此会出现连接在ws node1的用户收不到node2上的消息,而且websocket的session是无法共享的,加上session是有序无法存入到redis缓存中,这又进一步增加了多节点实现的难度。通过一定的资料查找,redis的订阅与发布模式可以很好的解决这一问题,但因各种原因导致目前还没有进行代码实现,待继续努力。

websocket的简介与应用相关推荐

  1. WebSocket API简介

    WebSocket是html5新增加的一种通信协议,目前流行的浏览器都支持这个协议,例如Chrome,Safari,Firefox,Opera,IE等等,对该协议支持最早的应该是chrome,从chr ...

  2. websocket协议简介

    概念介绍 单工通信:数据传输只允许在一个方向上传输,只能一方发送数据,另一方接收数据并发送. 半双工:数据传输允许两个方向上的传输,但在同一时间内,只可以有一方发送或接收数据. 全双工:同时可进行双向 ...

  3. WebSocket 对象简介

    WebSockets 是一种先进的技术.它可以在用户的浏览器和服务器之间打开交互式通信会话.使用此API,您可以向服务器发送消息并接收事件驱动的响应,而无需通过轮询服务器的方式以获得响应. 何为 We ...

  4. websocket 简介

    一   WebSocket是html5新增加的一种通信协议,目前流行的浏览器都支持这个协议,例如Chrome,Safrie,Firefox,Opera,IE等等,对该协议支持最早的应该是chrome, ...

  5. WebSocket学习与使用

    1.WebSocket是什么 WebSocket是一种在单个TCP连接上进行全双工通信的协议,其目的是在浏览器和服务器之间建立一个不受限的双向通信的通道,使得服务器可以主动发送消息给浏览器.在HTML ...

  6. jmeter 测试websocket接口(一)

    jmeter 测试websocket接口时,需要对jmeter添加测试websocket的jar包. 下载地址: https://download.csdn.net/download/qq_14913 ...

  7. netty系列之:使用netty搭建websocket服务器

    文章目录 简介 netty中的websocket websocket的版本 FrameDecoder和FrameEncoder WebSocketServerHandshaker WebSocketF ...

  8. Websocket教程SpringBoot+Maven整合(详情)

    1.大话websocket及课程介绍 简介: websocket介绍.使用场景分享.学习课程需要什么基础 websocket介绍: WebSocket协议是基于TCP的一种新的网络协议.它实现了浏览器 ...

  9. 最全面的SpringMVC教程(六)——WebSocket

    前言 本文为 [SpringMVC教程]WebSocket 相关知识介绍,具体将对WebSocket进行简介,并通过实战案例对WebSocket的使用进行详尽介绍~

最新文章

  1. 10玩rust_有趣的 Rust 类型系统: Trait
  2. 2021-7-14 深度学习服务器Linux终端网络训练training(顶会ECCV网络BiSeNet)
  3. centos7 搭建nfs共享文件
  4. 五一假期最后一天,会开了
  5. iOS 11开发教程(一)
  6. ActiveMQ中Queue生产者
  7. Java控制台如何输入一行、多行?
  8. mysql数据库21_MySQL数据库技术(21)[组图]_MySQL
  9. ORA-12154: TNS:could not resolve the connect identifier specified. Solved.
  10. flash as3与后台php交互用户注册例子,as3与PHP后台交互2
  11. 既然开发了 飞鸽传书 就一定要帮助人
  12. Atitit ABI FFI 的区别与联系 attilax总结
  13. k3c路由怎么设置虚拟服务器,搭建ngrok服务器!!给k3.k3c.K2.k2p路由器使用!!详细教程!!!...
  14. 长假之后,Scrum团队应该修改Sprint的结束时间吗?
  15. sip gw功能包括_米尔MYD-C335X-GW开发板,为工业网关量身打造
  16. SBUS转485增程方案,SBUS控制远程机器人方案
  17. 划水总结剑指offer 链表系列1
  18. 统计各个部门对应员工涨幅的次数总和,给出部门编码dept_no、部门名称dept_name以及次数sum
  19. 转-超声波CX20106A的内部电路图
  20. linux命令格式和常用命令

热门文章

  1. ​区块链技术的重要性
  2. 常用的数据分布(泊松分布,二项分布,伯努利分布,正态分布,均匀分布等)
  3. Windows安全模式密码错误、密码不正确、和账户登陆密码不一致解决方案
  4. java lombok 插件_关于java:ieda中的Lombok插件安装及测试
  5. cartographer 理解
  6. 01背包问题java_01背包问题 动态规划 java实现
  7. 苹果手机怎么投屏到电脑上
  8. 如何理解三维向量叉乘
  9. 昆仑通态复制的程序可以用吗_昆仑通态人机界面与单片机通信实战教程一:工程界面的设计...
  10. 1211_MISRA_C规范学习笔记_表达式的要求