快捷搜索:

使用 Apache MINA 2 开发网络应用

Apache MINA 2 是一个开拓高机能和高可伸缩性收集利用法度榜样的收集利用框架。它供给了一个抽象的事故驱动的异步 API,可以应用 TCP/IP、UDP/IP、串口和虚拟机内部的管道等传输要领。Apache MINA 2 可以作为开拓收集利用法度榜样的一个优越根基。本文将先容 Apache MINA 2 的基础观点和 API,包括 I/O 办事、I/O 会话、I/O 过滤器和 I/O 处置惩罚器。别的还将先容若何应用状态机。本文包孕简单的谋略器办事和繁杂的联机游戏两个示例利用。

Apache MINA 2 是一个开拓高机能和高可伸缩性收集利用法度榜样的收集利用框架。它供给了一个抽象的事故驱动的异步 API,可以应用 TCP/IP、UDP/IP、串口和虚拟机内部的管道等传输要领。Apache MINA 2 可以作为开拓收集利用法度榜样的一个优越根基。下面将首先简单先容一下 Apache MINA 2。

Apache MINA 2 先容

Apache MINA 是 Apache 基金会的一个开源项目,今朝最新的版本是 2.0.0-RC1。本文中应用的版本是 2.0.0-M6。从 参考资料 中可以找到相关的下载信息。下面首先先容基于 Apache MINA 的收集利用的一样平常架构。

基于 Apache MINA 的收集利用的架构

基于 Apache MINA 开拓的收集利用,有着相似的架构。图 1 中给出了架构的示意图。

图 1. 基于 Apache MINA 的收集利用的架构

如 图 1 所示,基于 Apache MINA 的收集利用有三个层次,分手是 I/O 办事、I/O 过滤器和 I/O 处置惩罚器:

I/O 办事:I/O 办事用来履行实际的 I/O 操作。Apache MINA 已经供给了一系列支持不合协议的 I/O 办事,如 TCP/IP、UDP/IP、串口和虚拟机内部的管道等。开拓职员也可以实现自己的 I/O 办事。

I/O 过滤器:I/O 办事能够传输的是字撙节,而上层利用必要的是特定的工具与数据布局。I/O 过滤器用来完成这两者之间的转换。I/O 过滤器的别的一个紧张感化是对输入输出的数据进行处置惩罚,满意横切的需求。多个 I/O 过滤器串联起来,形成 I/O 过滤器链。

I/O 处置惩罚器:I/O 处置惩罚器用来履行详细的营业逻辑。对接管到的消息履行特定的处置惩罚。

创建一个完备的基于 Apache MINA 的收集利用,必要分手构建这三个层次。Apache MINA 已经为 I/O 办事和 I/O 过滤器供给了不少的实现,是以这两个层次在大年夜多半环境下可以应用已有的实现。I/O 处置惩罚器因为是与详细的营业相关的,一样平常来说都是必要自己来实现的。

事故驱动的 API

Apache MINA 供给的是事故驱动的 API。它把与收集相关的各类活动抽象成事故。收集利用只必要对其感兴趣的事故进行处置惩罚即可。事故驱动的 API 使得基于 Apache MINA 开拓收集利用变得对照简单。利用不必要斟酌与底层传输相关的详细细节,而只必要处置惩罚抽象的 I/O 事故。比如在实现一个办事端利用的时刻,假如有新的连接进来,I/O 办事会孕育发生 sessionOpened这样一个事故。假如该利用必要在有连接打开的时刻,履行某些特定的操作,只必要在 I/O 处置惩罚器中此事故处置惩罚措施 sessionOpened中添加响应的代码即可。

在先容 Apache MINA 中的基础观点的细节之前,首先经由过程一个简单的利用来认识上面提到的三个层次的详细职责。

从简单利用开始

在应用 Apache MINA 开拓繁杂的利用之前,首先将先容一个简单的利用。经由过程此利用可以认识上面提到的三个层次,即 I/O 办事、I/O 过滤器和 I/O 处置惩罚器。该利用是一个简单的谋略器办事,客户端发送要谋略的表达式给办事器,办事器返回谋略结果。比如客户端发送 2+2,办事器返回 4.0作为结果。

在实现此谋略器的时刻,首先必要斟酌的是 I/O 办事。该谋略器应用 TCP/IP 协议,必要在指定端口监听,吸收客户真个连接。Apache MINA 供给了基于 Java NIO 的套接字实现,可以直接应用。其次要斟酌的是 I/O 过滤器。I/O 过滤器过滤所有的 I/O 事故和哀求,可以用来处置惩罚横切的需求,如记录日志、压缩等。着末便是 I/O 处置惩罚器。I/O 处置惩罚器用来处置惩罚营业逻辑。详细到该利用来说,便是在接管到消息之后,把该消息作为一个表达式来履行,并把结果发送回去。I/O 处置惩罚器必要实现 org.apache.mina.core.service.IoHandler接口或者承袭自 org.apache.mina.core.service.IoHandlerAdapter。该利用的 I/O 处置惩罚器的实现如 清单 1 所示。

清单 1. 谋略器办事的 I/O 处置惩罚器 CalculatorHandler

