MINA 基本类的描述在介绍架构之前先认识几个接口:
IoAccepter
相当于网络应用程序中的服务器端
IoConnector
相当于客户端
IoSession
当前客户端到服务器端的一个连接实例。此会话将一直保持连接,除非网络断开或用户主动断开连接(session.close())
IoHandler
业务处理逻辑
IoFilter
过滤器用于悬接通讯层接口与业务层接口
MINA 的基础架构下图是 MINA 的架构图,
图中的模块链中,IoService 便是应用程序的入口,相当于我们前面代码中的 IoAccepter,IoAccepter 便是 IoService 的一个扩展接口。IoService 接口可以用来添加多个 IoFilter,这些 IoFilter 符合责任链模式并由 IoProcessor 线程负责调用。而 IoAccepter 在 ioService 接口的基础上还提供绑定某个通讯端口以及取消绑定的接口。在上面的例子中,我们是这样使用 IoAccepter 的
IoAcceptor acceptor = new SocketAcceptor();
相当于我们使用了 Socket 通讯方式作为服务的接入,当前版本的 MINA 还提供了除 SocketAccepter 外的基于数据报文通讯的 DatagramAccepter 以及基于管道通讯的 VmPipeAccepter。另外还包括串口通讯接入方式,目前基于串口通讯的接入方式已经在最新测试版的 MINA 中提供。你也可以自行实现 IoService 接口来使用自己的通讯方式。
而在上图中最右端也就是 IoHandler,这便是业务处理模块。相当于前面例子中的 HelloHandler 类。在业务处理类中不需要去关心实际的通讯细节,只管处理客户端传输过来的信息即可。编写 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)
//当连接后打开时触发此方法,一般此方法与 sessionCreated 会被同时触发
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加密)等。
public class MinaTimeServer {
private static final int PORT = 9123;
public static void main(String[] args) throws IOException {
//Mina包装了J2SE中的ByteBuffer类,用法也一样
ByteBuffer.setUseDirectBuffers(false);
//设置缓存策略
ByteBuffer.setAllocator(new SimpleByteBufferAllocator());
//创建一个SocketAcceptor,用于在服务器端监听客户端的连接.
IoAcceptor acceptor = new SocketAcceptor();
//创建一个与SocketAcceptor相关联的配置对象.
SocketAcceptorConfig cfg = new SocketAcceptorConfig();
cfg.getSessionConfig().setReuseAddress( true );
cfg.getFilterChain().addLast( "logger", new LoggingFilter() );
cfg.getFilterChain().addLast( "codec", new ProtocolCodecFilter( new TextLineCodecFactory( Charset.forName( "UTF-8" ))));
//绑定端口,处理对象和配置对象.
acceptor.bind( new InetSocketAddress(PORT), new TimeServerHandler(), cfg);
System.out.println("MINA Time server started.");
}
}
下面我们编写一个处理连接的类 TimeServerHandler.java
public class TimeServerHandler extends IoHandlerAdapter {
//出现异常的时候调用.
public void exceptionCaught(IoSession session, Throwable t) throws Exception {
t.printStackTrace();
session.close();
}
//接收客户端新的消息的时候调用.
public void messageReceived(IoSession session, Object msg) throws Exception {
String str = msg.toString();
if( str.trim().equalsIgnoreCase("quit") ) {
session.close();
return;
}
Date date = new Date();
session.write( date.toString() );
System.out.println("Message written");
}
//当一个客户端连接到服务器的时候被调用.
public void sessionCreated(IoSession session) throws Exception {
System.out.println("Session created");
if( session.getTransportType() == TransportType.SOCKET )
((SocketSessionConfig) session.getConfig() ).setReceiveBufferSize( 2048 );
session.setIdleTime( IdleStatus.BOTH_IDLE, 10 );
}
}
输入命令 telnet 127.0.0.1 9123,按回车将显示服务器的时间信息.
输入 quit 将断开连接.
下面看一个示例:
具体说明:
import java.util.logging.Level;
import org.apache.mina.common.ByteBuffer;
import org.apache.mina.common.IdleStatus;
import org.apache.mina.common.SessionConfig;
import org.apache.mina.io.IoHandlerAdapter;
import org.apache.mina.io.IoSession;
import org.apache.mina.io.socket.SocketSessionConfig;
import org.apache.mina.util.SessionLog;
public class AppProtocolHandler extends IoHandlerAdapter {
private boolean isLogin;
public AppProtocolHandler() {
}
public void dataRead(IoSession session, ByteBuffer buffer) throws Exception {
//当有数据读入时此方法被调用,数据封装在ByteBuffer中
if(!isLogin){
//如果用户没有登陆过则检查用户的合法性
// TODO 检查用户的合法性,如果不合法者放回错误代码给客户端或直接关闭连接\n
//如果合法则置isLogin=true
session.close();
}else{
// TODO 如果用户已经登陆成功则读取消息并返回应答给客户端
}
}
public void dataWritten(IoSession session, Object mark) throws Exception {
//当数据被写入通道时此方法被调用
SessionLog.log(Level.INFO,session,mark.toString());//必要时打印所写入的内容,mark的内容就是session.write(session,mark)中的第二个参数
}
public void exceptionCaught(IoSession session, Throwable arg1)
throws Exception {
// 当出现网络异常时此方法被调用,这里要注意如果客户端要保持与服务器端的连接时不要在这里马上重新连接不然会抛出CancelKeyException运 行期异常直接导致程序死掉(特别是与服务器端有超过两个连接时一定会发生并且此异常无法捕获),建议的方法是启动一个单独的线程来完成与服务器端的重新连 接
session.close();
}
public void sessionClosed(IoSession session) throws Exception {
//当网络连接被关闭是此方法被调用
SessionLog.log(Level.INFO,session,"Close a Session");//必要时打印出信息
}
public void sessionCreated(IoSession session) throws Exception {
//当网络连接被创建时此方法被调用(这个肯定在sessionOpened(IoSession session)方法被调用),这里可以对Socket设置一些网络参数
SessionConfig cfg = session.getConfig();
if (cfg instanceof SocketSessionConfig) {
((SocketSessionConfig) cfg).setSessionReceiveBufferSize(2048);
((SocketSessionConfig) cfg).setKeepAlive(true);
((SocketSessionConfig) cfg).setSoLinger(true, 0);
((SocketSessionConfig) cfg).setTcpNoDelay(true);
((SocketSessionConfig) cfg).setWriteTimeout(1000 * 5);
}
}
public void sessionIdle(IoSession arg0, IdleStatus arg1) throws Exception {
// 当网络通道空闲时此方法被调用,在这里可以判断是读空闲、写空闲还是两个都空闲,以便做出正确的处理
一般的网络通讯程序都要与服务器端保持长连接,所以这里可以发一下网络测试数据以保持与服务器端的连接
}
public void sessionOpened(IoSession session) throws Exception {
//当网络连接被打开时此方法被调用,这里可以对session设置一些参数或者添加一些IoFilter的实现,也可以对客户端做一些认证之类的工作
session.getConfig().setIdleTime(IdleStatus.BOTH_IDLE, 60);
}
}