目的:
有一些场景下需要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注册的时候给挤掉了??
麻烦官方的大佬能给一下解答和帮助