public class CalculatorHandler extends IoHandlerAdapter {

private static final Logger LOGGER = LoggerFactory

.getLogger(CalculatorHandler.class);

private ScriptEngine jsEngine = null;

public CalculatorHandler() {

ScriptEngineManager sfm = new ScriptEngineManager();

jsEngine = sfm.getEngineByName("JavaScript");

if (jsEngine == null) {

throw new RuntimeException("找不到 JavaScript 引擎。");

}

}

public void exceptionCaught(IoSession session, Throwable cause)

throws Exception {

LOGGER.warn(cause.getMessage(), cause);

}

public void messageReceived(IoSession session, Object message)

throws Exception {

String expression = message.toString();

if ("quit".equalsIgnoreCase(expression.trim())) {

session.close(true);

return;

}

try {

Object result = jsEngine.eval(expression);

session.write(result.toString());

} catch (ScriptException e) {

LOGGER.warn(e.getMessage(), e);

session.write("Wrong expression, try again.");

}

}

}

在 清单 1 中,messageReceived 由 IoHandler 接口声明。当接管到新的消息的时刻,该措施就会被调用。此处的逻辑是假如传入了“quit”,则经由过程 session.close关闭当前连接;假如不是的话,就履行该表达式并把结果经由过程 session.write发送回去。此处履行表达式用的是 JDK 6 中供给的 JavaScript 脚本引擎。此处应用到了 I/O 会话相关的措施,会鄙人面进行阐明。

接下来只必要把 I/O 处置惩罚器和 I/O 过滤器设置设置设备摆设摆设到 I/O 办事上就可以了。详细的实现如 清单 2 所示。

清单 2. 谋略器办事主法度榜样 CalculatorServer

public class CalculatorServer {

private static final int PORT = 10010;

private static final Logger LOGGER = LoggerFactory

.getLogger(CalculatorServer.class);

public static void main(String[] args) throws IOException {

IoAcceptor acceptor = new NioSocketAcceptor();

acceptor.getFilterChain().addLast("logger", new LoggingFilter());

acceptor.getFilterChain().addLast(

"codec",

new ProtocolCodecFilter(new TextLineCodecFactory(Charset

.forName("UTF-8"))));

acceptor.setHandler(new CalculatorHandler());

acceptor.bind(new InetSocketAddress(PORT));

LOGGER.info("谋略器办事已启动,端口是" + PORT);

}

}

清单 2 中,起开创建一个 org.apache.mina.transport.socket.nio.NioSocketAcceptor 的实例,由它供给 I/O 办事;接着得到该 I/O 办事的过滤器链,并添加两个新的过滤器,一个用来记录相关日志,别的一个用来在字撙节和文本之间进行转换;着末设置设置设备摆设摆设 I/O 处置惩罚器。完成这些之后,经由过程 bind 措施来在特定的端口进行监听,接管连接。办事器启动之后,可以经由过程操作系统自带的 Telnet 对象来进行测试,如 图 2 所示。在输入表达式之后,谋略结果会呈现鄙人面一行。

图 2. 应用 Telnet 对象测试谋略器办事

在先容了简单的谋略器办事这个利用之后,下面阐明本文中会应用的繁杂的联机游戏利用。

联机游戏示例阐明

上一节中给出了一个简单的基于 Apache MINA 的收集利用的实现,可以用来认识基础的架构。而在实际开拓中,收集利用都是有必然繁杂度的。下面会以一个对照繁杂的联机游戏作为示例来具体先容 Apache MINA 的观点、API 和范例用法。

该联机游戏支持两小我进行俄罗斯方块的对战。这个游戏借鉴了 QQ 的“火拼俄罗斯”。用户在启动客户端之后,必要输入一个昵称进行注册。用户可以在“游戏大年夜厅”中查看当前已注册的所有其它用户。当前用户可以选择别的的一个用户发送游戏约请。约请被吸收之后就可以开始进行对战。在游戏历程中,当前用户可以看到对方的游戏状态,即方块的环境。该游戏的运行效果如 图 3 所示。

图 3. 联机游戏示例运行效果图

下面开始以这个利用为例来详细先容 Apache MINA 中的基础观点。先从 I/O 办事开始。

I/O 办事

I/O 办事用来履行真正的 I/O 操作,以及治理 I/O 会话。根据所应用的数据传输要领的不合,有不合的 I/O 办事的实现。因为 I/O 办事履行的是输入和输出两种操作,实际上有两种详细的子类型。一种称为“I/O 吸收器(I/O acceptor)”,用来吸收连接,一样平常用在办事器的实现中;别的一种称为“I/O 连接器(I/O connector)”,用来提议连接,一样平常用在客户真个实现中。对应在 Apache MINA 中的实现,org.apache.mina.core.service.IoService是 I/O 办事的接口,而承袭自它的接口 org.apache.mina.core.service.IoAcceptor 和 org.apache.mina.core.service.IoConnector 则分腕表示 I/O 吸收器和 I/O 连接器。IoService 接口供给的紧张措施如 表 1 所示。

表 1. IoService 中的紧张措施

措施

阐明

setHandler(IoHandler handler)

设置 I/O 处置惩罚器。该 I/O 处置惩罚器会认真处置惩罚该 I/O 办事所治理的所有 I/O 会话孕育发生的 I/O 事故。

