MINA 资料

一、基础框架

在这里插入图片描述

IoService

IoService相当于是Mina的Socket层,负责所有SocketIO事件的注册,select,分发等。它位于org.apache.mina.core.service包内,它有两个子接口,表示Server端接收方的IoAcceptorClient发起方的IoConnector,以及所有的实现类:
NioDatagramAcceptor/NioDatagramConnector:基于UDP的实现
NioSocketAcceptor/NioSocketConnector:基于TCP的实现
VmPipeAcceptor/VmPipeConnector:基于Pipe的实现

服务端初始化的方式一般是:

SocketAcceptor acceptor = new NioSocketAcceptor();  
acceptor.setReuseAddress(true);  
acceptor.setHandler(new EchoProtocolHandler());  
acceptor.bind(new InetSocketAddress(PORT));  

初始化NioSocketAcceptor的时候会做主要事情:
1.需要通过具体的实现提供的transportMetaData,来判断其中规定的session配置类(eg:DefaultSocketSessionConfig)是否来自接口SessionConfig.。

public NioSocketAcceptor() {  
        super(new DefaultSocketSessionConfig(), NioProcessor.class);  
        ((DefaultSocketSessionConfig) getSessionConfig()).init(this);  
 }  

2.默认启动了一个SimpleIoProcessorPool来包装NioProcessor.。

protected AbstractPollingIoAcceptor(IoSessionConfig sessionConfig, Class<? extends IoProcessor<T>> processorClass) {  
    this(sessionConfig, null, new SimpleIoProcessorPool<T>(processorClass), true);  
}  

而SimpleIoProcessorPool默认是启动CPU个数 +1个 NioProcess,并以数组形式管理。

private static final int DEFAULT_SIZE = Runtime.getRuntime() .availableProcessors() + 1;  
private final IoProcessor<T>[] pool;      
public SimpleIoProcessorPool(Class<? extends IoProcessor<T>> processorType) {  
        this(processorType, null, DEFAULT_SIZE);  
}  
public SimpleIoProcessorPool(Class<? extends IoProcessor<T>> processorType, Executor executor, int size) {  
        if (processorType == null) {  
            throw new NullPointerException("processorType");  
        }  
        if (size <= 0) {  
            throw new IllegalArgumentException("size: " + size  
                    + " (expected: positive integer)");  
        }  
  
        if (executor == null) {  
            this.executor = executor = Executors.newCachedThreadPool();  
            this.createdExecutor = true;  
        } else {  
            this.executor = executor;  
            this.createdExecutor = false;  
        }  
  
        pool = new IoProcessor[size];  
  
        boolean success = false;  
        Constructor<? extends IoProcessor<T>> processorConstructor = null;  
        boolean usesExecutorArg = true;  
        ....  
}  

在这里并没有指定Executor,因此用默认的Executors.newCachedThreadPool()。这个ThreadPool管理着NioSocketAcceptor和所有的IoProcessor处理线程。

在这里,NioSocketAcceptor可以理解为一个Server,而NioProcessor就是其中的并行的处理程序。NioProcessor默认个数是CPU个数+1,这正好是属于Processor的selector的个数,它们专门处理OP_READ/OP_WRITE事件。在NioSocketAcceptor里面也有个Selector,它是专门用作处理OP_ACCEPT事件.这个分离设计使OP_ACCEPT不被读写事件所影响。因此,采用默认设置,MINA会启动 CPU个数+2 Selector。

每个NioSocketAcceptor内部有个Acceptor线程对象,NioSocketAcceptor会使用传入的或者生成的Executor来执行这个Acceptor。
每个NioProcessor内部也有个Processor线程对象,NioProcessor会使用传入的或者生成的Executor来执行这个Processor。

3.IoService初始化时,建立与监听器容器IoServiceListenerSupport的双向关联,注册匿名内部实现serviceActivationListener到监听器容器。

客户端的启动方式:

