跑通第一个websocket原创
金蝶云社区-sharkv
sharkv
17人赞赏了该文章 3,209次浏览 未经作者许可,禁止转载编辑于2022年03月24日 17:10:53

关键字: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.png


图 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.png

图 2

l  本地localhost端,拉取服务端的静态资源文件更新到本地,即可产生相应的自定义控件目录。

3.png

图 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.png

图 4

5.   发送消息

服务端调用MsgSendFactory.getSender().send(identifytype,message)发送消息

四、效果图

 1.点击自定义控件向后端发起wss请求创建连接

5.png

图 5

2.后端开启后台线程向前端定时发送消息

6.png

图 6

3.前端定时收到消息并打印

7.png

图 7

五、开发环境版本

V4.0.0.14

六、FAQ

问题1:如何测试websocket接口(如何知道websocket连接是否创建成功了)

解决:编写websocket客户端代码时,首先得知道websocket接口url是什么。这时可以借助接口测试工具来验证,如http://coolaf.com/tool/chattest,如

正确的url:

8.png

图 8

 

错误的url:

9.png

图 9

问题2:websocket连接创建失败

解决:经过问题1的调试,可以判定websocket接口是通的,那么有可能是接口登录权限校验失败了。查看控制台可能有以下报错。

10.png

图 10

这时你可以继续调试kd.bos.msgjet.websocket.jetty.JettyParamManager.getToken(Session)看看是否是少了某个参数,或者是参数取值传递有误

11.png

图 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

八、附件


赞 17