getFilterChain()

获取 I/O 过滤器链,可以对 I/O 过滤器进行治理,包括添加和删除 I/O 过滤器。

getManagedSessions()

获取该 I/O 办事所治理的 I/O 会话。

下面详细先容 I/O 吸收器和 I/O 连接器。

I/O 吸收器

I/O 吸收器用来吸收连接,与对等体(客户端)进行通讯,并发出响应的 I/O 事故交给 I/O 处置惩罚器来处置惩罚。应用 I/O 吸收器的时刻,只必要调用 bind措施并指定要监听的套接字地址。当不再吸收连接的时刻,调用 unbind竣事监听即可。关于 I/O 吸收器的详细用法,可以参考 清单 2 中给出的谋略器办事的实现。

I/O 连接器

I/O 连接器用来提议连接,与对等体(办事器)进行通讯,并发出响应的 I/O 事故交给 I/O 处置惩罚器来处置惩罚。应用 I/O 连接器的时刻,只必要调用 connect措施连接指定的套接字地址。别的可以经由过程 setConnectTimeoutMillis设置连接超韶光阴(毫秒数)。

清单 3 中给出了应用 I/O 连接器的一个示例。

清单 3. I/O 连接器示例

SocketConnector connector = new NioSocketConnector();

connector.setConnectTimeoutMillis(CONNECT_TIMEOUT);

connector.getFilterChain().addLast("logger", new LoggingFilter());

connector.getFilterChain().addLast("protocol",

new ProtocolCodecFilter(new TetrisCodecFactory()));

ConnectFuture connectFuture = connector.connect(new InetSocketAddress(host, port));

connectFuture.awaitUninterruptibly();

在 清单 3 中,起开创建一个 Java NIO 的套接字连接器 NioSocketConnector 的实例,接着设置超韶光阴。再添加了 I/O 过滤器之后,经由过程 connect 措施连接到指定的地址和端口即可。

在先容完 I/O 办事之后,下面先容 I/O 会话。

I/O 会话

I/O 会话表示一个活动的收集连接,与所应用的传输要领无关。I/O 会话可以用来存储用户自定义的与利用相关的属性。这些属性平日用来保存利用的状态信息,还可以用来在 I/O 过滤器和 I/O 处置惩罚器之间互换数据。I/O 会话在感化上类似于 Servlet 规范中的 HTTP 会话。

Apache MINA 中 I/O 会话实现的接口是 org.apache.mina.core.session.IoSession。该接口中对照紧张的措施如 表 2 所示。

表 2. IoSession 中的紧张措施

措施

阐明

close(boolean immediately)

关闭当前连接。假如参数 immediately为 true的话,连接会等到行列步队中所有的数据发送哀求都完成之后才关闭;否则的话就急速关闭。

getAttribute(Object key)

从 I/O 会话中获取键为 key的用户自定义的属性。

setAttribute(Object key, Object value)

将键为 key,值为 value的用户自定义的属性存储到 I/O 会话中。

removeAttribute(Object key)

从 I/O 会话中删除键为 key的用户自定义的属性。

write(Object message)

将消息工具 message发送到当前连接的对等体。该措施是异步的,当消息被真正发送到对等体的时刻,IoHandler.messageSent(IoSession,Object)会被调用。假如必要的话,也可以等消息真正发送出去之后再继承履行后续操作。

在先容完 I/O 会话之后,下面先容 I/O 过滤器。

I/O 过滤器

从 I/O 办事发送过来的所有 I/O 事故和哀求,在到达 I/O 处置惩罚器之前,会先由 I/O 过滤器链中的 I/O 过滤器进行处置惩罚。Apache MINA 中的过滤器与 Servlet 规范中的过滤器是类似的。过滤器可以在很多环境下应用,比如记录日志、机能阐发、造访节制、负载均衡和消息转换等。过滤器异常得当满意收集利用中各类横切的非功能性需求。在一个基于 Apache MINA 的收集利用中,一样平常存在多个过滤器。这些过滤器相互串联,形成链条,称为过滤器链。每个过滤器依次对传入的 I/O 事故进行处置惩罚。当前过滤器完成处置惩罚之后,由过滤器链中的下一个过滤器继承处置惩罚。当前过滤器也可以不调用下一个过滤器,而提前停止,这样 I/O 事故就不会继承以后通报。比如认真用户认证的过滤器,假如碰到未认证的对等体发出的 I/O 事故,则会直接关闭连接。这可以包管这些事故不会经由过程此过滤器到达 I/O 处置惩罚器。

Apache MINA 中 I/O 过滤器都实现 org.apache.mina.core.filterchain.IoFilter接口。一样平常来说,不必要完备实现 IOFilter接口,只必要承袭 Apache MINA 供给的适配器 org.apache.mina.core.filterchain.IoFilterAdapter,并覆写所需的事故过滤措施即可,其它措施的默认实现是不做任何处置惩罚,而直接把事故转发到下一个过滤器。

IoFilter 接口具体阐明

IoFilter接口供给了 15 个措施。这 15 个措施大年夜致分成两类,一类是与过滤器的生命周期相关的,别的一类是用来过滤 I/O 事故的。第一类措施如 表 3 所示。

表 3. IoFilter 中与过滤器的生命周期相关的措施

