MINA Tutorial 中文版: A Date with MINA
作者: TrustinLee
译者: Donald
该手册为使用中的用户而写,用户可随意改进该手册。
目录
-
MINA Tutorial 中文版: A Date with MINA
-
目录
-
概述
-
MINA是什么?
-
I/O 层: 编写一个 Echo Server
-
IoSession
-
IoHandler
-
实现 IoHandler 以及启动代码
-
添加IoFilters
-
协议层: 实现反转Echo协议
-
ProtocolSession
-
ProtocolHandler
-
ProtocolEncoder 和 ProtocolDecoder
-
实现 ProtocolHandler
-
实现 ProtocolProvider 以及启动代码
-
添加 ProtocolFilters
-
高级主题
-
ByteBuffers
-
ByteBuffer 池
-
线程模式
-
更复杂的协议支持
-
VM 内部管道通讯
-
How to Contribute
-
How to Contact Us
-
How to Report Bugs
-
How Issues
-
Acknowledgements
概述
现在已经是World Wide Web的时代,无数的web应用框架被创造出来从而大大的提高了web开发的速度。抛开WWW的这个优势,我们知道还有很多协议是HTTP协议所无法替代的。有时,我们仍然需要构造c/s应用来实现适当的协议。
MINA是什么?
你有没有曾经使用java或者其他语言实现过某个协议栈?就像你所经历过的那样,编写网络应用即使对于有经验的开发者也不是容易的事情。这归咎于以下几个方面:
MINA是一个网络应用框架,在不牺牲性能和可扩展性的前提下用于解决上面的所有问题。
I/O 层: 编写一个 Echo Server
MINA包含两层:IO层和协议层。我们首先仅使用IO层来实现一个echo服务,因为协议层通常是建立在IO层之上的。
上面的图展示了MINA的IO层同客户端的交互。IoAcceptor执行所有底层IO,将他们翻译成抽象的IO事件,并把翻译过的事件和关联的IoSession 发送给IoHandler。
IoSession
一个代表了IoSession程序同一个远程实体的IO连接。通过IoSession,你可以写出message到远程实体,访问session的配置,并且更改session的属性。
IoHandler
sessionCreated: 当一个IO连接建立时被调用,这个方法在任何IO操作之前被调用,以便socket参数或session属性能够最先被设置。
sessionOpened: 在sessionCreated调用之后被调用。
sessionClosed: 当IO连接被关闭时被调用。
sessionIdle: 当在远程实体和用户程序之间没有数据传输的时候被调用。
exceptionCaught: 当IoAcceptor 或者你的IoHandler.中出现异常时被调用。
dataRead: 当从远程实体读取数据时被调用。
dataWritten: 当你想远程实体发出请求时被调用
下面我们看看如何实现echo协议的IoHandler。
实现 IoHandler 以及启动代码
通常,应用需要继承IoHandlerAdapter并实现需要的方法:
package org.apache.mina.examples.echoserver;
import org.apache.mina.common.*;
import org.apache.mina.io.*;
public class EchoProtocolHandler extends IoHandlerAdapter
{
public void sessionCreated( IoSession session )
{
SessionConfig cfg = session.getConfig();
if( cfg instanceof SocketSessionConfig )
{
SocketSessionConfig scfg = ( SocketSessionConfig ) cfg ) ;
scfg.setSessionReceiveBufferSize( 2048 );
}
}
public void exceptionCaught( IoSession session, Throwable cause )
{
session.close();
}
public void dataRead( IoSession session, ByteBuffer rb )
{
// Write the received data back to remote peer
ByteBuffer wb = ByteBuffer.allocate( rb.remaining() );
wb.put( rb );
wb.flip();
session.write( wb, null );
}
}
刚刚我们使用MINA实现echo协议,现在我们将handler绑定到一个server端口上。
package org.apache.mina.examples.echoserver;
import org.apache.mina.common.*;
import org.apache.mina.io.*;
import org.apache.mina.io.filter.*;
import org.apache.mina.registry.*;
public class Main
{
/** Choose your favorite port number. */
private static final int PORT = 8080;
public static void main( String[] args ) throws Exception
{
ServiceRegistry registry = new SimpleServiceRegistry();
// Bind
Service service = new Service( "echo",
TransportType.SOCKET, PORT );
registry.bind( service, new EchoProtocolHandler() );
System.out.println( "Listening on port " + PORT );
}
}
添加IoFilters
IoFilter提供了更加有力的方式来扩展MINA。它拦截所有的IO事件进行事件的预处理和后处理。你可以把它想象成Servlet的filters。IoFilter能够实现以下几种目的:
事件日志
性能检测
数据转换(e.g. SSL support)
防火墙…等等
我们的echo协议handler不对任何IO事件进行日志。我们可以通过添加一个filter来增加日志能力。MINA提供了IoLoggingFilter来进行日志。我们只要添加日志filter到ServiceRegistry即可。
private static void addLogger( ServiceRegistry registry )
{
IoAcceptor acceptor =
registry.getIoAcceptor( TransportType.SOCKET );
acceptor.getFilterChain().addLast( "logger",
new IoLoggingFilter() );
System.out.println( "Logging ON" );
}
想使用SSL?MINA也提供了一个SSL的filter,但它需要JDK1.5。
private static void addSSLSupport( ServiceRegistry registry )
throws Exception
{
SSLFilter sslFilter =
new SSLFilter( BogusSSLContextFactory.getInstance( true ) );
IoAcceptor acceptor =
registry.getIoAcceptor( TransportType.SOCKET );
acceptor.getFilterChain().addLast( "sslFilter", sslFilter );
System.out.println( "SSL ON" );
}
协议层: 实现反转Echo协议
在上面我们通过简单的echo
server的例子学习了如何使用IO层,但是如果想实现复杂的如LDAP这样的协议怎么办呢?它似乎是一个恶梦,因为IO层没有帮助你分离
‘message解析’和‘实际的业务逻辑(比如访问一个目录数据库)’。MINA提供了一个协议层来解决这个问题。协议层将ByteBuffer事件转换成高层的POJO事件:
使用协议层必须实现5个接口:ProtocolHandler, ProtocolProvider, ProtocolCodecFactory, ProtocolEncoder, 和 ProtocolDecoder:
可能看上去有点麻烦,但是请注意ProtocolCodecFactory, ProtocolEncoder, 和 ProtocolDecoder是
可以完全复用的;Apache的ASN1项目为MINA提供了ASN.1解码器,更通用的解码器如:XML、java对象序列化和简单的文本将在MINA
的下一个版本中提供。一旦你实现了一个灵活的解码器,你可以在未来的应用中复用它,即使你不打算复用你的解码器,MINA也提供了一个很简单的方法来实现
复杂的协议。(请参考高级主题) 在这一章中,我们添加一个‘反转’server,它用于反转它接到的所有文本,我们通过它来示范如何编写一个协议层。
ProtocolSession
ProtocolSession同IO层的IoSession同样继承自Session。就像前面提到的,你只需撰写面向POJO的message而不是ByteBuffer的。ProtocolEncoder 将message对象解释成ByteBuffers以便IO层能够将他们输出到socket。
ProtocolHandler
ProtocolHandler类似于IO层的IoHandler.dataRead和dataWritten方法被替换成messageReceived和messageSent。这是因为ProtocolDecoder 已经将IO层接收到的 ByteBuffers转换成了message对象。
ProtocolEncoder 和 ProtocolDecoder
ProtocolEncoder 和ProtocolDecoder只有一个方法。ProtocolEncoder将message对象转换成一个ByteBuffer,而ProtocolDecoder将一个ByteBuffer转换成message对象。下面我们将学习如何实现这些接口。
实现 ProtocolHandler
让我们首先实现一个ProtocolHandler。如同刚才实现IoHandler那样,我们继承ProtocolHandlerAdapter:
package org.apache.mina.examples.reverser;
import org.apache.mina.protocol.*;
public class ReverseProtocolHandler extends ProtocolHandlerAdapter
{
public void exceptionCaught( ProtocolSession session,
Throwable cause )
{
// Close connection when unexpected exception is caught.
session.close();
}
public void messageReceived( ProtocolSession session,
Object message )
{
// Reverse reveiced string
String str = message.toString();
StringBuffer buf = new StringBuffer( str.length() );
for( int i = str.length() - 1; i >= 0; i-- )
{
buf.append( str.charAt( i ) );
}
// and write it back.
session.write( buf.toString() );
}
}
实现 ProtocolProvider 以及启动代码
要实现反转协议要实作的唯一接口就是ProtocolProvider。它非常简单: (注:Provider用于在一个统一的类中提供该协议相关的Handler、Decoder和Encoder。)
package org.apache.mina.examples.reverser;
import org.apache.mina.protocol.*;
/**
* {@link ProtocolProvider} implementation for reverser server protocol.
*/
public class ReverseProtocolProvider implements ProtocolProvider
{
// Protocol handler is usually a singleton.
private static ProtocolHandler HANDLER =
new ReverseProtocolHandler();
// Codec factory is also usually a singleton.
private static ProtocolCodecFactory CODEC_FACTORY =
new ProtocolCodecFactory()
{
public ProtocolEncoder newEncoder()
{
// Create a new encoder.
return new TextLineEncoder();
}
public ProtocolDecoder newDecoder()
{
// Create a new decoder.
return new TextLineDecoder();
}
};
public ProtocolCodecFactory getCodecFactory()
{
return CODEC_FACTORY;
}
public ProtocolHandler getHandler()
{
return HANDLER;
}
}
这样,反转协议就被完全实现了。启动的部分同echo server非常相似:
package org.apache.mina.examples.reverser;
import org.apache.mina.common.*;
import org.apache.mina.protocol.*;
import org.apache.mina.registry.*;
/**
* (<b>Entry point</b>) Reverser server which reverses all text lines from
* clients.
*
* @author Trustin Lee (trustin@apache.org)
* @version $Rev: 165594 $, $Date: 2005-05-02 16:21:22 +0900 $,
*/
public class Main
{
private static final int PORT = 8080;
public static void main( String[] args ) throws Exception
{
ServiceRegistry registry = new SimpleServiceRegistry();
// Bind
Service service = new Service( "reverse", TransportType.SOCKET, PORT );
registry.bind( service, new ReverseProtocolProvider() );
System.out.println( "Listening on port " + PORT );
}
}
添加 ProtocolFilters
ProtocolFilter 同IO层的IoFilter类似:
添加IoLoggingFilter来记录底层IO事件是为了debug。我们可以用ProtocolLoggingFilter代替它来记录高层事件:
private static void addLogger( ServiceRegistry registry )
{
ProtocolAcceptor acceptor = registry.getProtocolAcceptor( TransportType.SOCKET );
acceptor.getFilterChain().addLast( "logger", new ProtocolLoggingFilter() );
System.out.println( "Logging ON" );
}
高级主题
在这里我们为MINA的高端用户讲解一些高级话题。
ByteBuffers
MINA没有直接使用使用java NIO的ByteBuffer类。它使用一个自制的ByteBuffer来扩展java NIO ByteBuffer的功能。 以下是它们的一些区别:
MINA ByteBuffer是一个抽象类,用户可以自由的扩展它
MINA 管理 MINA ByteBuffers 并对其提供对象池. Users can control the point the buffers are released by providing acquire() and release() methods.
MINA ByteBuffer提供很多便利的方法,如:无符号数值的getter和基于String的getter和putter
如果你使用MINA,你将不需要直接使用NIO buffers,因为仅使用MINA buffers就可以完成大多数buffer操作。
ByteBuffer 池
MINA有一个全局的ByteBuffer池,它被在同一个虚拟机下的所有MINA应用共享。任何分配的buffers将在IO操作或者事件处理方法被执行之后被释放。所以你可以调用ByteBuffer.allocate()来从池中得到一个ByteBuffer而不需要将它返回到池中。请查阅ByteBuffer JavaDocs获得更多信息。
线程模式
MINA通过它灵活的filter机制来提供多种线程模型。没有线程池过滤器被使用时MINA运行在一个单线程模式。如果添加了一个IoThreadPoolFilter 到IoAcceptor,你将得到一个leader-follower模式的线程池。如果再添加一个ProtocolThreadPoolFilter,你的server将有两个线程池;一个(IoThreadPoolFilter)被用于对message对象进行转换,另外一个(ProtocolThreadPoolFilter)被用于处理业务逻辑。
SimpleServiceRegistry加上IoThreadPoolFilter和ProtocolThreadPoolFilter的缺省实现即可适用于需要高伸缩性的应用。如果你想使用自己的线程模型,请查看SimpleServiceRegistry的源代码,并且自己初始化Acceptor。显然,这是个繁琐的工作。
IoThreadPoolFilter threadPool = new IoThreadPoolFilter();
threadPool.start();
IoAcceptor acceptor = new SocketAcceptor();
acceptor.getFilterChain().addLast( "threadPool", threadPool );
ProtocolThreadPoolFilter threadPool2 = new ProtocolThreadPoolFilter();
threadPool2.start();
ProtocolAcceptor acceptor2 = new IoProtocolAcceptor( acceptor );
acceptor2.getFilterChain().addLast( "threadPool", threadPool2 );
...
threadPool2.stop();
threadPool.stop();
更复杂的协议支持
‘Reverser’示例相对于其他复杂的协议来说仍然过于简单。要想让一个server工作,仍然有许多message类型和它们的转换的工作需要作。MINA提供了一下工具类来提供帮助:
更多细节请参考 JavaDocs 。
VM 内部管道通讯
你一定已经知道协议层是建立在IO层之上的,但是有时也不一定。虽然我们通常使用协议层来包装IO层,但仍有一种特殊的协议层实现,称作:’
in-VM pipe communication’ 让我们假设你需要使用MINA实现一个SMTP server和一个Spam Filter
server。SMTP server可能需要同Spam Filter server通讯以便发现spam
message或者RBL中列出的客户端。如果这两个server是在同一个java虚拟机中,一个IO层是多余的,你可以绕过message对象的编解
码的过程。In-VM pipe communication可以使你使用同样的代码而不管spam filter
server是否在同一个虚拟机中。 请查看随源码分发的’ Tennis’示例。
How to Contribute
We want MINA to evolve actively, reacting to user requests, and
therefore we need as much feedback from you as possible. The Apache
Directory team will strive to satisfy all possible use cases of MINA.
Please feel free to contact us.
How to Contact Us
How to Report Bugs
You can report any bugs found from MINA to our issue tracker page.
Please attach any test cases that can reproduce your issue if possible.
How Issues
Any patches and comments are welcome! You can browse the list of unresolved issues in JIRA:
Or, you could do some performance benchmarks on MINA and tune it.
Acknowledgements
MINA couldn’t exist without strong support of many contributors:
The Apache Directory team for letting me join the team
All users of Netty2 forum and dev@directory.apache.org for great feedbacks
Jan Andersson and his team for SSLFilter
Vel Pandian for enabling client mode for SSLFilter
Vinod Panicker for performance benchmark and active feedbacks