前言

Apache的MINA框架是一个早年非常流行的NIO框架,它出自于Netty之父Trustin Lee大神之手。虽然目前市场份额已经逐渐被Netty取代了,但是其作为NIO初学者入门学习框架是非常合适的,因为MINA足够的简单,它的实现相对于Netty的难易程度,大概只有Netty的40%左右(个人在对比了MINA和Netty的底层实现得出的结论);然而其在整体架构上的设计是非常类似的,因此在学习完MINA之后再去看Netty,也会相对简单一些。与此同时,一些老的系统在底层实现上也有很多使用了MINA来进行通信的,如果在接手后不懂其原理,也是很难去维护的。因此个人觉得还是有必要去花些时间去好好研究一下它!

MINA宏观架构

 
MINA宏观的体系结构

从宏观上面看,MINA作为应用程序与底层网络协议的中间桥梁,它所支持的底层协议包括TCP、UDP、in-VM、 RS-232C串口编程协议等。对于我们开发者而言,无论你是在编写客户端还是服务器端程序,我们都只需在MINA之上设计你的应用本身即可,而无需关注底层网络层协议的复杂处理。

MINA中的组件

上面我们从宏观的角度看完了MINA的整体结构,下面让我们MINA中的组件做一个剖析。

 
MINA中的组件

从上面的图中,我们可以看到,从广义的划分方式来说,一个基于MINA的应用,无论是服务端还是客户端,它都一共分为三个部分:

  • I/O Service:负责端口绑定、接受网络连接或者是主动发起网络连接、网络IO读写、生成对应的IOSession(IOSession是MINA对底层网络连接的一个封装)等功能。
  • I/O Filter Chain:数据过滤、数据报文的编解码、黑白名单过滤等等。
  • I/O Handler:执行真正的业务逻辑。

所以,如果我们想要创建一个基于MINA的网络应用程序,其实只需要3步:

  1. 创建I/O Service,通常直接使用MINA内置的IO Service即可。
  2. 创建一个Filter Chain,MINA也内置了大量Filter Chain的实现,在某些特殊情况下需要自定义IoFilter,比如实现自定义协议的编解码功能。
  3. 创建I/O Handler,编写我们真正的业务逻辑,处理不同的消息。

MINA服务器端架构

上面我们已经分析了MINA的整体架构,下面对其再细分一下,我们一起来看一下MINA服务器端的架构组成。

 
MINA服务器端架构

细心的同学会发现,上面的图与之前的整体架构图对比起来看,其实真正变化的内容就是从I/O Service变成了I/O Acceptor

MINA对I/O Service做了一个整体的抽象,在服务器端,因为是接收连接,因此是I/O Acceptor;而在客户端,因为是主动发起连接,因此就是I/O Connnector。但是无论IOAcceptor还是IOConnector,它们都继承了IOService这个接口。

 
IOService的结构图

MINA服务器示例

好了,说了这么多,下面搞个MINA服务端的例子来感受一下,我们使用的MINA版本是2.0.16,JDK的版本为1.8

1.添加maven的pom依赖
  <properties>
<mina.version>2.0.16</mina.version>
<logback.version>1.2.3</logback.version>
<java.version>1.8</java.version>
</properties> <dependencyManagement>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.apache.mina/mina-core -->
<dependency>
<groupId>org.apache.mina</groupId>
<artifactId>mina-core</artifactId>
<version>${mina.version}</version>
<!--Mina自带会引入SLF4J包-->
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
</exclusions>
</dependency> <!--使用Logback作为日志系统,自带会引入SLF4J-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
</dependency> </dependencies>
</dependencyManagement> <dependencies> <dependency>
<groupId>org.apache.mina</groupId>
<artifactId>mina-core</artifactId>
</dependency> <dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency> </dependencies> <build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.2</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
</plugins>
</build>

MINA自带就会使用SLF4J作为其日志框架,但是其引入的版本是1.7.21,而我们使用Logback也会自动引入SLF4J的包,其使用的版本是1.7.25。我们这里排除掉MINA自动引入的版本,使用1.7.25的SLF4J-API包。

2.编写Server端主程序
package com.panlingxiao.mina.quickstart;

