一开始想用session监听器,不过有过期时间,并非实事。而且,如果不用jsp,就需要写个rest服务,供前段轮询调用接口,比如5秒一次,来刷新在线人数。影响性能。后来想到用WebSocket来做。刚好之前有用过socketio来推送消息,于是敲定方案。
我使用的是开源库,https://github.com/mrniko/netty-socketio, 有近3000个star,还是不错的。
因为业务上需求是跟进登录账号来统计,而对登录IP、浏览器不做区分,即同一个账号,无论在哪里登录,总数都算1.基于次,前端连接时只需把登录账号传到后台即可。
1. 后台服务OnlineUserCounter.java:
public class OnlineUserCounter {
private static SocketIOServer socketServer = null; /** * 在线用户数 */ public static Set<String> userSet = new HashSet<>(500); private static JSONObject result = new JSONObject(); /** * 客户端计数 */ public static AtomicInteger clientCount = new AtomicInteger(); private static SimpleDateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); public static void startSocketIOServer() { try { Configuration config = new Configuration(); InetAddress localHost; String localIp = "127.0.0.1"; try { localHost = Inet4Address.getLocalHost(); localIp = localHost.getHostAddress(); } catch (Exception e) { ServiceLog.error(e); } String ip = AppHandle.getHandle(Constants.MODULE).getProperty("ONLINE_SERVER_IP", localIp); config.setHostname(ip); int port = Integer.parseInt(AppHandle.getHandle(Constants.MODULE) .getProperty("ONLINE_SERVER_PORT", "9092")); config.setPort(port); SocketConfig sockConfig = new SocketConfig(); sockConfig.setReuseAddress(true);//解决SOCKET服务端重启"Address already in use"异常 sockConfig.setTcpKeepAlive(false); config.setSocketConfig(sockConfig); socketServer = new SocketIOServer(config); socketServer.addConnectListener(new ConnectListener() { @Override public void onConnect(SocketIOClient client) { int count = clientCount.incrementAndGet(); HandshakeData handshakeData = client.getHandshakeData(); String userCode = handshakeData.getSingleUrlParam("userCode"); ServiceLog.debug(userCode + " connet, total count:" + count); addUser(userCode); sendOnlineMessage(); } }); socketServer.addDisconnectListener(new DisconnectListener() { @Override public void onDisconnect(SocketIOClient client) { if(clientCount.get() > 0) { clientCount.decrementAndGet(); } HandshakeData handshakeData = client.getHandshakeData(); String userCode = handshakeData.getSingleUrlParam("userCode"); ServiceLog.debug(userCode + " disconnet, total count:" + clientCount.get()); removeUser(userCode, client); sendOnlineMessage(); } }); socketServer.addEventListener("HEARTBEAT_EVENT", String.class, new DataListener<String>(){ @Override public void onData(SocketIOClient client, String data, AckRequest ackSender) throws Exception { JSONObject dataJson = JSON.parseObject(data); String userCode = dataJson.getString("userCode"); addUser(userCode); sendOnlineMessage(); } }); socketServer.start(); }catch(Exception e) { ServiceLog.error("启动在线统计用户服务失败: " + e.getMessage(), e); } } private static void addUser(String userCode) { if(StringUtil.isEmpty(userCode)) { return; } if(!userSet.contains(userCode)) { userSet.add(userCode); } } private static void removeUser(String userCode, SocketIOClient client) { if(StringUtil.isEmpty(userCode)) { return; } userSet.clear(); Collection<SocketIOClient> clients = socketServer.getAllClients(); for (SocketIOClient c : clients) { if(c.getSessionId().equals(client.getSessionId())) { continue; } HandshakeData handshakeData = c.getHandshakeData(); String uCode = handshakeData.getSingleUrlParam("userCode"); userSet.add(uCode); } } private static void sendOnlineMessage() { Collection<SocketIOClient> clients = socketServer.getAllClients(); //result.put("CLIENT_NUM", clientCount.get()); result.put("ONLINE_NUM", userSet.size()); for (SocketIOClient c : clients) { c.sendEvent("HEARTBEAT_EVENT", result); } } public static void stopSocketIOServer(){ if(socketServer != null){ socketServer.stop(); socketServer = null; } }}只需要在ServletContextListener初始化时调用:OnlineUserCounter.startSocketIOServer();
;销毁时调用:OnlineUserCounter.stopSocketIOServer();
2.前端js示例代码:var socket = io.connect('http://172.28.50.113:9093?userCode=admin');
socket.on('connect', function() { console.log('连接成功,可以处理初始化工作'); }); socket.on('HEARTBEAT_EVENT', function(data) { //监听HEARTBEAT_EVENT事件,只要用户数量有变化,即能及时收到推送消息 console.log("后台推送消息:"+data); });后记:利用socketio,不仅可以统计用户量,还可以记录所有客户端的连接列表,或者登录用户列表信息。包括用户的IP、登录名等任何希望记录的业务信息。