措施

阐明

init()

当过滤器第一次被添加到过滤器链中的时刻,此措施被调用。用来完成过滤器的初始化事情。

onPreAdd(IoFilterChain parent, String name, IoFilter.NextFilter nextFilter)

当过滤器即将被添加到过滤器链中的时刻,此措施被调用。

onPostAdd(IoFilterChain parent, String name, IoFilter.NextFilter nextFilter)

当过滤器已经被添加到过滤器链中之后,此措施被调用。

onPreRemove(IoFilterChain parent, String name, IoFilter.NextFilter nextFilter)

当过滤器即将被从过滤器链中删除的时刻,此措施被调用。

onPostRemove(IoFilterChain parent, String name, IoFilter.NextFilter nextFilter)

当过滤器已经被从过滤器链中删除的时刻,此措施被调用。

destroy()

当过滤器不再必要的时刻,它将被销毁,此措施被调用。

在 表 3 中给出的措施中,参数 parent 表示包孕此过滤器的过滤器链,参数 name 表示过滤器的名称,参数 nextFilter 表示过滤器链中的下一个过滤器。

第二类措施如 表 4 所示。

表 4. IoFilter 中过滤 I/O 事故的措施

措施

阐明

filterClose(IoFilter.NextFilter nextFilter, IoSession session)

过滤对 IoSession的 close措施的调用。

filterWrite(IoFilter.NextFilter nextFilter, IoSession session, WriteRequest writeRequest)

过滤对 IoSession的 write措施的调用。

exceptionCaught(IoFilter.NextFilter nextFilter, IoSession session, Throwable cause)

过滤对 IoHandler的 exceptionCaught措施的调用。

messageReceived(IoFilter.NextFilter nextFilter, IoSession session, Object message)

过滤对 IoHandler的 messageReceived措施的调用。

messageSent(IoFilter.NextFilter nextFilter, IoSession session, WriteRequest writeRequest)

过滤对 IoHandler的 messageSent措施的调用。

sessionClosed(IoFilter.NextFilter nextFilter, IoSession session)

过滤对 IoHandler的 sessionClosed措施的调用。

sessionCreated(IoFilter.NextFilter nextFilter, IoSession session)

过滤对 IoHandler的 sessionCreated措施的调用。

sessionIdle(IoFilter.NextFilter nextFilter, IoSession session, IdleStatus status)

过滤对 IoHandler的 sessionIdle措施的调用。

sessionOpened(IoFilter.NextFilter nextFilter, IoSession session)

过滤对 IoHandler的 sessionOpened措施的调用。

对付 表 4 中给出的与 I/O 事故相关的措施,它们都有一个参数是 nextFilter,表示过滤器链中的下一个过滤器。假如当前过滤器完成处置惩罚之后,可以经由过程调用 nextFilter 中的措施,把 I/O 事故通报到下一个过滤器。假如当前过滤器不调用 nextFilter 中的措施的话,该 I/O 事故就不能继承以后通报。别的一个合营的参数是 session,用来表示当前的 I/O 会话,可以用来发送消息给对等体。下面经由过程详细的实例来阐明过滤器的实现。

BlacklistFilter

BlacklistFilter是 Apache MINA 自带的一个过滤器实现,其功能是阻拦来自特定地址的连接,即所谓的“黑名单”功能。BlacklistFilter承袭自 IoFilterAdapter,并覆写了 IoHandler相关的措施。清单 4 中给出了部分实现。

清单 4. 阻拦来自特定地址连接的 BlacklistFilter

public void messageReceived(NextFilter nextFilter, IoSession session, Object message) {

if (!isBlocked(session)) {

nextFilter.messageReceived(session, message);

} else {

blockSession(session);

}

}

private void blockSession(IoSession session) {

session.close(true);

}

在 清单 4 中 messageReceived 措施的实现中,首先经由过程 isBlocked 来判断当前连接是否应该被阻拦,假如不是的话,则经由过程 nextFilter.messageReceived 把该 I/O 事故通报到下一个过滤器;否则的话,则经由过程 blockSession 来阻拦当前连接。

应用 ProtocolCodecFilter

ProtocolCodecFilter 用来在字撙节和消息工具之间相互转换。当该过滤器接管到字撙节的时刻,必要首先判断消息的界限,然后把表示一条消息的字节提掏出来,经由过程必然的逻辑转换成消息工具,再把消息工具以后通报,交给 I/O 处置惩罚器来履行营业逻辑。这个历程称为“解码”。与“解码”对应的是“编码”历程。在“编码”的时刻,过滤器接管到的是消息工具,经由过程与“解码”相反的逻辑,把消息工具转换成字节,并反向通报,交给 I/O 办事来履行 I/O 操作。

在“编码”和“解码”中的一个紧张问题是若何在字撙节中判断消息的界限。平日来说,有三种法子办理这个问题:

应用固定长度的消息。这种要领实现起来对照简单,只必要每次读取特定命量的字节即可。

应用固定长度的消息头来指明消息主体的长度。比如每个消息开始的 4 个字节的值表示了后面紧跟的消息主体的长度。只必要首先读取该长度,再读取指定命量的字节即可。

应用分隔符。消息之间经由过程特定模式的分隔符来分隔。每次只要碰到该模式的字节,就表示到了一个消息的末端。

