【仅供内部供应商使用,不提供对外解答和培训】
【仅供内部供应商使用,不提供对外解答和培训】
目的:
有一些场景下需要websoket实时通知或者互动,特别是跨屏时,所以我尝试使用fr原生的websocket来解决一些业务场景,本文主要是记录我的尝试和分析,不一定是官方的效果。
受限fr的websocket是基于socket.io 2.0版本实现的,通过分析
com/fr/web/socketio/SocketIOServerFactory.class
com/fr/third/socketio/SocketIOServer.class
两个文件可以知道fr的websocket的启动和使用过程(服务器端),首先需要提到的是fr使用websocket实际上是基于namespace的,默认是“/system”命名空间,在后台发送消息给前端时,如果是自己写的js自己去连接的需要注意命名空间的隔离。
使用fr的websocket有一个很方便的地方在于不用我们自己去处理clients的保存,假如我们自己实现,当集群环境的时候这个clients保存就不是很方便了,所以推荐直接用fr原生的websocket。
前端使用:
前端如果在决策平台就不需要在引入socket.io的库了,如果是自己写html建议引入下面这个库,不要自己去网上下载,容易不兼容。
<script type="text/javascript"
src="${fineServletURL}/file?path=/com/fr/web/socketio/dist/socket.io.js&type=plain&parser=plain"></script>
因为fr自己使用websocket是在决策平台使用的,所以我分析js部分注册websocket是在
com/fr/web/ui/materials.min.js
/** * 初始化socket连接 * @param {*} options 连接socket的参数 * @param {*} cb 返回连接结果 */ initSocket: function (options, cb) { var ports = options.webSocketPort, portIndex = 0, isInit = true; var socket = null, callback = BI.isFunction(cb) ? cb : BI.emptyFn; function reconnect (v) { if (v >= BI.size(ports)) { isInit = false; callback("fail"); return; } socket.io.uri = options.requestUrl + ":" + ports[v] + options.webSocketNameSpace; socket.io.connect(); socket.connect(); } try { socket = io.connect(options.requestUrl + ":" + ports[portIndex] + options.webSocketNameSpace, BI.extend({ query: options.query, path: options.webSocketContextName }, options.options)); // 监听到失败或者超时时使用下一个端口, 初始化成功之后无效 socket.on("connect_error", function () { isInit && reconnect(++portIndex); }); socket.on("connect", function () { isInit = false; callback("success"); }); return socket; } catch (e) { throw new Error(e); } } });
实际上上面的代码是帮忙处理了链接和重试的工作。
在业务注册的时候需要提供token 以供认证,这里的token就是fr的 fine_auth_token 使用方法如下:
var token = "${fine_auth_token}"; var t = window.location.protocol + "//" + window.location.hostname var config = BI.extend({ requestUrl: t, query: { token: token } }, { webSocketContextName: "/socket.io", webSocketNameSpace: "/system", webSocketPort: [38888, 39888, 38889], webSocketProtocol: "plain" }); //这个socket对象就可以做socket.io的操作了 var socket = window.scocket = BI.initSocket(config, function (e) { console.info("连接socket 成功"); if (socket) { socket.emit("joinDP","success") console.info("发送加入房间 成功"); } })
使用了上面的代码注册之后会获得一个socket对象,这个对象就是socket.io的操作对象了,可以on 接收消息,可以emit 发送消息。但是这里需要注意的是,这样获得的对象默认namespace就是/system.从后台发消息需要注意namespace不要弄错了。
好了经过上面两个代码前端算是能连上websocket了,那么后台怎么广播消息呢?
就在最上面我提到两个类文件,实际上我们后台代码主要操作的也主要是这两个类,通过下面代码就可以给前端发消息了。
SocketIOServerFactory.getBroadcastOperations().sendEvent("消息名", "消息内容");
注意这个消息名,需要前端的socket对象用on关注这个消息才可以收到例如
socket.on("消息名",function(msg){ alert("收到"+msg) })
这样就能把服务器或者其他人发的消息打印出来了。
最后一个问题,后台如何接收和处理前端发送的消息?
这里要用到 SocketIOServer 这个实例,通过这个实例的 addEventListener 函数来关注前端发送到后台的消息,我这里给大家举一个例子:
socketIOServer.getNamespace("/system").addEventListener("joinDP", String.class, (client, data, ackReq) -> { FineLoggerFactory.getLogger().info("收到用户加入事件:{}", client); });
我们通过socketIOServer 的getNamespace 获取/system的命名空间,在这个命名空间中添加一个joinDP的事件监听,第二个参数String.class标识监听前端发送过来的内容的类型是字符串,第三个参数是一个回调函数当收到前端的消息时会触发
这个函数,我在这个函数中做了一个打印日志的工作,其实大家也可以通过 SocketIOServerFactory.getBroadcastOperations().sendEvent("事件名",data); 将这个事件广播出去。或者通过 找到 SocketIOClient 去调用每一个client的发送方法
去发送消息。
最后遗留的问题:
1.是否fr提供了用户到clients的映射关系?
2.目前这个websocket只能用token去认证,并且只能一个网页连接,另一个网页再连接前一个就会收不到消息,怀疑是clients注册的时候给挤掉了??
麻烦官方的大佬能给一下解答和帮助