Springboot怎么实现WebSocket通信
Posted 凡夫贩夫
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Springboot怎么实现WebSocket通信相关的知识,希望对你有一定的参考价值。
前言
上一篇文章分享了单机模式下,websocket的基本使用方法,但在实际的业务中,通常是不会这样使用的,大部项目都是分布式部署的,一个工程布署了多个服务节点,前端并不直接请求具体服务节点,而是先到nginx或其他代理服务器,通过nginx的负载均衡机制再转发到具体的服务节点。这种场景下,产生了几个问题:
第一,前端发起websocket连接时,是应该先连接到nginx,再由nginx转发到具体的节点?还是直接连接到具体的服务节点?
第二,nginx作为后端服务节点的代理者,nginx如何配置才能实现websocket协议的连接请求的转发?
第三,同一浏览器端发起的同一个websocket请求的session如何在多个服务节点之间实现共享?
这篇文章来和大家分享一下,实际业务开发中如何解决上述问题。
分布式场景下的websocket的工作流程
实际业务开发中,大部分项目都是采用前后端分离的开发方式和部署方式,对于http请求来说,通常是浏览器端作为客户端向服务器端发起http请求,http请求会先到nginx,经过nginx的负载均衡机制处理后,最终会把http请求转发到众多服务节点中的一个,完成业务逻辑处理后,把结果响应到客户端,客户端收到数据后渲染在浏览器中。
而对于websocket协议来说,分布式场景下的工作流程和http也比较类似,只是协议本身来说各有各的不同,前端浏览器在向服务器端发起websocket连接时,连接请求也是先到nginx上,由nginx来完转发,最终会有一个服务节点与浏览器端建立websocket连接;
浏览器端与服务器端建立websocket连接后,浏览器端和服务器端就之间操持了一个真正的长连接,浏览器端可以给服务器端发送消息,服务器端也可以给浏览器端发送消息了;
nginx与websocket
nginx从1.3版本开始是支持websocket协议的,虽然websocket协议与http协议不同,但是websocket建立连接前的握手与http是兼容的,HTTP升级机制使用Connection头Upgrade将连接从http升级到websocket;
如果nginx要实现分布式场景下websocket连接的转发,需要额外的配置,实现浏览器端和服务器端之间建立隧道,额外的配置即Upgrade的相关配置,nginx.conf的配置如下:
worker_processes 1;
events
worker_connections 1024;
http
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
upstream lezu
#模拟的服务节点1
server 127.0.0.1:8080 weight=1;
#模拟的服务节点2
server 127.0.0.1:8081 weight=1;
server
listen 80;
server_name localhost;
location /
root html;
index index.html index.htm;
proxy_pass http://lezu;
# 重点的两行配置
# 将nginx的请求头从http升级到websocket.
proxy_set_header Upgrade $http_upgrade;
#进行nginx连接websocket
proxy_set_header Connection "upgrade";
error_page 500 502 503 504 /50x.html;
location = /50x.html
root html;
浏览器端在发起websocket连接请求时,请求的服务器端地址是nginx的代理地址,连接建立后的消息发送、接收以及连接关闭等具体的使用方法和单机模式下是一模一样的;
<!DOCTYPE HTML>
<html lang="en">
<head>
<title>websocket测试</title>
<meta charset="UTF-8">
<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
</head>
<body>
<input type="button" name="" id="open" value="建立连接"/>
<input type="button" name="" id="close" value="关闭连接">
<input type="button" name="" id="send" value="发送信息">
<div id="message"></div>
</body>
<script type="text/javascript">
var websocket = null;
$("#open").click(function ()
if (websocket != null)
if (websocket.readyState == 1)
throw "连接已建立!"
//判断当前浏览器是否支持WebSocket
if ('WebSocket' in window)
//nginx的代理地址
websocket = new WebSocket("ws://172.18.229.61/websocket/gaox2");
else
alert('Not support websocket')
//连接发生错误的回调方法
websocket.onerror = function ()
document.getElementById('message').innerHTML += ("发生错误") + '<br/>';
;
//连接成功建立的回调方法
websocket.onopen = function (event)
document.getElementById('message').innerHTML += ("建立连接") + '<br/>';
console.log("建立连接");
//接收到消息的回调方法
websocket.onmessage = function (event)
console.log(event.data);
document.getElementById('message').innerHTML += event.data + '<br/>';
//连接关闭的回调方法
websocket.onclose = function ()
document.getElementById('message').innerHTML += ("关闭连接") + '<br/>';
console.log("关闭连接");
//监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
window.onbeforeunload = function ()
alert("已关闭连接");
websocket.close();
)
var i = 0;
$("#send").click(function ()
for (let j = 0; j < 100; j++)
i = i + 1;
websocket.send("hello" + i);
)
$("#close").click(function ()
websocket.close();
i=0;
)
</script>
</html>
session共享
通过nginx的关于websocket协议的相关配置后,浏览器端与服务器端一个具体的服务节点建立了websocket连接,但这里需要特别注意的是,如果服务器端主动发消息给浏览器端就会用到session,这个session是javax.websocket.Session,是不能序列化的,所以不能像http请求中把session放入到缓存中间件中来实现共享,这样就会出现下面的情况:两个浏览器端用户A和B可能会连接到两个不同的服务节点上,这个时候如果用户A想给用户B发一个消息就有问题了,用户A的websocket连接的session在服务节点1,用户B的websocket连接的session在服务节点2,session又是jvm级别的,无法实现共享,那么用户A和B之间是无法通信的。
解决这个问题的思路也很简单,实际上是没有太好的办法了,具体就是:
1、在上一篇文章里,浏览器端与服务器端成功建立websocket连接后,会把连接信息包含session缓存起来;
2、用户A的消息发送到服务器端的服务节点1时,服务节点1利用第三方的消息中间件的发布订阅模式,这里以redis为例,对其他节点广播该消息;
3、所有的服务节点都订阅这个主题的消息,收到订阅消息的服务节点,再根据消息的接收方身份标识查找接收方与服务器端的session,如果接收方与服务器端的连接没有中断,一定可以找到用户B与服务器端的websocke连接的session,找到之后就可以把消息发送给用户B了;
WebSocket业务场景
基于websocket的通信特点,大概的应用场景有:
1、在线聊天,实时消息发送;
2、视频弹幕实时更新;
3、重要通知、资讯的实时更新;
当然,实际的业务场景不止这些,有的场景需要考虑websocket session共享的问题,有的则不需要,但是在设计相关业务功能实现方案的时候,一定得考虑到websocket session共享的问题;
总结
至此,websocket的使用方法以及如何应用到实际的业务开发中的坑算是填平了。在这两篇文章的输出过程中,除了websocket本身,我get到了两点:
1、学习是一个循序渐进的过程,先学会基本使用,再深入研究更高级的用法,会比较容易且符合自然的思考逻辑;
2、任何一个相对比较新的东西,如果没有正式应用到生产的经验,则一定要根据实际业务场景反复思考、认证、测试,做出最合理的设计;
其实这篇文章还有另外一个坑未真,那就是只考虑了session共享的问题,还未考虑实际业务场景中高并发高可用情况下,websocket资源消耗与评估的问题,这个坑就靠开发和测试的同学在做压力测试的时候填上了。
vue+websocket+Springboot实现的即时通信开源项目
往期精彩推荐:
https://chat.comsince.cn
https://gitee.com/comsince/vue-chat
飞享
该项目是飞享
聊天系统客户端源码
基于universe-push的vue即时通讯web端实现,使用websocket进行消息通讯,支持文本,图片类型发送,支持实时音视频,支持音视频与android-chat客户端互通
项目截图
消息提示
文字消息
图片消息
视频消息
项目演示
请选择其中任何一个帐号密码进行登录即可
帐号:13800000000, 13800000001, 13800000002
密码:556677
版本规划
V1.0.0
登录认证流程
实现朋友列表展示,用户信息获取
会话信息拉取,会话消息缓存
纯文本消息通讯
支持图片,视频消息展示
群会话功能
V1.0.1
增加全屏幕模式支持,点击用户头像即可切换
V1.0.2
计划增加音视频聊天功能
实现与android客户端音视频互通
语音通话
视频通话
V1.0.3
增加好友搜索,好友添加功能,形成功能闭环
V1.0.4
群组用户列表功能
V1.0.5
增加websocket异步回调接口
增加创建群组功能
退出群聊
撤回消息
群组踢人与拉人
修改群名称
V1.0.6
增加解散群组的功能
优化群组退出与解散交互体验
对于解散的群组与退出的群组,做删除会话处理
Build Setup
# install dependencies
npm install
# serve with hot reload at localhost:8080
npm run dev
# 运行请先检查如下配置:TCP服务配置,HTTPS配置,是否支持WSS,是否支持HTTPS,HTTP监听端口8081,HTTPS监听端口8443
# build for production with minification
npm run build
# build for production and view the bundle analyzer report
npm run build --report
For detailed explanation on how things work, checkout the guide and docs for vue-loader.
参考项目
Vue-chat
依赖组件
常用的 vue 视频插件
西瓜播放器
图标Icon支持
推荐项目
vue-wechat
vue-chat
QRCodeLogin 说明二维码和密码登录的切换操作
开源协议
本项目使用非商业性署名协议Creative Commons Attribution Non Commercial 3.0 Unported
加锋哥微信 拉你进微信群扯淡^_^:
以上是关于Springboot怎么实现WebSocket通信的主要内容,如果未能解决你的问题,请参考以下文章
springboot整合websocket实现客户端与服务端通信