详细到示例利用来说,客户端和办事器之间的通信协议对照繁杂,有不合种类的消息。每种消息的款式都不相同,同类消息的内容也不尽相同。是以,应用固定长度的消息头来指明消息主体的长度就成了最好的选择。

示例利用中的每种消息主体由两部分组成,第一部分是固定长度的消息类又名称,第二部分是每种消息的主体内容。图 4 中给出了示例利用中一条完备的消息的布局。

图 4. 示例利用中消息的布局

AbstractTetrisCommand用来描述联机游戏示例利用中的消息。它是一个抽象类,是所有详细消息的基类。其详细实现如 清单 5 所示。

清单 5. 联机游戏示例利用中的消息 AbstractTetrisCommand

public abstract class AbstractTetrisCommand implements TetrisCommand {

public abstract String getName();

public abstract byte[] bodyToBytes() throws Exception;

public abstract void bodyFromBytes(byte[] bytes) throws Exception;

public byte[] toBytes() throws Exception {

byte[] body = bodyToBytes();

int commandNameLength = Constants.COMMAND_NAME_LENGTH;

int len = commandNameLength + body.length;

byte[] bytes = new byte[len];

String name = StringUtils.rightPad(getName(), commandNameLength,

Constants.COMMAND_NAME_PAD_CHAR);

name = name.substring(0, commandNameLength);

System.arraycopy(name.getBytes(), 0, bytes, 0, commandNameLength);

System.arraycopy(body, 0, bytes, commandNameLength, body.length);

return bytes;

}

}

如 清单 5 所示,AbstractTetrisCommand 中定义了 3 个抽象措施:getName、bodyToBytes 和 bodyFromBytes,分手用来获取消息的名称、把消息的主体转换成字节数组和从字节数组中构建消息。bodyToBytes对应于前面提到的“编码”历程,而 bodyFromBytes对应于“解码”历程。每种详细的消息都应该实现这 3 个措施。AbstractTetrisCommand 中的措施 toBytes 封装了把消息的主体转换成字节数组的逻辑,在字节数组中,首先是长度固定为 Constants.COMMAND_NAME_LENGTH的消息类又名称,紧接着是每种消息特定的主体内容,由 bodyToBytes 措施来天生。

在先容完示例利用中的消息钱式之后,下面将评论争论详细的“编码”和“解码”历程。“编码”历程由编码器来完成,编码器必要实现 org.apache.mina.filter.codec.ProtocolEncoder 接口,一样平常来说承袭自 org.apache.mina.filter.codec.ProtocolEncoderAdapter 并覆写所需的措施即可。清单 6 中给出了示例利用中消息编码器 CommandEncoder 的实现。

清单 6. 联机游戏示例利用中消息编码器 CommandEncoder

public class CommandEncoder extends ProtocolEncoderAdapter {

public void encode(IoSession session, Object message,

ProtocolEncoderOutput out) throws Exception {

AbstractTetrisCommand command = (AbstractTetrisCommand) message;

byte[] bytes = command.toBytes();

IoBuffer buf = IoBuffer.allocate(bytes.length, false);

buf.setAutoExpand(true);

buf.putInt(bytes.length);

buf.put(bytes);

buf.flip();

out.write(buf);

}

}

在 清单 6 中,encode 措施封装了编码的逻辑。因为 AbstractTetrisCommand的 toBytes已经完成了到字节数组的转换,encode 措施直接应用即可。首先写入消息主体字节数组的长度,再是字节数组本身,就完成了编码的历程。

与编码历程比拟,解码历程要相对繁杂一些。详细的实现如 清单 7 所示。

清单 7. 联机游戏示例利用中消息解码器 CommandDecoder

public class CommandDecoder extends CumulativeProtocolDecoder {

protected boolean doDecode(IoSession session, IoBuffer in,

ProtocolDecoderOutput out) throws Exception {

if (in.prefixedDataAvailable(4, Constants.MAX_COMMAND_LENGTH)) {

int length = in.getInt();

byte[] bytes = new byte[length];

in.get(bytes);

int commandNameLength = Constants.COMMAND_NAME_LENGTH;

byte[] cmdNameBytes = new byte[commandNameLength];

System.arraycopy(bytes, 0, cmdNameBytes, 0, commandNameLength);

String cmdName = StringUtils.trim(new String(cmdNameBytes));

AbstractTetrisCommand command = TetrisCommandFactory

.newCommand(cmdName);

if (command != null) {

byte[] cmdBodyBytes = new byte[length - commandNameLength];

System.arraycopy(bytes, commandNameLength, cmdBodyBytes, 0,

length - commandNameLength);

command.bodyFromBytes(cmdBodyBytes);

out.write(command);

}

return true;

} else {

return false;

}

}

}