扫描二维码关注公众号,回复: 4258104 查看本文章
NioSocketConnector connector = new NioSocketConnector();  
connector.getFilterChain().addLast( "logger", new LoggingFilter() );  
connector.getFilterChain().addLast( "codec", new ProtocolCodecFilter( new TextLineCodecFactory( Charset.forName( "UTF-8" ))));  
connector.setConnectTimeout(30);  
connector.setHandler(new TimeClientHandler());  
ConnectFuture cf = connector.connect(  
new InetSocketAddress("127.0.0.1", 8833));  

其基本过程跟NioSocketAcceptor差不多.。

IoFilter

IoFilter也是一个接口,有点像ServletFilter,在事件被 IoHandler处理之前或之后进行一些特定的操作,比如记录日志(LoggingFilter)、压缩数据(CompressionFilter),SSL加密(SSLFilter)黑名单过滤(BlackListFilter),心跳检测(KeepAliveFilter)等等。这些都是MINA自带的。
实际项目中,有个东西肯定是存在的,就是ProtocolCodecFilter或者跟它类似的filter.我们不可能让IoHandler直接去操作字节流,所以在这一层,一定要把二进制流转成java对象或者文本,这样才能不枉费MINA的良好分层设计。

ProtocolCodecFilter 使用ProtocolCodecFilter很简单,我们只要把ProtocolCodecFilter加入到FilterChain就可以了,但是我们需要提供一个ProtocolCodecFactory。其实ProtocolCodecFilter仅仅是实现了过滤器部分的功能,它会将最终的转换工作,交给从ProtocolCodecFactory获得的Encode和Decode。如果我们需要编写自己的ProtocolCodec,就应该从 ProtocolCodecFactory入手。MINA内置了几个ProtocolCodecFactory,比较常用的就是 ObjectSerializationCodecFactory和TextLineCodecFactory。

ObjectSerializationCodecFactory是Java Object序列化之后的内容直接跟ByteBuffer互相转化,比较适合两端都是Java的情况使用。在笔者所做的自定义协议环境下,在这里就需要重写ObjectSerializationCodecFactory。

TextLineCodecFactory就是 String跟ByteBuffer的转化,比如HTTP,RTSP这类纯文本协议。

IoFilter的顺序问题:IoFilter是有加入顺序的,例如,先加入LoggingFilter再加入ProtocolCodecFilter,和先加入ProtocolCodecFilter再加入LoggingFilter的效果是不一样的,前者 LoggingFilter写入日志的内容是ByteBuffer,而后者写入日志的是转换后具体的类,例如String。

ExecutorFilter
有一个比较重要的过滤器就是ExecutorFilter。前面已经知道,每个NioProcess对应的是一个Process内部线程对象,在Process的run方法里面会循环的调用process方法。也就是第一个事件没有执行完,第二个事件不会执行,如果某次消息处理太耗时,就会导致其他消息等待,整体的吞吐量下降。ExecutorFilter的的作用就是将同一个类型的消息合并起来按顺序调用。

public final void messageReceived(NextFilter nextFilter, IoSession session, Object message) {  
        if (eventTypes.contains(IoEventType.MESSAGE_RECEIVED)) {  
            IoFilterEvent event = new IoFilterEvent(nextFilter,  
                IoEventType.MESSAGE_RECEIVED, session, message);   
            fireEvent(event);  
        } else {  
            nextFilter.messageReceived(session, message);  
        }  
}  

从上面的代码可以看到,如果满足事件集合条件,就组装成IoFilterEvent交给Executor异步执行。这样在filter上就不会因为某个事件filter执行时间过长而block后面的事件。