import org.apache.mina.core.service.IoAcceptor;
import org.apache.mina.core.service.IoService;
import org.apache.mina.core.service.IoServiceListener;
import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.codec.textline.TextLineCodecFactory;
import org.apache.mina.filter.logging.LoggingFilter;
import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.charset.Charset;
import java.util.concurrent.TimeUnit; /**
* Created by panlingxiao on 2017/8/13.
* 基于Mina的时间服务器
*/
public class MinaTimeServer { private static final int PORT = 9123; private static final Logger logger = LoggerFactory.getLogger(MinaTimeServer.class); public static void main(String[] args) throws Exception { IoAcceptor acceptor = new NioSocketAcceptor(); // 1
//添加日志处理器 2.
acceptor.getFilterChain().addLast("logger", new LoggingFilter());
//添加编解码处理器,读取一行数据作为一个报文
acceptor.getFilterChain().addLast("codec", new ProtocolCodecFilter(new TextLineCodecFactory(Charset.forName("UTF-8"))));
//3.设置IoHandler
acceptor.setHandler(new TimeServerHandler()); //4.配置IoSession的属性
acceptor.getSessionConfig().setReadBufferSize(2048);
acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, 10); //5.在端口绑定前添加IOServiceListener,否则事件无法监听到
acceptor.addListener(new IoServiceListener() {
@Override
public void serviceActivated(IoService service) throws Exception {
logger.info("{} active", service);
} @Override
public void serviceIdle(IoService service, IdleStatus idleStatus) throws Exception { } @Override
public void serviceDeactivated(IoService service) throws Exception { } @Override
public void sessionCreated(IoSession session) throws Exception {
logger.info("{} create session,session:{}", session);
} @Override
public void sessionClosed(IoSession session) throws Exception { } @Override
public void sessionDestroyed(IoSession session) throws Exception { }
}); //6.如果不指定端口,则由OS随机分配一个可用端口
//acceptor.bind();
acceptor.bind(new InetSocketAddress(PORT));
}
}
  1. 首选我们创建了一个IOAcceptor,它用于监听网络端口,等待进来的连接,以及客户端发送过来的报文。对于一个新的连接,一个新的Session会被创建。Session是MINA对客户端连接的一个封装实现,不让用户直接去处理JDK原生的SocketChannel。

  2. 设置了两个IoFilter,第一个是用于日志记录,每当有接受到连接或者读取到数据,都将日志输出。第二个是用于数据的编解码,以一行数据作为一个报文的形式,将读取的数据解码成一个字符串,同时也将输出的字符串转换成对应的ByteBuffer输出。

  3. 设置IoHandler,它的作用就是用于处理具体的业务逻辑的。这里的业务逻辑非常简单,就是将当前的日期向客户端输出。

  4. 通过IoSessionConfig设置IoSession的属性,setReadBufferSize(2048)表示每一次读取最大的字节数为2048个字节,setIdleTime(IdleStatus.BOTH_IDLE, 10)表示设置一个网络连接在10秒之内都没有读写,则认为是一次闲置。后面可以统计到一个连接出现了多少次闲置,业务上可以根据闲置的次数,当其达到最大值时,将连接断开,从而降低不必要的资源开销。

  5. 添加IoServiceListener去监听当前IoAcceptor所发生的事件。在Server端,如果当端口绑定成功之后,IoAcceptor就会处于active状态,此时IoServiceListener的serviceActivated就会得到通知。

  6. 最后,我们通过指定服务器端的端口,让IoAcceptor监听给定的端口。

3.编写IoHandler
package com.panlingxiao.mina.quickstart;

import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.core.session.IoSession; import java.util.Date; /**
* Created by panlingxiao on 2017/8/13.
*/
public class TimeServerHandler extends IoHandlerAdapter { private static final Logger logger = LoggerFactory.getLogger(TimeServerHandler.class); //1
@Override
public void exceptionCaught(IoSession session, Throwable cause) throws Exception {
cause.printStackTrace();
} //2
@Override
public void sessionIdle(IoSession session, IdleStatus status) throws Exception {
logger.info("IDLE " + session.getIdleCount(status));
} //3
@Override
public void messageReceived(IoSession session, Object message) throws Exception {
String str = message.toString();
if (str.trim().equalsIgnoreCase("quit")) {
session.closeNow().addListener(future -> {
logger.info("Session Close");
});
return;
} Date date = new Date();
//通过添加IOFutureListener,回调得到结果
session.write(date.toString()).addListener((ioFuture) -> {
if (ioFuture.isDone()) {
logger.info("Message written...");
}
}); }
}