在 清单 7 中可以看到,解码器 CommandDecoder 承袭自 CumulativeProtocolDecoder。这是 Apache MINA 供给的一个赞助类,它会自动缓存所有已经接管到的数据,直到编码器觉得可以开始进行编码。这样在实现自己的编码器的时刻,就只必要斟酌若何判断消息的界限即可。假如一条消息的后续数据还没有接管到,CumulativeProtocolDecoder会自动进行缓存。在之条件到过,解码历程的一个紧张问题是判断消息的界限。对付固定长度的消息来说,只必要应用 Apache MINA 的 IoBuffer的 remaining措施来判断当前缓存中的字节数目,假如大年夜于消息长度的话,就进行解码;对付应用固定长度消息头来指明消息主体的长度的环境,IoBuffer供给了 prefixedDataAvailable措施来满意这一需求。prefixedDataAvailable会反省当前缓存中是否有固定长度的消息头,并且由此消息头指定长度的消息主体是否已经整个在缓存中。假如这两个前提都满意的话,阐明一条完备的消息已经接管到,可以进行解码了。解码的历程本身并不繁杂,首先读取消息的类又名称,然后经由过程 TetrisCommandFactory.newCommand措施来天生一个该类消息的实例,接着经由过程该实例的 bodyFromBytes措施就可以从字节数组中规复消息的内容,获得一个完备的消息工具。每次成功解码一个消息工具,必要调用 ProtocolDecoderOutput的 write把此消息工具以后通报。消息工具会经由过程过滤器链,终极达到 I/O 处置惩罚器,在 IoHandler.messageReceived中接管到此消息工具。假如当前缓存的数据不够以用来解码一条消息的话,doDecode只必要返回 false即可。接管到新的数据之后,doDecode会被再次调用。

过滤器链

过滤器只有在添加到过滤器链中的时刻才起感化。过滤器链是过滤器的容器。过滤器链与 I/O 会话是逐一对应的关系。org.apache.mina.core.filterchain.IoFilterChain是 Apache MINA 中过滤器链的接口,此中供给了一系列措施对此中包孕的过滤器进行操作,包括查询、添加、删除和调换等。如 表 5 所示。

表 5. IoFilterChain 接口的措施

措施

阐明

addFirst(String name, IoFilter filter)

将指定名称的过滤器添加到过滤器链的开首。

addLast(String name, IoFilter filter)

将指定名称的过滤器添加到过滤器链的末端。

contains(String name)

判断过滤器链中是否包孕指定名称的过滤器。

get(String name)

从过滤器链中获取指定名称的过滤器。

remove(String name)

从过滤器链中删除指定名称的过滤器。

replace(String name, IoFilter newFilter)

用过滤器 newFilter调换掉落过滤器链中名为 name的过滤器。

getSession()

获取与过滤器链逐一对应的 I/O 会话。

在先容完 I/O 过滤器和过滤器链之后,下面先容 I/O 处置惩罚器。

I/O 处置惩罚器

I/O 事故经由过程过滤器链之后会到达 I/O 处置惩罚器。I/O 处置惩罚器中与 I/O 事故对应的措施会被调用。Apache MINA 中 org.apache.mina.core.service.IoHandler是 I/O 处置惩罚器要实现的接口,一样平常环境下,只必要承袭自 org.apache.mina.core.service.IoHandlerAdapter并覆写所需措施即可。IoHandler接口的措施如 表 6 所示。

表 6. IoHandler 接口的措施

措施

阐明

sessionCreated(IoSession session)

当有新的连接建立的时刻,该措施被调用。

sessionOpened(IoSession session)

当有新的连接打开的时刻,该措施被调用。该措施在 sessionCreated之后被调用。

sessionClosed(IoSession session)

当连接被关闭的时刻,此措施被调用。

sessionIdle(IoSession session, IdleStatus status)

当连接变成闲置状态的时刻,此措施被调用。

exceptionCaught(IoSession session, Throwable cause)

当 I/O 处置惩罚器的实现或是 Apache MINA 中有非常抛出的时刻,此措施被调用。

messageReceived(IoSession session, Object message)

当接管到新的消息的时刻,此措施被调用。

messageSent(IoSession session, Object message)

当消息被成功发送出去的时刻,此措施被调用。

对付 表 6 中的措施,有几个必要重点的阐明一下。首先是 sessionCreated 和 sessionOpened 的差别。sessionCreated措施是由 I/O 处置惩罚线程来调用的,而 sessionOpened 是由其它线程来调用的。是以从机能方面斟酌,不要在 sessionCreated 措施中履行过多的操作。对付 sessionIdle,默认环境下,闲置光阴设置是禁用的,也便是说 sessionIdle 并不会被调用。可以经由过程 IoSessionConfig.setIdleTime(IdleStatus, int) 来进行设置。

Apache MINA 中的基础观点已经先容完了,下面先容状态机的应用。

应用状态机

在 I/O 处置惩罚器中实现营业逻辑的时刻,对付简单的环境,一样平常只必要在 messageReceived 措施中对传入的消息进行处置惩罚。假如必要写回数据到对等体的话,用 IoSession.write 措施即可。在别的的一些环境下,客户端和办事器真个通信协议对照繁杂,客户端着实是有状态变迁的。这个时刻可以用 Apache MINA 供给的状态机实现,可以使得 I/O 处置惩罚器的实现加倍简单。

状态机中两个紧张的元素是状态以及状态之间的迁移。示例利用中客户真个状态以及迁移如 图 5 所示。

图 5. 联机游戏示例利用中客户真个状态以及迁移

