最近由于项目本身的需要,正在进行Mina框架的学习,并且将其整合到正在开发的系统中。下面将会根据实际的工作情况分享一些心得感受。
一、 项目需求:
我们正在开发的系统,现在主要分为两个部分,正两个部分之间需要使用TCP Socket进行网络通讯。具体开发的难点是发送消息的部分。由于需要考虑到每次创建连接时造成的系统开销,所以使用的连接方式必须是长连接,就是保存连接,不能断开。而且在连接的另一端发生当机的情况下能够及时回复,不会就此丢掉和这一端的通讯。在连接发送消息后,能够判定另一端无法及时收到消息的情况,并且做出正确处理。
综上所述,能够整理出如下三条需求:
l 两端的连接通讯必须是长连接,不能每次重新建立。
l 在连接断开的情况下能够及时处理,并能有效恢复。
l 发送数据需要有超时机制。
我们这一阶段的Mina框架的使用便是围绕着这三条需求展开的。
二、 Mina框架相关知识简介:
在正式开始Mina框架的实际应用前,先简单介绍一些Mina的基本知识,以便于下面的实用场景分析。中间会穿插架构图和示例代码。
在介绍架构之前先认识几个接口:
IoAccepter 相当于网络应用程序中的服务器端
IoConnector 相当于客户端
IoSession 当前客户端到服务器端的一个连接实例
IoHandler 业务处理逻辑
IoFilter 过滤器用于悬接通讯层接口与业务层接口
然后可以看一下Mina的架构图,如图2-1Mina框架图所示。
在图中的模块链中,IoService便是应用程序的入口,相当于基本接口中的IoAccepter,IoAccepter便是IoService的一个扩展接口。IoService接口可以用来添加多个IoFilter,这些IoFilter符合责任链模式并由IoProcessor线程负责调用。而IoAccepter在ioService接口的基础上还提供绑定某个通讯端口以及取消绑定的接口。在日常应用中,我们可以这样使用IoAccepter:
IoAcceptor acceptor = new SocketAcceptor(); |
相当于我们使用了 Socket 通讯方式作为服务的接入,当前版本的 Mina 还提供了除SocketAccepter外的基于数据报文通讯的DatagramAccepter以及基于管道通讯的VmPipeAccepter。另外还包括串口通讯接入方式,目前基于串口通讯的接入方式已经在最新测试版的MINA中提供。我们也可以自行实现IoService接口来使用自己的通讯方式。
而在上图中最右端也就是IoHandler,这便是业务处理模块。我们的项目大部分的工作也就是在这个接口的实现类中完成。在业务处理类中不需要去关心实际的通讯细节,只管处理客户端传输过来的信息即可。编写Handler类就是使用Mina开发网络应用程序的重心所在,相当于Mina已经帮你处理了所有的通讯方面的细节问题。为了简化Handler类,MINA提供了IoHandlerAdapter类,此类仅仅是实现了IoHandler接口,但并不做任何处理。
一个IoHandler接口中具有如下一些方法(摘自Mina的API文档):
void exceptionCaught(IoSession session, Throwable cause) |
void messageReceived(IoSession session, Object message) |
void messageSent(IoSession session, Object message) |
void sessionClosed(IoSession session) |
void sessionCreated(IoSession session) |
void sessionIdle(IoSession session, IdleStatus status) |
void sessionOpened(IoSession session) |
前面我们提到IoService是负责底层通讯接入,而IoHandler是负责业务处理的。那么Mina架构图中的IoFilter作何用途呢?答案是我们想作何用途都可以。但是有一个用途却是必须的,那就是作为IoService和IoHandler之间的桥梁。IoHandler接口中最重要的一个方法是messageReceived,这个方法的第二个参数是一个Object型的消息,众所周知,Object是所有Java对象的基础,那到底谁来决定这个消息到底是什么类型呢?答案也就在这个IoFilter中。在我们的应用中,我们添加了一个IoFilter是new ProtocolCodecFilter(new TextLineCodecFactory()),这个过滤器的作用是将来自客户端输入的信息转换成一行行的文本后传递给IoHandler,因此我们可以在messageReceived中直接将msg对象强制转换成String对象。
而如果我们不提供任何过滤器的话,那么在messageReceived方法中的第二个参数类型就是一个byte的缓冲区,对应的类是org.apache.mina.common.ByteBuffer。虽然你也可以将解析客户端信息放在IoHandler中来做,但这并不是推荐的做法,使原来清晰的模型又模糊起来,变得IoHandler不只是业务处理,还得充当协议解析的任务。
Mina自身带有一些常用的过滤器,例如LoggingFilter(日志记录)、BlackListFilter(黑名单过滤)、CompressionFilter(压缩)、SSLFilter(SSL加密)等。
在我们的项目中,主要的工作是在发送消息的部分,所以Mina框架的实现主要是围绕着IoHandler和IoSession进行展开。根据上面的讲解,在实际使用中,可以用下面的代码创建一个简单的用户发送消息的客户端。
SocketConnector connector = new SocketConnector();
IoFilter filter = new ProtocolCodecFilter(new TextLineCodecFactory());
connector.getFilterChain().addLast("audit", filter);
SocketAddress address = new InetSocketAddress(ip, port);
ConnectFuture future = connector.connect(address, new ClientHandler());
future.join();
if (!future.isConnected()) {
logger.error("不能建立网络连接。" + address);
return null;
}
session = future.getSession();
这样便可以使用session进行消息发送,方法是使用write方法,创建一个WriteFuture就可以将信息发送出去了。下面将结合我们的三个网络通讯的需求,在实际项目中分析Mina框架的使用。
三、 实用场景分析:
首先是长连接的问题,为了避免每次重复创建连接,就要对连接进行管理。每一个连接在Mina中的就是通过session进行体现的,换言之,就是将session管理起来。所以,我们索性就把网络通讯的部分,与项目的其他部分进行隔离,实现一个网络通讯层,在其中统一管理session。然后在通讯层中实现一个静态Map,Key是IP+Port,Value就是对应的session。然后在每次需要进行连接的时候,从这个Map中通过IP和Port获取对应的session。然后判断这个session是否被创建,或者是session是否被关闭。如果这个session有效,便直接进行使用。如果session无效,再重新创建这个session,然后放到Session Map中。如图3-1通讯层结构类图所示。
然后是连接断开和恢复的处理,在我们实现的Client Handler中,有一个可以被覆盖的方法,void exceptionCaught(IoSession session, Throwable cause)。这个方法在服务器端非正常当机的情况下可以捕获到异常,而且服务器端在线上环境下是不会进行主动连接断开的。所以异常情况便可以包括现有的连接断开情况。如果有这样的情况发生,就将这个session关闭到,然后再需要重新获取这个session的时候,便会判定这个session已经断开,这时会重新创建一个新的session,将Session Map中的元素覆盖掉。
最后是发送信息超时,这个是在每个session的写操作的时候处理,每个写操作在Mina框架中都是一个异步操作,本身程序是不会等待整个操作的结束的,因为这是根据性能上的考虑。但是如果我们需要知道发送消息是否超时,便可以在前期的简单实现过程中使用这种方式。首先为session设定一个写操作的超时时间,我们设置5秒钟,然后在每个写操作之后都使用join方法,等待异步操作结束。最后便可以判断写操作是否成功进行,这样就可以处理发送消息超时的问题。可以用下面的代码表示。
public void sessionCreated(IoSession session) throws Exception {
super.sessionCreated(session);
session.setWriteTimeout(5);
if (session.getTransportType() == TransportType.SOCKET) {
((SocketSessionConfig) session.getConfig()).setKeepAlive(true);
}
}
这个是在session被创建的时候,同时还有一项设置是,将Session的连接模式设置成长连接。这样连接就不会有超时中断的现象。
public static boolean setMessage(String ip, int port, String message) {
IoSession session = getSession(ip, port);
if (session != null) {
WriteFuture wf = session.write(message);
wf.join();
if (wf.isWritten()) {
return true;
}
}
return false;
}
这个是发送消息的部分,在发送消息后,判断消息是否成功发送,这样就可以做到处理发送消息超时的问题。
四、 后期优化:
在项目中,现在的Mina框架实现形式还是有许多需要改进,并优化的部分。比如说超时的处理和多线程的问题。
在发送超时的问题上,如果每次发送消息后,都进行异步操作的等待,那么在数据量十分庞大的情况下便会产生效率问题。根据Mina框架中存在的Future模式,可以使用listener来处理是否发送消息超时。可以在Session中添加专门用来处理消息发送超时的listener,然后在需要发送的消息上标注是否超时,如果超时进行重发,或者是其他操作。这样可以大大加快发送消息的速度,但是对于程序的复杂性,便会有很大的提升。由于现在正处在项目的初期是现阶段,可以不需要考虑这种复杂的模式。但是可以最为后期优化的内容。
至于多线程的问题,由于Mina框架本身就拥有线程池的功能,所以它是可以做到多线程的消息发送的。可是这需要消息缓冲区的配合,而且会造成现有系统整合上的冲突,所以,也不在目前情况的考虑范围内。但是,后期可以考虑实现这方面的功能。
PS: mina是日语,意思是大家。作者使用这个名字,可能是希望大家都来使用这个框架,并且通过这个网络框架,将大家联系在一起。