在这里,我们并没有直接去实现IoHandler接口,而是去继承IoHandlerAdapter。这里使用了一个典型的适配器模式,通过在IoHandlerAdapter中去重写需要的方法。下面我们看一下这几个重写的方法:

  1. exceptionCaught方法见名知意,当在执行处理过程中发生异常时会得到调用。这里的异常包括由网络引起的IOExcetpion,或者是由于IoHandler处理业务逻辑而引起的异常等,都会调用该方法来处理。这里我们只是简单地将异常输出而已,在实际的开发中,我们可以根据自己的业务,向另外一方影响一个错误处理的消息。

  2. 当连接在指定时间内没有发生网络读写时,IoService就将该连接的Idle次数加1。此时sessionIdle方法就会得到调用。该方法只针对面向连接的协议有效,如果我们使用UDP这种无连接的传输模式时,该方法就不会被执行。

  3. 最后我们完成当接受到消息的处理。由于前面已经经过IoFilter对数据的解码处理,因此我们接受到的数据就是一个String类型。如果我们接受到的数据是一个quit,那么服务器端则主动关闭连接;否则的话,我们将当前时间返回给客户端。需要注意的是,MINA与Netty一样,都是一个异步处理的框架,因此我们在处理一个操作的时候,建议通过添加IoFutureListener的方式来获取执行的返回结果。

4.演示结果
 
Server端日志
 
通过telnet连接

客户端架构

 
客户端架构

在分析完服务器的结构之后,我们再来看一下客户端的结构。仔细看我们会发现,客户端结构与服务器端结构唯一的不同就在于,IOAcceptor被换成了IOConnector。

客户端代码示例

package com.panlingxiao.mina.quickstart.client;

import org.apache.mina.core.service.IoConnector;
import org.apache.mina.core.service.IoService;
import org.apache.mina.core.service.IoServiceListener;
import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.codec.textline.TextLineCodecFactory;
import org.apache.mina.filter.logging.LoggingFilter;
import org.apache.mina.transport.socket.nio.NioSocketConnector; import java.net.InetSocketAddress;
import java.nio.charset.Charset; /**
* Created by panlingxiao on 2017/8/28.
*/
public class MinaTimeClient { public static void main(String[] args) throws Throwable {
//创建IOConnector,用于完成与服务器建立连接
IoConnector connector = new NioSocketConnector();
//添加IoFilter
connector.getFilterChain().addLast("logger", new LoggingFilter());
connector.getFilterChain().addLast("codec", new ProtocolCodecFilter(new TextLineCodecFactory(Charset.forName("UTF-8"))));
//设置IoHandler
connector.setHandler(new TimeClientHandler()); //添加监听器,获取IOConnector的事件通知
connector.addListener(new IoServiceListener() {
@Override
public void serviceActivated(IoService service) throws Exception { } @Override
public void serviceIdle(IoService service, IdleStatus idleStatus) throws Exception { } @Override
public void serviceDeactivated(IoService service) throws Exception { } @Override
public void sessionCreated(IoSession session) throws Exception { } @Override
public void sessionClosed(IoSession session) throws Exception { } @Override
public void sessionDestroyed(IoSession session) throws Exception {
//当关闭完成session销毁之后,将IOConnector资源释放
connector.dispose();
}
}); //连接服务器
connector.connect(new InetSocketAddress(9123));
}
}
package com.panlingxiao.mina.quickstart.client;

import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IoSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; /**
* Created by panlingxiao on 2017/8/28.
*/
public class TimeClientHandler extends IoHandlerAdapter {
private static final Logger logger = LoggerFactory.getLogger(TimeClientHandler.class); private int counter = 0; private int num = 100; @Override
public void exceptionCaught(IoSession session, Throwable cause) throws Exception {
super.exceptionCaught(session, cause);
} @Override
public void sessionOpened(IoSession session) throws Exception {
//当连接建立之后,客户端主动向服务器发送100条消息
for (int i = 0; i < num; i++) {
session.write("hello:" + i);
}
} @Override
public void messageReceived(IoSession session, Object message) throws Exception {
counter++;
logger.info("receive message:{},counter:{}", message, counter);
//当客户端接受到100条消息后,主动断开连接
if(num == counter){
session.closeNow();
}
}
}
 
客户端运行结果

总结

至此,我们已经学习完了MINA的整体结构以及它的组成部分。在后面会开始分析NIOSocketAcceptor的实现,分析端口的绑定过程、网络数据的读写过程、Session的创建过程、以及一个网络事件是如果通过IoFilter一层层地传递到IoHandler等内容。

链接:https://www.jianshu.com/p/5d47e56f89de