客户端初始化的时刻,其状态为“未连接”,表示客户端还没有在办事器上面注册,此时还不能进行游戏;接着用户必要输入一个昵称来注册到办事器上面,完成之后状态迁移到“闲置”。此时客户端会接管到当前在线的所有其它用户的列表。当前用户可以约请其它用户和他一块游戏,也可以接管来自其它用户的约请。约请发送出去之后,客户真个状态迁移到“约请已发送”。假如吸收了其它用户的约请,客户真个状态迁移到“约请已接管”。假如某个用户的约请被别的一个用户吸收的话,两个客户真个状态都邑迁移到“游戏中”。

要实现这样较为繁杂的状态机的话,只必要在 I/O 处置惩罚器中以声明式的要领定义状态和迁移前提就可以了。首先必要声明状态机中状态,如 清单 8 所示。

清单 8. 联机游戏示例利用中的状态声明

@State public static final String ROOT = "Root";

@State(ROOT) public static final String NOT_CONNECTED = "NotConnected";

@State(ROOT) public static final String IDLE = "Idle";

@State(ROOT) public static final String INVITATION_SENT = "InvitationSent";

@State(ROOT) public static final String INVITATION_ACCEPTED = "InvitationAccepted";

@State(ROOT) public static final String PLAYING = "Playing";

如 清单 8 所示,上面定义了一共六个状态。经由过程标注 @State就声清楚明了一个状态。必要留意的是状态之间是可以承袭的。假如状态机接管到一个事故的时刻,在当前状态中找不到对应的迁移,就会在其父状态上继承查找。状态的承袭在某些环境下是很有用的,比如盼望为所有的状态都增添同样的迁移逻辑,就可以直接把迁移前提添加在父状态上面。一个范例的场景便是差错处置惩罚,一样平常来说,所有的状态都必要差错处置惩罚,而差错处置惩罚的逻辑一样平常都是相同的。把发生差错时刻的迁移放在父状态中,可以简洁的描述这一场景。

定义了状态之后,下面应该声明状态之间的迁移。如 清单 9 所示。

清单 9. 联机游戏示例利用中的状态迁移声明

@IoHandlerTransition(on = MESSAGE_RECEIVED, in = NOT_CONNECTED, next = IDLE)

public void login(TetrisServerContext context, IoSession session, LoginCommand cmd) {

String nickName = cmd.getNickName();

context.nickName = nickName;

session.setAttribute("nickname", nickName);

session.setAttribute("status", UserStatus.IDLE);

sessions.add(session);

users.add(nickName);

RefreshPlayersListCommand command = createRefreshPlayersListCommand();

broadcast(command);

}

@IoHandlerTransition(on = EXCEPTION_CAUGHT, in = ROOT, weight = 10)

public void exceptionCaught(IoSession session, Exception e) {

LOGGER.warn("Unexpected error.", e);

session.close(true);

}

@IoHandlerTransition(in = ROOT, weight = 100)

public void unhandledEvent() {

LOGGER.warn("Unhandled event.");

}

清单 9 中,应用标注 @IoHandlerTransition声清楚明了一个状态迁移。每个状态迁移可以有四个属性:on、in、next和 weight,此中属性 in是必须的,另外是可选的。属性 on表示触发此状态迁移的事故名称,假如省略该属性的话,则默觉得匹配所有事故的通配符。该属性的值可所以表中给出的 I/O 处置惩罚器中能处置惩罚的七种事故类型。属性 in表示状态迁移的肇端状态。属性 next表示状态迁移的停止状态,假如省略该属性的话,则默觉得表示当前状态 的 _self_。属性 weight用来指明状态迁移的权重。一个状态的所有迁移是按照其权重升序排列的。对付当前状态,假如有多个可能的迁移,排序靠前的迁移将会发生。代码中的第一个标注声清楚明了假如当前状态是“未连接”,并且接管到了 MESSAGE_RECEIVED事故,而且消息的内容是一个 LoginCommand工具的话,login措施会被调用,调用完成之后,当前状态迁移到“闲置”。第二个标注声清楚明了对付任何的状态,假如接管到了 EXCEPTION_CAUGHT事故,exceptionCaught措施会被调用。着末一个标注声清楚明了一个状态迁移,其肇端状态是 ROOT,表示该迁移对所有的事故都起感化。不过它的 weight是 100,优先级对照低。该状态迁移的感化是处置惩罚其它没有对应状态迁移的事故。

应用了 Apache MINA 供给的状态机之后,创建 I/O 处置惩罚器的要领也发生了变更。I/O 处置惩罚器的实例由状态机来创建,如 清单 10 所示。

清单 10. 在状态机中创建 I/O 处置惩罚器

private static IoHandler createIoHandler() {

StateMachine sm = StateMachineFactory.getInstance(

IoHandlerTransition.class).create(ServerHandler.NOT_CONNECTED,

new ServerHandler());

return new StateMachineProxyBuilder().setStateContextLookup(

new IoSessionStateContextLookup(new StateContextFactory() {

public StateContext create() {

return new ServerHandler.TetrisServerContext();

}

})).create(IoHandler.class, sm);

}

在 清单 10 中,TetrisServerContext是供给给状态机的高低文工具,用来在状态之间共享数据。当然用 IoSession也是可以实现的,不过高低文工具的好处是类型安然,不必要做额外的类型转换。

