从mina中学习超时程序编写

在很多情况下,程序需要使用计时器定,在指定的时间内检查连接过期。例如,要实现一个mqtt服务,为了保证QOS,在服务端发送消息后,需要等待客户端的ack,确保客户端接收到消息,当服务端等待一段时间后,仍未获得客户端ack,就会将消息重新传送。在Mina中,每个链接都可以设置read ideal 和write ideal 时间,当链接read ideal或者write ideal超时后,可以触发用户自定义的一个动作(比如关闭链接等),本文主要是探讨mina中如何实现这种超时策略,也给大家带来参考(IndexIdleChecker.java)。

1.    主要的数据结构

在IndexIdleChecker中,有如下属性定义:

private final Set<AbstractIoSession>[] readIdleSessionIndex = new Set[MAX_IDLE_TIME_IN_SEC];

private final Set<AbstractIoSession>[] writeIdleSessionIndex = new Set[MAX_IDLE_TIME_IN_SEC];

分别用于记录readIdle和writeIdle,以下仅以readIdle为例,因为writeIdle与readIdle流程基本一样。

注意到readIdleSessionIndex的类型是数组,并且数组中的每个元素是Set类型,Set中的每一个元素都是Session(AbstractIoSession的简写)类型,readIdle数组实际大小为3600,mina默认的session最大过期时间为一小时,因此,数组的每一个元素记录了这一秒内将要过期的session集合(注意,算法使用的是循环数组,因为默认最多一个小时就过期)。

IndexIdleChecker还有一个属性:

private long lastCheckTimeMs = System.currentTimeMillis();

用于记录上次处理过期请求的时间。

每个session对象中都有一个property,用于记录这个session目前在readIdle数组中的位置(READ_IDLE_INDEX)。

2.    Read(或者write)请求ideal处理策略

Read事件到来的方法签名如下:

public void sessionRead(AbstractIoSession session, long timeInMs) ;

先通过session的READ_IDLE_INDEX 属性,获取session在readIdeal中的位置(oldIndex),之后删除readIdeal中对应oldIndex的set中的当前session,然后计算当前session下次的过期时间,将过期时间%3600后,求出其对应的readIdeal数组下标(index),将session放入readIdeal的index位置的set中,并且设置session的READ_IDLE_INDEX 的属性值为index。

3.    Worker线程

在运行时Mina会启动一个Worker守护线程,代码如下:

@Override

public void run() {

while (running) {

try {

sleep(GRANULARITY_IN_MS);

processIdleSession(System.currentTimeMillis());

} catch (InterruptedException e) {

break;

}

}

}

Worker进程在每次处理完session过期后,都会sleep 1s,然后进行下一次的过期处理,在processIdleSession方法中,仅需每次处理

int startIdx = ((int) (Math.max(lastCheckTimeMs, timeMs - MAX_IDLE_TIME_IN_MS + 1) / 1000L))

% MAX_IDLE_TIME_IN_SEC;

int endIdx = ((int) (timeMs / 1000L)) % MAX_IDLE_TIME_IN_SEC;

startIdx和endIdx之间的readIdeal数组即可。针对每个idx,拿出set,将set中的所有session的READ_IDLE_INDEX 置为null,并调用session中的用户处理过程即可(比如关闭session)。(注意,如果用户设置过期时间为-1,表示永远不过期,此时不做任何处理)。需要注意的是,worker线程每次至少会处理一个index,有时会处理多个index,比如,当前这次处理index的时间超过了3s,之后worker又sleep 1s,那么下次worker被唤醒,将处理这4s内的4个index。

小结:

在大多数情况下,链接线程比较活跃的情况下,session的存储位置会在数组中不断的向后移动(因为是循环数组,所以没有边界),因此当worker要处理启动处理过期时,活跃的session的index一定会在过期处理的index之前,因此,仅有那些不活跃的到期的session才会被查询和处理掉,worker的处理代价并不高。

使用这种方式不需要计时器等支持,实现简单,是一种比较好的超时处理方式。

参考:

<dependency>

<groupId>org.apache.mina</groupId>

<artifactId>mina-core</artifactId>

<version>3.0.0-M2</version>

</dependency>

源码:

public class IndexedIdleChecker implements IdleChecker {

/**Maximum idle time in second : default to 1 hour */

private static final int MAX_IDLE_TIME_IN_SEC = 60 * 60;

/**Maximum idle time in milliseconds : default to 1 hour */

private static final long MAX_IDLE_TIME_IN_MS = MAX_IDLE_TIME_IN_SEC * 1000L;

/** Alogger for this class */

private static final Logger LOG = LoggerFactory.getLogger(IndexedIdleChecker.class);

// Aspeedup for logs

private static final boolean IS_DEBUG = LOG.isDebugEnabled();

private static final AttributeKey<Integer> READ_IDLE_INDEX = AttributeKey.createKey(Integer.class,

"idle.read.index");

private static final AttributeKey<Integer> WRITE_IDLE_INDEX = AttributeKey.createKey(Integer.class,

"idle.write.index");

private long lastCheckTimeMs = System.currentTimeMillis();

@SuppressWarnings("unchecked")

private final Set<AbstractIoSession>[] readIdleSessionIndex = new Set[MAX_IDLE_TIME_IN_SEC];

@SuppressWarnings("unchecked")

private final Set<AbstractIoSession>[] writeIdleSessionIndex = new Set[MAX_IDLE_TIME_IN_SEC];

/** Theelapsed period between two checks : 1 second */

private static final int GRANULARITY_IN_MS = 1000;

private final Worker worker = new Worker();

private volatile boolean running = true;

/**

* {@inheritDoc}

*/

@Override

public void start() {

worker.start();

}

/**

* {@inheritDoc}

*/

@Override

public void destroy() {

running = false;

try {

// interrupt the sleep

worker.interrupt();

// wait for worker to stop

worker.join();

} catch (InterruptedException e) {

// interrupted, we don't care much

}

}

/**

* {@inheritDoc}

*/

@Override

public void sessionRead(AbstractIoSession session, long timeInMs) {

if (IS_DEBUG) {

LOG.debug("session read event, compute idle index of session {}", session);

}

// remove from the old index position

Integer oldIndex =session.getAttribute(READ_IDLE_INDEX);

if (oldIndex != null && readIdleSessionIndex[oldIndex] != null) {

if (IS_DEBUG) {

LOG.debug("remove for old index {}", oldIndex);

}

readIdleSessionIndex[oldIndex].remove(session);

}

long idleTimeInMs = session.getConfig().getIdleTimeInMillis(IdleStatus.READ_IDLE);

// is idle enabled ?

if (idleTimeInMs <= 0L) {

if (IS_DEBUG) {

LOG.debug("no read idle configuration");

}

} else {

int nextIdleTimeInSeconds = (int) ((timeInMs +idleTimeInMs) / 1000L);

int index = nextIdleTimeInSeconds % MAX_IDLE_TIME_IN_SEC;

if (IS_DEBUG) {

LOG.debug("computed index : {}", index);

}

if (readIdleSessionIndex[index] == null) {

readIdleSessionIndex[index] =Collections

.newSetFromMap(newConcurrentHashMap<AbstractIoSession, Boolean>());

}

if (IS_DEBUG) {

LOG.debug("marking session {} idle for index {}", session, index);

}

readIdleSessionIndex[index].add(session);

session.setAttribute(READ_IDLE_INDEX, index);

}

}

/**

* {@inheritDoc}

*/

@Override

public void sessionWritten(AbstractIoSession session, long timeInMs) {

if (IS_DEBUG) {

LOG.debug("session write event, compute idle index of session {}", session);

}

// remove from the old index position

Integer oldIndex =session.getAttribute(WRITE_IDLE_INDEX);

if (oldIndex != null && writeIdleSessionIndex[oldIndex] != null) {

if (IS_DEBUG) {

LOG.debug("remove for old index {}", oldIndex);

}

writeIdleSessionIndex[oldIndex].remove(session);

}

long idleTimeInMs = session.getConfig().getIdleTimeInMillis(IdleStatus.WRITE_IDLE);

// is idle enabled ?

if (idleTimeInMs <= 0L) {

if (IS_DEBUG) {

LOG.debug("no write idle configuration");

}

} else {

int nextIdleTimeInSeconds = (int) ((timeInMs +idleTimeInMs) / 1000L);

int index = nextIdleTimeInSeconds % MAX_IDLE_TIME_IN_SEC;

if (writeIdleSessionIndex[index] == null) {

writeIdleSessionIndex[index] =Collections

.newSetFromMap(newConcurrentHashMap<AbstractIoSession, Boolean>());

}

writeIdleSessionIndex[index].add(session);

session.setAttribute(WRITE_IDLE_INDEX, index);

}

}

/**

* {@inheritDoc}

*/

@Override

public int processIdleSession(long timeMs) {

int counter = 0;

long delta = timeMs - lastCheckTimeMs;

if (LOG.isDebugEnabled()) {

LOG.debug("checking idle time, last = {}, now = {}, delta = {}", new Object[] { lastCheckTimeMs, timeMs,

delta });

}

if (delta < 1000) {

LOG.debug("not a second between the last checks, abort");

return 0;

}

// if (lastCheckTimeMs == 0) {

// LOG.debug("first check, we start now");

// lastCheckTimeMs = System.currentTimeMillis() - 1000;

// }

int startIdx = ((int) (Math.max(lastCheckTimeMs, timeMs - MAX_IDLE_TIME_IN_MS + 1) / 1000L))

% MAX_IDLE_TIME_IN_SEC;

int endIdx = ((int) (timeMs / 1000L)) % MAX_IDLE_TIME_IN_SEC;

LOG.debug("scaning from index {} to index {}", startIdx, endIdx);

int index = startIdx;

do {

LOG.trace("scanningindex {}", index);

// look at the read idle index

counter += processIndex(readIdleSessionIndex, index, IdleStatus.READ_IDLE);

counter += processIndex(writeIdleSessionIndex, index, IdleStatus.WRITE_IDLE);

index = (index + 1) % MAX_IDLE_TIME_IN_SEC;

} while (index != endIdx);

// save last check time for next call

lastCheckTimeMs = timeMs;

LOG.debug("detected {} idleing sessions", counter);

return counter;

}

private int processIndex(Set<AbstractIoSession>[]indexByTime, int position, IdleStatus status) {

Set<AbstractIoSession> sessions =indexByTime[position];

if (sessions == null) {

return 0;

}

int counter = 0;

for (AbstractIoSession idleSession : sessions) {

idleSession.setAttribute(status ==IdleStatus.READ_IDLE ? READ_IDLE_INDEX : WRITE_IDLE_INDEX, null);

// check if idle detection wasn't disabled since the index update

if (idleSession.getConfig().getIdleTimeInMillis(status)> 0) {

idleSession.processSessionIdle(status);

}

counter++;

}

// clear the processed index entry

indexByTime[position] = null;

return counter;

}

/**

* Thread in charge of checking the idleingsessions and fire events

*/

private class Worker extends Thread {

public Worker() {

super("IdleChecker");

setDaemon(true);

}

@Override

public void run() {

while (running) {

try {

sleep(GRANULARITY_IN_MS);

processIdleSession(System.currentTimeMillis());

} catch (InterruptedException e) {

break;

}

}

}

}

}