protected void fireEvent(IoFilterEvent event) {  
        executor.execute(event);  
}  
public void fire() {  

        IoSession session = getSession();  
        NextFilter nextFilter = getNextFilter();  
        IoEventType type = getType();  
  
        if ( LOGGER.isDebugEnabled()) {  
            LOGGER.debug( "Firing a {} event for session {}",type, session.getId() );  
        }  
  
        switch (type) {  
        case MESSAGE_RECEIVED:  
            Object parameter = getParameter();  
            nextFilter.messageReceived(session, parameter);  
            break;  
        case MESSAGE_SENT:  
            WriteRequest writeRequest = (WriteRequest)getParameter();  
            nextFilter.messageSent(session, writeRequest);  
            break;  
        case WRITE:  
            writeRequest = (WriteRequest)getParameter();  
            nextFilter.filterWrite(session, writeRequest);  
            break;  
        case CLOSE:  
            nextFilter.filterClose(session);  
            break;  
        case EXCEPTION_CAUGHT:  
            Throwable throwable = (Throwable)getParameter();  
            nextFilter.exceptionCaught(session, throwable);  
            break;  
        case SESSION_IDLE:  
            nextFilter.sessionIdle(session, (IdleStatus) getParameter());  
            break;  
        case SESSION_OPENED:  
            nextFilter.sessionOpened(session);  
            break;  
        case SESSION_CREATED:  
            nextFilter.sessionCreated(session);  
            break;  
        case SESSION_CLOSED:  
            nextFilter.sessionClosed(session);  
            break;  
        default:  
            throw new IllegalArgumentException("Unknown event type: " + type);  
        }  
          
        if ( LOGGER.isDebugEnabled()) {  
            LOGGER.debug( "Event {} has been fired for session {}", type, session.getId() );  
        }  
}  

实际上MINA是用filterChain的方式顺序调用所有注册的filter。默认的DefaultIoFilterChain。

if (readBytes > 0) {  
	IoFilterChain filterChain = session.getFilterChain();  
	filterChain.fireMessageReceived(buf); 
}

然后就会一个filter接一个的传下去,直到最后的尾调用IoHandler。

IoHandler

IoHanlder则是所有事件最终产生响应的位置,一般用来处理业务比如分析数据,数据库操作等。

void sessionCreated(IoSession session) throws Exception;  
void sessionOpened(IoSession session) throws Exception;  
void sessionClosed(IoSession session) throws Exception;  
void sessionIdle(IoSession session, IdleStatus status) throws Exception;  
void exceptionCaught(IoSession session, Throwable cause) throws Exception;  
void messageReceived(IoSession session, Object message) throws Exception;  
void messageSent(IoSession session, Object message) throws Exception;  

messageReceived是接收客户端消息的事件,我们应该在这里实现业务逻辑。
messageSent是服务器发送消息的事件,一般情况下不会使用它。
sessionClosed是客户端断开连接的事件,可以在这里进行一些资源回收等操作。
sessionCreated和sessionOpened,两者稍有不同,sessionCreated是由I/O processor线程触发的,而sessionOpened在其后,由业务线程触发的,由于MINA的I/O processor线程非常少,因此如果我们真的需要使用sessionCreated,也必须是耗时短的操作,一般情况下,我们应该把业务初始化的功能放在sessionOpened事件中。

IoSession

IoSession是一个接口,它提供了对当前连接的操作功能,还有用户定义属性的存储功能,就像HttpSession。一个IoSession就代表一个Client与Server的连接。IoSession是线程安全的,第一层子类AbstractIoSession内部用到了大量的lock机制,因此可以放心的使用而不用担心并发问题。常用的方法:

//连接操作
WriteFuture write(Object message)  
CloseFuture close();  
  
//属性相关操作   
Object getAttribute(String key);  
Object setAttribute(String key, Object value);  
Object removeAttribute(String key);  
Set getAttributeKeys();  

//连接状态操作  
boolean isConnected();  
boolean isClosing();  
SocketAddress getRemoteAddress();  
boolean isIdle(IdleStatus status);  

主要方法可以分为三类:
连接操作
最主要的方法有两个,向客户端发送消息和断开连接。可以看的出,write接受的变量是一个Object,实际传入的类型应该是最后一个filter处理后的结果。最初始的状态下,message是一个IoBuffer。

IoBuffer buf = IoBuffer.allocate(config.getReadBufferSize());
IoFilterChain filterChain = session.getFilterChain();  
filterChain.fireMessageReceived(buf);  

另外注意的是,write返回的WriteFuture类,这样调用IoSession.write方法是不会阻塞的。调用了write方法之后,消息内容会发到底层等候发送,至于什么时候发出,就看线程的调度处理了。非常典型的Future模式的应用。
如果需要确认消息是否成功的发送出去了,只需要wait一下,然后检查下future的状态。

WriteFuture future = session.write(xx);  
future.awaitUninterruptibly();  
boolean isCompleted = future.isWritten()  

