一、对客户端连接(也就是SocketChannel)的管理
客户端与服务端建立连接后,服务端来维护其连接,首先定义了一个GVConnection类,用来定义连接;
此类包括四个成员:token、userId、channel、lastAccessTime;如下:
public class GVConnection { private String token; private String userId; private SocketChannel channel; private int lastAccessTime; public GVConnection(String token,String userId, SocketChannel channel, int lastAccessTime) { this.token = token; this.userId = userId; this.channel = channel; this.lastAccessTime = lastAccessTime; } public void setLastAccessTime(int lastAccessTime){ this.lastAccessTime = lastAccessTime; } public SocketChannel getChannel() { return channel; } public String getToken() { return token; } public String getUserId(){ return userId; } public int getLastAccessTime() { return lastAccessTime; }}
2.对GVConnection的操作单独封装一个类,GVConnTools,在这个类中,我定义了两个map,用来保存userId与GVConnection的对应关系,以及token与GVConnection的对应关系。这两个Map需要保持同步,在删除的时候,可以通过userId或者token进行删除。这两个方法有可能同步执行会遭遇异常,所以我增加了一个互斥锁(我不确定这样做的合理性,敬请指导)。其他新增和更新的方法我均采用了同步方法。
public class GVConnTools { private static MapCONNECTION_BY_USERID = new ConcurrentHashMap (); private static Map CONNECTION_BY_TOKEN = new ConcurrentHashMap (); private static Lock lock = new ReentrantLock(); public static GVConnection getConnByToken(String token) { if (CONNECTION_BY_TOKEN.get(token) != null) { return CONNECTION_BY_TOKEN.get(token); } else { return null; } } public static GVConnection getConnByUserId(String userId) { return CONNECTION_BY_USERID.get(userId); } public static SocketChannel getChannelByUserId(String userId) { if (CONNECTION_BY_USERID.get(userId) != null) { return CONNECTION_BY_USERID.get(userId).getChannel(); } else { return null; } } public static SocketChannel getChannelByToken(String token) { return CONNECTION_BY_TOKEN.get(token).getChannel(); } public static synchronized void addConn2Cache(GVConnection conn) { CONNECTION_BY_TOKEN.put(conn.getToken(), conn); CONNECTION_BY_USERID.put(conn.getUserId(), conn); } public static void removeConnByUserId(String userId) { lock.lock(); try { String token = CONNECTION_BY_USERID.get(userId).getToken(); CONNECTION_BY_USERID.remove(userId); CONNECTION_BY_TOKEN.remove(token); } finally { lock.unlock(); } } public static void removeConnByToken(String token) { lock.lock(); try { String userId = CONNECTION_BY_TOKEN.get(token).getToken(); CONNECTION_BY_TOKEN.remove(token); CONNECTION_BY_USERID.remove(userId); } finally { lock.unlock(); } } public static String getUserIdByToken(String token) { return CONNECTION_BY_TOKEN.get(token).getUserId(); } public static String getTokenByUserId(String userId) { return CONNECTION_BY_USERID.get(userId).getToken(); } public static synchronized void updLastAccessTime(String token, int lastAccessTime) { String userId = CONNECTION_BY_TOKEN.get(token).getUserId(); CONNECTION_BY_USERID.get(userId).setLastAccessTime(lastAccessTime); CONNECTION_BY_TOKEN.get(token).setLastAccessTime(lastAccessTime); } public static Map getConnCache() { return CONNECTION_BY_TOKEN; }}
3.通道清理线程,目前的连接清理采用的是超时清理:此处我遇到一个问题,就是我根本无法判断客户端的异常中断,所以我只能依靠客户端发给服务端的心跳,不断的更新GVConnection中的lastAccessTime,在我的超时线程中,对GVConnection的集合进行遍历,一旦发现有channel关闭的以及超时的,就将其进行清理。而客户端主动关闭,我是可以获取的,在GVServer
public class ConnTimeoutCleanThread implements Runnable { /** * 超时时间 */ private int outTime; /** * 执行周期 */ private int cycle; private static Logger logger = LogManager.getLogger(ConnTimeoutCleanThread.class.getName()); public ConnTimeoutCleanThread(int outTime, int cycle) { this.outTime = outTime; this.cycle = cycle; } public void run() { logger.info("定期通道清理线程启动……"); while (true) { logger.info("定期通道清理开启……"); MapconnCache = GVConnTools.getConnCache(); for (String token : connCache.keySet()) { int currentTime = CommonTools.systemTimeUtc(); GVConnection gvConn = GVConnTools.getConnByToken(token); SocketChannel socketChannel = gvConn.getChannel(); if (!socketChannel.isOpen()) { try { socketChannel.close(); GVConnTools.removeConnByToken(token); logger.info("清理了关闭的连接:token:<" + token + ">"); } catch (IOException e) { e.printStackTrace(); } }else if (currentTime - gvConn.getLastAccessTime() > outTime) { if (socketChannel.isOpen()) { try { socketChannel.close(); GVConnTools.removeConnByToken(token); logger.info("清理了超时的连接:token:<" + token + ">"); } catch (IOException e) { e.printStackTrace(); } } } try { Thread.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } } logger.info("定期通道清理执行完毕!"); try { Thread.sleep(cycle * 1000); } catch (InterruptedException e) { e.printStackTrace(); } } }}
三、再谈发博文目的:
我之所以发博文是因为:
我的代码其实大部分是我四处拼拼凑凑,有些是我自己想的,毕竟9年没有写过代码了,我的java思维仍然停留在jdk1.4刚刚出现的时候,所以我希望大家能够一起学习进步,代码中有很多幼稚的、不科学的、不确定的、甚至我自己也不明白的地方,但是是可执行,并且经过我测试的。希望有时间、有精力的朋友能够帮助我,或者想学习的可以跟我一起学习。
我的代码仍然在不断完善中,因为我本身不是搞开发的,完全是一种兴趣,所以更新的频率不会太快。可能会出现4、5天都不更新的情况。
自从看了少帮主的znet之后,觉得自己做的简直不堪入目,所以我会对我的代码进行改写。不过改写之前会讲之前已经做的都整理出来,至少可以看看成长的经历。