从mina中学习超时程序编写的更多相关文章

  1. ·通过wifi_scan学习esp32wifi程序编写

    在ESP32的设计开发中,我们必然会需要使用到wifi或ble功能,今天就讲解下如何将WIFI功能纳入到ESP32中来. 初始化WiFi环境 首先,WiFi子系统的初始化需要由我们自己来自行,当我们写 ...

  2. zigbee学习:示例程序SampleApp中按键工作流程

    zigbee学习:示例程序SampleApp中按键工作流程 本文博客链接:http://blog.csdn.net/jdh99,作者:jdh,转载请注明. 环境: 主机:WIN7 开发环境:IAR8. ...

  3. zigbee学习:示例程序SampleApp中通讯流程

    zigbee学习:示例程序SampleApp中通讯流程 本文博客链接:http://blog.csdn.net/jdh99,作者:jdh,转载请注明. 参考链接: http://wjf88223.bl ...

  4. C++学习笔记1(Windows程序运行原理及程序编写流程)

    窗口产生过程,句柄原理,消息队列,回调函数,窗口关闭与应用程序退出的工作关系,使用VC++的若干小技巧,stdcall与Lessonecl调用规范的比较,初学者常犯错误及注意事项.以下是应用程序与操作 ...

  5. C# 你什么让程序员寂寞成酱紫 (男生版 娱乐中学习 抽象类 接口 继承 实现方法 )

    你什么让程序员寂寞成酱紫 (男生版 娱乐中学习 抽象类 接口 继承 实现方法 )   一个家庭 相当于 一个空间,这个空间里 有 很多元素,比如 爱,爱这个抽象事物,可能有很多动作,接吻.交流,对于一 ...

  6. 从别人的代码中学习golang系列--01

    自己最近在思考一个问题,如何让自己的代码质量逐渐提高,于是想到整理这个系列,通过阅读别人的代码,从别人的代码中学习,来逐渐提高自己的代码质量.本篇是这个系列的第一篇,我也不知道自己会写多少篇,但是希望 ...

  7. Android Camera 相机程序编写

    Android Camera 相机程序编写 要自己写一个相机应用直接使用相机硬件,首先应用需要一个权限设置,在AndroidManifest.xml中加上使用设备相机的权限: <uses-per ...

  8. Hadoop学习笔记(5) ——编写HelloWorld(2)

    Hadoop学习笔记(5) ——编写HelloWorld(2) 前面我们写了一个Hadoop程序,并让它跑起来了.但想想不对啊,Hadoop不是有两块功能么,DFS和MapReduce.没错,上一节我 ...

  9. HTML5 canvas绘制雪花飘落动画(需求分析、知识点、程序编写分布详解)

    看到网上很多展示html5雪花飞动的效果,确实非常引人入胜,我相信大家也跟我一样看着心动的同时,也很好奇,想研究下代码如何实现:虽然哦很多地方也能下载这些源码,不过也不知道别人制作此类动画时的思路及难 ...

随机推荐

  1. Spring Boot——开发新一代Spring应用

    Spring官方网站本身使用Spring框架开发,随着功能以及业务逻辑的日益复杂,应用伴随着大量的XML配置文件以及复杂的Bean依赖关系.随着Spring 3.0的发布,Spring IO团队逐渐开 ...

  2. boost.asio系列——io_service

    IO模型 io_service对象是asio框架中的调度器,所有异步io事件都是通过它来分发处理的(io对象的构造函数中都需要传入一个io_service对象). asio::io_service i ...

  3. JavaScript快速入门(三)——JavaScript语句

    JavaScript基本语句 基本概述 JavaScript是脚本语言,从上到下解释执行,最小单位为语句或语句块,每个语句以分号结尾,每个语句块以右大括号结尾. JavaScript可以将多条语句或语 ...

  4. 2014Esri全球用户大会——亮点系列之精彩应用案例

    在2014 Esri UC上,Esri邀请不少用户到场分享了自己企业的案例,在这里与大家进行分享. 一.City of Mineapolis 1.案例背景 Mineapolis市使用GIS已经数十年, ...

  5. 如何设置Java虚拟机内存以适应大程序的装载

    Java虚拟机对于运行时的程序所占内存是有限制的,当我们的项目或者程序很大时,往往会照成内存溢出. 举个例子: public class SmallTest1 { public static void ...

  6. c/c++使用VS2013连接MySQL与ubuntu下c链接mysql

    vs连接数据库事实上就是将mysql数据库.h头文件接口.lib链接文件和dll运行文件增加到项目中.以下是配置怎样增加. 转于http://www.cnblogs.com/justinzhang/a ...

  7. ThinkPhp学习03

    原文:ThinkPhp学习03 一.ThinkPHP 3 的输出      (重点) a.通过 echo 等PHP原生的输出方式在页面中输出 b.通过display方法输出   想分配变量可以使用as ...

  8. 实现 select中指定option选中触发事件

    我们在用到下拉列表框select时,需要对选中的<option>选项触发事件,其实<option>本身没有触发事件方法,我们只有在select里的onchange方法里触发. ...

  9. 模式识别 - 处理多个演示样本研究(MIL)特点(matlab)

    处理多个演示样本研究(MIL)特点(matlab) 本文地址: http://blog.csdn.net/caroline_wendy/article/details/27206325 多演示样例学习 ...

  10. 【剑指offer】和为定值的两个数

    转载请注明出处:http://blog.csdn.net/ns_code/article/details/24933341 题目描写叙述: 输入一个递增排序的数组和一个数字S,在数组中查找两个数,是的 ...