MINA2官方教程翻译
(1) 2.x与1.x的变化
一、包与命名
所有的类和方法严格使用驼峰法命名。
例如SSLFilter被更名为SslFilter,其它很多类也是如此。
所有NIO传输类在命名时增加‘Nio’前缀。
因为NIO并不只是socket/datagram传输的实现,所有‘Nio’前缀加在了所有的NIO传输类上。
改变之前:
- SocketAcceptor acceptor = new SocketAcceptor();
改变之后:
- SocketAcceptor acceptor = new NioSocketAcceptor();
Filter类被重新整理进多重子包内。
随着框架自带的filter实现的数量的增加,所有的filter都被移动到适当的子包中(例如,StreamWriteFilter移至org.apache.mina.filter.stream)。
*.support的所有包被移动到了其父包(或者其他包)中。
为了避免循环依赖,*.support包中的所有类都被移至其父包或者其他包中。你可以在IDE(例如Eclipse)中简单的修正这些包的导入从而避免编译错误。
二、Buffers
MINA ByteBuffer被重命名为IoBuffer。
因为MINA ByteBuffer与JDK中NIO ByteBuffer同名,很多用户发现与其组员沟通时存在很多困难。根据用户的反馈,我们将MINA ByteBuffer重命名为IoBuffer,这不仅使类名称简化,也是类名称更加明晰。
放弃Buffer池,默认使用IoBuffer.allocate(int)来分配heap buffer。
- acquire()与release()两个方法将不再是容易发生错误的。如果你愿意,你可以调用free()方法,但这是可选的。请自己承担使用这个方法的风险。
- 在大多数JVM中,框架内置的IoBuffer性能更加强劲、稳定。
Direct buffer池是MINA早期版本所标榜的众多特性之一。然而根据当今的尺度,在主流的JVM中direct buffers的表现要比heap buffers差。此外,当direct buffer memory的最大值没有被正确设定时,不可预期的OutOfMemoryError也经常出现。
为了使系统内置的IoBuffer性能更加强劲、稳定,Apache MINA项目组将默认的buffer类型由direct改为heap。因为heap buffers并不需要池化,PooledByteBufferAllocator也被移除掉了。由于没有了池的概念,ByteBuffer.acquire() 和 ByteBuffer.release()也被移除掉了。
然而,如果使用的速度太快,分配heap buffers也会成为瓶颈。这是因为分配字节数据如要将所有的元素都置为0,这个操作是消耗内存带宽的。CachedBufferAllocator是针对这种情况使用的,但是在大多数情况下,你还是应该使用默认的SimpleBufferAllocator。
三、启动和配置
IoService的配置被简化了。
在1.x版本中,有很多种方式来配置IoService和它的子接口(例如 IoAcceptor 和 IoConnector)。基本上,有两种配置方法:
在调用bind() 或 connect()时,具体指定一个IoServiceConfig
- SocketAcceptor acceptor = new SocketAcceptor();
- SocketAcceptorConfig myServiceConfig = new SocketAcceptorConfig();
- myServiceConfig.setReuseAddress(true);
- acceptor.bind(myHandler, myServiceConfig);
使用IoService.defaultConfig属性,此时不需要指定一个IoServiceConfig
- SocketAcceptor acceptor = new SocketAcceptor();
- acceptor.getDefaultConfig().setReuseAddress(true);
- acceptor.bind(new InetSocketAddress(8080), myHandler);
配置IoFilterChain是另一个令人头痛的问题,因为除了IoServiceConfig内的IoFilterChainBuilder外,还有一个全局的IoFilterChainBuilder,这就意味着使用两个IoFilterChainBuilders来配置一个IoFilterChain。大多数用户使用全局的IoFilterChainBuilder来配置IoFilterChain,并且这就足够了。
针对这种复杂情况,MINA 2.0简化了网络应用程序的启动,请比较下面的代码与前面代码的不同
- SocketAcceptor acceptor = new SocketAcceptor();
- acceptor.setReuseAddress(true);
- acceptor.getFilterChain().addLast("myFilter1", new MyFirstFilter());
- acceptor.getFilterChain().addLast("myFilter2", new MySecondFilter());
- acceptor.getSessionConfig().setTcpNoDelay(true);
- // You can specify more than one addresses to bind to multiple addresses or interface cards.
- acceptor.setLocalAddress(new InetSocketAddress(8080));
- acceptor.setHandler(myHandler);
- acceptor.bind();
- // New API restricts one bind per acceptor, and you can't bind more than once.
- // The following statement will raise an exception.
- acceptor.bind();
你也许意识到与Spring框架整合也将变得更加简单。
四、线程
ThreadModel被移除了。
最初引入ThreadModel的概念为的是简化一个IoService预定义的线程模式。然而,配置线程模式却变得非常简单以至于不能引入新的组建。与其易用性相比,线程模式带了更多的混乱。在2.x中,当你需要的时候,你必须明确的增加一个ExecutorFilter。
ExecutorFilter使用一个特定的Executor实现来维系事件顺序。
在1.x中,可以使用任意的Executor实现来来维系事件顺序,但2.x提供了两个新的ThreadPoolExecutor实现,OrderedThreadPoolExecutor和UnorderedThreadPoolExecutor,ExecutorFilter维系事件顺序,当以下两种情况:当使用默认构造方法时,ExecutorFilter创建一个OrderedThreadPoolExecutor,或者
明确指明使用OrderedThreadPoolExecutor时
OrderedThreadPoolExecutor 和 UnorderedThreadPoolExecutor内部使用了一些架构来防止发生OutOfMemoryError,所以你应该尽量使用这两个类而不是其他Executor的实现。
五、协议编解码
DemuxingProtocolCodecFactory被重写了。
新增了DemuxingProtocolEncoder和DemuxingProtocolDecoder两个类,DemuxingProtocolCodecFactory只是这两个类的外壳。register() 方法被重命名为addMessageEncoder() 和addMessageDecoder(),这个变化使混合使用多个encoders和decoders变得更加自由。
MessageEncoder接口也发生了改变,MessageEncoder.getMessageTypes()被移除了,当你调用addMessageEncoder(),你只需要指明信息的类型,encoder就可以进行正确的编码了。
六、集成
JMX集成被重新设计了。
Sping集成被简化了。
七、其他方面的改变
TransportType更名为TransportMetadata。
TransportType改名是因为它的角色是元数据而不仅仅是一种枚举。
IoSessionLogger被重新设计了。
IoSessionLogger现在实现了SLF4J Logger接口,所以你可以像声明简单SLF4J logger实例一样声明它,这个变化使你不必向其他不必要的部分暴露IoSessionLogger对象。另外,在使用MDC时,请考虑使用简单的MdcInjectionFilter,这时IoSessionLogger是没有必要的。
改变之前:
- IoSessionLogger.debug(session, ...);
改变之后:
- Logger logger = IoSessionLogger.getLogger(session);
- logger.debug(...);
BroadcastIoSession被合并到IoSession中。
ReadThrottleFilterBuilder被ReadThrottleFilter替代并最终移除。
(2) 快速上手指南
一、介绍
该教程通过构建一个time server,带你走进给予MINA的应用程序开发的大门,但在开始之前我们需要具备下面的必要条件:
- MINA 2.x的核心包
- JDK 1.5 或更高版本
- SLF4J 1.3.0 或更高版本
- Log4J 1.2的用户:slf4j-api.jar, slf4j-log4j12.jar, and Log4J 1.2.x
- Log4J 1.3的用户:slf4j-api.jar, slf4j-log4j13.jar, and Log4J 1.3.x
- java.util.logging的用户:slf4j-api.jar and slf4j-jdk14.jar
注意:请务必确认你所使用的slf4j-*.jar要与你的日志框架相匹配。例如,slf4j-log4j12.jar 和 log4j-1.3.x.jar不能在一起使用,否则会引起混乱。
我已经在Windows? 2000 professional 和 linux平台上测试了这个程序,如果你在运行这个程序的过程中遇到了问题,请立即联系我们的开发人员。
当然,这个程序是与开发环境(IDE, editors等等)无关的,它可以在任何你熟悉的平台中运行。另外,为了简化,编译命令与运行脚本都没有体现,如果你需要学习如何编译并运行java程序,请参考Java tutorial。
二、编写基于MINA框架的time server
我们从创建一个名为MinaTimeServer.java的文件开始,最初的代码如下:
- public class MinaTimeServer {
- public static void main(String[] args) {
- // code will go here next
- }
- }
对所有人来说,这段代码应该是简单易懂的,我们只是简单的定义了一个main方法是这个程序能够正常运行起来。从现在开始,我们将逐步加入代码是其最终成为一个可用的server。首先,我们需要一个可以监听连接到来的对象,既然我们的程序是基于TCP/IP的,所以我们在程序中加入一个SocketAcceptor。
- import org.apache.mina.core.service.IoAcceptor;
- import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
- public class MinaTimeServer
- {
- public static void main( String[] args )
- {
- IoAcceptor acceptor = new NioSocketAcceptor();
- }
- }
加入NioSocketAcceptor之后,我们可以继续定义一个handler类,并将其与NioSocketAcceptor绑定到一个端口上。
下面,我们在配置中增加一个filter,这个filter将把二进制数据或是协议相关的数据转换成为一个消息对象,反之亦然。我们使用现有的TextLine工厂类,以为它可以处理基于文本的信息(你不需要自己来实现编解码部分)。
- import java.nio.charset.Charset;
- import org.apache.mina.core.service.IoAcceptor;
- 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;
- public class MinaTimeServer
- {
- public static void main( String[] args )
- {
- IoAcceptor acceptor = new NioSocketAcceptor();
- acceptor.getFilterChain().addLast( "logger", new LoggingFilter() );
- acceptor.getFilterChain().addLast( "codec", new ProtocolCodecFilter( new TextLineCodecFactory( Charset.forName( "UTF-8" ))));
- }
- }
然后,我们定义一个handler,这个handler将对客户端的连接以及过去当前时间的请求做出服务。handler类必须实现IoHandler接口。对于大多数基于MINA的应用程序,这个操作无疑是一个很大的负担,因为它将处理客户端说有的请求。在这个教程中,我们的handler将继承自IoHandlerAdapter,这个类依照适配器模式来简化实现IoHandler接口所带来的代码量。
- import java.io.IOException;
- import java.nio.charset.Charset;
- import org.apache.mina.core.service.IoAcceptor;
- 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;
- public class MinaTimeServer
- {
- 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 TimeServerHandler() );
- }
- }
现在,我们在NioSocketAcceptor增加一些Socket相关的配置:
- import java.io.IOException;
- import java.nio.charset.Charset;
- import org.apache.mina.core.session.IdleStatus;
- import org.apache.mina.core.service.IoAcceptor;
- 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;
- public class MinaTimeServer
- {
- 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 TimeServerHandler() );
- idle sessions
- acceptor.getSessionConfig().setReadBufferSize( 2048 );
- acceptor.getSessionConfig().setIdleTime( IdleStatus.BOTH_IDLE, 10 );
- }
- }
在MinaTimeServer增加了两行新的内容,这些set方法分别设置了IoHandler、input buffer size和session对象上的idle属性。buffer size指明了底层操作系统应该给与新到来的数据分配多少空间;第二行指明了什么时候应该检测idle sessions。在setIdleTime这个方法中,第一参数指明了在检测session是否idle时,应该关心那一种活动,第二个参数指明了session变为idle状态时需要经过多长的时间。
handler的代码如下:
- import java.util.Date;
- import org.apache.mina.core.session.IdleStatus;
- import org.apache.mina.core.service.IoHandlerAdapter;
- import org.apache.mina.core.session.IoSession;
- public class TimeServerHandler extends IoHandlerAdapter
- {
- @Override
- public void exceptionCaught( IoSession session, Throwable cause ) throws Exception
- {
- cause.printStackTrace();
- }
- @Override
- public void messageReceived( IoSession session, Object message ) throws Exception
- {
- String str = message.toString();
- if( str.trim().equalsIgnoreCase("quit") ) {
- session.close();
- return;
- }
- Date date = new Date();
- session.write( date.toString() );
- System.out.println("Message written...");
- }
- @Override
- public void sessionIdle( IoSession session, IdleStatus status ) throws Exception
- {
- System.out.println( "IDLE " + session.getIdleCount( status ));
- }
- }
该类用到的方法有exceptionCaught、messageReceived和sessionIdle。在handler中,一定要定义exceptionCaught方法,该方法用来处理在远程连接中处理过程中发生的各种异常,如果这个方法没有被定义,我们可能不能发现这些异常。
在这个handler中,exceptionCaught方法只是简单地打印出异常堆栈信息并关闭连接,对于大多数程序来说,这是一种比较标准的操作,除非连接可以在异常条件下恢复。
messageReceived方法会接收客户端的数据并返回当前的的系统时间,如果从客户端接收到了消息‘quit’,则session会被关闭。与调用session.write(Object)的情况相同,不同的协议编解码器决定了传入该方法的对象(第二个参数)也是不同的。如果你没有指定协议编解码器,你最有可能接收到一个IoBuffer对象,当然,调用session.write(Object)也是一个IoBuffer对象。
当session持续idle的时间与acceptor.getSessionConfig().setIdleTime( IdleStatus.BOTH_IDLE, 10 )设置的时间一致时,sessionIdle方法将被调用。
现在剩下的工作只是定义一个server监听的地址和端口了,当然还需要启动服务。代码如下:
正如你所见,这里调用了acceptor.setLocalAddress( new InetSocketAddress(PORT) );方法,该方法指明了server将在哪个IP和端口上监听。最后一步调用了IoAcceptor.bind()方法,该方法将端口与具体的客户端进程绑定在一起。
三、验证Time server
现在,我们编译上面的程序,编译完成后就可以运行并查看运行结果了。最简单的测试途径就是启动程序,并使用telnet与之建立连接:
Client Output |
Server Output |
user@myhost:~> telnet 127.0.0.1 9123 |
MINA Time server started. |
四、接下来
获取更多资源,请浏览MINA的Documentation。你也可以阅读其他教程。
(3) MINA的应用程序架构
一、简介
有个问题经常被提出:基于MINA的应用程序应该是什么样的呢?这篇文章将给出一个答案。我们已经收集了很多基于MINA的描述信息。下面是架构图:
让我们在来关于一下细节
这张图片选取自Trustin Lee在JavaOne 2008上的报告"Rapid Network Application Development with Apache MINA“
从广义上讲,基于MINA的应用程序分为3层
- I/O Service - 完成实际的I/O操作
- I/O Filter Chain - 将字节过滤或转换成为预想的数据结构,反之亦然
- I/O Handler - 完成实际的业务逻辑操作
那我们如何创建一个基于MINA的应用程序呢?
- Create I/O service - 从现有的Services (*Acceptor)中选择一个或者创建自己的
- Create Filter Chain - 从现有的Filters中选择或者创建一个传输request/response的自定义Filter
- Create I/O Handler - 编写业务逻辑, 处理不同的报文
创建MINA程序就如上文所述的一样。
(4) 日志配置
一、背景
MINA框架允许开发人员在编写基于MINA的应用程序时使用自己熟悉的日志系统。
二、SLF4J
MINA框架使用Simple Logging Facade for Java (SLF4J)。你可以在这里 获取到更多关于SLF4J的信息,这种日志系统兼容各种日志系统的实现。你可能会使用log4j、java.util.logging或其他的日志系统,使用这种日志框架的好处在于如果你在开发过程中,将日志系统从java.util.logging改为log4j,你根本需要修改你的代码。
选择正确的jar包
Logging framework |
Required JARs |
Log4J 1.2.x | slf4j-api.jar , slf4j-log4j12.jar |
Log4J 1.3.x | slf4j-api.jar , slf4j-log4j13.jar |
java.util.logging | slf4j-api.jar , slf4j-jdk14.jar |
Commons Logging | slf4j-api.jar , slf4j-jcl.jar |
下面几点还需要注意:
- 对于任意一种日志系统,slf4j-api.jar是必须的;
- 重要:在classpath上不能放置多于一个日志系统实现jar包(例如slf4j-log4j12.jar and slf4j-jdk14.jar),这将导致日志出席不可预知的行为;
- slf4j-api.jar 和 slf4j-<impl>.jar的版本应该是一致的。
如果SLF4J配置正确,你可以继续配置你真正使用的日志系统(例如修改log4j.properties )。
重载Jakarta Commons Logging
SLF4J提供了一种机制可以使现有的应用程序从使用Jakarta Commons Logging变更为SLF4J而不需要修改代码,只需要将commons-loggong JAR文件充classpath中除去,并将jcl104-over-slf4j.jar 加入到classpath中。
三、log4j范例
我们以log4j为例,然后将下面的代码片段加入到log4j.properties中:
- # Set root logger level to DEBUG and its only appender to A1.
- log4j.rootLogger=DEBUG, A1
- # A1 is set to be a ConsoleAppender.
- log4j.appender.A1=org.apache.log4j.ConsoleAppender
- # A1 uses PatternLayout.
- log4j.appender.A1.layout=org.apache.log4j.PatternLayout
- log4j.appender.A1.layout.ConversionPattern=%-4r [%t] %-5p %c{1} %x - %m%n
我们将这个文件放置在工程的src目录中,如果你使用IDE,当你测试代码是,你实际上是想把这个文件放置在classpath上。
注意 :这里只是在IoAcceptor 上设置了日志,但slf4j 可以在程序中广泛使用,有了它的帮助,你可以根据需要获取到有用的信息。
下面我们编写一个简单的server从而生成一些日志信息,这里我们使用EchoServer 的范例工程来增加日志:
- public static void main(String[] args) throws Exception {
- IoAcceptor acceptor = new SocketAcceptor();
- DefaultIoFilterChainBuilder chain = acceptor.getFilterChain();
- LoggingFilter loggingFilter = new LoggingFilter();
- chain.addLast("logging", loggingFilter);
- acceptor.setLocalAddress(new InetSocketAddress(PORT));
- acceptor.setHandler(new EchoProtocolHandler());
- acceptor.bind();
- System.out.println("Listening on port " + PORT);
- }
正如你所见,在EchoServer 范例中,我们删除了addLogger方法并新增加了两行代码。通过LoggingFilter 的引用,你可以在这里设置与IoAcceptor 相关的所有事件的日志级别。在这里,可以使用LoggingFilter 中的setLogLevel(IoEventType, LogLevel)方法来区分触发IoHandler日志的时间以及对应的日志级别,下面是这个方法选项:
IoEventType |
Description |
SESSION_CREATED | 一个新的session被创建时触发 |
SESSION_OPENED | 一个新的session打开时触发 |
SESSION_CLOSED | 一个session被关闭时触发 |
MESSAGE_RECEIVED | 接收到数据时触发 |
MESSAGE_SENT | 数据被发送后触发 |
SESSION_IDLE | 一个session空闲了一定时间后触发 |
EXCEPTION_CAUGHT | 当有异常抛出时触发 |
下面是日志级别的描述:
LogLevel |
Description |
NONE | 无论如何配置,日志都不会产生 |
TRACE | 在日志系统中创建一个TRACE事件 |
DEBUG | 在日志系统中生成debug信息 |
INFO | 在日志系统中生成提示信息 |
WARN | 在日志系统中生成警告信息 |
ERROR | 在日志系统中生成错误信息 |
根据这些信息,你应该可以扩展这些范例来构建一个使用日志的简单系统,这些日志将为你提供有用的信息。
(5) 基本概念之IoBuffer
简介
IoBuffer是MINA应用程序中使用的一种字节缓冲区,它是JDK中ByteBuffer类的替代品。MINA框架出于下面两个原因没有直接使用JDK中nio包内的ByteBuffe:
- 没有提供可用的getters和putters方法,例如fill, get/putString, 和get/putAsciiInt();
- 由于它的容量是固定的,所以不利于存储变长数据。
MINA 3 将改变这种情况。MINA框架对nio ByteBuffer做了一层封装的最主要原因是希望能够拥有一种可扩展的缓冲区。这并不是一个很好的决定。缓冲区就是缓冲区:一个用于存储临时数据的临时空间,直到这些数据被使用。其实还有些其他的解决方案,例如可以对一组nio ByteBuffer进行包装来避免数据从一个缓冲区向两个容量更大的缓冲区复制,从而得到一个容量可扩展的缓冲区。
或许在filter之间传递数据时使用InputStrea来代替字节缓冲区会更加舒适,因为这不需要提供一种可以存储数据的特性,这种数据结构可以使字节数组、字符串或者其他类型的消息等等。
最后,但并非最不重要的一点是,当前的实现并没有达成一个目标:零拷贝策略(例如当我们从socket中读取了一些数据,我们希望避免持续的数据拷贝)。如果我们使用了可以扩展的字节缓冲区,那么我们只需要在管理大数据消息时进行数据拷贝。请记住MINA ByteBuffer只不过是NIO ByteBuffer的顶层封装,当我们使用direct buffers时,很可能是一个很严重的问题。
IoBuffer 操作
分配一个新的Buffer
IoBuffer 是一个抽象类,所以它不能直接被实例化。分配IoBuffer,我们可以使用两种allocate()方法。
- // Allocates a new buffer with a specific size, defining its type (direct or heap)
- public static IoBuffer allocate(int capacity, boolean direct)
- // Allocates a new buffer with a specific size
- public static IoBuffer allocate(int capacity)
allocate()方法是用一个或两个参数。第一种形式使用两个参数:
- capacity - buffer的容量
- direct -buffer的类型。true 意味着得到一个direct buffer,false 意味着得到一个heap buffer
默认的buffer分配是由SimpleBufferAllocator 处理的。
可选的, 下面的形式也可以使用:
- IoBuffer buffer = IoBuffer.allocate(8);
- buffer.setAutoExpand(true);
- buffer.putString("12345678", encoder);
- // Add more to this buffer
- buffer.put((byte)10);
按照上面的例子,如果数据的长度大于8byte的话,IoBuffe会根据情况重新分配其内置的ByteBuffer,它的容量会被加倍,它的limit会增长到String被写入时的最后position。这种行为与StringBuffer工作的方式十分类似。
注意:这种程序结构在MINA3.0时会被废弃,因为这并不是增长buffer容量的最好方式。这种方式很可能被一种类似InputStream的方式所替代,在InputStream的背后很可能是一组固定长度的ByteBuffers。
创建自动收缩的Buffer
为了节省内存,在有些情形下我们需要释放被额外分配的内存,IoBuffer提供了autoShrink 属性来达到此目的。如果autoShrink属性被打开,当compact()方法被调用时,IoBuffer回将部分的回收其容量,只使用四分之一或是更少的容量。如果需要手动控制收缩行为,请使用shrink()方法。
让我们实践一下:
- IoBuffer buffer = IoBuffer.allocate(16);
- buffer.setAutoShrink(true);
- buffer.put((byte)1);
- System.out.println("Initial Buffer capacity = "+buffer.capacity());
- buffer.shrink();
- System.out.println("Initial Buffer capacity after shrink = "+buffer.capacity());
- buffer.capacity(32);
- System.out.println("Buffer capacity after incrementing capacity to 32 = "+buffer.capacity());
- buffer.shrink();
- System.out.println("Buffer capacity after shrink= "+buffer.capacity());
我们初始化分配一个容量为16的buffer,并将自动收缩设置为true。
让我们看一下输出的结果:
- Initial Buffer capacity = 16
- Initial Buffer capacity after shrink = 16
- Buffer capacity after incrementing capacity to 32 = 32
- Buffer capacity after shrink= 16
让我们分析一下输出:
- 初始化buffer的容量为16,因为我们使用16指定了该buffer的容量,16也就成了该buffer的最小容量
- 调用shrink()方法后,容量仍旧为16,所以无论怎么调用紧缩方法,容量都不好小于其初始容量
- 增加该buffer的容量至32,该buffer的容量达到32
- 调用 shrink()方法,容量回收至16,从而剔除了冗余的容量
再次强调,这种方式是一种默认的行为,我们不需要明确指明一个buffer是否能被收缩。
Buffer分配
IoBufferAllocater负责分配并管理buffer,如果你希望使用你的方式精确控制分配行为,请自己实现IoBufferAllocater 接口。
MINA提供了IoBufferAllocater的两种实现,如下:
- SimpleBufferAllocator (默认) - 每次创建一个新的buffer
- CachedBufferAllocator - 缓存buffer,使buffer在扩展时可以被重用
注意:在新版本的JVM中,使用cached IoBuffer并不能明显提高性能。
你可以自己实现IoBufferAllocator接口并在IoBuffer上调用setAllocator()方法来指定使用你的实现。
(6) 基本概念之IoHandler
简介
Handler用来处理MINA触发的I/O事件。IoHandler是一个核心接口,它定义了Filter链末端需要的所有行为。IoHandler接口包含以下方法:
- sessionCreated
- sessionOpened
- sessionClosed
- sessionIdle
- exceptionCaught
- messageReceived
- messageSent
sessionCreated事件
一个新的connection被创建时,会触发SessionCreated事件。对于TCP来说,这个事件代表连接的建立;对于UDP来说,它代表收到了一个UDP数据包。这个方法可以用作初始化session的各种属性,也可以用来在一个新建的connection上触发一些一次性的行为。
I/O processor线程会调用这个方法,所以在实现该方法时,只加入一些耗时较少的操作,因为I/O processor线程是用来处理多会话的。
sessionOpened事件
当一个connection打开时会触发sessionOpened事件,这个事件永远在sessionCreated之后触发。如果配置了线程模式,那么这个方法会被非I/O processor线程调用。
sessionClosed事件
当一个session关闭的时候会触发sessionClosed事件。可以将session的清理操作放在这个方法里进行。
sessionIdle事件
当一个session空闲的时候会触发sessionIdle事件。当使用UDP时该方法将不会被调用。
exceptionCaught事件
当用户代码或MINA框架抛出异常时,会触发事件事件。如果该异常是一个IOException,那么connection会被关闭。
messageReceived事件
当接收到消息的时候会触发messageReceived事件。所有的业务处理代码应该写在这里,但要留心你所要的消息类型。
messageSent事件
当消息已被远端接收到的时候,会触发messageSent事件(调用IoSession.write()发送消息)。
(7)传输特性之串口
使用MINA2.0,你可以像编写基于TCP/IP的程序那样编写基于串口的程序。
获得MINA2.0
MINA 2.0的最终版本还没有release,但是你可以下载最新的版本。如果你希望从trunk构建代码,可以参考开发者指南。
前提
在访问串口之前,Java应用程序需要一个native库。MINA使用ftp://ftp.qbang.org/pub/rxtx/rxtx-2.1-7-bins-r2.zip,请把它放到你的JDK或JRE的lib/i386/下,并在程序启动的命令行中加入-Djava.library.path=来指定你的native库的位置。
连接到串口
串口通讯通过IoConnector来实现,这是有通讯媒介的点对点特性来决定的。我们假定你已经通过MINA的教程了解到了IoConnector的相关知识。连接到串口需要SerialConnector:
- // create your connector
- IoConnector connector = new SerialConnector()
- connector.setHandler( ... here your buisness logic IoHandler ... );
与SocketConnector,并没有什么不同。让我们创建一个地址来连接串口:
- SerialAddress portAddress=new SerialAddress( "/dev/ttyS0", 38400, 8, StopBits.BITS_1, Parity.NONE, FlowControl.NONE );
第一个参数代表串口的标识符。对于Windows系统,串口一般叫做"COM1"、"COM2"以此类推,对于Linux或者一些Unix系统,通常由"/dev/ttyS0"、"/dev/ttyS1"、"/dev/ttyUSB0"来表示。
剩下的参数取决于你的硬件设备的连接特性。
- 波特率
- 数据位数
- 奇偶校验
- 流控制机制
当这些都具备,就可以连接到该地址:
- ConnectFuture future = connector.connect( portAddress );
- future.await();
- IoSession sessin = future.getSession();
其他的事情和使用TCP协议等一样,你可以加入你的filters和codecs。
(8)传输特性之UDP
该教程可以帮助你使用MINA框架编写基于UDP的Socket应用程序。在这篇教程中,我们将编写一个server端程序,server可以通过连接该程序来展现client端程序的内存使用情况。现实中的很多程序都已经具备与该程序类似的功能,可以监控程序来内存使用情况。
构建代码
MINA 2.0的最终版本还没有release,但是你可以下载最新的版本。如果你希望从trunk构建代码,可以参考开发者指南。
应用程序
如上图所示,该server在端口18567监听,client端连接server端并向server端发送内存使用数据。在上图所展示应用中,共有两个client连接到了server。
程序执行流程
- server在18567端口监听客户端请求;
- client连接server端,server会在Session Created事件处理时增加一个Tab页;
- clients向server发送内存使用数据;
- server端把接收到的数据展现在Tab上。
Server端代码分析
我们可以在MINA的示例代的org.apache.mina.example.udp包下找到这些代码。对于每个实例,我们只需要关系MINA相关的代码。
要想构建该server,我们需要做如下操作:
- 创建一个数据报socket,监听client端请求。(请参考MemoryMonitor.java);
- 创建一个IoHandler来处理MINA框架生成的各种事件。(请参考MemoryMonitorHandler.java)。
第一步可以通过该程序片来实现:
- NioDatagramAcceptor acceptor = new NioDatagramAcceptor();
- acceptor.setHandler(new MemoryMonitorHandler(this));
这里,我们创建一个NioDatagramAcceptor来监听client端请求,并且设置它的IoHandler。变量"PORT"是一个int值。下一步我们家filter链中加入了一个logging filter。LoggingFilter是一个非常不错的记录日志的方式,它可以在很多节点处生成日志信息,这些热值能够展现出MINA框架的工作方式。
- DefaultIoFilterChainBuilder chain = acceptor.getFilterChain();
- chain.addLast("logger", new LoggingFilter());
下一步我们深入一下UDP传输所特有的代码。我们设置acceptor可以重用address。
- DatagramSessionConfig dcfg = acceptor.getSessionConfig();
- dcfg.setReuseAddress(true);acceptor.bind(new InetSocketAddress(PORT));
当然,最后一件事情是调用bind()方法来绑定端口。
IoHandler实现
Server端主要关心如下三个事件:
- Session创建
- Message接收
- Session关闭
详细代码如下:
Session Created Event
- @Override
- public void sessionCreated(IoSession session) throws Exception {
- SocketAddress remoteAddress = session.getRemoteAddress();
- server.addClient(remoteAddress);
- }
在session创建事件中,我们调用addClient()方法来增加一个Tab页。
Message Received Event
- @Override
- public void messageReceived(IoSession session, Object message) throws Exception {
- if (message instanceof IoBuffer) {
- IoBuffer buffer = (IoBuffer) message;
- SocketAddress remoteAddress = session.getRemoteAddress();
- server.recvUpdate(remoteAddress, buffer.getLong());
- }
- }
在message接收事件中,我们从接收到的消息中得到所关心的内存使用的数据;应用程序还需要发送响应信息。在这个方法中,处理消息和发送响应都是通过session完成的。
Session Closed Event
- @Override
- public void sessionClosed(IoSession session) throws Exception {
- System.out.println("Session closed...");
- SocketAddress remoteAddress = session.getRemoteAddress();
- server.removeClient(remoteAddress);
- }
在session关闭事件中,我们需要删除对应的Tab页。
Client端代码分析
在这一节,我们将解释一下客户端代码。实现客户端我们需要进行如下操作:
- 创建Socket并连接到server端
- 设置IoHandler
- 收集内存使用信息
- 发送数据到server端
我们从MemMonClient.java开始,可以在org.apache.mina.example.udp.client包下找到它。开始的几行代码非常简单:
- connector = new NioDatagramConnector();
- connector.setHandler( this );
- ConnectFuture connFuture = connector.connect( new InetSocketAddress("localhost", MemoryMonitor.PORT ));
这里我们创建了一个NioDatagramConnector,设置了它的handler并且连接到server端。我们必须在InetSocketAddress对象中设定host,否则程序不能正常运行。该程序是在Windows XP环境下开发运行的,所以与其他环境可能存在差别。下一步我们等待确认client已经连接到server端,一旦连接建立,我们可以开始向server端发送数据。代码如下:
- connFuture.addListener( new IoFutureListener(){
- public void operationComplete(IoFuture future) {
- ConnectFuture connFuture = (ConnectFuture)future;
- if( connFuture.isConnected() ){
- session = future.getSession();
- try {
- sendData();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- } else {
- log.error("Not connected...exiting");
- }
- }
- });
这里我们在ConnectFuture对象中加入一个listener,当client连接到server端时,operationComplete方法将被回调,这时我们开始发送数据。我们通过调用sendData方法向server端发送数据,该方法如下:
- private void sendData() throws InterruptedException {
- for (int i = 0; i < 30; i++) {
- long free = Runtime.getRuntime().freeMemory();
- IoBuffer buffer = IoBuffer.allocate(8);
- buffer.putLong(free);
- buffer.flip();
- session.write(buffer);
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- throw new InterruptedException(e.getMessage());
- }
- }
- }
该方法会在30秒内每秒向server端发送当前的剩余内存Cincinnati。你可以看到我们分配了一个足够大的IoBuffer来装载一个long型的变量。最后这个buffer被flipped并发送至server端。
(9)传输特性之APR
简介
APR(Apache portable Run-time libraries,Apache可移植运行库)的目的如其名称一样,主要为上层的应用程序提供一个可以跨越多操作系统平台使用的底层支持接口库。MINA目前也能够支持APR。本章我们将讨论一下使用MINA进行APR传输的基本过程。我们使用Time Server为例。
前提
APR传输依赖于下列组件:
- APR运行库 - 从http://www.apache.org/dist/tomcat/tomcat-connectors/native/处下载并安装适当版本。
- JNI封装 - tomcat-apr-5.5.23.jar包含该release,将本地库加入路径。
使用APR传输
请参考Time Server例子的完整代码。基于NIO的Time server实现如下列代码所示:
- 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 TimeServerHandler() );
- acceptor.getSessionConfig().setReadBufferSize( 2048 );
- acceptor.getSessionConfig().setIdleTime( IdleStatus.BOTH_IDLE, 10 );
- acceptor.bind( new InetSocketAddress(PORT) );
如何使用APR传输如下列代码所示:
- IoAcceptor acceptor = new AprSocketAcceptor();
- acceptor.getFilterChain().addLast( "logger", new LoggingFilter() );
- acceptor.getFilterChain().addLast( "codec", new ProtocolCodecFilter( new TextLineCodecFactory( Charset.forName( "UTF-8" ))));
- acceptor.setHandler( new TimeServerHandler() );
- acceptor.getSessionConfig().setReadBufferSize( 2048 );
- acceptor.getSessionConfig().setIdleTime( IdleStatus.BOTH_IDLE, 10 );
- acceptor.bind( new InetSocketAddress(PORT) );
我们只是将NioSocketAcceptor改为AprSocketAcceptor,只通过这一个小改动,我们的程序就可以支持APR传输,其余的代码与之前都是相同的。
(10)与Spring整合
我们通过这篇文章来介绍如何与Spring框架整合MINA应用。
程序结构
我们将编写一个简单的MINA应用程序,其组成包括:
- 一个Handler
- 两个Filter - Logging Filter和ProtocolCodec Filter
- 数据报Socket
初始化代码
让我们先看一下代码。为了简化,我们做了一些省略。
- public void initialize() throws IOException {
- // Create an Acceptor
- NioDatagramAcceptor acceptor = new NioDatagramAcceptor();
- // Add Handler
- acceptor.setHandler(new ServerHandler());
- acceptor.getFilterChain().addLast("logging", new LoggingFilter());
- acceptor.getFilterChain().addLast("codec", new ProtocolCodecFilter(new SNMPCodecFactory()));
- // Create Session Configuration
- DatagramSessionConfig dcfg = acceptor.getSessionConfig();
- dcfg.setReuseAddress(true);
- logger.debug("Starting Server......");
- // Bind and be ready to listen
- acceptor.bind(new InetSocketAddress(DEFAULT_PORT));
- logger.debug("Server listening on "+DEFAULT_PORT);
- }
整合过程
与Spring框架整合,我们需要以下操作:
- 设置IO handler
- 创建Filters并将它们加入到Filter链
- 创建Socket并设置相关参数
注意 :如同MINA之前的release,最近release的版本中并没有Spring特定的包,目前这个包叫做Integration Beans,它用来实现与所有的DI框架整合而不仅限于Spring。
让我们看一下Spring的xml文件。我删除了通用部分,只保留了与我们实现整合相关的内容。这个例子脱胎于MINA实例中的Chat应用,请参考该实例中完整的xml文件。现在我们开始整合,首先是定义IO Handler:
- <!-- The IoHandler implementation -->
- <bean id="trapHandler" class="com.ashishpaliwal.udp.mina.server.ServerHandler" />
然后创建Filter链:
- <bean id="snmpCodecFilter" class="org.apache.mina.filter.codec.ProtocolCodecFilter">
- <constructor-arg>
- bean class="com.ashishpaliwal.udp.mina.snmp.SNMPCodecFactory" />
- </constructor-arg>
- </bean>
- <bean id="loggingFilter" class="org.apache.mina.filter.logging.LoggingFilter" />
- <!-- The filter chain. -->
- <bean id="filterChainBuilder" class="org.apache.mina.core.filterchain.DefaultIoFilterChainBuilder">
- <property name="filters">
- <map>
- <entry key="loggingFilter" value-ref="loggingFilter"/>
- <entry key="codecFilter" value-ref="snmpCodecFilter"/>
- </map>
- </property>
- </bean>
这里,我们创佳了自己的IoFilter实例。对于ProtocolCodec来说,注入SNMPCodecFactory时我们使用了构造注入。Logging Filter是被直接创建的,没有注入其他属性。一旦我们定义了所有filters的bean定义,我们就可以将它们组装成Filter链。我们定义一个id为FilterChainBuidler的bean,然后将定义好的filter bean注入其中。万事俱备了,我们只差创建Socket并调用bind()方法。
让我们完成最后一部分,创建Socket并使用Filter链:
- <bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
- <property name="customEditors">
- <map>
- <entry key="java.net.SocketAddress">
- <bean class="org.apache.mina.integration.beans.InetSocketAddressEditor" />
- </entry>
- </map>
- </property>
- </bean>
- <!-- The IoAcceptor which binds to port 161 -->
- <bean id="ioAcceptor" class="org.apache.mina.transport.socket.nio.NioDatagramAcceptor" init-method="bind" destroy-method="unbind">
- <property name="defaultLocalAddress" value=":161" />
- <property name="handler" ref="trapHandler" />
- <property name="filterChainBuilder" ref="filterChainBuilder" />
- </bean>
我们创建了ioAcceptor,注入了IO handler和Filter链。现在我们需要编写一个方法去读取Spring的xml文件并启动应用,代码如下:
- public void initializeViaSpring() throws Exception {
- new ClassPathXmlApplicationContext("trapReceiverContext.xml");
- }
现在我们需要从main方法运行程序,MINA应用便可以初始化。