【MINA学习笔记】—— 1.体系结构分析[z]的更多相关文章

  1. 区间无修改莫队学习笔记(lg1494小z的袜子)

    这几天感觉要学的要做的有点多,就偷了个懒没写笔记,赶紧补一下 莫队嘛,一个离线处理各种区间(或树上)询问的神奇算法 简单而言,按左端点排个序然后指针l,r递推就好了 复杂度证明貌似是不待修改的n^1. ...

  2. Android学习笔记__1__Android体系架构

    Android 体系结构图 Android作为一个移动设备的平台,其软件层次结构包括了一个操作系统(OS),中间件(MiddleWare)和应用程序(Application).根据Android的软件 ...

  3. Oracle学习笔记—oracle体系架构及状态(nomount、mount和open)简介

    oracle体系架构简介 先来简要了解一下Oracle数据库体系架构以便于后面深入理解,Oracle Server主要由实例(instance)和数据库(database)组成.实例(instance ...

  4. [Cocos2d-x for WP8学习笔记] HelloWorld结构分析

    先来看一下目录结构: Assets:游戏资源文件,图片音频等,Resource文件夹也有类似功能 include:用于放置游戏头文件 Shaders:渲染器着色器文件(大雾) cocos2dorig. ...

  5. BZOJ 2038: [2009国家集训队]小Z的袜子(hose)【莫队算法裸题&&学习笔记】

    2038: [2009国家集训队]小Z的袜子(hose) Time Limit: 20 Sec  Memory Limit: 259 MBSubmit: 9894  Solved: 4561[Subm ...

  6. Spring Boot学习笔记2——基本使用之最佳实践[z]

    前言 在上一篇文章Spring Boot 学习笔记1——初体验之3分钟启动你的Web应用已经对Spring Boot的基本体系与基本使用进行了学习,本文主要目的是更加进一步的来说明对于Spring B ...

  7. Mina框架的学习笔记——Android客户端的实现

    Apache MINA(Multipurpose Infrastructure for Network Applications) 是 Apache 组织一个较新的项目,它为开发高性能和高可用性的网络 ...

  8. MINA学习之体系介绍

    基于MINA应用程序结构图: 我们可以看出,MINA是应用程序(客户端或服务端)和底层基于TCP,UDP等通讯协议的网络层之间的粘合剂.而且各个模块之间是相互独立的,你只需要在MINA体 系基础上设计 ...

  9. 【工作笔记】BAT批处理学习笔记与示例

    BAT批处理学习笔记 一.批注里定义:批处理文件是将一系列命令按一定的顺序集合为一个可执行的文本文件,其扩展名为BAT或者CMD,这些命令统称批处理命令. 二.常见的批处理指令: 命令清单: 1.RE ...

随机推荐

  1. bat脚本基础教程

    bat脚本就是DOS批处理脚本,就是将一系列DOS命令按照一定顺序排列而形成的集合,运行在windows命令行环境上.本文主要介绍bat脚本基础语法,希望完成本文内容学习之后具备基础的bat脚本开发能 ...

  2. Linux查看CPU、内存、IO占用高的进程

    查看CPU占用高的top15进程 | | 查看内存占用高的top15进程 | | 查看IO占用高的top15进程 ./ind_high_io_process.py 3 4 5.其中3表示间隔3秒获取一 ...

  3. hbase高可用集群部署(cdh)

    一.概要 本文记录hbase高可用集群部署过程,在部署hbase之前需要事先部署好hadoop集群,因为hbase的数据需要存放在hdfs上,hadoop集群的部署后续会有一篇文章记录,本文假设had ...

  4. Linux 创建用户 限制SFTP用户只能访问某个目录

    Linux 限制SFTP用户只能访问某个目录 1. 新建用户并设置密码 > useradd suser > passwd suser   // 输入密码 2. 设置sshd配置文件 > ...

  5. Netty 能做什么

    作为一个学Java的,如果没有研究过Netty,那么你对Java语言的使用和理解仅仅停留在表面水平,会点SSH,写几个MVC,访问数据库和缓存,这些只是初等Java程序员干的事.如果你要进阶,想了解J ...

  6. Implementing a Dispose method

    [Implementing a Dispose method] 1.实现System.IDsiposable.Dispose()方法.不能将此方法设置为virtual,子类不能override此方法. ...

  7. web漏洞详解及修复建议

    1.漏洞描述 跨站脚本攻击(Cross-site scripting,通常简称为XSS)发生在客户端,可被用于进行窃取隐私.钓鱼欺骗.偷取密码.传播恶意代码等攻击行为. 恶意的攻击者将对客户端有危害的 ...

  8. ISE软件报错

    ISE弹出如下报错并关闭程序或在编译时出现PATH类报错 一,解决办法 本人自己试了一下  E:\ISE\14.7\ISE_DS\settings64.bat E:\ISE\14.7\ISE_DS\I ...

  9. 复杂链表的复制(python)

    题目描述 输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head.(注意,输出结果中请不要返回参数中的节点引用,否 ...

  10. 反转链表(python)

    题目描述 输入一个链表,反转链表后,输出新链表的表头. # -*- coding:utf-8 -*- # class ListNode: # def __init__(self, x): # self ...