在先容完状态机后,下面先容一些高档话题,包括异步操作以及 Apache MINA 与 JMX 和 Spring 的集成。

高档话题

在前面章节中先容了 Apache MINA 的基础观点和详细用法,下面评论争论一些高档话题。

异步操作

Apache MINA 中的很多操作都是异步的,比如连接的建立、连接的关闭、还稀有据的发送等。在编写收集利用的时刻,必要斟酌这一点。比如 IoConnector的 connect措施,其返回值是 org.apache.mina.core.future.ConnectFuture类的工具。经由过程此工具,可以查询连接操作的状态。清单 3 中已经应用了 ConnectFuture。别的一个常用的是发送数据时应用的 org.apache.mina.core.future.WriteFuture,如 清单 11 所示。

清单 11. WriteFuture 的应用

IoSession session = ...; // 获取 I/O 会话工具

WriteFuture future = session.write("Hello World"); // 发送数据

future.awaitUninterruptibly(); // 等待发送数据操作完成

if(future.isWritten())

{

// 数据已经被成功发送

}

else

{

// 数据发送掉败

}

因为这样的需求很常见,I/O 处置惩罚器中供给了 messageSent措施,当数据发送成功的时刻,该措施会被调用。

JMX 集成

Apache MINA 可以集成 JMX 来对收集利用进行治理和监测。下面经由过程对前面给出的谋略器办事进行简单改动,来阐明若何集成 JMX。所需的改动如 清单 12 所示。

清单 12. Apache MINA 与 JMX 的集成

MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();

IoAcceptor acceptor = new NioSocketAcceptor();

IoServiceMBean acceptorMBean = new IoServiceMBean(acceptor);

ObjectName acceptorName = new ObjectName(acceptor.getClass()

.getPackage().getName()

+ ":type=acceptor,name=" + acceptor.getClass().getSimpleName());

mBeanServer.registerMBean(acceptorMBean, acceptorName);

如 清单 12 所示,首先获取平台供给的受控 bean 的办事器,接着创建受控 bean(MBean)来包装想要治理和监测的工具,这里应用的是 I/O 连接器工具。着末把创建出来的受控 bean 注册到办事器即可。

在启动谋略器办事利用的时刻,添加下面的启动参数:-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=8084 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false,就启用了 JMX。接着经由过程 JVM 供给的“Java 监视和治理节制台”(运行 jconsole)就可以连接到此利用进行治理和监测了。监测的结果如 图 6 所示。

图 6. 经由过程“Java 监视和治理节制台”治理和监测基于 Apache MINA 的利用

Spring 集成

Apache MINA 可以和盛行的开源框架 Spring 进行集成,由 Spring 来治理 Apache MINA 中的工具。与 Spring 集成的要领也对照简单,只必要编写响应的 Spring 设置设置设备摆设摆设文件即可。清单 13 中给出了与 Spring 集成之后的谋略器办事的设置设置设备摆设摆设文件。

清单 13. Apache MINA 与 Spring 集成的设置设置设备摆设摆设文件

清单 13 中创建 I/O 处置惩罚器和 I/O 过滤器的要领很直接。因为不能直接从 I/O 吸收器获取过滤器链,这里创建了一个 org.apache.mina.core.filterchain.DefaultIoFilterChainBuilder类的 bean,用来构建过滤器链。由 Apache MINA 供给的收集地址编辑器 org.apache.mina.integration.beans.InetSocketAddressEditor容许以“主机名 : 端口”的形式指定收集地址。在声明 I/O 吸收器的时刻,经由过程 init-method指清楚明了当 I/O 吸收器创建成功之后,调用其 bind措施来吸收连接;经由过程 destroy-method声清楚明了当其被销毁的时刻,调用其 unbind来竣事监听。

总结

Apache MINA 是一个很好的收集利用框架,它经由过程事故驱动的 API 为开拓收集利用供给了优越的架构根基,同时也供给了富厚的 I/O 办事和 I/O 过滤器的实现,使得开拓收集利用变得简单。本文具体先容了 Apache MINA 中的基础观点,包括 I/O 办事、I/O 会话、I/O 过滤器和 I/O 处置惩罚器等,同时先容了若何使用状态机来实现逻辑繁杂的 I/O 处置惩罚器。除此之外,还评论争论了 Apache MINA 若何与 JMX 和 Spring 进行集成。本文供给了一个简单的谋略器办事和繁杂的俄罗斯方块联机游戏作为示例,可以赞助读者更好的掌握基于 Apache MINA 的收集利用开拓。

下载

描述

名字

大年夜小

下载措施

谋略器办事示例源代码1

CalculatorMina.zip

7 KB

HTTP

关于下载措施的信息

留意:

源代码中包孕了与 JMX 和 Spring 集成的代码,运行必要 Apache Maven。

得到产品和技巧

下载 下载 Apache MINA 2 的最新版本。

关于作者

成富任职于 IBM 中国软件开拓中间,今朝在 Lotus 部门从事 IBM Mashup Center 的开拓事情。他卒业于北京大年夜学信息科学技巧学院,得到谋略机软件与理论专业硕士学位。他的小我网站是 http://www.cheng-fu.com。

您可能还会对下面的文章感兴趣: