文档章节

基于NIO的消息路由的实现(五) 服务端连接管理

皮鞋铮亮
 皮鞋铮亮
发布于 2015/08/26 15:17
字数 1030
阅读 39
收藏 1
点赞 0
评论 0

一、对客户端连接(也就是SocketChannel)的管理

  1. 客户端与服务端建立连接后,服务端来维护其连接,首先定义了一个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 Map<String, GVConnection> CONNECTION_BY_USERID = new ConcurrentHashMap<String, GVConnection>();
    private static Map<String, GVConnection> CONNECTION_BY_TOKEN = new ConcurrentHashMap<String, GVConnection>();

    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("定期通道清理开启……");

            Map<String, GVConnection> connCache = 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();
            }
        }
    }
}


三、再谈发博文目的:

我之所以发博文是因为:

  1. 我的代码其实大部分是我四处拼拼凑凑,有些是我自己想的,毕竟9年没有写过代码了,我的java思维仍然停留在jdk1.4刚刚出现的时候,所以我希望大家能够一起学习进步,代码中有很多幼稚的、不科学的、不确定的、甚至我自己也不明白的地方,但是是可执行,并且经过我测试的。希望有时间、有精力的朋友能够帮助我,或者想学习的可以跟我一起学习。

  2. 我的代码仍然在不断完善中,因为我本身不是搞开发的,完全是一种兴趣,所以更新的频率不会太快。可能会出现4、5天都不更新的情况。

  3. 自从看了少帮主的znet之后,觉得自己做的简直不堪入目,所以我会对我的代码进行改写。不过改写之前会讲之前已经做的都整理出来,至少可以看看成长的经历。


© 著作权归作者所有

共有 人打赏支持
皮鞋铮亮
粉丝 36
博文 12
码字总数 11603
作品 0
沈阳
基于NIO的消息路由的实现(一) 前言

一、前言: 已经很久没有碰编码了,大概有9年的时间,日新月异的框架和新东西让我眼花缭乱。之前一直在做web相关的应用。由于项目不大,分布式开发在我编码的那个年代里没有做过,后来走上管...

皮鞋铮亮 ⋅ 2015/08/17 ⋅ 16

郑大侠/jetty

##Ketty 基于netty实现的服务端Nio MVC业务开发平台,提供性能监控,日志分析,动态扩展的功能。 ###ketty-srv模块 基于netty实现支持自定义协议扩展的Nio MVC高性能业务框架 ####协议 Http...

郑大侠 ⋅ 2015/11/02 ⋅ 0

基于netty实现的服务端Nio MVC业务开发--ketty

Ketty 基于 netty 实现的服务端 Nio MVC 业务开发平台,提供性能监控,日志分析,动态扩展的功能。 ketty-srv模块 基于netty实现支持自定义协议扩展的Nio MVC高性能业务框架 协议 Http Ketty...

郑大侠 ⋅ 2015/11/09 ⋅ 0

Netty干货分享:京东京麦的生产级TCP网关技术实践总结

1、引言 京东的京麦商家后台2014年构建网关,从HTTP网关发展到TCP网关。在2016年重构完成基于Netty4.x+Protobuf3.x实现对接PC和App上下行通信的高可用、高性能、高稳定的TCP长连接网关。 早期...

JackJiang2011 ⋅ 2017/12/01 ⋅ 0

Java Nio服务端怎么发送数据给Socket IO客户端

服务端用Nio实现。 客户端用Socket实现。 服务端接受客户端发来的消息并返回给客户端消息。 现在的问题是,服务端可以接受到客户端的消息,但服务端write数据之后,客户端得不到数据。 Nio是...

清幽之地 ⋅ 2017/04/12 ⋅ 4

netty高性能浅析

最近在看内部的rpc框架,感觉rpc最重要的就是高效的网络传输,所以学习netty的使用和原理极其重要,于是把netty的基础再总结一下。 首先我们先来看一下netty中的概念,熟悉了基本概念后再来进...

KKys ⋅ 2017/08/30 ⋅ 0

京东的Netty实践,京麦TCP网关长连接容器架构

京麦从 2014 年构建网关,从 HTTP 网关发展到 TCP 网关。在 2016 年重构完成基于 Netty4.x+Protobuf3.x 实现对接 PC 和 App 上下行通信的高可用、高性能、高稳定的 TCP 长连接网关。本文重点...