当调用write的时候,会通过跟read相反的次序依次传递,直至由IoService负责把数据发送给客户端.
如果在很短的时间里,对同一个IoSession进行了两次write操作,客户端有可能只收到一条消息,而这条消息就是服务器发出的两条消息前后接起来。这样的设计可以在高并发的时候节省网络开销。

属性存储操作
一般用于状态维护。参考HttpSession的作用。跟Attribute相关的4个方法都是跟这个相关的。

连接状态
最后面的4个方法全是连接属性的查询。

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

基本介绍

Apache MINA 2是一个开发高性能和高可伸缩性网络应用程序的网络应用框架。它提供了一个抽象的事件驱动的异步API,可以使用TCP/IP、UDP/IP、串口和虚拟机内部的管道等传输方式。Apache MINA 2可以作为开发网络应用程序的一个良好基础。

Mina 的API 将真正的网络通信与我们的应用程序隔离开来,你只需要关心你要发送、接收的数据以及你的业务逻辑即可。

Mina的基本架构:
在图中的模块链中,IoService 便是应用程序的入口,相当于我们前面代码中的 IoAccepter,IoAccepter 便是 IoService 的一个扩展接口。IoService 接口可以用来添加多个 IoFilter,这些 IoFilter 符合责任链模式并由 IoProcessor 线程负责调用。而 IoAccepter 在 ioService 接口的基础上还提供绑定某个通讯端口以及取消绑定的接口。ioHandler则为应用逻辑处理类。

主要类以及接口:

(1) IoService:这个接口在一个线程上负责套接字的建立,拥有自己的Selector,监听是否有连接被建立。

(2) IoProcessor:这个接口在另一个线程上负责检查是否有数据在通道上读写,也就是说它也拥有自己的Selector,这是与我们使用JAVA NIO编码时的一个不同之处,通常在JAVA NIO编码中,我们都是使用一个Selector,也就是不区分IoService与IoProcessor两个功能接口。另外,IoProcessor负责调用注册在IoService上的过滤器,并在过滤器链之后调用IoHandler。

(3) IoFilter:这个接口定义一组拦截器,这些拦截器可以包括日志输出、黑名单过滤、数据的编码(write方向)与解码(read方向)等功能,其中数据的encode与decode是最为重要的、也是你在使用Mina时最主要关注的地方。

(4) IoHandler:这个接口负责编写业务逻辑,也就是接收、发送数据的地方。

(5) IoSession:Session可以理解为服务器与客户端的特定连接,该连接由服务器地址、端口以及客户端地址、端口来决定。客户端发起请求时,指定服务器地址和端口,客户端也会指定或者根据网络路由信息自动指定一个地址、自动分配一个端口。这个地址、端口对构成一个Session。Session是服务器端对这种连接的抽象,MINA对其进行了封装,定义了IoSession接口,用来代表客户端与服务器的连接,在服务器端来指代客户端,实现对客户端的操作、绑定与客户端有关的信息与对象。通过利用Session的这个概念,编写程序时就可以在服务器端非常方便地区分出是当前处理的是哪个客户端的请求、维持客户端的状态信息、可以实现客户端之间相互通讯。

服务端代码大致如下:

//初始化Acceptor—可以不指定线程数量,MINA2里面默认是CPU数量+2
//是你的工作主线程
NioSocketAcceptor acceptor = new NioSocketAcceptor(5);
//建立线程池
java.util.concurrent.Executor threadPool = newFixedThreadPool(1500);
//加入过滤器(Filter)到Acceptor
acceptor.getFilterChain().addLast(“exector”, new ExecutorFilter(threadPool));
//编码解码器
acceptor.getFilterChain().addLast(“codec”,new ProtocolCodecFilter(new WebDecoder(),new XmlEncoder()));
//日志
LoggingFilter filter = new LoggingFilter();
filter.setExceptionCaughtLogLevel(LogLevel.DEBUG);
filter.setMessageReceivedLogLevel(LogLevel.DEBUG);
filter.setMessageSentLogLevel(LogLevel.DEBUG);
filter.setSessionClosedLogLevel(LogLevel.DEBUG);
filter.setSessionCreatedLogLevel(LogLevel.DEBUG);
filter.setSessionIdleLogLevel(LogLevel.DEBUG);
filter.setSessionOpenedLogLevel(LogLevel.DEBUG);
acceptor.getFilterChain().addLast(“logger”, filter);
//设置的是主服务监听的端口可以重用
acceptor.setReuseAddress(true);
//设置每一个非主监听连接的端口可以重用
acceptor.getSessionConfig().setReuseAddress(true);
//MINA2中,当启动一个服务端的时候,要设定初始化缓冲区的长度,如果不设置这个值,系统默认为2048,当客户端发过来的消息超过设定值的时候,
//MINA2的机制是分段接受的,将字符是放入缓冲区中读取,所以在读取消息的时候,需要判断有多少次。这样的好处就是可以节省通讯的流量。
//设置输入缓冲区的大小
acceptor.getSessionConfig().setReceiveBufferSize(1024);
//设置输出缓冲区的大小
acceptor.getSessionConfig().setSendBufferSize(10240);
//设置为非延迟发送,为true则不组装成大包发送,收到东西马上发出
acceptor.getSessionConfig().setTcpNoDelay(true);
//设置主服务监听端口的监听队列的最大值为100,如果当前已经有100个连接,再新的连接来将被服务器拒绝
acceptor.setBacklog(100);
acceptor.setDefaultLocalAddress(new InetSocketAddress(port));
//加入处理器(Handler)到Acceptor
acceptor.setHandler(new YourHandler());
acceptor.bind();

客户端代码大致如下:(客户端的初始化和服务器端其实是一样的,就是初始化类不一样,客户端是作为发送者的。)

SocketConnector connector = new NioSocketConnector();
connector.getFilterChain().addLast(“codec”, new ProtocolCodecFilter(new XmlCodecFactory(Charset.forName(charsetName), null, sertType)));
//指定线程池
connector.getFilterChain().addLast(“executor”, new ExecutorFilter());
//指定业务处理类
connector.setHandler(this);

在IoHandler中定义了一些事件方法,比如messageReceived,sessionOpend,sessionCreated,exceptionCaught等,用户只需要在方法内部实现对应的处理逻辑即可。

心跳机制:
mina自身带的心跳机制好处在于,它附加了处理,让心跳消息不会传到业务层,在底层就完成了。

事件模型:
MINA可以看成是事件驱动的。通常在网络通讯中,可以将整个过程划分为几个基本的阶段,如建立连接、数据通信、关闭连接。数据通信一般包括数据的发送和接收,由于在通信过程中,可能要多次发送和接收数据,以进行不同的业务交互。
不可能一直都接收和发送数据,因此就有Idle出现,在MINA中,如果在设定的时间内没有数据发送或接收,那么就会触发一个Idle事件。

附录:对与协议的理解,摘自ppt

http协议 —> 对应于应用层
tcp协议 —> 对应于传输层
ip协议 —> 对应于网络层
三者本质上没有可比性。 何况HTTP协议是基于TCP连接的。

TCP/IP是传输层协议,主要解决数据如何在网络中传输;而HTTP是应用层协议,主要解决如何包装数据。
我们在传输数据时,可以只使用传输层(TCP/IP),但是那样的话,由于没有应用层,便无法识别数据内容,如果想要使传输的数据有意义,则必须使用应用层协议,应用层协议很多,有HTTP、FTP、TELNET等等,也可以自己定义应用层协议。WEB使用HTTP作传输层协议,以封装HTTP文本信息,然后使用TCP/IP做传输层协议将它发送到网络上。

Socket是对TCP/IP协议的封装,Socket本身并不是协议,而是一个调用接口(API),通过Socket,我们才能使用TCP/IP协议。
这也就不难理解为什么有些内部的系统调用采用socket,而不是http。本身web的这种系统,HTTP已经将报文信息封装好了。各种JEE的WEB框架,都能够直接获取报文中的信息,而socket方式,可以双方很方便的自己定义报文的内容,加密方式等等。

猜你喜欢

转载自blog.csdn.net/zhang5690800/article/details/84561640
今日推荐