深入理解Apache Mina

644866551

贡献于2014-10-13

字数:0 关键词: 网络工具包 Apache Apache MINA

Mina 状态机介绍 Translator:中國壹石頭 ---------------------------------------------------------------------------------------------------------------------- What we call human nature is actually human habbit . Mina 状态机介绍 (Introduction to mina-statemachine) 如果 你使用 Mina 开发 一个 复杂 的网络 应用 时, 你可 能在 某些 地方会 遇到 那个 古老而 又好 用 的状 态模 式, 来 使 用这 个模 式解 决你的 复杂 应用 。 然 而, 在 你做 这个 决定 之前, 你 或 许 想 检 出Mina 的状 态机 的代 码, 它会 根据 当前对 象的 状态 来返回 对接 收到 的简短 的数 据的 处理 信 息。 注意: 现在正式发布 Mina 的状态 机。因此你 要自己在 Mina 的SVN 服 务 器上 检 出 该 代码 , 并自 己编 译, 请 参 考 开发指 南, 来 获取 更多 的关于 检出 和编译 Mina 源码 的信 息。 Mina 的状 态机 可以 和所 有已经 发布 的版本 Mina 配合 使用 (1.0.x, 1.1.x 和当前 发布 的版 本 )。 一个简单的例子一个简单的例子一个简单的例子一个简单的例子 让我 们使 用一个 简单 的例 子来 展示一下 Mina 的状 态机 是如 何工作 的。 下 面的图 片展 示了 一 个录 音机 的状 态机。 其 中 的椭 圆是 状态, 箭 头 表示 事务 。 每 个 事务 都有 一个事 件的 名字 来 标 记该 事务 。 初始 化时 , 录 音 机的状 态是 空的 。 当 磁 带放如 录音 机的 时候, 加 载的 事件 被触发 , 录 音 机 进入 到加 载状 态。 在 加载 的状 态下 , 退 出 的事 件会 使录 音机进 入到 空的 状态, 播 放 的事 件 会 使加 载的 状态 进入到 播放 状态 。等等 ......我想 你可 以推 断后后 面的 结果 :) 现在 让我 们写 一些代 码。 外部 (录音 机中 使用 该代码 的地 方 )只能 看到 录音 机的接 口: ----------------------START---------------------- public interface TapeDeck { void load(String nameOfTape); void eject(); void start(); void pause(); void stop(); } -----------------------END------------------------ 下面 我们 开始 编写真 正执 行的 代码, 这 些 代码 在一个 事务 被触 发时 , 会 在 状态 机中 执行 。 首 先我 们定 义一个 状态 。这 些状 态都使 用字 符串 常量来 定义 ,并 且使用 @state 标记 来声 明。 ----------------------START---------------------- public class TapeDeckHandler { Mina 状态机介绍 Translator:中國壹石頭 ---------------------------------------------------------------------------------------------------------------------- What we call human nature is actually human habbit . @State public static final String EMPTY = "Empty"; @State public static final String LOADED = "Loaded"; @State public static final String PLAYING = "Playing"; @State public static final String PAUSED = "Paused"; } -----------------------END------------------------ 现在 我们 已经 定义了 录音 机中 的所有 状态 , 我 们 可 以根据 每个 事务 来创建 相应 的代 码。 每 个 事务 都和 一个 TapeDeckHandler 的方 法对 应。 每个事 务的 方法 都使用 @Transtration 标签 来 声明 , 这个标 签定 义了 事件的 ID,该 ID会触 发事 务的 执行。 事务 开始 时的 状态使用 start, 事务 结束 使用 next,事 务正 在运 行使用 on。 ----------------------START---------------------- public class TapeDeckHandler { @State public static final String EMPTY = "Empty"; @State public static final String LOADED = "Loaded"; @State public static final String PLAYING = "Playing"; @State public static final String PAUSED = "Paused"; @Transition(on = "load", in = EMPTY, next = LOADED) public void loadTape(String nameOfTape) { System.out.print ln("Tape '" + nameOfTape + "' loaded"); } @Transitions({ @Transition(on = "play", in = LOADED, next = PLAYING), @Transition(on = "play", in = PAUSED, next = PLAYING) }) public void playTape() { System.out.print ln("Playing tape"); } @Transition(on = "pause", in = PLAYING, next = PAUSED) public void pauseTape() { System.out.print ln("Tape paused"); } @Transition(on = "stop", in = PLAYING, next = LOADED) public void stopTape() { System.out.print ln("Tape stopped"); } @Transition(on = "eject", in = LOADED, next = EMPTY) public void ejectTape() { System.out.print ln("Tape ejected"); } Mina 状态机介绍 Translator:中國壹石頭 ---------------------------------------------------------------------------------------------------------------------- What we call human nature is actually human habbit . } -----------------------END------------------------ 请注 意, TapeDeckHandler 类没 有实现 TapeDeck ,呵 呵, 这是 故意的 。 现在 让我 们亲 密接触 一下 这个 代码。在 loadTape 方法 上的 @Transition 标签 : ----------------------START---------------------- @Transition(on = "load", in = EMPTY, next = LOADED) public void loadTape(String nameOfTape) {} -----------------------END----------------------- 指定了 这个状态后 ,当录音 机处于空状 态时,磁带 装载事件 启动后会触发 loadTape 方法, 并且录音机状态将会变换到Loaded 状态。@Transition 标签中关于 pauseTape,stopTape,ejectTape 的方 法就 不需 要在多 介绍 了。 关于 playTape 的标 签和 其他 的 标签 看起 来不 太一样 。 从 上 面的 图中 我们可 以知 道, 当 录 音 机的状 态在 Loaded或者 Paused 时, play 事件 都会 播放 磁带。 当多 个事 务同时 条用 同一个 方法 时, @Transition 标签 需要 按 下面 的方 法使 用: ----------------------START---------------------- @Transitions({ @Transition(on = "play", in = LOADED, next = PLAYING), @Transition(on = "play", in = PAUSED, next = PLAYING) }) public void playTape(){} -----------------------END----------------------- @Transition 标签 清晰 的列 出了声 明的 方法 被多个 事务 调用 的情况 。 ###################################################### 要点 : 更多关于 @Transition 标签的参数 (1)如果你省略了 on 参数,系统 会将该值默认为“*”,这样任何事件都可以触发 该方法。 (2)如果你省略了 next 参数,系统 会将默认值改为 “_self_ ”,这个是和当前的状态相关的, 如果你要实现一个 循环的事务,你所需要做的就是省略状态机中的 next 参数。 (3)weight 参数用于定义事务的查 询顺序,一般的状态的事务 是根据 weight 的值 按升序排列的, weight 默认的是 0. ###################################################### 现在 最后 一步 就是使 用声 明类 创建一个 状 态机 的对象 , 并 且 使用这 个状 态机 的实例 创建 一 个 代理 对象 ,该 代理对 象实 现了 TapeDeck接口 : ----------------------START---------------------- public static void main(String[] args) { // 创建 录音 机事 件的句 柄 TapeDeckHandler handler = new TapeDeckHandler(); // 创建 录音 机的 状态机 StateMachine sm = StateMachineFactory.getInstance(Transition.class).create(TapeDeckHandler.EMPTY, handler); // 使用 上面 的状 态机, 通过 一个 代理创 建一个 TapeDeck的实 例 TapeDeck deck = new StateMachineProxyBuilder().create(TapeDeck.class, sm); Mina 状态机介绍 Translator:中國壹石頭 ---------------------------------------------------------------------------------------------------------------------- What we call human nature is actually human habbit . // 加载 磁带 deck.load("The Knife - Silent Shout"); // 播放 deck.play(); // 暂停 deck.pause(); // 播放 deck.play(); // 停止 deck.stop(); // 退出 deck.eject(); } -----------------------END----------------------- 这一行 ----------------------START---------------------- TapeDeckHandler handler = new TapeDeckHandler(); StateMachine sm = StateMachineFactory.getInstance(Transition.class).create(TapeDeckHandler.EMPTY, handler); -----------------------END----------------------- 使用 TapeDeckHandler 创建 一个 状态 机的实 例。 StateMachineFactory.getInstance(...) 调用 的方 法中 使用的 Transition.class 是通 知工 厂我 们使用 @Transition 标签 创建 一个 状态机 。 我 们指 定了 状态 机开始 时状 态是 空的。 一 个 状态 机是 一个基 本的 指示 图。 状 态 对象和 图中 的 节 点对应,事 务对 象和 箭头 指向 的方 向对 应。 我们 在 TapeDeckHandler 中使用的 每一个 @Transition 标签 都和 一个 事务的 实例 对应 。 ###################################################### 要点 : 那么, @Transition 和 Transition 有什么不同吗? @Transition 是你用来标记当事务在状态之间变化时 应该使用那个方法。在后台处理中, Mina 的状态机会为 MethodTransition 中每一个事务标签创建一个事务的实例。 MethodTransition 实现了 Transition 接口。作为一个 Mina 状态机的使用者,你不用直接使用 Transition 或者 MethodTransition 类型的对象。 ###################################################### 录音机 TapeDeck 的实 例是 通过 调用 StateMachineProxyBuilder 来创 建的 : ----------------------START---------------------- TapeDeck deck = new StateMachineProxyBuilder().create(TapeDeck.class, sm); -----------------------END----------------------- StateMachineProxyBuilder.create()使用 的接 口需 要由代 理的 对象 来实现 , 状 态机 的实 例将 接 收由 代理 产生 的事件 所触 发的 方法。 当代 码执 行时 ,输出 的结 果如 下: ----------------------START---------------------- Tape 'The Knife - Silent Shout' loaded Playing tape Mina 状态机介绍 Translator:中國壹石頭 ---------------------------------------------------------------------------------------------------------------------- What we call human nature is actually human habbit . Tape paused Playing tape Tape stopped Tape ejected -----------------------END----------------------- ##################################################### 要点: 这和 Mina 有什么关系? 或许你已经注意到,在这个例子中没有对 Mina 进行任何配置。但 是不要着急。 稍后我们将 会看到如何为 Mina 的IoHandler 接口创建一个状态机。 ###################################################### 它是怎样工作的?它是怎样工作的?它是怎样工作的?它是怎样工作的? 让我 们走 马观 花的看 看当 代理 调用一个 方 法的 时候发 生了 什么 。 查看 一个 StateContext(状态 的上 下文 )对象 状态 上下 文之 所以重 要是 因为 它保存 了当 前的 状态。 代理 调用 一个方 法时 ,状 态上下 文 会通知 StateContextLookup 实例 去方 法的 参数中 获取 一个 状态的 上下 文。 一般情 况下 , StateContextLookup 的实 现将 会循 环方法 中的 参数 ,并查 找一个 指 定类型 的对 象, 并且 使用 这个 对象 反转出 一个 上下 文对象 。 如 果没 有声 明一个 状态 上下 文, StateContextLookup 将会 创一个 , 并将其 存放 到对 象中。 当代理 Mina 的IoHandler 接口时 ,我们将使用 IoSessoinStateContextLookup 实例, 该实 例用来 查询一个 IoSession 中的方 法参数。它 将会使用 IoSession 的属性 值为每一个 Mina 的session 来存 放一个 独 立的状 态上 下文 的实例 。 这 中方 式下, 同 样 的状 态机可 以让 所有 的 Mina 的会 话使 用, 而不会 使每 个会 话彼此 产生 影响 。 ###################################################### 要点 : 在上面的例子中,当我们使用 StateMachineProxyBuilder 创建一个代 理时,我们 一直没有我们一直没有配置 StateContex tLookup 使用哪种实现。如果没 有配置,系统会 使用 SingletonStateContextLookup 。SingletonStateContextLookup 总是不理会方法中 传递给它的参数,它一直返回一个 相同的状态上下文。很明显,这中方式在多个客户端 并发的情况下使用同一个同一个状态机 是没有意义的。这种情况下的配置会 在后面的关于 IoHandler 的代理配置时进行说明。 ###################################################### 将方法 请求反转成 一个事件 对象所有在 代理对象上 的方法请 求都会有代 理对象转换 成事件 对象 。一个事 件有一个 ID或者 0个或 多个参数 。事件的 ID和方 法的名字 相当, 事件的参 数和方法的参数相 当。调用 方 法 deck.load("The Knife - Silent Shout") 相当于事件 {id = "load", arguments = ["The Knife - Silent Shout"]}.事件对象中 包含一 个状 态上 下文的引 用, 该状 态上 下文是 当前 查找 到的。 触发状态机触发状态机触发状态机触发状态机 一旦事件对象被创建,代理会调用StateMachine.handle(Event). 方法。 StateMachine.handle(Event)遍历 事务对 象中当 前的 状态, 来查找 能够接 收当前 事件 的事务 的实 例。 这 个 过程 会在事 务的 实例 找到后 停止 。 这 个 查 询的 顺序是 由事 务的 重量值 来决 定 的 Mina 状态机介绍 Translator:中國壹石頭 ---------------------------------------------------------------------------------------------------------------------- What we call human nature is actually human habbit . (重 量值 一般 在 @Transition 标签中指定)。 执行事务执行事务执行事务执行事务 最后 一部 就是在 Transition 中调 用匹 配事 件对象的 Transition.execute(Event)方法 。当 事件 已经 执行 ,这 个状态 机将 更新 当前的 状态 ,更 新后的 值是 你在 事务中 定义 的后 面的状 态。 ###################################################### 要点 : 事务是一个 接口。每次 你使用 @Transition 标签时, MethodTransition 对象将会被创建。 ###################################################### MethodTransition(MethodTransition(MethodTransition(MethodTransition(方法事务方法事务方法事务方法事务)))) MethodTransition 非常 重要 , 它 还需 要一些 补 充说明 。 如 果 事件 ID和@Transition 标签 中 的 on 参数 匹配 , 事 件 的参 数和 @Transition 中的 参数 匹配 , 那么 MethodTransition 和这 个事 件 匹配 。 所以 如果 事件 看起来 像 {id = "foo", arguments = [a, b, c]},那 么下 面的 方法: ----------------------START---------------------- @Transition(on = "foo") public void someMethod(One one, Two two, Three three) {...} -----------------------END----------------------- 只和 这个 事 件 匹 配 ((a instanceof One && b instanceof Two && c instanceof Three) == true).。当 匹配 时, 这个方 法将 会被 与其匹 配的 事件 使用绑 定的 参数 调用。 ###################################################### 要点 : Integer, Double, Float, 等也和他们的基本类型 int, double, float, 等匹配。 ###################################################### 因此 ,上 面的 状态是 一个 子集 ,需要 和下 面的 方法匹 配: ----------------------START---------------------- @Transition(on = "foo") public void someMethod(Two two) {...} -----------------------END----------------------- 上面 的方法 和 ((a instanceof Two || b instanceof Two || c instanceof Two) == true)是等 价 的。 在这 种情 况下, 第一个 被 匹配的 事件 的参 数将会 和该 方法 绑定, 在它 被调 用的时 候。 一个 方法 如果 没有参 数, 在其 事件的 ID匹配 时, 仍然 会被调 用: ----------------------START---------------------- @Transition(on = "foo") public void someMethod() {...} -----------------------END----------------------- 这样 做让 事件 的处理 变得 有点 复杂, 开 始 的 两个方 法的 参数 和事件 的类 及状 态的上 下文 接 口 相匹 配。 这意 味着: ----------------------START---------------------- @Transition(on = "foo") public void someMethod(Event event, StateContext context, One one, Two two, Three three) {...} Mina 状态机介绍 Translator:中國壹石頭 ---------------------------------------------------------------------------------------------------------------------- What we call human nature is actually human habbit . @Transition(on = "foo") public void someMethod(Event event, One one, Two two, Three three) {...} @Transition(on = "foo") public void someMethod(StateContext context, One one, Two two, Three three) {...} -----------------------END----------------------- 上面 的 方 法 和 事 件 {id = "foo", arguments = [a, b, c]} if ((a instanceof One && b instanceof Two&& c instanceof Three) == true) 是匹 配的 。当 前的事 件对 象和 事件的 方法 绑定 ,当 前的 状态上 下文 和该 方法被 调用 时的 上下文 绑定 。 在此 之前 一个 事件的 参数 的集 合将会 被使 用。 当 然 , 一 个 指 定的状 态上 下文 的实现 将会 被 指 定, 以用 来替 代通用 的上 下文 接口。 ----------------------START---------------------- @Transition(on = "foo") public void someMethod(MyStateContext context, Two two) {...} -----------------------END----------------------- ###################################################### 要点 : 方法中参数的顺序很重要。若方法需要 访问当前的事件,它必须被配置为第一个 方法参数。当事件为第一个 参数的时候,状态上下问不能配置为第二个参数,它也不能 配置为第一个方法的参 数。事件的参数也要按正确的顺序进行匹配。方法的事务不会在 查找匹配事件方法的时 候重新排序。 ###################################################### 到现 在如 果你 已经掌 握了 上面 的内容 , 恭 喜你 ! 我 知 道 上面的 内容 会有 点难以 消化 。 希 望 下 面的例子能让你对上面的内容有更清晰的了解。注意这个事件Event {id = "messageReceived", arguments = [ArrayList a = [...], Integer b = 1024]}。下 面的方法 将 和这 个事 件是 等价的 : ----------------------START---------------------- // All method arguments matches all event arguments directly @Transition(on = "messageReceived") public void messageReceived(ArrayList l, Integer i) {...} // Matches since ((a instanceof List && b instanceof Number) == true) @Transition(on = "messageReceived") public void messageReceived(List l, Number n) {...} // Matches since ((b instanceof Number) == true) @Transition(on = "messageReceived") public void messageReceived(Number n) {...} // Methods with no arguments always matches @Transition(on = "messageReceived") public void messageReceived() {...} // Methods only interested in the current Event or StateContext always matches @Transition(on = "messageReceived") Mina 状态机介绍 Translator:中國壹石頭 ---------------------------------------------------------------------------------------------------------------------- What we call human nature is actually human habbit . public void messageReceived(StateContext context) {...} // Matches since ((a instanceof Collect ion) == true) @Transition(on = "messageReceived") public void messageReceived(Event event, Collect ion c) {...} -----------------------END----------------------- 但是 下面 的方 法不会 和这 个事 件相匹 配: ----------------------START---------------------- // Incorrect ordering @Transition(on = "messageReceived") public void messageReceived(Integer i, List l) {...} //((a instanceof LinkedList) == false) @Transition(on = "messageReceived") public void messageReceived(LinkedList l, Number n) {...} // Event must be first argument @Transition(on = "messageReceived") public void messageReceived(ArrayList l, Event event) {...} // StateContext must be second argument if Event is used @Transition(on = "messageReceived") public void messageReceived(Event event, ArrayList l, StateContext context) {...} // Event must come before StateContext @Transition(on = "messageReceived") public void messageReceived(StateContext context, Event event) {...} -----------------------END----------------------- 状态继承状态继承状态继承状态继承 状态 的实 例将 会有一个 父 类的 状态。 如果 StateMachine.handle(Event)的方 法不 能找 到一 个 事务 和当 前的 事件在 当前 的状 态中匹 配, 它 将 会寻 找父类 中的 装。 如 果 仍然 没有找 到, 那 么 事务 将会 自动 寻找父 类的 父类 ,知道 找到 为止 。 这个 特性 很有 用, 当 你想 为所 有的 状态添 加一些 通 用的代 码时 , 不 需 要 为每 一个状 态的 方 法 来声 明事 务。 这里你 可以 创建 一个类 的继 承体 系,使 用下 面的 方法即 可: ----------------------START---------------------- @State public static final String A = "A"; @State(A) public static final String B = "A->B"; @State(A) public static final String C = "A->C"; @State(B) public static final String D = "A->B->D"; @State(C) public static final String E = "A->C->E"; -----------------------END----------------------- 使用 状态 继承 来处理 错误 信息 Mina 状态机介绍 Translator:中國壹石頭 ---------------------------------------------------------------------------------------------------------------------- What we call human nature is actually human habbit . 让我 们回 到录 音机的 例子 。 如 果录 音机 里没有 磁带 , 当 你调用 deck.play()方法 时将 会怎 样 ? 让我 们试 试: 示例 代码 : ----------------------START---------------------- public static void main(String[] args) { ... deck.load("The Knife - Silent Shout"); deck.play(); deck.pause(); deck.play(); deck.stop(); deck.eject(); deck.play(); } -----------------------END----------------------- 运行 结果 : ----------------------START---------------------- ... Tape stopped Tape ejected Exception in thread "main" o.a.m.sm.event.UnhandledEventException: Unhandled event: org.apache.mina.statemachine.event.Event@15eb0a9[id=play,...] at org.apache.mina.statemachine.StateMachine.handle(StateMachine.java:285) at org.apache.mina.statemachine.StateMachine.processEvents(StateMachine.java:142) ... -----------------------END----------------------- 哦, 我 们 得到了 一个 无法 处理的 异常 UnhandledEventException, 这 是因 为在 录音机 的空 状 态 时, 没有事 务来 处理 播放 的 状态 。 我们 将添 加一 个指 定 的事 务来 处理 所 有不 能匹 配的 事 件。 ----------------------START---------------------- @Transitions({ @Transition(on = "*", in = EMPTY, weight = 100), @Transition(on = "*", in = LOADED, weight = 100), @Transition(on = "*", in = PLAYING, weight = 100), @Transition(on = "*", in = PAUSED, weight = 100) }) public void error(Event event) { System.out.print ln("Cannot '" + event.getId() + "' at this time"); } -----------------------END----------------------- 现在 当你 运行 上面的 main()方法 时, 你将 不会再 得到 一个 异常, 输出 如下 : ----------------------START---------------------- Mina 状态机介绍 Translator:中國壹石頭 ---------------------------------------------------------------------------------------------------------------------- What we call human nature is actually human habbit . ... Tape stopped Tape ejected Cannot 'play' at this time. -----------------------END----------------------- 现在 这些 看起 来运行 的都 很好 , 是吗? 但是 如果 们有 30 个状 态而 不是 4个, 那 该怎 么办 ? 那么 我们 需要 在上面 的错 误方 法处理 中配置 30 个事 务的 声明 。这 样不 好。 让我 们用状 态 继 承来 解决 : ----------------------START---------------------- public static class TapeDeckHandler { @State public static final String ROOT = "Root"; @State(ROOT) public static final String EMPTY = "Empty"; @State(ROOT) public static final String LOADED = "Loaded"; @State(ROOT) public static final String PLAYING = "Playing"; @State(ROOT) public static final String PAUSED = "Paused"; ... @Transition(on = "*", in = ROOT) public void error(Event event) { System.out.print ln("Cannot '" + event.getId() + "' at this time"); } } -----------------------END----------------------- 这个 运行 的结 果和上 面的 是一样 的, 但是 它比 要每个 方法 都配 置声明 要简 单的 多。 MinaMinaMinaMina的状态机和的状态机和的状态机和的状态机和IoHandler IoHandler IoHandler IoHandler 配合使用配合使用配合使用配合使用 现在 我们将 上面的 录音 机程序 改造成 一个 TCP服务 器,并 扩展一些 方 法。服 务器将 接收一 些命 令类 似于 : load , play, stop等等 。 服 务 器 响应的 信息 将会 是 + 或 者是 - 。 协议是 基于 Mina 自身 提供 的一个 文本 协议 ,所 有的 命令和 响应 编码 都 是基于 UTF-8。这 里有 一个 简单的 会话 示例 : ----------------------START---------------------- telnet localhost 12345 S: + Greetings from your tape deck! C: list S: + (1: "The Knife - Silent Shout", 2: "Kings of convenience - Riot on an empty street") C: load 1 S: + "The Knife - Silent Shout" loaded C: play S: + Playing "The Knife - Silent Shout" C: pause S: + "The Knife - Silent Shout" paused C: play S: + Playing "The Knife - Silent Shout" C: info S: + Tape deck is playing. Current tape: "The Knife - Silent Shout" Mina 状态机介绍 Translator:中國壹石頭 ---------------------------------------------------------------------------------------------------------------------- What we call human nature is actually human habbit . C: eject S:- Cannot eject while playing C: stop S: + "The Knife - Silent Shout" stopped C: eject S: + "The Knife - Silent Shout" ejected C: quit S: + Bye! Please come back! -----------------------END----------------------- 该程序完整的代码在 org.apache.mina.example.tapedeck 包中,这个可以通过检出 Mina 源码的 SVN 库中的 mina-example 来得 到。 代 码 使用 Mina 的ProtocolCodecFilter 来编 解 码 传输 的二 进数 据对象 。 这 里只 是为 每个状 态对 服务 器的请 求实 现了 一个简 单的 编解 码器。 在 此不 在对 Mina 中编 解码 的实 现做过 多的 讲解 。 现在 我们看 一下这 个服 务器是 如何工 作的。 这里面 一个 重要的 类就是 实现了 录音机 程序的 TapeDeckServer 类。 这里 我们 要做的 第一件 事 情就是 去定 义这 些状态 : ----------------------START---------------------- @State public static final String ROOT = "Root"; @State(ROOT) public static final String EMPTY = "Empty"; @State(ROOT) public static final String LOADED = "Loaded"; @State(ROOT) public static final String PLAYING = "Playing"; @State(ROOT) public static final String PAUSED = "Paused"; -----------------------END----------------------- 在这 里没 有什 么新增 的内 容。 然 而 , 但 是 处理 这些 事件 的方法 看起 来将 会不一样 。 让 我 们 看 看playTape 的方 法。 ----------------------START---------------------- @IoHandlerTransitions({ @IoHandlerTransition(on = MESSAGE_RECEIVED, in = LOADED, next = PLAYING), @IoHandlerTransition(on = MESSAGE_RECEIVED, in = PAUSED, next = PLAYING) }) public void playTape(TapeDeckContext context, IoSession session, PlayCommand cmd) { session.write("+ Playing \"" + context.tapeName + "\""); } -----------------------END----------------------- 这里没有使 用通 用的 @Transition 和@Transitions 的事务声明 ,而 是使 用 了 Mina 指定的 @IoHandlerTransition 和@IoHandlerTransitions 声明 。当为 Mina 的IoHandler 创建 一个状 态机时 ,它会选择 让你使用 Java enum (枚举 )类型来替 代我们上 面使用的字 符串类型。 这个在 Mina 的IoFilter 中也 是一样 的 。 我们现在使用MESSAGE_RECEIVED 来替代"play" 来作为事件的名字(on 是 @IoHandlerTransition 的一个 属 性 )。这 个 常量 是 在 org.apache.mina.statemachine.event.IoHandlerEvents 中定 义 的 , 它 的 值 是 "messageReceived", 这 个和 Mina 的IoHandler 中的 messageReceived()方法 是一致 的 。 谢 谢Java 5中的静态 导入,我们在使 用该变量的时候 就不用再通过类 的名字来调用该 常量, Mina 状态机介绍 Translator:中國壹石頭 ---------------------------------------------------------------------------------------------------------------------- What we call human nature is actually human habbit . 我们 只需 要按 下面的 方法 导入 该类: ----------------------START---------------------- import static org.apache.mina.statemachine.event.IoHandlerEvents.*; -----------------------END----------------------- 这样 状态 内容 就被导 入了 。 另外一个要改变的内容是我们自定了一个StateContext 状态上下文的实现-- TapeDeckContext。这 个类 主要 是用于 返回 当前 录音机 的状 态的 名字。 ----------------------START---------------------- static class TapeDeckContext extends AbstractStateContext { public String tapeName; } -----------------------END----------------------- ###################################################### 要点 : 为什么不把状态的名字保存到 IoSession 中? 我们可以将录音机状态的名字保存到 IoSession 中,但是使用一个自定义的 StateContex t 来保存这个状态将 会使这个类型更 加安全。 ###################################################### 最后 需要 注意 的事情是 playTape()方法 使用了 PlayCommand命令 来作 为它 的最后 的一个 参 数。 最后一个 参数和 IoHandler's messageReceived(IoSession session, Object message)方 法匹 配。 这 意 味着 只有 在客 户端发 送的 信息 被编码成 playCommand 命令 时, 该 方 法才 会 被 调用 。 在录 音机 开始 进行播 放前 , 它 要 做 的事情 就是 要装 载磁带 。 当 装载 的命 令从 客户端 发送 过 来 时, 服务 器提 供的磁 带的 数字 代号将 会从 磁带 列表中 将可 用的 磁带的 名字 取出 : ----------------------START---------------------- @IoHandlerTransition(on = MESSAGE_RECEIVED, in = EMPTY, next = LOADED) public void loadTape(TapeDeckContext context, IoSession session, LoadCommand cmd) { if (cmd.getTapeNumber() < 1 || cmd.getTapeNumber() > tapes.length) { session.write("- Unknown tape number: " + cmd.getTapeNumber()); StateControl.breakAndGotoNext(EMPTY); } else { context.tapeName = tapes[cmd.getTapeNumber() - 1]; session.write("+ \"" + context.tapeName + "\" loaded"); } } -----------------------END----------------------- 这段 代码 使用了 StateControl 状态 控制 器来 重写了 下一个 状 态。 如 果 用户 指定 了一个 非法 的 数字 ,我 们将 不会将 加载 状态 删除, 而是 使用 一个空 状态 来代 替。代 码如 下所 示: ----------------------START---------------------- StateControl.breakAndGotoNext(EMPTY); -----------------------END----------------------- 状态 控制 器将 会在后 面的 章节 中详细 的讲 述。 connect()方法 将会在 Mina 开启 一个 会话 并调用 sessionOpened()方法 时触 发。 ----------------------START---------------------- Mina 状态机介绍 Translator:中國壹石頭 ---------------------------------------------------------------------------------------------------------------------- What we call human nature is actually human habbit . @IoHandlerTransition(on = SESSION_OPENED, in = EMPTY) public void connect(IoSession session) { session.write("+ Greetings from your tape deck!"); } -----------------------END----------------------- 它所 做的 工作 就是向 客户 端发 送欢迎 的信 息。 状态机 将会 保持 空的状 态。 pauseTape(), stopTape() 和ejectTape() 方法 和 playTape()很相 似。 这 里 不再进 行过 多 的 讲述 。 listTapes(), info() 和quit() 方法 也很容易 理,也 不再进行 过多的 讲解。请 注意后 面的 三个 方法 是在根 状态 下使 用的。 这 意 味着 listTapes(), info() 和quit() 可以 在任 何 状 态中 使用 。 现在 让我 们看 一下错 误处 理。 error()将会 在客 户端 发送一个 非 法的 操作时 触发 : ----------------------START---------------------- @IoHandlerTransition(on = MESSAGE_RECEIVED, in = ROOT, weight = 10) public void error(Event event, StateContext context, IoSession session, Command cmd) { session.write("- Cannot " + cmd.getName() + " while " + context.getCurrentState().getId().toLowerCase()); } -----------------------END----------------------- error()已经 被指定 了一个 高于 listTapes(), info() 和quit() 的重 量值来 阻止客 户端 调用上 面的 方法 。 注意 error()方法 是怎 样使 用状态 上下 文来 保存当 前状 态的 ID的。 字 符 串常 量 值 由@State annotation (Empty, Loaded etc) 声明 。 这 个 将会由 Mina 的状 态机 当成 状态的 ID 来使 用。 commandSyntaxError()方法将会在 ProtocolDecoder 抛出 CommandSyntaxException 异常 时被 调用 。它 将会简 单的 输出 客户 端发 送的 信息不 能解 码为 一个状 态命 令。 exceptionCaught() 方法 将会 在任 何异常 发生 时调 用, 除了 CommandSyntaxException 异常 (这个 异常 有一个 较高 的重 量值 )。 它将 会立 刻关 闭会话 。 最后一个@IoHandlerTransition 的方法是unhandledEvent() ,它将会在 @IoHandlerTransition 中的 方法 没有 事件匹 配时 调用 。 我们 需要 这个 方法是 因为 我们 没有 @IoHandlerTransition 的方 法来 处理 所有可 能的 事件 (例 如: 我们 没有 处理 messageSent(Event) 方法 )。没 有这 个方 法, Mina 的状 态机 将会 在执行 一个 事件 的时候 抛出 一个 异常。 最后 一点 我们 要看的 是那 个类 创建了 IoHandler 的代 理, main()方法 也在 其中 : ----------------------START---------------------- private static IoHandler createIoHandler() { StateMachine sm = StateMachineFactory.getInstance(IoHandlerTransition.class).create(EMPTY, new Mina 状态机介绍 Translator:中國壹石頭 ---------------------------------------------------------------------------------------------------------------------- What we call human nature is actually human habbit . TapeDeckServer()); return new StateMachineProxyBuilder().setStateContextLookup( new IoSessionStateContextLookup(new StateContextFactory() { public StateContext create() { return new TapeDeckContext(); } })).create(IoHandler.class, sm); } // This code will work with MINA 1.0/1.1: public static void main(String[] args) throws Exception { SocketAcceptor acceptor = new SocketAcceptor(); SocketAcceptorConfig config = new SocketAcceptorConfig(); config.setReuseAddress(true); ProtocolCodecFilter pcf = new ProtocolCodecFilter( new TextLineEncoder(), new CommandDecoder()); config.getFilterChain().addLast("codec", pcf); acceptor.bind(new InetSocketAddress(12345), createIoHandler(), config); } // This code will work with MINA trunk: public static void main(String[] args) throws Exception { SocketAcceptor acceptor = new NioSocketAcceptor(); acceptor.setReuseAddress(true); ProtocolCodecFilter pcf = new ProtocolCodecFilter( new TextLineEncoder(), new CommandDecoder()); acceptor.getFilterChain().addLast("codec", pcf); acceptor.setHandler(createIoHandler()); acceptor.setLocalAddress(new InetSocketAddress(PORT)); acceptor.bind(); } -----------------------END----------------------- createIoHandler() 方法 创建了 一个状 态机 ,这个 和我们 之前所 做的相 似除 了我们 指定一个 IoHandlerTransition.clas 类来 代替 Transition.class 在StateMachineFactory.getInstance(...) 方法 中。 这是 我们在 使用 @IoHandlerTransition 声明 的时 候必 须要做 的。 当然 这时我 们使 用了 一个 IoSessionStateContextLookup 和一个 自 定义的 StateContextFactory 类,这个在我 们创建一个IoHandler 代理时被使用到了。如果我们没有使用 IoSessionStateContextLookup , 那么所 有的 客户 端将会 使用 同一个 状态 机, 这是 我们 不 希 望看 到的 。 main()方法 创建了 SocketAcceptor实例 ,并且 绑定了 一个 ProtocolCodecFilter ,它 用于编 解码命 令对象。最 后它绑定了 12345 端口和 IoHandler 的实例 。这个 IoHandler 实例是由 createIoHandler()方法 创建 的。 Mina 状态机介绍 Translator:中國壹石頭 ---------------------------------------------------------------------------------------------------------------------- What we call human nature is actually human habbit . 深入理解 Apache Mina ---- Mina 的几个类 Author:中國壹石頭 ---------------------------------------------------------------------------------------------------------------------- What we call human nature is actually human habbit . 深入理解 Apache Mina ---- Mina的几个类 最近 一直 在看 Mina 的源 码, 用了 Mina 这么 长时 间, 说 实话 , 现在 才开 始对 Mina 有了 一 些 深刻 的理 解, 关于 Mina 的基 本知 识的 介绍, 这里 就不 多说 了, 网上已 经有 很多 不错的 文 章 都对 Mina 做了 较深 刻的 剖析, 现 在就 是想从 Mina 的最 根本 的地 方来对 Mina 做一些 深 层 次 上的 探讨 。 还是 先从 Mina 的入 口程序来 说,每 当要启动 一个 Mina 的程 序(包括 服务器 和客户端 )时 候, 这里 只是 对服 务器重 点做 一些 讲解, 至于说 Mina 的客 户端 的应 用, 这里只 是简 单的 涉 及一点 , 不 会对 其做 很深 入的探 讨。 但是 Mina 的服 务器 和客 户端在 很大 的程 度上都 是一 样, 所以 这里 就 “挂一漏 万 ”的简 单讲 解一下 。 在 此之 前我 一 直想 找一 种 “串糖 葫芦 ”的方 式来 讲解 一下 Mina, 可 是 一直没 有时 间来看 Mina 的源 码, 真的 是无 从下手 ,虽 然网 上的 很多关于 Mina 的一些 文 章, 讲解 的非常 透彻 了, 但 是可 能对 于初 学者来 说, 显得 有些 深奥, 在 这里特 别的 提一下 洞庭 散人对 Mina 源码 的透 彻 的分 析,如果 你对 Mina 已经 有了一定 的了解 或者是正 在学习 Mina 的源 码,建议 你去看 看 他的 博客 , 里 面 有 很多东 西讲 的是 相当到 位的 。 在 这 里 就不在 多举 例子 了。 写 这篇 文档 主 要 是想 对刚 接触 Mina 的人 讲解 一些 Mina 的基 本知 识, 由 浅 入深 , 一步 一步 的 学习 Mina 思想 的精 髓 , 我接触 Mina 的时 间也 比较 长了, 几乎 天天 在和 它打交 道, 每当 你发 现一个 新奇 的 用法 的时候, 你真的 会被 Mina 所折 服,我这 里不是对 Mina 的吹 捧,记得 我曾经 和同事开 玩笑说,“等真 正的 懂得了 Mina, 你 就知 道什么叫 Java了”, 所 以, 我 现在 想急切 的把 现 在 所知 道和 了解 的所有 关于 Mina 的一些 东 西都 想在这 篇文 章里 面写出 来, 如 果有写 的不 到 位 的地 方还 请各 位同学 多多 指正 ,下面 就开 始对 Mina 做一个 完 整的 介绍。 一、 先说说 Mina 的几 个类 和接 口 (1)IoService (2)BaseIoService (3)BaseIoAcceptor (4)IoAcceptor (5)IoConnector 这几 个类 和接 口是整 个服 务器 或客户 端程 序 ( IoConnector) 的 入口 程序 , 其 中就 Mina 的整 体 上来 说, IoService 是所有 IO通信 的入 口程 序, 下 面 的几 个接口 和类 都是 继承或 者实 现 了 IoService 接口 。 深入理解 Apache Mina ---- Mina 的几个类 Author:中國壹石頭 ---------------------------------------------------------------------------------------------------------------------- What we call human nature is actually human habbit . 下面 先给出 Mina(入 口程 序) 的整体 架构 图: Mina 的整 体架 构图 在这 里先 提出 几个个 问题 : (1)为 什么 有了 一个 IoService 还要 再有 一个 BaseIoService? (2)BaseIoService 和IoAcceptor(IoConnector)有什 么区 别? (3)BaseIoAcceptor(BaseIoConnector)为什 么不 去直 接实现 IoService,而 是又 添加 了 IoAcceptor(IoConnector)? 带着 这几 个问 题我们 来解 读一下 Mina 的源 码: 首先 , 解 答 第一个 问题 , 为 什 么有了 一个 IoService 还要 再有 一个 BaseIoService?IoService 和BaseIoService 最明 显的 区别 就是 IoService 是 一 个接口 , 而 BaseIoService 是一个 抽 象 类。 BaseIoService 实现了 IoService 中的 部分 方法 。 这里 先把 IoService 接口 中要 实现 的方法和 BaseIoService 中实 现的 方法 列举如 下: 深入理解 Apache Mina ---- Mina 的几个类 Author:中國壹石頭 ---------------------------------------------------------------------------------------------------------------------- What we call human nature is actually human habbit . 通过对 IoService 和BaseIoService 的比 较可 以发 现, 除了 getDefaultConfig() 这个 方法 没 有 在BaseIoService 中实 现之 外, 其他的 方法 都已 经在 BaseIoService 实现 了。 这里 就有一个 问题 ,为 什么 BaseIoService 只是 实现了 IoService 的部 分方 法, 而没有 全部 实现 IoService 的方 法呢 ?通 常都知 道, 接 口 中的 方法是 必须 要由 实现类 来实 现的 , 这 点 是毋 庸置 疑的 。 你 可以 写一个 空 方法, 里 面 没有 任何 的逻辑 处理 , 但 是 你 的实现 类中 却不 能没有 该方 法。 但 是 在Mina 中作 为 实现 类的 BaseIoService 却没有 IoService 指定 的方法 getDefaultConfig() , 难道 Mina 真的 有独 到之 处?不 是! 仔 细看看 BaseIoService 你就 会知 道, BaseIoService 是 一个 抽象 类, 抽 象 类就是 用来 被继 承的, 它 提 供了 一些 其子类 公用 的一些 方法 , 当 抽 象 类 实 现一个 接 口时 , 抽 象 类可 以有 选择 性的实 现其 所有 子类都 需要 的实 现的一些 方 法, 对 于 接 口 中指 定法 方法 , 抽 象 类可 以选 择全 部实现 或者 部分 实现。 在 Mina 中如 果没有 BaseIoService 这个 抽象 类, 而是由 BaseIoAcceptor 和BaseIoConnector 直接 去实现 BaseIoService 接口, 那么 必然 会导 致这个 两个 实现 类中都 要重 写相 应的方 法, 这样 就脱离 了面 向对 象设计 的本 质, 没 有 达到 复用 的目的 。 在 BaseIoAcceptor/BaseIoConnector和BaseIoService 之间 添 加 一个 BaseIoService 就是 为了 达到 代码复 用的 目的 。在这 个问 题上 主要是 要记 住两 点: 1)抽象类在实现接口的时候可以部分或者全部实现接口中的方法。但是当抽象类只是实 现了接口中的部分方法的时候,抽象类的子类必须要实现抽象类中未实现的接口的方 法。在此处,IoService 的getDefaultConfig( ) 方法在 BaseIoService(BaseIoAcceptor 是BaseIoService 的子类,但它也是一个抽象类,所以它也没有实现 getDefaultConfig( )) ,getDefaultConfig( ) 是由 BaseIoAcceptor 的子类们来实现的(如 SocketAcceptor, 这是一个具体实现类)。所以接口的所有方法必须被具体的实现类实 现和抽象类在实现接口的时候可以部分或者全部实现接口中的方法是不矛盾的。 2)注意代码的重用。在面向对象的编程语言中都提供了抽象类和接口,抽象类和接口最 大的区别就是抽象类提供了方法的具体实现,供其子类来调用;而接口只是提供了对 方法的声明,其方法的实现要由其具体实现类来做。在 Java 中一个子类只能有一个 父类,但是却能实现多个接口。个人认为接口和抽象类各有特色,接口的使用比较灵 活,不同的接口可以让其子类扮演不同的角色,侧重于类的复用,在很大程度上解决 了代码复用的问题;抽象类更侧重的是方法的复用,某种意义上讲,抽象类的使用对 于程序来说使用起来更加轻松,但是是使用抽象类还是接口要根据具体的情况而定。 对于接口和抽象类的具体的用法请参考闫宏的《Java 与模式》中相关部分的讲解。 之所 以在 这里 罗列这 么些 问题 ,目 的不仅 仅是 为了 讲解 Mina 的原 理, 而是 想从 一个高 的 角 度来 看待 的这 个经典 的开 源项 目, 通过对 Mina 的学 习和 理解 ,能 够真正 的懂 得什 么是一 个 项目 ,什 么是 面向对 象编 程, 更本质 的东 西是 怎么灵 活运用 Java来 达到 上面 的两 个 目的 。 这个 才是 最重 要的, 哪怕 是你 在看 完本文 后对 Mina 的理 解还 是有 点模糊 ,但 是你 至少 要 知 道在 编写 一个 程序的 时候 怎样 从面向 对象 的角 度上去 思考 一个 问题, 而 不 是 在用着 面向 对 象 的语 言写 着结 构化的 程序 。 这 些 东 西都是 自己 做开 发这么 长时 间的 一些心 得, 在 这 里总 结 出 来, 目 的主 要是 用于交 流 和学 习, 不 是 在 卖弄, 只 是想 让更 多的初 学者 少走 一些弯 路, 懂 得 学习 的方 法。 还是 回到对 Mina 的刚 提出 的那 几个问 题上 来, 现在 ,第 一个问 题已 经解 决了, 为什 么有 了 一个 IoService 还要 再有 一个 BaseIoService?答 案就 是为 了代码 的复 用。 深入理解 Apache Mina ---- Mina 的几个类 Author:中國壹石頭 ---------------------------------------------------------------------------------------------------------------------- What we call human nature is actually human habbit . 其次 ,下 面开 始讨论 第二 个问 题, BaseIoService 和IoAcceptor(IoConnector)有什 么区 别? 在讨 论这 个问 题之前 ,还 是先 给出这 两个 类 (接口 )提供 的方 法, 如下图 : 在讨 论第 一个 问题的 时候 我们 已经看 过了 BaseIoService 的方 法了 , 但 是没 有对 这些 方法 的 功能 做些 梳理 ,现在 就对 这些 方法做 些简 单的 介绍: getFilterChainBuilder() 和setFilterChainBuilder(): 这两个方法主要是对一个服务的 IoFilter 的操 作,关于 IoFilter 的详细介绍会在后面给出,现在你可以将其理解为是一个处理业务逻辑的模块,例如: 黑名单的处理、数据的转换、日志信息的处理 等等都可以 在这个 IoFilter 中实现,它的工作原理和 Servlet 中的过滤器很相似。 addListener( ) 和removeListener():这两个方法通过名字看就可以理解了,就是给当前的服务添加和删除 一个监听器,这个监听器主要是用于对当前连接到服务的 IoSession 进行管理,这个也会在后面做详细的 讲解。 getManagerServiceAddress()和getManagerSessions():这两个方法的功能比较相似,一个是获取当前服 务所管理的远程地址,一个是获取当前服务所管理的会话IoSession ,IoSession 对SocketAddress 做了一 个完整的封装,你也可以先将这两个方法的功能理解为是一回事,具体的区别会在后面给出。 isManaged(): 检测某个 SocketAddress 是否处于被管理的状态。 getListeners(): 获取当前服务的监听器。 看了 上面对 BaseIoService 功能 的介 绍, 现在我 们可 以理解 BaseIoService 提供 的方 法主 要 是用 于对 当前 服务的 管理 。 那 么 要 管理一个 服 务, 前 提 条件是 这个 服务 必须存 在, 存 在 的 前 提是 什么 ,就 是要启 动一个 服 务,或 者是 连接 到一个 远程 主机 上,这 两个 任务 分别是 IoAcceptor 和IoConnector来完 成的 , 此 处要 注意 的是 这两个 对象 都是 接口, 没 有 具体 的 实 现, 具体 的实 现会由 下面 介绍 的它们 相关 的子 类 (SocketAcceptor 等)来实 现。 这样 IoAcceptor/IoConnector的功 能我 们就 可以总 结 出来 了, 就是 启动和 停止 一个 服务。 对于 一个 完整 的服务 来说 , 既 要 有 启动这 样的 前提 条件, 还 要 有对 服务 的管理 和对 服务 响 应 深入理解 Apache Mina ---- Mina 的几个类 Author:中國壹石頭 ---------------------------------------------------------------------------------------------------------------------- What we call human nature is actually human habbit . 的 逻辑 处理 , 这两个缺 一 不可 , 回到第 二 个问 题, BaseIoService 和IoAcceptor(IoConnector) 有什 么区 别? 区别就 在于 它们 实现的 功能 不一样 ,但 都是 为了 一个完 整的 服务 来打基 础的 , 两者 缺一都 不 能称为 一个 完整 的服务 。 这三 个都是 IoService 子类 (子接 口 ),IoService 只是 提供 了一些 服 务应该 具有 多基 本的方 法, BaseIoService 提供了 IoService 部分 方法 的具 体 实 现,而 IoAcceptor(IoConnector)是对 特定 服务 要具备 的操 作的 做了一些 扩 展, 这样一个 服 务完 整的 模型 正在 逐渐 向我们 清晰 的展 现出来 。 再次 ,讨 论一下 第三 个问 题。 BaseIoAcceptor(BaseIoConnector)为什 么不 去直 接实现 IoService,而 是又 添加了 IoAcceptor(IoConnector)?这个 问题 其实 在上面 已经 有所 涉及, 为了 达到 对象 复用的 目的 ,所以 Mina 的设 计者 给出 了一个 BaseIoService, IoAcceptor(IoConnector)是实 现一个 特 定服务 必须 要提 供的一些 方 法。 更具体 一点 , IoAcceptor(IoConnector)是为 了一个 特 定的服 务 (服务 器 /客户 端 )而设 计的 ,而 IoService 只 是提 供了 一个 服务应 该具 备的 一些基 本的 方法 。所 以在 Mina 中给 出了 一个 针对具 体服 务 的 一个 接口 IoAcceptor(IoConnector),这样 BaseIoAcceptor(BaseIoConnector)就提 供了 一 个 服务 所必 备的 一些条 件。 因为 它即实 现了 IoAcceptor(IoConnector)接口 又继 承了 抽象类 BaseIoService, 这 样就 实现了 IoService 中的 所有 方法 , 并 且也添 加了 特定 服务应 该具 有 的 方法 (即IoAcceptor(IoConnector)中的 方法 )。以 上就 是第 三个问 题的 答案 。 二、 Mina 中提 供的 几个 特定的 服务 从上 面的 讨论 中我们 已经 知道了 Mina 上层 的类 和接 口的一些 功 能。 即图 中所 示的 已经在 上 面解 释清 楚了 。 Mina 的上 层结 构图 (抽象 层 ) 在此 我们 可以把 Mina 的上 层结 构简 单的定 义为 Mina 的“抽象 层 ”,既 然有 了抽 象层, 肯 定就 会有 其具 体实现 ,抽 象中 最重要 的两 个类是 BaseIoAcceptor和BaseIoConnector,它 们分 别是 用于 服务器 和客 户端 的一个 入口 程序 。 首先 ,说 一下 BaseIoAcceptor中的 三个 具体 实现类 : (1) DatagramAcceptorDelegate:数 据报 UDP通信 的服 务器 入口程 序。 该类 使用 UDP协 议进 行通 信, UDP协议 主要 是用 在视频 、远 程服 务的监 听 (如心 跳程 序 )中等 数据 传输 要求 不是 很高 的地方 。 (2) VmPipeAcceptor:虚拟通道 (VM)通信 的服 务器 入口程 序。 虚 拟管 道协 议主 要用于 无线 通 信方 面。 深入理解 Apache Mina ---- Mina 的几个类 Author:中國壹石頭 ---------------------------------------------------------------------------------------------------------------------- What we call human nature is actually human habbit . (3) SocketAcceptor:TCP/IP通信 的服 务器 入口程 序。 这个 是比 较常用 的协 议, 该协议 主 要 数据 传输 要求 较高的 地方 ,比 如实时 系统 、游 戏服务 器等 。 BaseIoAcceptor及其 子类 与BaseIoAcceptor相对 应的 就是 BaseIoConnector,该 类主 要用 于客户 端程 序。 其具体 的 子类 就不 再赘 述,这 里只 给出 BaseIoConnector 及其 子类 的结 构图。 BaseIoConnector 及其 子类 关于 SocketAcceptor、IoFilter、IoProcessor、IoHandler 等会 有专 门的 文章来 讨论 。 这里 就 不在 对这 些组 件类做 详细 的说 明了。 深入理解 Apache Mina ---- 与IoFilter 相关的几个类 Author:中國壹石頭 ---------------------------------------------------------------------------------------------------------------------- What we call human nature is actually human habbit . 深入理解 Apache Mina ---- 与IoFilter 相关 的几个类 从名 字上看知道 IoFilter 应该 是一个过 滤器, 不错,它 确实是 一个过滤 器,它和 Servlet 中 的过 滤器 类似 ,主要 用于 拦截 和过滤 I/O 操作 中的 各种 信息。在 Mina 的官 方文 档中 已经提 到了 IoFilter 的作 用: (1)记 录事 件的 日志( 这个 在本 文中关于 LoggingFilter 的讲 述中 会提 到) (2)测 量系 统性 能 (3)信 息验 证 (4)过 载控 制 (5) 信 息的转 换 (例如 : 编码 和解 码, 这 个会 在关于 ProtocolCodecFilter 的讲 述中 会提 到 ) (6)和 其他 更多 的信息 还是 上一篇 文档一样 , 先提出 几个问 题,然 后沿着 这几 个问题 的思路 一个一个 的对 IoFilter 进行 讲解 。 (1)什 么时 候需 要用到 IoFilter,如 果在 自己 的应用 中不 添加 过滤器 可以 吗? (2)如 果在 IoService 中添 加多 个过 滤器可 以吗 ?若 可以, 如何 进行 添加, 这多 个过 滤 器是 如何 工作 的? (3)Mina 中提 供了 协议 编、 解 码器, IoFilter 也可 以实现 IO数据 的编 解码 功能, 在 实 际 的使 用中 如何 选择? 在开 始对 上面 的问题 进行 讨论 前, 为 了对 IoFilter 提供 的方 法有 一个具 体的 了解 , 先对 Mina 自身 提供的一个 最简 单的过滤 器进行 一些讲解 ----LoggingFilter(源 码在附件 中,配 有中文 翻译)。 首先还是看一下 LoggingFilter 中提供的几个方法 。列举如下(方法中的参数就不 再给出, 完整 方法 的实 现请参 考附 件中 LoggingFilter 的源码): (1)sessionCreated() (2)sessionOpened() (3)sessionClosed() (4)sessionIdle() (5)exceptionCaught() (6)messageReceived() (7)messageSent() (8)filterWrite() (9)filterClose() 这几 个方 法都 由相应 会话 ( 或 者说是 连接 的状 态, 读 、 写 、 空 闲、 连 接 的开 闭等) 的 状 态 的 改变 来触发的。当一个会话开启时,LoggingFilter 捕获 到会话开启的事件,会触发 sessionCreated()方法 , 记 录 该会话 开启 的日 志信息 。 同 样 当一个 会话 发送 数据 时, Logging 捕获 到会 话发 送消息 的事 件会 记录消 息发 送的 日志信 息。 这 里 只是 给出 messageReceived() 的完 成方 法的 实现, 其他 方法 的完整 实现 请参 考附件中 LoggingFilter 的源 码。 深入理解 Apache Mina ---- 与IoFilter 相关的几个类 Author:中國壹石頭 ---------------------------------------------------------------------------------------------------------------------- What we call human nature is actually human habbit . LoggingFilter 继承与 IoFilterAdpater,IoFilterAdpater 是IoFilter 的一个实 现类,该类只是 提供了 IoFilter 方法 的简单实 现 ----将传 递到各方 法中的 消息转发 到下一个 过滤器 中。你 可 以根 据自 己的 需求继承 IoFilterAdpater, 并 重 写相 关的方 法。 LoggingFilter 就是 重写 了上 面 提到 的几 个方 法,用 于记 录当 前的会 话各 种操 作的日 志信 息。 通过 上面 的例 子, 我们 可以大 体的 了解了 IoFilter 的基 本功 能: 根据 当前 会话状 态, 来捕 获 和处 理当 前会 话中所 传递 的消 息。 IoFilter 的UML图如 下: 从上 面的 类图 我们可 以清 晰的 看到 IoFilter 是一个 接 口, 它有两 个具 体的 实现类 : IoFilterAdpa ter :该类提供了 IoFilter 所有方法的方法体,但是没有任何逻辑处理,你可以根据你具体 的需求继承该类,并重写相关的方法。IoFilterAdpater 是在过滤器中使用的较多的一个类。 ReferenceCountingIoFilter:该类封装 IoFilter 的实例,它使用监视使用该 IoFilter 的对象的数量,当 没有任何对象使用该 IoFilter 时,该类会销毁该 IoFilter 。 IoFilterAdpater 有三 个子 类, 它们的 作用 分别 如下: LoggingFilter: 日志工具,该类处理记录 IoFilter 每个状态触发时的日志信息外不对数据做任何处理。它 实现了 IoFilter 接口的所有方法。你可以通过阅读该类的源码学习如何实现你自己的 IoFilter 。 ExcuterFilter: 这个 Mina 自身提供的一个线程池,在 Mina 中你可以使用这个类配置你自己的线程池,由 于创建和销毁一个线程,需要耗费很多资源,特别是在高性能的程序中这点尤其重要,因此在你的程序中 配置一个线程池是很重要的。它有助于你提高你的应用程序的性能。关于配置 Mina 的线程池在后续的文档 中会给出详细的配置方法。 ProtocolFilter: 该类是 Mina 提供的一个协议编解码器,在socket 通信中最重要的就是协议的编码和解码 工作,Mina 提供了几个默认的编解码器的实现,在下面的例子中使用了 ObjectSerializationCodecFactory ,这是 Mina 提供的一个 Java 对象的序列化和反序列化方法。使用这个 /** * 记录会 话接收 信息时的 信息 ,然 后将该 信息传 递到过 滤 器链中的下 一个 过滤 器 **/ public public public public voidvoidvoidvoid messageReceived(NextFilter nextFilter, IoSession session, Object message) { ifififif (SessionLog .isInfoEnabled(session)) { SessionLog .info(session, "RECEIVED:" + message); } nextFilter.messageReceived(session, message); } 深入理解 Apache Mina ---- 与IoFilter 相关的几个类 Author:中國壹石頭 ---------------------------------------------------------------------------------------------------------------------- What we call human nature is actually human habbit . 编解码器,你可以在你的 Java 客户端和服务器之间传递任何类型的 Java 对象。但是对于不同的平台之间 的数据传递需要自己定义编解码器,关于这点的介绍会在后续的文档中给出。 为了 更加 清楚 的理解 这个 过滤 器的作 用我 们先 来看一个 简 单的 例子, 这 个 例 子的功 能就 是 服 务器 在客 户端 连接到 服务 器时 创建一个 会 话, 然后向 客户 端发 送一个 字符 串 (完整 的源 码在 附件 中, 这里 只给出 程序 的简 要内容 ): ServerMain: ServerHandler: publicpublicpublicpublic classclassclassclass ServerMain { publicpublicpublicpublic staticstaticstaticstatic voidvoidvoidvoid main(String[] args) throwsthrowsthrowsthrows IOException { SocketAddress address = newnewnewnew InetSocketAddress("localhost" , 4321); IoAcceptor acceptor = newnewnewnew SocketAcceptor(); IoServiceConfig config = acceptor.getDefaultConfig(); // 配置 数据 的编 解码器 config.getFilterChain().addLast("codec", newnewnewnew ProtocolCodecFilter(newnewnewnew ObjectSerializationCodecFactory())); // 绑定 服务 器端 口 acceptor.bind(address, newnewnewnew ServerHandler()); System.out.print ln(" 服务 器开 始在 8000 端口 监听 ......."); } } publicpublicpublicpublic classclassclassclass ServerHandler extendsextendsextendsextends IoHandlerAdapter { // 创建 会话 publicpublicpublicpublic voidvoidvoidvoid sessionOpened(IoSession session) throwsthrowsthrowsthrows Exception { System.out.print ln(" 服务 器创 建了 会话 "); session.write(" 服务 器创 建会 话时发 送的 信息 。"); } // 发送 信息 publicpublicpublicpublic voidvoidvoidvoid messageSent(IoSession session, Object message) throwsthrowsthrowsthrows Exception { } // 接收 信息 publicpublicpublicpublic voidvoidvoidvoid messageReceived(IoSession session, Object message) throwsthrowsthrowsthrows Exception { } } 深入理解 Apache Mina ---- 与IoFilter 相关的几个类 Author:中國壹石頭 ---------------------------------------------------------------------------------------------------------------------- What we call human nature is actually human habbit . ClientMain: ClientHandler: 其中 ServerMain 和ClientMain 分别 是服务器和客户端的主程序,ServerHandler 和 ClientHandler 是服 务器 和客 户端的 数据 处理 句柄, 关于 IoHandler 会在 后面 的文 档中做 详 细 的讲 解, 这里 只是 简单说 明一下 , IoHandler 主要 是对 数据 进行逻 辑操 作, 也可 以理解 为 程 序的 业务 逻辑 层。其 中: 这行 代码 的功 能是将 网络 传输 中的数 据在 发送 时编码 成二 进制 数据, 解 码 时 将二进 制数 据 还 publicpublicpublicpublic classclassclassclass ClientMain { publicpublicpublicpublic staticstaticstaticstatic voidvoidvoidvoid main(String[] args) { SocketAddress address = newnewnewnew InetSocketAddress("localhost" , 4321); IoConnector connector = newnewnewnew SocketConnector(); IoServiceConfig config = connector.getDefaultConfig(); // 配置 数据 的编 解码器 config.getFilterChain().addLast("codec", newnewnewnew ProtocolCodecFilter(newnewnewnew ObjectSerializationCodecFactory())); config.getFilterChain().addLast("logger", newnewnewnew LoggingFilter()); // 连接 到服 务器 connector.connect(address, newnewnewnew ClientHandler()); System.out.print ln(" 已经 连接 到了 服务器 " + address); } } publicpublicpublicpublic classclassclassclass ClientHandler extendsextendsextendsextends IoHandlerAdapter { // 发送 信息 publicpublicpublicpublic voidvoidvoidvoid messageSent(IoSession session, Object message) throwsthrowsthrowsthrows Exception { } // 接收 信息 publicpublicpublicpublic voidvoidvoidvoid messageReceived(IoSession session, Object message) throwsthrowsthrowsthrows Exception { System.out.print ln(" 客户 端接 收到 的服务 器的 信息 是 " + message); } } // 配置 数据 的编 解码器 config.getFilterChain().addLast("codec", newnewnewnew ProtocolCodecFilter(newnewnewnew ObjectSerializationCodecFactory())); 深入理解 Apache Mina ---- 与IoFilter 相关的几个类 Author:中國壹石頭 ---------------------------------------------------------------------------------------------------------------------- What we call human nature is actually human habbit . 原成 一个 对象 或者是 基本 类型 的数据 。 运行 这个 程序 会得到 如下 结果 : 其中 的红 字部 分是 LoggingFilter 打印 出的 事件 信息。 黑 体 部分 是程 序中 System.out 的输 出。 在ClientMain 中的 这两 行代 码是向 过滤 器链 中添加 IoFilter: 上图 表示 了数 据在本 程序 中通 过过滤 器链 的过 程, 日 志过 滤器 是根 据会话 (IoSession)的状 态 (创建 、 开启 、 发送 、 接收 、 异常 等等 )来记 录会 话的 事件信 息的 , 编解 码器是 根据 会话 的 接 收和 发送 数据 来触发 事件 的, 从 这 里我 们也 可以了 解通 过过 滤器我 们可 以专 门针对 会话 的 某 个或 某几 个状 态来专 门处 理相 关的事 件, 如 异 常事 件, 我 们 可以 专门 定义一个 Exception 的 IoFilter 来处理 Mina 在通 信中 所发 生的异 常信 息。 还有 一个 比较 有意思 的问 题是 ,假如 我们 将上 面过滤 器的 顺序 该成下 面的 样子 : 程序 的执 行结 果如下 : 已经连 接到了服 务 器 localhost/127.0.0.1: 4321 2009-7- 9 23:36: 4 6 org.apache.min a.util.S essionLog info 信息 :[localhost/127.0.0.1:4 321] CREATED 2009-7- 9 23:36: 4 6 org.apache.min a.util.S essionLog info 信息 :[localhost/127.0.0.1:4 321] OPENED 2009-7- 9 23:36: 4 6 org.apache.min a.util.S essionLog info 信息 :[localhost/127.0.0.1:4 321] RECEIVED: 服务器 创建 会话时发 送的信 息 。 客户端 接收到的服 务器的信 息是 服务器 创建 会话时发 送的信 息 。 // 配置 数据 的编 解码器 config.getFilterChain().addLast("codec", newnewnewnew ProtocolCodecFilter(newnewnewnew ObjectSerializationCodecFactory())); config.getFilterChain().addLast("logger", newnewnewnew LoggingFilter());//添加 日志 工具 config.getFilterChain().addLast("logger", newnewnewnew LoggingFilter());//添加 日志 工具 // 配置 数据 的编 解码器 config.getFilterChain().addLast("codec", newnewnewnew ProtocolCodecFilter(newnewnewnew ObjectSerializationCodecFactory())); 已经连 接到了服 务 器 localhost/127.0.0.1: 4321 2009-7- 10 0:30:12 org.apache.min a.util.S essionLog info 信息 :[localhost/127.0.0.1:4 321] CREATED 2009-7- 10 0:30:12 org.apache.min a.util.S essionLog info 深入理解 Apache Mina ---- 与IoFilter 相关的几个类 Author:中國壹石頭 ---------------------------------------------------------------------------------------------------------------------- What we call human nature is actually human habbit . 很明 显的 是在 顺序变 化了 之后 , 日 志 中多 了接 收到 的二进 制数 据, 这 是 因为 在上面 数据 已 经 有解 码器 将数 据还原 成了 Java对象 ,所 以我 们就看 不到 二进 制数据 了, 而在 顺序变 换后 , 由于 先执 行的 是打印 信息 ,此 时的数 据还 没有 还原成 java对象 ,所 以接 收到的 数据 是二 进 制的 。 在上 面的 例子 中我们 清楚 了整个 IoFilter 或者是 IoFilter 的工 作流 程, 那么 IoFilter 在Mina 中的 作用 如何 ?所有 的数 据在 发送到 Mina 程序 中时 , 数 据 都是 先通过 IoFilter, 经过处 理 后 再转 发到 业务 层。这里 IoFilter 就起 到了 一个 承上启 下的 作用 。 到这 里我 们就 可以回 答本 文开 始提到 的问 题了 : (1)什 么时 候需 要用到 IoFilter,如 果在 自己 的应用 中不 添加 过滤器 可以 吗? 在你 自己 的程 序中可 以添 加过 滤器, 也 可 以不 添加 , 但 是 在数 据发 送之 前, 所 发送 的数 据 必 须转 换成 二进 制数据 , 这 个 可以有 IoFilter 完成 , 也 可 以由 ProtocolCodecFilter 完成 (关于 这 个问 题会 在后 面的文 章中 详细 讲述 ),否则Mina 会抛出 Write requests must be transformed to class org.apache.mina.common.ByteBuffer: 异常 。 这 是 因为 网络 中传输 的数 据只 能是 二 进制 数据 。因 此无论 添加 不添 加过滤 器, 都必 须将要 发送 的数 据转换 成二 进制 数据。 (2)如 果在 IoService 中添 加多 个过 滤器可 以吗 ?若 可以, 如何 进行 添加, 这多 个过 滤 器是 如何 工作 的? 在IoService 中可 以添 加多 个过滤 器, 这个 在上面 的程 序中 已经给 处理 ,添 加的方 式 也很 简单 ,通 过程序 一目 了然 。 (3)Mina 中提 供了 协议 编、解 码器 , IoFilter 也可 以实现 IO数据 的编 解码 功能, 在实 际 的使 用中 如何 选择? Mina 的编 解码 器在 Mina 的使 用中 是最 关键的 一个 问题 ,特别 是在 不同 语言之 间进 行 通信 的时 候, 比如 Java和C/C++等, 由于 Mina 自身 没有 提供 这些编 解码 器, 所 以 需 要自 己来 实现 。 Mina 提供 了一个 Decoder/Encoder, 你 可以 实现两 个类 来完 成不同 平 之间 的通 信。 关于这 个问 题会 在后面 的文 档给 出具体 的实 习方 法。 至此 ,关于 IoFilter 的作 用就 讲述 完了, 希望 对你 能有所 帮助 。 :) 信息 :[localhost/127.0.0.1:4 321] OPENED 2009-7- 10 0:30:12 org.apache.min a.util.S essionLog info 信息 :[localhost/127.0.0.1:4 321] RECEIVED: DirectBuffer[pos=0 lim=56 cap=1024: 00 00 00 34 ACED 00 05 74 00 2D 20 20 E6 9C 8D E5 8A A1 E5 99 A8 E5 88 9B E5 BBBA E4 BC 9A E8 AF 9D E6 97 B6 E5 8F 91 E9 80 81 E7 9A 84 E4 BF A1 E6 81 AF 20 E3 80 82] 客户端 接收到的服 务器的信 息是 服务器 创建 会话时发 送的信 息 。 深入理解 Apache Mina ---- 与IoHandler 相关的几个类 Author:中國壹石頭 ---------------------------------------------------------------------------------------------------------------------- What we call human nature is actually human habbit . 深入理解 Apache Mina ---- 与IoHandler 相关的几 个类 在上 一篇 文档 中我们 已经 了解了 IoFilter 的用 法和 其在 Mina 中的 作用 , 作为 Mina 数据 传 输 过程 中比较重 要的组 件, IoFilter 起到 了承上启 下的作 用 ----接收 数据,编 /解码 ,将数据 传 递到 逻辑层 ,当数 据传 递地到 逻辑层 时, IoFilter 的使 命就完 成了, 那么 逻辑层 的数据 由谁 来处 理呢 ?如 何处理 的? 这就 是本文 要讲 述的 内容 ----IoHandler。 在介绍 IoFilter 的时 候, 文 中首 先是从 IoFilter 的结 构和 其在 Mina 中的 作用 谈起 的, 最 后 添 加了 一个 使用 IoFilter 的例 子, 之前 我将 其传 给几个 同学 看时 ,感 觉这 种方式 比较 晦涩 , 应 该将 例子 提到 前面, 由 于时 间的关 系我 不能 在对 IoFilter 的介 绍做 过多 的修改 , 所以在 本 篇 文档 中我就先 以一个 例子开头 ,介绍 IoHandler,希 望这种讲 述方式 能对你理解 Mina 有更 多的 帮助 。 好了 , 言归正 传, 我们 的例 子还 是以上 篇文 档中的 IoFilter 的例 子为 基础 ,在 此基础 上着 重 突出 IoHandler 的作 用。 ServerMain: ServerHandler: public public public public class class class class ServerMain { public public public public static static static static voidvoidvoidvoid main(String[] args) throws throws throws throws IOException { SocketAddress address = new new new new InetSocketAddress( "localhost" , 4321); IoAcceptor acceptor = new new new new SocketAcceptor(); IoServiceConfig config = acceptor.getDef aultConfig(); // 配置数 据的编解 码 器 config.getFilterChain().addLast("codec", new new new new ProtocolCodecFilter( new new new new ObjectSerializationCodecFactory())); config.getFilterChain().addLast("logger", new new new new LoggingFilter()); // 绑定服 务器 端口 acceptor.bind(address, new new new new ServerHandler()); System. out.println(" 服务器 开始在 8000 端口监听 ......." ); } } public public public public class class class class ServerHandler extends extends extends extends IoHandlerAdapter { // 创建会 话 public public public public voidvoidvoidvoid sessionOpened(IoSession session) throws throws throws throws Exception { System. out.println(" 服务器 创建了会 话 "); session.write( " 服务器 创建 会话时发 送的信 息 。"); } // 发送信息 深入理解 Apache Mina ---- 与IoHandler 相关的几个类 Author:中國壹石頭 ---------------------------------------------------------------------------------------------------------------------- What we call human nature is actually human habbit . ClientMain: ClientHandler: 上面 给出 里这 个例子 中的 主要 的代码 , 当 先后 启动 服务器 和客 户端 后, 服 务 器会在 客户 端 连 接到服 务 器 后发 送 一 个 字符 串 的 消 息。 这 个 消息 的 发 送 就是 在 IoHandler 中发送 的。 public public public public voidvoidvoidvoid messageS ent(IoSession session, Object message) throws throws throws throws Exception { } // 接收信息 public public public public voidvoidvoidvoid messageReceived(IoSession session, Object message) throws throws throws throws Exception { } } public public public public class class class class ClientMain { public public public public static static static static voidvoidvoidvoid main(String[] args) { SocketAddress address = new new new new InetSocketAddress( "localhost" , 4321); IoConnector connector = new new new new SocketConnector(); IoServiceConfig config = connector.getDef aultConfig(); // 配置数 据的编解 码 器 config.getFilterChain().addLast("codec", new new new new ProtocolCodecFilter( new new new new ObjectSerializationCodecFactory())); config.getFilterChain().addLast("logger", new new new new LoggingFilter()); // 连接到服 务 器 connector.connect(address, new new new new ClientHandler()); System. out.println(" 已经连 接到了服 务 器 " + address); } } public public public public class class class class ClientHandler extends extends extends extends IoHandlerAdapter { // 发送信息 public public public public voidvoidvoidvoid messageS ent(IoSession session, Object message) throws throws throws throws Exception { } // 接收信息 public public public public voidvoidvoidvoid messageReceived(IoSession session, Object message) throws throws throws throws Exception { System. out.println(" 客户端 接收到的服 务器的信 息是 " + message); } } 深入理解 Apache Mina ---- 与IoHandler 相关的几个类 Author:中國壹石頭 ---------------------------------------------------------------------------------------------------------------------- What we call human nature is actually human habbit . IoHandler 在Mina 中属 于业务 层,这 里的 IoHandler 更相是 J2EE 中的 Servlet 的作 用,在 IoHandler 中你 可以不用 考虑底 层数据的 封装和 转换,前 提是你 已经在 IoFilter 中已 经完成 了数 据的转换 。这里 需要提到 的一个 是,所谓 数据的 转换,是 指将二 进制数据 转换成 Java 中的 可用 对象 或者是 基本 类型 的数据 。 由 于网 络传 输中传 输的 都是 二进制 数据 , 这 就 需 要 有 一个 专门 的数 据转换 层,就 Mina 中的 编解 码器 来实现 这个 功能 。如 果使用 RMI, 对于这 个 问题 应该 不陌 生,二 进制 和对 象之间 的转 化过 程其实 就是 对象 的序列 化 和反 序列 化的 过 程。 关于 Mina 中实 现对 象的 序列化 和反 序列 化会在 后续 的文 档中详 细介 绍, 在此不 在赘 述。 既然 IoHandler 是逻 辑层 , 我 们就用 IoHandler 实现 一个 简单 的逻辑 实现 。 先 听一个 小 故 事: 一个 淘气 的小 孩要去 KFC买汉 堡, 由于 KFC生意 比较 好, 人比较 多, 服务 员忙不 过来 ,于 是KFC专门 设立 了一个 自动 售汉 堡的 机器, 这 个 机器 只是 一个简 单的 数据 收发装 置, ( 由 于 汉堡 的价格 时常变 化, 所以价 格会实 时更新 ,因此 该机 器需要和 KFC的汉 堡的价 格服务 器 相连 ) 小 朋 友买 汉堡时 只要 向机 器中投 入硬 币, 机 器 就会 查询 服务 器, 看 价 格是 否符 合, 若 符合 ,则 送给 小朋友 一个 汉堡 ,若不 符合 则提 示小朋 友钱 不够 ,买不 到这 个汉 堡。 上面 是我自 己虚构 的一个 小故 事,我 们先不 管现实 中的 KFC是如 何运作 的,我 们就 当故事 是真 的了 ,那 么现在 这个 小的 项目分 配给 了我 们,我 们需 要抽 象出它 的需 求: (1)客户 端要 向服 务器发 送数 据, 查询价 格, 根据 价格是 否合 理给 出相应 的显 示 (2)服务 器接 收客 户度的 价格 查询 请求, 根 据 服 务器中 存储 的价 格信息 , 返 回 响应 的结果 。 根据 上面 的需 求, 我 们 使用 Mina 来实 现上 面的 服务器 和客 户端 , 程 序 的代 码如 下 (完整 代 码 在附 件中 ): KFCFoodPriceHandler(服务器 句柄 ): public class KFCFoodPriceHandler extends IoHandlerAdapter { // 创建会 话 public void sessionOpened(IoSession session) throws Exception { // System.out.prin tln(" 服务器 创建了会 话 "); } // 接收信息 public void messageReceived(IoSession session, Object message) throws Exception { HashMap map = (HashMap) message; String buythings = (String) map.get( "购买 "); // System.out.prin tln(" 服务器 接收到的信 息 " + buythings); if (buythings.equals( "汉堡 ")){ HashMap map 2 = new HashMap(); map 2.put( "食品 ","汉堡 "); map 2.put( "价格 ", 4); session.write(map 2); } else if (buythings.equals( "鸡翅 ")){ HashMap map 2 = new HashMap(); map 2.put( "食品 ","鸡翅 "); map 2.put( "价格 ", 5); 深入理解 Apache Mina ---- 与IoHandler 相关的几个类 Author:中國壹石頭 ---------------------------------------------------------------------------------------------------------------------- What we call human nature is actually human habbit . KFCSellerHandler (客户端 句柄 ): session.write(map 2); } else { session.write( " 该种物品已 经出售 完毕 ,谢 谢惠顾! "); } } } public class KFCSellerHandler extends IoHandlerAdapter { private Integer childInputMoney_Ham = 4; private Integer childInputMoney_Chick = 4; // 创建会 话 public void sessionOpened(IoSession session) throws Exception { HashMap map = new HashMap(); map.put( "购买 ","汉堡 "); session.write(map); } // 接收信息 public void messageReceived(IoSession session, Object message) throws Exception { // System.out.prin tln(" 客户端 接收到的服 务器的信 息是 " // + (HashMap) message); HashMap priceInfor = (HashMap) message; // System.out.prin tln("============" + priceInfor.get(" 食品 ")); String foodName = (String) priceInfor.get( "食品 "); if (foodName.equals( "汉堡 ")){ Integer foodPrice = (Integer) priceInfor.get( "价格 "); if (foodPrice.equals( childInputMoney_Ham)){ System.out.println(" 您好,请收 好你的汉 堡,欢 迎下次 光临! "); } else { System.out.println(" 对不起,你 投如的 钱币 数 量不够, 钱已 经如 数归还 ,请 收 好! "); } } else if (foodName.equals( "鸡翅 ")){ Integer foodPrice = (Integer) priceInfor.get( "价格 "); if (foodPrice.equals( childInputMoney_Chick)){ System.out.println(" 您好,请收 好你的汉 堡,欢 迎下次 光临! "); } else { System.out.println(" 对不起,你 投如的 钱币 数 量不够, 钱已 经如 数归还 ,请 收 好! "); } } 深入理解 Apache Mina ---- 与IoHandler 相关的几个类 Author:中國壹石頭 ---------------------------------------------------------------------------------------------------------------------- What we call human nature is actually human habbit . 通过 上面 的程 序我们 可以 看出 Mina 的中 业务 逻辑 处理都 可以在 IoHandler 中, 而不 需要 考 虑对 象的序列 化和反 序列化问 题。关于 IoHandler 的简 单用法就 说这么 多。下面 再看看与 IoHandler 相关 的几 个中 要的类 。 按照 惯例 ,还 是先给出 IoHandler 及其 相关 类的 类图 : 从上 面的 类图 我们可 以清 晰的 看到 IoHandler 是一个 接 口, 它有两 个子 类: IoHandlerAdpater:它只是提供了 IoHandler 中定义的方 法体,没有任何的逻辑处理,你可以根据你自己 的 需求重写该类中的相关方法。这个类在实际的开发中使用的是较多的。我们上面写的例子都是继承于这个 类来实现的。 SingleSessionIoHandlerDelegate: 这是一个服务器和客户端只有一个会话时使用的类,在该类的方法中没 有提供 session 的参数,该类在实际的开 发中使用的较少,如果需要对该类 进行更深入的了解,请参考Mina 1.1.7 的API 文档。 在Mina 提供的 IoHandler 的具体实现中,大 部分的实现类都是继承与 IoHandlerApater, IoHandlerAdpater 在Mina 1.1.7中的 子类 有三 个: ChainedIoHandler:这个类主要是用于处理 IoHandler 的messageReceived 事件,它和 IoHandlerChain 配 合使 用。当 在业务 逻辑中有多 个 IoHandler 需要 处理 时 ,你可 以 将 你的 每 个 IoHandler 添加到 IoHandlerChain 中,这个和过滤器链比较相似,关于 IoFilter 和IoHandlerChain 的具体用法和区别会在 后续的文档中给出。 StreamIoHandler: 该类也是用于处理 IoHandler 的messageReceived 事件,它主要用于文件传输的系统中 , 比如 FTP 服务器中,如果需要对该类进行更深入的了解,请参考 Mina 1.1.7 的API 文档。 DemuxingIoHandler: 该类主要是用于处理多个 IoHandler 的messageReceived ,由于在 TCP/IP 协议的数据 传输中会出现数据的截断现象(由于 socket 传输的数据包的长度是固定的,当数据包大于该长度,数据包 就会被截断),所以提供这个类主要是保证 IoHandler 所处理的数据包的完整性,这个和编解码器中的 CumulativeProtocolDecoder 类似,关于这两个类的具体介绍会在后续的文档中给出。 } } 深入理解 Apache Mina ---- IoFilter 和IoHandler 的区别和联系 Author:中國壹石頭 ---------------------------------------------------------------------------------------------------------------------- What we call human nature is actually human habbit . 深入理解 Apache Mina ---- IoFilter 和IoHandler 的区 别和联系 在《与IoFilter 相关的几个类》和《与IoHandler 相 关的 几个 类》 两篇文 档中 我们 了解 了 IoFilter 和IoHandler 的基本用法, 以及 其相 关类 的作 用和 用途 。在 本文 中主 要探 讨 IoFilter 和 IoHandler 的主 要区 别和 联系。 在上面的两篇文档中都提到了IoFilter 和IoHandler 都是对服务器或客户端 (IoAcceptor/IoConnector) 接收 到的数据进行处理。在Mina 的官 方 文 档 《 The high- performance protocol construction toolkit》给出了 IoFilter 和IoHandler 在Mina 数据传输 中的 执行 顺序 ,如下 图: 上图 显示了 IoService 进行 数据 读写 时,各 主要 组件 的执行 顺序 : (1)IoService 读取 数据 时个 组件的 执行 顺序 是: IoProcessor-->IoFilter-->IoHandler。 (2)IoService 发送 数据 时的 执行数 顺序 : IoHandler-->IoFilter-->IoProcessor。 IoProcessor是一个 处 理线 程, 它 的 主要 作用 是根据 当前 连接 的状态 的变 化 (创建 会话 、 开 启 会话 、接收数 据、发 送数据、 发生异 常等等 ),来 将数据或 事件通 知到 IoFilter,当 IoFilter 的相 应的 方法 接收到 该状 态的 变化信 息是 会对 接收到 的数 据进 行处理 , 处 理 完毕后 会将 该 事 件转 发到 IoHandler 中,有 IoHandler 完成 最终的处 理。在 这里 IoProcessor的主 要功能是 创建 资源 (创建 /分配 线程给 IoFilter)和数 据转 发 ( 转发到 IoFilter),IoFilter 对数 据进 行基 本 的 分类( 如编 解码 ) , IoHandler 则负 责具 体的 逻辑实 现。 也 就是说 IoFilter 对接 收到 的数 据 包的 具体 内容 不做处 理, 而 是有 IoHandler 来对 所接 收到 的数据 包进 行处 理, 根 据 数 据包 的 内容 向客 户端 返回响 应的 信息 。 我们 以 《与 IoHandler 相关 的几 个类 》中 KFC售货 机的 例子 来做一个 具 体的 解释, 在 该 例 子 中, 客户 端需 要想服 务器 发送 查询价 格的 请求 ,服务 器根 据接 收到的 请求 查询 物品的 价格 , 然后 将该 物品 的价格 返回 到客 户端。 客 户 端在 向服务 器发 送数 据前会有 IoFilterr 将发 送的 信 息序 列化 为二 进制数 据, 然后有 IoProcess发送 出去 ,简 化如下 : IoHandler 发送 客户 端数 据 -->IoFilter 进行 序列 化 -->IoProcessor 上面 是数 据的 发送过 程, 当 服务 器接收 到客 户端 的的请 求数 据后 , 先有 IoProcessor将该 数 据转发到 IoFilter,IoFilter 将对象进行反序列 化,反序 列化的 结果完成 后将数 据转发 到 深入理解 Apache Mina ---- IoFilter 和IoHandler 的区别和联系 Author:中國壹石頭 ---------------------------------------------------------------------------------------------------------------------- What we call human nature is actually human habbit . IoHandler 中, 过程 简化 如下: IoProcessor接收 客户 度端 的数据 -->IoFilter进行 反序 列化 -->IoHandler 根据 请求 查询 价 格 这样 一个 完整 的数据 请求 的过 程就完 成了 。 上面 简单 介绍了 IoFilter 和IoHandler 在Mina 中 的作 用, 前者 是数 据的 转 换层 , 后者是 业 务 层。 但 是 两者 在很 多地方 都有 相似 之处, 为 了 将两 者的 区别做 更详 细的 讨论, 先 给 出两 者 的 结构 图: 图中的 IoFilter 比IoHandler 中多出 的一个最重 要的方法 就是 filterWriter(),该方 法会在程 序调用 session.write()的时 候触 发, 该 方法 的重 要之 处就在 于它 表明了 IoFilter 和IoHandler 的重 要区 别, 即 进行 IoFilter 是数 据的 收发 层, 也 可以 说是 一个 数据的 收发 器, 而 IoHandler 则 是逻 辑层 , 并不 负责 数据 的收 发 , 如果 把 IoProcessor说 成是 底层 的数 据 收发 层, 则 IoFilter 则是一个 上层的数据收发 层。关于 IoFilter 中on*()的方法的 使用和作用请参 考帮 助文档, 这里 不再 给出 具体的 解释 。 到此 我们 就可 以明白了 IoFilter 是一个 数 据收 发和转 化的 装置 ,而 IoHandler 则是 一个 单一 的业 务处 理装 置,你 的所 有业 务逻辑 都应 该写 在这个 类中 。 如果 没有在 IoService 中配置 IoFilter,那么在IoHandler 中接 收到 的数 据是一个 ByteBuffer , 你需 要在 你的 IoHandler(业务 层 )中完 成数 据的 转化, 但 是 这 样就破 坏了 Mina 中各 个组 件 层 的关 系, 这 样 你的程 序结 构就 不在清 晰, 因 此建 议在 使用 Mina 时将 数据 的转 化 (即二 进制 与 对象 之间 的转 换放在 IoFilter 层来 处理 )。在 Mina 中必 须要 配置 IoHandler, 因为 Mina 中提 供的 IoService 中的 bind 方法 必须 要有 一个 IoHandler,因此 IoHandler 不能 省略 。 到这 里对于 IoFilter 和IoHandler 的内 容已 经讲 述完毕 ,下 面的 内容是 对我 在开 发中遇 到的 一些 问题 的一些 总结 ,顺 便也 给自己 以前 的问 题写出 答案 : (1)IoHandler 和IoHandlerCommand 的区 别和 联系 。 IoHandler 和IoHandlerCommand 是两个接口,在开发中经常遇到的他们两个 实现类分别是 IoHandlerAdpater 和IoHandlerChain ,IoHandlerAdpater 的子类 ChainedIoHandler 和IoHandlerChain 结合使用可以实现多个逻辑功能,IoHandlerChain (代表 IoHandlerCommand )是业务逻辑的处理单元,而 ChainedIoHan dler (代表 Iohandler )则是处理这些逻辑单元的组件。因此它们的区别是:IoHandler 是刀俎, 深入理解 Apache Mina ---- IoFilter 和IoHandler 的区别和联系 Author:中國壹石頭 ---------------------------------------------------------------------------------------------------------------------- What we call human nature is actually human habbit . 而IoHandlerCommand 则是鱼肉。他们的一般用法如下: (2)IoFilter 和IoHandler 可以 同时 使用 吗? IoFilter 和IoHandler 由于分工不同,因此他们需要同时使用,但是这不是绝对的,在 Mina 的IoService 中可以不配置 IoFilter ,但是必须配置 IoHandler 。但是,这不是提倡的方式, 因为这破坏的 mina 的分层结构,因此建议在使用 Mina 的时候同时使用 IoFilter 和 IoHandler 。 (3)IoFilter 和IoHandlerCommand/IoHandler 的区 别和 联系 。 这 个问 题的 答案 请参 考问 题( 1)和 ( 2) 给出 的解 释。 (4)IoHandlerAdpater 和IoFilterAdpater 的区 别和 联系 。 IoHandlerAdpater 和IoFilterAdpater 一个是业务逻辑层的监听器,一个数据传输层的监 听器,他们的区别就是 IoHandler 和IoFilter 的区别,这个在上面已经讨论清楚了,不在 详细说明。 (5)IoFilterChainBuilder 和ChainedIoHandler 的区 别和 联系 。 IoHandlerChain chain = new new new new IoHandlerChain(); // 创建逻 辑处理组 件 chain.addLast( "first" , new new new new FistCommand); // 添加逻 辑组 件单元一 chain.addLast( "second", new new new new SecondCommand); // 逻辑组 件单 元二 ChainedIoHandler chained = new new new new ChainedIoHandler(chain); // 创建逻 辑组 件执行 模块 chained.messageReceived(session, message); // 当messageReceived 触发该 事件 深入理解 Apache Mina ---- Mina 的ByteBuffer Author:中國壹石頭 ---------------------------------------------------------------------------------------------------------------------- What we call human nature is actually human habbit . 深入理解 Apache Mina ---- 配置 Mina 的 线程模型 在Mina 的使 用中 ,线 程池的 配置 一个 比较关 键的 环节 ,同时 它也是 Mina 性能 提高 的一个 有效 的方 法,在 Mina 的2.0以上 版本 中已 经不再 需要对 Mina 线程 池的 配置 了,本 系列 文 章都 是基 于当 前的稳 定版本 Mina 1.1.7版来 进行 讲述 的, Mina 的2.0以上 版本 现在 还都是 M(millestone,即里 程碑 )版的 ,在 1.5版本上 2.0M 版为 稳定 版本 ,但是在 1.5+以上 则为 非 稳定 版本 ,所 以,为 了更 好的 进行讨 论和 学习 ,还是 基于 Mina 1.1.7版本 进行 讨论 ,如果 使用 Mina 2.0进行 开发 要注意 JDK 的版 本问 题, 当然如 果有 能力 的话 也可以 自行 修改 和编 译Mina 的2.0版本 , 这 里对 此就 不再 多说, 使用 2.0版本 的同 学可 以不用 理会 本文 的内容 。 上面 的内 容都 是基于 Apache Mina 提供 的文 档讲 述,如 有需 要, 请自行 查找 相关 资料, 在 此不 再赘 述。 下面 开始对 Mina 的线 程模 型的 配置、 使用 、及 ExcutorFilter 的 基本 原理 进行 简 单的 讲解 。 (一)配置 (一)配置 (一)配置 (一)配置 Mina Mina Mina Mina 的三种工作线程的三种工作线程的三种工作线程的三种工作线程 在Mina 的NIO模式 中有 三种 I/O 工作 线程 (这 三种线 程模 型只在 NIO Socket 中有 效, 在 NIO数据 包和 虚拟 管道中 没有 ,也 不需要 配置 ) : (1)Acceptor thread 该线程的作用是接收客户端的连接,并将客户端的连接导入到 I/O processor线程模型中。所谓的 I/O processor线程模型就是 Mina 的I/O processor thread。Acceptor thread在调用了 Acceptor.bind()方法后 启动。每个 Acceptor只能创建一个 Acceptor thread,该线程模型不能配置,它由 Mina 自身提供。 (2)Connector thread 该线程模型是客户端的连接线程模型,它的作用和 Acceptor thread类似,它将客户端与服务器的连接导入 到I/O processor线程模型中。同样地,该线程模型也是由 Mina 的客户端自动创建,该线程模型也不能进 行配置。 (3)I/O processor thread 该线程模型的主要作用就行接收和发送数据,所有的 IO 操作在服务器与客户端的连接建立后,所有的数据 的接收和发送都是有该线程模型来负责的,知道客户端与服务器的连接关闭,该线程模型才停止工作。该 线程模型可以由程序员根据需要进行配置。该线程模型默认的线程的数量为 cpu 的核数+1。若你的 cpu 为 双核的,则你的 I/O processor 线程的最大数量为 3,同理若你的若你的 cpu 为四核的,那么你的 I/O processor 线程的最大数量为 5。 由上 面的 内容 我们可 以知 道在 Mina 中可 以配 置的 线程数 量只有 I/O processor,对 于每个 IoService 再创 建其 实例 的时候 可以 配置该 IoService 的I/O processor的线 程数 量。在 SokcetConnector 和SocketAccpetor中I/O Processor的数 量是由 CPU 的核 数 +1来决 定的 。 他们 的配 置方 式如下 : /*** * 配置 SocketAcceptor 监听器的 I/O Processor 的线程的数 量 , * 此处的 I/O Processor 的线程 数量由 CPU的核数决定 ,但 Acceptor * 的线程 数量只有一 个, 也就 是 接收 客户端 连 接的线程 数只有 一个, 深入理解 Apache Mina ---- Mina 的ByteBuffer Author:中國壹石頭 ---------------------------------------------------------------------------------------------------------------------- What we call human nature is actually human habbit . 在上 面的 配置 比较难 以理 解的 地方就是 Runtime.getRuntime().availableProcessors() + 1, 它的 意思 就是由 JVM 根据 系统 的情 况 (即CPU 的核 数 )来决定 IO Processor的线 程的 数量 。 虽然 这个 线程 的数量 是在 SocketAcceptor /SocketConnector 的构 造器 中进 行的, 但 是对 于 SocketAcceptor /SocketConnector 自 身的 线程 没有 影 响, SocketAcceptor /SocketConnector 的线 程数 量仍 然为 1。因为 SocketAcceptor /SocketConnector 本身 就封 装了 IO Processor, SocketAcceptor /SocketConnector 只是 由一个 单 独的线 程来 负责 接收外 部连 接 /向外 部请 求 建立 连接 , 当 连 接建 立后 , SocketAcceptor /SocketConnector会把 数据 收发 的任务 转交 I/O Processor的线 程。 这 个在 本系列 文章 的 《 IoFilter 和IoHandler 的区 别和 联系 》中 的图 示 中 可以 看。 图中 清晰 的显 示了 IO Processor就是 位于 IoService 和IoFilter 之间 , IoService 负责 和外 部 建立 连接 ,而 IoFilter 则负 责处 理接 收到的 数据 , IoProcessor则负 责数 据的 收发工 作。 关于 配置 IO Processor的线 程数 量还 有一种 比较 “笨”的办 法, 那就 一个一个 试 ,你 可以 根据 你的 PC 的硬 件情 况从 1开始 , 每 次加 1, 然后 得 出 IO Processor的最 佳的 线程 的数 量。 但是 这种 方式 个人建 议最 好不 要用了 ,上 面的 方法足 矣。 配置 方法如 下: (二)(二)(二)(二)为为为为Mina Mina Mina Mina 的的的的IoFilterChainIoFilterChainIoFilterChainIoFilterChain添加线程池添加线程池添加线程池添加线程池 在Mina 的API 中提 供了 一个 ExecutorFilter,该 线程 池实 现了 IoFilter 接口 ,它 可以 作为一 * Acceptor 的线程 数量不能 配 置。 **/ SocketAcceptor acceptor = new new new new SocketAcceptor(Run time. getRuntime () .availableProcessors() + 1, Executors.new Cac hedThreadPool ()); /*** * 配置 SocketConnector 监听器的 I/O Processor 的线程的数 量 , * 此处的 I/O Processor 的线程 数量由 CPU的核数决定 ,但 SocketConnector * 的线程 数量只有一 个, 也就 是 接收 客户端 连 接的线程 数只有 一个, * SocketConnector 的线程 数量不能 配 置。 **/ SocketConnector connector = new new new new SocketConnector(Run time. getRuntime() .availableProcessors() + 1, Executors.new Cac hedThreadPool ()); //从1--N 开始尝 试, N的最大 数量为 CPU核数 +1 SocketAcceptor acceptor = new SocketAcceptor(N, Executors.newCachedThreadPool()); 深入理解 Apache Mina ---- Mina 的ByteBuffer Author:中國壹石頭 ---------------------------------------------------------------------------------------------------------------------- What we call human nature is actually human habbit . 个IoFilter 添加到 IoFilterChain 中, 它的 作用 就是将 I/O Processor中的 事件 通过 其自身 封 装的 一个 线程 池来转 发到 下一个 过滤 器中 。在 没有添 加该 线程 模型时 , I/O Processor的事 件是 通过 方法 来触发 的, 然后 转发给 IoHandler。 在没有 添加 该线 程池的 时候 ,所 有的 事 件 都是 在单 线程 模式下 运行 的, 也 就 是说 有的事 件和 处理 (IO Processor,IoHandler,IoFilter) 都是 运行 在同 一个线 程上 ,这 个线程 就是 IO Processor的线 程, 但是 这个线 程的 数量 受到 CPU 核数 的 影响 ,因 此系 统的性 能也 直接受 CPU 核数 的影 响。 比较 复杂 的应 用一般 都会 用到 该线程 池, 你 可 以根 据你 的需 求在 IoFilterchain 中你 可以 添 加 任意 数量 的线 程池, 这些 线程 池可以 组合 成一个 事件 驱动 (SEDA)的处 理模 型。 对于一般 的 应用 来说 不是 线程的 数量 越多 越好, 线程 的数 量越多 可能 会加剧 CPU 切换 线程 所耗 费的时 间, 反而 会影 响系统 的性 能, 因此, 线程 的数 量需要 根据 实际 的需要 由小 到大 ,逐步 添加 , 知道 找到 适合 你系统 的最 佳线 程的数 量。 ExcutorFilter 的配 置过 程如 下: 在配 置该 线程 池的时 候需 要注 意的一个 问 题是 , 当 你 使用自 定的 ProtocolCodecFactory 时候 一定 要将 线程 池配置 在该 过滤 器之后 ,如 下所 示: 因为 你自 己实 现的 ProtocolCodecFactory 直接 读取 和转 换的是 二进 制数 据, 这 些 数据 都是 由 和CPU 绑定的 I/O Processor来读 取和 发送 的, 因 此 为 了不影 响系 统的 性能, 也 应 该将数 据 的编 解码 操作 绑定到 I/O Processor线 程中 , 因为 在 Java中创 建和 线程 切换都 是比 较耗 资 源 的, 因此 建议将 ProtocolCodecFactory 配置在 ExecutorFilter 的前 面。 关于 ProtocolCodecFactory详细 讲述 会在 后续的 文档 中给 出,此 处就 不多 说了。 最后 给出 一个 服务器 线程 模型 完整配 置的 例子 , 该例 子和 KFCClient 一起 配置 使用 , 详细 代 码在 附件 中, 此处只 给出 代码 的主要 部分 : SocketAcceptor acceptor = ...; DefaultIoFilterChainBuilder filterChainBuild er = acceptor.getDef aultConfig().getFilterChain(); filterChainBuild er.addLast("thr eadPool", new ExecutorFilter(Executors.n ewCachedThreadPool()); DefaultIoFilterChainBuilder filterChainBuild er = acceptor.getDef aultConfig().getFilterChain(); // 和CPU绑定的操 作配 置在过 滤 器的前 面 filterChainBuild er.addLast("codec", new ProtocolCodecFactory(...)); // 添加线程 池 filterChainBuild er.addLast("thr eadPool", new ExecutorFilter(Executors.n ewCachedThreadPool()); SocketAddress address = new new new new InetSocketAddress( "localhost" , 4321); /*** * 配置 SocketAcceptor 监听器的 I/O Processor 的线程的数 量 , 此处的 I/O * Processor 的线程 数量由 CPU的核数决定 ,但 Acceptor 的线程 数量只有 一个 ,也 就 是接收 客户端 连接的线 程 数只有一 个, * Acceptor 的线程 数量不能 配 置。 **/ IoAcceptor acceptor = new new new new SocketAcceptor(Run time. getRuntime () .availableProcessors() + 1, Executors.new Cac hedThreadPool ()); acceptor.getDef aultConfig().setThreadModel(ThreadModel. MANUAL); // 配置数 据的编解 码 器 acceptor.getDef aultConfig().getFilterChain().addLast( "codec", new new new new ProtocolCodecFilter( new new new new ObjectSerializationCodecFactory())); 深入理解 Apache Mina ---- Mina 的ByteBuffer Author:中國壹石頭 ---------------------------------------------------------------------------------------------------------------------- What we call human nature is actually human habbit . // 此处为 你自己实 现的编 解 码 器 // config .getFilterChain().addLast(" codec ", new // ProtocolCodecFactory(...)); // 为IoFilterChain 添加线程 池 acceptor.getDef aultConfig().getFilterChain().addLast( "threadPool", new new new new ExecutorFilter(Executors. new Cac hedThread Pool ())); acceptor.getDef aultConfig().getFilterChain().addLast( "logger", new new new new LoggingFilter()); // 绑定服 务器 端口 acceptor.bind(address, new new new new KFCFoodPriceHandler()); System. out.println(" 服务器 开始在 8000 端口监听 ......." ); // ==========================================// // 此处为 客户端的 I/O Processor 线程数的配 置,你 可以模 仿 // // IoAcceptor 配置来实 现 // // ==========================================// /*** * 配置 SocketConnector 监听器的 I/O Processor 的线程的数 量 , 此处的 I/O * Processor 的线程 数量由 CPU的核数决定 ,但 SocketConnector * 的线程数量只有一个,也就是接收客户端连接的线程数只有一个, SocketConnector 的线程 数量不能 配 置。 **/ // SocketConnector connector = new SocketConnector(Run time.g etRun time() //.availableProcessors() + 1, Executors.newCachedThreadPool()); } Java Nio ByteBuffer 与Mina ByteBuffer 的区别 Author:中國壹石頭 Time:2009 年9月13 日 ____________________________________________________________________________________________ What we call human nature is actually human habit. 深入理解 Apache Mina ---- Java Nio ByteBuffer 与 Mina ByteBuffer 的区别 为了 对后 续关于 Mina 的ProtocolFilter(编解 码器 )的编 写有 一个 更好 的理 解, 本文 讲述 一下 关于 Mina ByteBuffer 和Java Nio ByteBuffer 的区 别。 关于 Java Nio ByteBuffer 和Mina ByteBuffer 及其 子类 的类 图在 附件 中都 已经 给出 了。 因为 Mina 的ByteBuffer 在Mina 2.0 以 上的 版本 中都 改称 IoBuffer。 为 了 使后 文关于 ByteBuffer 的名 字不 致混 淆, Mina ByteBuffer 都统称 IoBuffer,Java Nio ByteBuffer 统称 ByteBuffer。 关于 IoBuffer 中的对 ByteBuffer 扩 展及 一些 重要 的方 法都在 IoBuffer 的类 图中 用红 色方 框标 出。 详细 的信 息请 参考 附件 中。 在开 始对 IoBuffer 的讨 论前 , 先 简 单的 讲述 一下 ByteBuffer 的用法。IoBuffer 是对 ByteBuffer 的一 个封 装。 IoBuffer 中的 很多 方法 都是对 ByteBuffer 的直 接继 承。 只 是对 ByteBuffer 添加 了一 些扩 展了 更加 实用 的方 法。 (1)ByteBuffer 简介 ByteBuffer 继承于 Buffer 类,ByteBuffer 中存 放的 是字 节, 如 果 要将 它们 转换 成字 符串 则 需 要使用 Charset,Charset 是字 符编 码。 它 提 供了 把字 节流 转换 成字 符串 (解码 )和将 字符 串 转 换成 字节 流 (编码 )的方 法。 这 个和 后面 讲述的 Mina 的编 解码 的工 作原 理类 似。 对 ByteBuffer 的访 问可 以使用 read(),write()等方 法。 ByteBuffer 有一 下三 个重 要的 属性 : 1) 容量 (capacity):表 示该 缓存 区可 以存 放多 少数 据。 2) 极限 (limit):表 示读 写缓 存的 位置 ,不 能对 超过 位置 进行 数据 的读 或写 操作 。 3) 位置 (position):表 示下 一个 缓存 区的 读写 单元 。每 读写 一次 缓存 区, 位置 都会 变化 。位 置是 一个 非负 整数 。 ByteBuffer 的这 三个 属性 相当 于三 个标 记位 ,来 表示 程序 可以 读写 的区 域: 容量 极限 位置 上图 简单 的表 示了 容量 、 极 限 、 位 置 在缓 存区 中的 位置 。 其 中极 限只 能标 记容 量以 内的 位 置, 即极 限值 的大 小不 能超 过容 量。 同 样 位置 是用 来标 记程 序对 缓存 区进 行读 或写 操作 的开 始 位 置。 程 序 只能 在极 限以 内的 范围 进行 读写 , 即 读 写操 作不 能超 过极 限的 范围 , 所 以 位置 值 的 大小 也不 能超 过极 限。 三者 的大 小关 系为 :容 量 >极限 >位置 >=0。 上面 说到 ByteBuffer 的三 个属 性只 是缓 存区 的标 记位 置。 那 么如 何改 变这 些标 记的 位置 呢 ? ByteBuffer 提供 了一 下三 种方 法来 改变 上面 的属 性值 。 缓存 区 Java Nio ByteBuffer 与Mina ByteBuffer 的区别 Author:中國壹石頭 Time:2009 年9月13 日 ____________________________________________________________________________________________ What we call human nature is actually human habit. 1)clear():极限 设置 为容 量, 位置 设为 0。 2)Flip():极限 设为 位置 ,位 置设为 0。 3)Rewind():不改 变极 限, 位置 设为 0。 上面 只是 一个 简单 的演 示程 序, 功 能是 实现 对字 符串 的读 写, 比 较 “笨”, 呵 呵 。 关 于 如何 向 ByteBuffer 读写 字符 串会在 IoBuffer 中详 细讲 解。 (2)IoBuffer 简介 IoBuffer 是对 ByteBuffer 的扩 展, 并不 是和 ByteBuffer 毫无 关系 的。对 Mina 或者 Socket 应用 来说 , ByteBuffer 提供 的方 法存 在一 下不 足: 1)它没 有提 供足 够可 用的 put 和set 方法 , 例 如 : fill、get/putString、get/putAsciiInt()等。 2)很难 将可 变长 度的 数据 放入 ByteBuffer。 基于 以上 的缺 点, Mina 提供了 IoBuffer 来补 充了 ByteBuffer 的不 足之 处。 Let's drink code,来看看 Mina 的IoBuffer 是如 何读 写字 符串 的。 //JDK没有 提供 ByteBuffer的公 开构 造方 法只 能通 过该 // 方法 来创 建一 个缓 存区 。 ByteBuffer buffer = ByteBuffer.allocate(1024); // ========测试 缓存 读写 一个 字符 串 =======// String userName ="chinaestone"; charcharcharchar[] charArray = userName.toCharArray(); System.out.println("这是 往缓 存中 存放 的 字符 串 "); forforforfor(intintintint i=0;i

下载文档,方便阅读与编辑

文档的实际排版效果,会与网站的显示效果略有不同!!

需要 8 金币 [ 分享文档获得金币 ] 2 人已下载

下载文档

相关文档