张松然 ⋅ 01/03 ⋅ 0

新手入门一篇就够:从零开发移动端IM

一、前言 IM发展至今,已是非常重要的互联网应用形态之一,尤其移动互联网时代,它正以无与论比的优势降低了沟通成本和沟通代价,对各种应用形态产生了深远影响。 做为IM开发者或即将成为IM开...

JackJiang- ⋅ 2016/08/03 ⋅ 4

NIO框架入门(三):iOS与MINA2、Netty4的跨平台UDP双向通信实战

前言 本文将演示一个iOS客户端程序,通过UDP协议与两个典型的NIO框架服务端,实现跨平台双向通信的完整Demo。服务端将分别用MINA2和Netty4进行实现,而通信时服务端你只需选其一就行了。同时...

JackJiang- ⋅ 2016/06/28 ⋅ 0

构建消息推送系统之HTTP长连接实践

前言 从Servlet3规范出来以后,利用Servlet3支持的异步特性,我们创建异步上下文asyncContext之后将它保存下来,同时不释放,那么这样就达到了长连接的目的。同时在配合tomcat nio的使用,利...

新栋BOOK ⋅ 2017/11/12 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

vue-cli是什么?

vue-cli是什么? vue-cli 是vue.js的脚手架,用于自动生成vue.js+webpack的项目模板,分为vue init webpack-simple 项目名 和vue init webpack 项目名 两种。 当然首先你的安装vue,webpack...

韦姣敏 ⋅ 25分钟前 ⋅ 0

12c rman中输入sql命令

12c之前版本,要在rman中执行sql语句,必须使用sql "alter system switch logfile"; 而在12c版本中,可以支持大量的sql语句了: 比如: C:\Users\zhengquan>rman target / 恢复管理器: Release 1...

tututu_jiang ⋅ 31分钟前 ⋅ 0

java 线程池

概述 减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务 可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为因为消耗过多的内存,而把服务器累趴下(...

轨迹_ ⋅ 36分钟前 ⋅ 0

Nginx的https配置记录以及http强制跳转到https的方法梳理

Nginx的https配置记录以及http强制跳转到https的方法梳理 一、Nginx安装(略) 安装的时候需要注意加上 --with-httpsslmodule,因为httpsslmodule不属于Nginx的基本模块。 Nginx安装方法: ...

Yomut ⋅ 48分钟前 ⋅ 0

SpringCloud Feign 传递复杂参数对象需要注意的地方

1.传递复杂参数对象需要用Post,另外需要注意,Feign不支持使用GetMapping 和PostMapping @RequestMapping(value="user/save",method=RequestMethod.POST) 2.在传递的过程中,复杂对象使用...

@林文龙 ⋅ 49分钟前 ⋅ 0

如何显示 word 左侧目录大纲

打开word说明文档,如下图,我们发现左侧根本就没有目录,给我们带来很大的阅读障碍 2 在word文档的头部菜单栏中,切换到”视图“选项卡 3 然后勾选“导航窗格”选项 4 我们会惊奇的发现左侧...

二营长意大利炮 ⋅ 53分钟前 ⋅ 0

智能合约编程语言Solidity之线上开发工具

工具地址:https://ethereum.github.io/browser-solidity/ 实例实验: 1.创建hello.sol文件 2.调试输出结果

硅谷课堂 ⋅ 54分钟前 ⋅ 0

ffmpeg 视频格式转换

转 Mp4 格式 #> ffmpeg -i input.avi -c:v libx264 output.mp4#> ffmpeg -i input.avi -c:v libx264 -strict -2 output.mp4#> ffmpeg -i input.avi -c:v libx264 -strict -2 -s 1......

Contac ⋅ 今天 ⋅ 0

VCS仿真生成vpd文件(verilog)

VCS仿真生成vpd文件(verilog): https://www.cnblogs.com/OneFri/p/5987673.html SYNOPSYS VCS常用命令使用详解 https://blog.csdn.net/hemmingway/article/details/49382551 DVE是synopsys公......

whoisliang ⋅ 今天 ⋅ 0

Spring Boot启动配置原理

几个重要的事件回调机制 配置在META-INF/spring.factories ApplicationContextInitializer SpringApplicationRunListener 只需要放在ioc容器中 ApplicationRunner CommandLineRunner 启动流程......

小致dad ⋅ 今天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部