本文讨论了如何使用WebSocket技术实现后台主动向前台发送指令的需求,如弹窗、页面刷新等。WebSocket是一种全双工通信协议,允许服务端主动推送数据到客户端。实现步骤包括将系统升级至HTTPS并配置nginx以支持wss协议,编写WebSocket客户端和服务端监听器。客户端通过WebSocket API与服务端建立连接,并监听消息和连接状态。服务端则通过特定监听器处理客户端的连接和数据传输请求。
关键字:websocket、全双工通信、后端主动通知
一、需求
后台如何主动向前台发送指令?如弹窗、刷新页面等。
二、思路与方案
2.1 分析思路
我们可以引入websocket。webSocket是一种在单个TCP连接上进行全双工通信的协议。WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
2.2 实现原理
如图1所示为苍穹中websocket的运行机制。
① 由浏览器前端向服务端发起wss请求,创建websocket连接。
② nginx监听443端口,转发到8080端口,并升级协议
③ 这一步是在服务端发生的,拦截所有cookies,并根据wss请求参数中的tenantsessionkey获取KERPSESSIONID,校验其是否为有效会话。否则websocket连接创建失败。
④ 这一步发生在服务端启动阶段,根据/META-INF/services下的文件初始化websocket监听器。
⑤ 登录校验通过后,根据listenerType匹配相应的websocket监听器进行处理,匹配规则:监听器中的getType().equals(listenerType)
图 1
三、实现过程
1.协调运维人员将系统升级至https,并增加nginx配置,使得系统支持wss协议。
nginx关键配置:(示例)
server { listen 443 ssl; server_name localhost; ssl_certificate E:/cloudHub/downloads/localhost.crt; ssl_certificate_key E:/cloudHub/downloads/localhost.key; ssl_session_cache shared:SSL:1m; ssl_session_timeout 5m; location /ierp/ { proxy_pass http://localhost:8080/ierp/; } location ^~ /msgwatch/ { proxy_pass http://127.0.0.1:8080/msgwatch/; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_read_timeout 3600s; } location ^~/ierp/msgwatch/ { proxy_pass http://127.0.0.1:8080/msgwatch/; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_read_timeout 3600s; } }
2. 编写websocket客户端
Tips:这里以自定义控件为例,自定义控件不知道怎么玩的,先看视频帖子学一学https://club.kdcloud.com/school/liveCourse/338
这里已经写好了一个自定义控件方案【文末附件】,可以自行上传。上传方式:
l 登录服务器端的苍穹地址(非localhost访问),通过自定义控件——》新增方案;
图 2
l 本地localhost端,拉取服务端的静态资源文件更新到本地,即可产生相应的自定义控件目录。
图 3
l 编辑index.js文件即可做自定义控件开发。
var initEvent = function(model,props){ $(model.dom).find('button').click(function(){ socket=new WebSocket("wss://localhost/msgwatch/?identifytype=1223344&tenantsessionkey=KERPSESSIONID&listenerType=kd.demo.sci.wslistener.RefreshPageWSHandler"); socket.onopen=function(){ alert("Socket has been opened"); //socket.send("这是来自客户端的消息" + location.href + new Date()); }; //获得消息事件 socket.onmessage=function(msg){ alert(msg.data); }; //关闭事件 socket.onclose=function(){ alert("Socket has been closed"); }; //发生了错误事件 socket.onerror=function(){ alert("error"); } }) }
参数说明:
· -【 identifytype】 所创建的websocket的标识Id,微服务端使用该标识Id决定向哪个或者哪些websocket发送消息;
· -【 tenantsessionkey】 该参数为cookie中会话Id所使用的Key,服务端将使用该Key从上下文cookie中获取会话Id并进行校验;
· -【 listenerType】 服务端根据此参数鉴别向哪个监听listener发送消息,参数值取实现了WebSocketListener接口的类名;
3. 实现websocket监听接口
实现接口kd.bos.msgjet.websocket.WebSocketListener,创建 kd.demo.sci.wslistener.RefreshPageWSHandler
实现方法解释如下
方法 | 功能 |
onConnect(WebSocketObject object); | 监听websocket连接 |
onMessage(WebSocketObject object, String msg) | 监听接收页面消息 |
onClose (WebSocketObject object); | 连接关闭监听 |
public class RefreshPageWSHandler implements WebSocketListener { private final static Log logger = LogFactory.getLog(RefreshPageWSHandler.class); @Override public void onConnect(WebSocketObject paramWebSocketObject) { String identifyType = paramWebSocketObject.getIdentifyType(); String sessionId = paramWebSocketObject.getSessionId(); logger.info("ws开始建立链接--sessionId:"+sessionId+"--identifyType:"+identifyType); ThreadPools.executeOnce("wsDemo", new Runnable() { @Override public void run() { for(;;) { MsgSendFactory.getSender().send(identifyType,"hello world!"); try { Thread.sleep(8000); } catch (InterruptedException e) { e.printStackTrace(); } } } }); } @Override public void onMessage(WebSocketObject paramWebSocketObject, String paramString) { } @Override public void onClose(WebSocketObject paramWebSocketObject) { } @Override public String getType() { return RefreshPageWSHandler.class.getName(); } }
4. 注册websocket监听
实现接口的类的全限定名需要告知到苍穹,websocket初始化时需要初始化注册此实现类。
增加配置项:websocket.listener.config,内容为接口实现类的全限定名,多个以逗号分开,如:websocket.listener.config=kd.demo.sci.wslistener.RefreshPageWSHandler
两种添加方式:①可以在mc中添加参数,发布集群②开发环境,debugserver中添加配置项System.setProperty("websocket.listener.config","xxxxxxxxx");
实现接口后需要在工程resources目录下新建META-INF/services文件夹,并在新建文件夹新建以接口 kd.bos.msgjet.websocket.WebSocketListener命名的文件,文件内容为你接口实现类的全限定类名,若有多个实现类,直接换行添加就行;
图 4
5. 发送消息
服务端调用MsgSendFactory.getSender().send(identifytype,message)发送消息
四、效果图
1.点击自定义控件向后端发起wss请求创建连接
图 5
2.后端开启后台线程向前端定时发送消息
图 6
3.前端定时收到消息并打印
图 7
五、开发环境版本
V4.0.0.14
六、FAQ
问题1:如何测试websocket接口(如何知道websocket连接是否创建成功了)
解决:编写websocket客户端代码时,首先得知道websocket接口url是什么。这时可以借助接口测试工具来验证,如http://coolaf.com/tool/chattest,如
正确的url:
图 8
错误的url:
图 9
问题2:websocket连接创建失败
解决:经过问题1的调试,可以判定websocket接口是通的,那么有可能是接口登录权限校验失败了。查看控制台可能有以下报错。
图 10
这时你可以继续调试kd.bos.msgjet.websocket.jetty.JettyParamManager.getToken(Session)看看是否是少了某个参数,或者是参数取值传递有误
图 11
常见的有:
• 没有传tenantsessionkey或者值不正确。token不建议使用,后续平台会去掉token机制
• Cookie为null。出于安全性考虑,一些浏览器对cookie的传递检查比较严格,需要部署https证书,并通过wss协议传输才会带上cookie。
问题3:断点不进onConnect
解决:先根据问题2进行排查。如果没有上述报错,见以下情况:
• 那很可能是websocket监听没有注册到服务端中,参照本文【操作步骤】里面第3点进行配置检查
• wss请求中的listenerType参数与监听器getType()方法返回的值不匹配
七、参考资料
【苍穹3.0新特性-唐建兵】https://club.kdcloud.com/school/137195751334834688
【苍穹websocket开发原创-贺召军】https://club.kdcloud.com/article/115412010488892160
八、附件
avatar_button.zip(20.13KB)
推荐阅读