现在已经是World Wide Web的时代,无数的web应用框架被创造出来从而大大的提高了web开发的速度。抛开WWW的这个优势,我们知道还有很多协议是HTTP协议所无法替代的。有时,我们仍然需要构造c/s应用来实现适当的协议。
 === MINA是什么? ===
 你有没有曾经使用java或者其他语言实现过某个协议栈?就像你所经历过的那样,编写网络应用即使对于有经验的开发者也不是容易的事情。这归咎于以下几个方面:
 
   * 没有为开发者设计的合适的网络应用框架. 
     * 使你无法在有限的时间内创建你的应用. 
   * 网络I/O编码,消息的编/解码,业务逻辑常常纠缠在一起. 
     * 使程序失去可维护性和可复用性 
   * 网络应用难于进行单元测试 
     * 你失去了敏捷性 
 
 MINA是一个网络应用框架,在不牺牲性能和可扩展性的前提下用于解决上面的所有问题。
 == I/O 层: 编写一个 Echo Server ==
 MINA包含两层:IO层和协议层。我们首先仅使用IO层来实现一个echo服务,因为协议层通常是建立在IO层之上的。 
 attachment:Arch1.gif
 上面的图展示了MINA的IO层同客户端的交互。IoAcceptor执行所有底层IO,将他们翻译成抽象的IO事件,并把翻译过的事件和关联的IoSession
发送给IoHandler。
 === IoSession ===
 attachment:IoSession.gif
  一个代表了IoSession程序同一个远程实体的IO连接。通过IoSession,你可以写出message到远程实体,访问session的配置,并且更改session的属性。
 === IoHandler === 
 attachment:IoHandler.gif
   * sessionCreated: 当一个IO连接建立时被调用,这个方法在任何IO操作之前被调用,以便socket参数或session属性能够最先被设置。
   * sessionOpened: 在sessionCreated调用之后被调用。
   * sessionClosed: 当IO连接被关闭时被调用。
   * sessionIdle: 当在远程实体和用户程序之间没有数据传输的时候被调用。
   * exceptionCaught: 当IoAcceptor 或者你的IoHandler.中出现异常时被调用。
   * messageReceived: 当接收到新的协议消息时被调用。可以在这里实现你的控制流程。
   * messageSent: 当用户请求的消息通过 IoSession#write(Object) 确实发送后被调用。
 
 下面我们看看如何实现echo协议的IoHandler。
 === 实现 IoHandler 以及启动代码 ===
 通常,应用需要继承IoHandlerAdapter并实现需要的方法:
 
 package org.apache.mina.examples.echoserver;
package org.apache.mina.examples.echoserver;
 
 
 import org.apache.mina.common.ByteBuffer;
 import org.apache.mina.common.ByteBuffer;
 import org.apache.mina.common.IoHandlerAdapter;
 import org.apache.mina.common.IoHandlerAdapter;
 import org.apache.mina.common.IoSession;
 import org.apache.mina.common.IoSession;
 import org.apache.mina.common.TransportType;
 import org.apache.mina.common.TransportType;
 import org.apache.mina.transport.socket.nio.SocketSessionConfig;
 import org.apache.mina.transport.socket.nio.SocketSessionConfig;
 
 
 public class EchoProtocolHandler extends IoHandlerAdapter
 public class EchoProtocolHandler extends IoHandlerAdapter

 
  {
{
 public void sessionCreated( IoSession session )
     public void sessionCreated( IoSession session )

 
      {
{

 if (session.getTransportType() == TransportType.SOCKET)
         if (session.getTransportType() == TransportType.SOCKET)  {
{
 ((SocketSessionConfig)session.getConfig()).setReceiveBufferSize(2048);
           ((SocketSessionConfig)session.getConfig()).setReceiveBufferSize(2048);
 }
     }
 
 
 public void exceptionCaught( IoSession session, Throwable cause )
     public void exceptionCaught( IoSession session, Throwable cause )

 
      {
{
 session.close();
         session.close();
 }
     }
 
 
 public void messageReceived( IoSession session, Object message )
     public void messageReceived( IoSession session, Object message )

 
      {
{
 if (!(message instanceof ByteBuffer))
         if (!(message instanceof ByteBuffer))
 return;
             return;
 
      
 ByteBuffer rb = (ByteBuffer)message;
         ByteBuffer rb = (ByteBuffer)message;
 // Write the received data back to remote peer
         // Write the received data back to remote peer
 ByteBuffer wb = ByteBuffer.allocate( rb.remaining() );
         ByteBuffer wb = ByteBuffer.allocate( rb.remaining() );
 wb.put( rb );
         wb.put( rb );
 wb.flip();
         wb.flip();
 session.write( wb );
         session.write( wb );
 }
     }
 }
 }
  刚刚我们使用MINA实现echo协议,现在我们将handler绑定到一个server端口上。
 import java.net.InetSocketAddress;
import java.net.InetSocketAddress;
 import org.apache.mina.transport.socket.nio.SocketAcceptor;
 import org.apache.mina.transport.socket.nio.SocketAcceptor;
 import org.apache.mina.transport.socket.nio.SocketAcceptorConfig;
 import org.apache.mina.transport.socket.nio.SocketAcceptorConfig;
 
 
 public class Main
 public class Main

 
  {
{

 /** *//** Choose your favorite port number. */
     /** *//** Choose your favorite port number. */
 private static final int PORT = 8080;
     private static final int PORT = 8080;
 
     
 
 
 public static void main( String[] args ) throws Exception
     public static void main( String[] args ) throws Exception

 
      {
{
 SocketAcceptor acceptor = new SocketAcceptor();
         SocketAcceptor acceptor = new SocketAcceptor();
 SocketAcceptorConfig defaultConfig = new SocketAcceptorConfig();
         SocketAcceptorConfig defaultConfig = new SocketAcceptorConfig();
 defaultConfig.setReuseAddress(true);
         defaultConfig.setReuseAddress(true);
 
      
 // Bind
         // Bind
 acceptor.bind(new InetSocketAddress(PORT), new EchoProtocolHandler(), defaultConfig);
         acceptor.bind(new InetSocketAddress(PORT), new EchoProtocolHandler(), defaultConfig);
 
 
 System.out.println( "Listening on port " + PORT );
         System.out.println( "Listening on port " + PORT );
 }
     }
 }
 }
=== 添加IoFilters ===
 IoFilter提供了更加有力的方式来扩展MINA。它拦截所有的IO事件进行事件的预处理和后处理。你可以把它想象成Servlet的filters。IoFilter能够实现以下几种目的:
 
   * 事件日志
   * 性能检测
   * 数据转换(e.g. SSL support)
   * 防火墙…等等
 attachment:Arch2.gif
 我们的echo协议handler不对任何IO事件进行日志。我们可以通过添加一个filter来增加日志能力。MINA提供了IoLoggingFilter来进行日志。我们只要添加日志filter到ServiceRegistry即可。
 
 
  .
.
 DefaultIoFilterChainBuilder chain = config.getFilterChain();
 DefaultIoFilterChainBuilder chain = config.getFilterChain();
 addLogger(chain);
 addLogger(chain);
 
  .
.
 
 
 private static void addLogger( DefaultIoFilterChainBuilder chain ) throws Exception
 private static void addLogger( DefaultIoFilterChainBuilder chain ) throws Exception

 
  {
{
 chain.addLast( "logger", new LoggingFilter() );
     chain.addLast( "logger", new LoggingFilter() );
 }
 }
 想使用SSL?MINA也提供了一个SSL的filter,但它需要JDK1.5。
 
  .
.
 DefaultIoFilterChainBuilder chain = config.getFilterChain();
 DefaultIoFilterChainBuilder chain = config.getFilterChain();
 addLogger(chain);
 addLogger(chain);
 
  .
.
 
 
 private static void addSSLSupport( DefaultIoFilterChainBuilder chain )
 private static void addSSLSupport( DefaultIoFilterChainBuilder chain )
 throws Exception
     throws Exception

 
  {
{
 SSLFilter sslFilter =
     SSLFilter sslFilter =
 new SSLFilter( BogusSSLContextFactory.getInstance( true ) );
         new SSLFilter( BogusSSLContextFactory.getInstance( true ) );
 chain.addLast( "sslFilter", sslFilter );
     chain.addLast( "sslFilter", sslFilter );
 System.out.println( "SSL ON" );
     System.out.println( "SSL ON" );
 }
 }
== 协议层: 实现反转Echo协议 ==
 在上面我们通过简单的echo server的例子学习了如何使用IO层,但是如果想实现复杂的如LDAP这样的协议怎么办呢?它似乎是一个恶梦,因为IO层没有帮助你分离‘message解析’和‘实际的业务逻辑(比如访问一个目录数据库)’。MINA提供了一个协议层来解决这个问题。协议层将ByteBuffer事件转换成高层的POJO事件:
 
 attachment:Arch3.gif
 
 使用协议层必须实现5个接口:ProtocolHandler, ProtocolProvider, ProtocolCodecFactory,
ProtocolEncoder, 和 ProtocolDecoder:
 
 attachment:ProtocolClasses.gif
 
 可能看上去有点麻烦,但是请注意ProtocolCodecFactory, ProtocolEncoder, 和
ProtocolDecoder是可以完全复用的;Apache的ASN1项目为MINA提供了ASN.1解码器,更通用的解码器如:XML、java对象序列化和简单的文本将在MINA的下一个版本中提供。一旦你实现了一个灵活的解码器,你可以在未来的应用中复用它,即使你不打算复用你的解码器,MINA也提供了一个很简单的方法来实现复杂的协议。(请参考高级主题)
 在这一章中,我们添加一个‘反转’server,它用于反转它接到的所有文本,我们通过它来示范如何编写一个协议层。
 === ProtocolSession ===
 attachment:ProtocolSession.gif
 ProtocolSession同IO层的IoSession同样继承自Session。就像前面提到的,你只需撰写面向POJO的message而不是ByteBuffer的。ProtocolEncoder
将message对象解释成ByteBuffers以便IO层能够将他们输出到socket。
 === ProtocolHandler ===
 ProtocolHandler类似于IO层的IoHandler.dataRead和dataWritten方法被替换成messageReceived和messageSent。这是因为ProtocolDecoder
已经将IO层接收到的 ByteBuffers转换成了message对象。
 package org.apache.mina.examples.reverser;
package org.apache.mina.examples.reverser;
 
 
 import org.apache.mina.common.IoHandler;
 import org.apache.mina.common.IoHandler;
 import org.apache.mina.common.IoHandlerAdapter;
 import org.apache.mina.common.IoHandlerAdapter;
 import org.apache.mina.common.IoSession;
 import org.apache.mina.common.IoSession;
 
 
 public class ReverseProtocolHandler extends IoHandlerAdapter
 public class ReverseProtocolHandler extends IoHandlerAdapter

 
  {
{
 public void exceptionCaught( IoSession session, Throwable cause )
     public void exceptionCaught( IoSession session, Throwable cause )

 
      {
{
 // Close connection when unexpected exception is caught.
         // Close connection when unexpected exception is caught.
 session.close();
         session.close();
 }
     }
 
 
 public void messageReceived( IoSession session, Object message )
     public void messageReceived( IoSession session, Object message )

 
      {
{
 // Reverse received string
         // Reverse received string
 String str = message.toString();
         String str = message.toString();
 StringBuffer buf = new StringBuffer( str.length() );
         StringBuffer buf = new StringBuffer( str.length() );
 for( int i = str.length()  1; i >= 0; i )
         for( int i = str.length()  1; i >= 0; i )

 
          {
{
 buf.append( str.charAt( i ) );
             buf.append( str.charAt( i ) );
 }
         }
 
 
 // and write it back.
         // and write it back.
 session.write( buf.toString() );
         session.write( buf.toString() );
 }
     }
 }
 }
 === ProtocolEncoder 和 ProtocolDecoder ===
 attachment:ProtocolCodec.gif
 ProtocolEncoder 和ProtocolDecoder只有一个方法。ProtocolEncoder将message对象转换成一个ByteBuffer,而ProtocolDecoder将一个ByteBuffer转换成message对象。下面我们将学习如何实现这些接口。要实现反转协议要实作的唯一接口就是ProtocolProvider。它非常简单:
 (注:Provider用于在一个统一的类中提供该协议相关的Handler、Decoder和Encoder。)
 package org.apache.mina.examples.reverser;
package org.apache.mina.examples.reverser;
 
 
 import org.apache.mina.protocol.*;
 import org.apache.mina.protocol.*;
 
 

 /** *//**
 /** *//**
 * {@link ProtocolProvider} implementation for reverser server protocol.
  * {@link ProtocolProvider} implementation for reverser server protocol.
 */
 */
 public class ReverseProtocolProvider implements ProtocolProvider
 public class ReverseProtocolProvider implements ProtocolProvider

 
  {
{
 // Protocol handler is usually a singleton.
     // Protocol handler is usually a singleton.
 private static ProtocolHandler HANDLER =
     private static ProtocolHandler HANDLER =
 new ReverseProtocolHandler();
 new ReverseProtocolHandler();
 
 
 // Codec factory is also usually a singleton.
     // Codec factory is also usually a singleton.
 private static ProtocolCodecFactory CODEC_FACTORY =
     private static ProtocolCodecFactory CODEC_FACTORY =
 new ProtocolCodecFactory()
 new ProtocolCodecFactory()

 
      {
{
 public ProtocolEncoder newEncoder()
         public ProtocolEncoder newEncoder()

 
          {
{
 // Create a new encoder.
             // Create a new encoder.
 return new TextLineEncoder();
             return new TextLineEncoder();
 }
         }
 
 
 public ProtocolDecoder newDecoder()
         public ProtocolDecoder newDecoder()

 
          {
{
 // Create a new decoder.
             // Create a new decoder.
 return new TextLineDecoder();
             return new TextLineDecoder();
 }
         }
 };
     };
 
 
 public ProtocolCodecFactory getCodecFactory()
     public ProtocolCodecFactory getCodecFactory()

 
      {
{
 return CODEC_FACTORY;
         return CODEC_FACTORY;
 }
     }
 
 
 public ProtocolHandler getHandler()
     public ProtocolHandler getHandler()

 
      {
{
 return HANDLER;
         return HANDLER;
 }
     }
 }
 }
这样,反转协议就被完全实现了。启动的部分同echo server非常相似:
 package org.apache.mina.examples.reverser;
package org.apache.mina.examples.reverser;
 
 
 import org.apache.mina.common.*;
 import org.apache.mina.common.*;
 import org.apache.mina.protocol.*;
 import org.apache.mina.protocol.*;
 import org.apache.mina.registry.*;
 import org.apache.mina.registry.*;
 
 

 /** *//**
 /** *//**
 * (<b>Entry point</b>) Reverser server which reverses all text lines from
  * (<b>Entry point</b>) Reverser server which reverses all text lines from
 * clients.
  * clients.
 *
  * 
 * @author Trustin Lee (trustin@apache.org)
  * @author Trustin Lee (trustin@apache.org)
 * @version $Rev: 165594 $, $Date: 20050502 16:21:22 +0900 $,
  * @version $Rev: 165594 $, $Date: 20050502 16:21:22 +0900 $,
 */
  */
 public class Main
 public class Main

 
  {
{
 private static final int PORT = 8080;
     private static final int PORT = 8080;
 
 
 public static void main( String[] args ) throws Exception
     public static void main( String[] args ) throws Exception

 
      {
{
 ServiceRegistry registry = new SimpleServiceRegistry();
         ServiceRegistry registry = new SimpleServiceRegistry();
 
 
 // Bind
         // Bind
 Service service = new Service( "reverse", TransportType.SOCKET, PORT );
         Service service = new Service( "reverse", TransportType.SOCKET, PORT );
 registry.bind( service, new ReverseProtocolProvider() );
         registry.bind( service, new ReverseProtocolProvider() );
 
 
 System.out.println( "Listening on port " + PORT );
         System.out.println( "Listening on port " + PORT );
 }
     }
 }
 }
 === 添加 ProtocolFilters ==
 ProtocolFilter 同IO层的IoFilter类似:
 attachment:Arch4.gif
 
 添加IoLoggingFilter来记录底层IO事件是为了debug。我们可以用ProtocolLoggingFilter代替它来记录高层事件:
 private static void addLogger( ServiceRegistry registry )
 private static void addLogger( ServiceRegistry registry )

 
      {
{
 ProtocolAcceptor acceptor = registry.getProtocolAcceptor( TransportType.SOCKET );
         ProtocolAcceptor acceptor = registry.getProtocolAcceptor( TransportType.SOCKET );
 acceptor.getFilterChain().addLast( "logger", new ProtocolLoggingFilter() );
         acceptor.getFilterChain().addLast( "logger", new ProtocolLoggingFilter() );
 System.out.println( "Logging ON" );
         System.out.println( "Logging ON" );
 }
     }
=== 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,你将得到一个leaderfollower模式的线程池。如果再添加一个ProtocolThreadPoolFilter,你的server将有两个线程池;一个(IoThreadPoolFilter)被用于对message对象进行转换,另外一个(ProtocolThreadPoolFilter)被用于处理业务逻辑。
 
 SimpleServiceRegistry加上IoThreadPoolFilter和ProtocolThreadPoolFilter的缺省实现即可适用于需要高伸缩性的应用。如果你想使用自己的线程模型,请查看SimpleServiceRegistry的源代码,并且自己初始化Acceptor。显然,这是个繁琐的工作。
 IoThreadPoolFilter threadPool = new IoThreadPoolFilter();
IoThreadPoolFilter threadPool = new IoThreadPoolFilter();
 threadPool.start();
 threadPool.start();
 
 
 IoAcceptor acceptor = new SocketAcceptor();
 IoAcceptor acceptor = new SocketAcceptor();
 acceptor.getFilterChain().addLast( "threadPool", threadPool );
 acceptor.getFilterChain().addLast( "threadPool", threadPool );
 
 
 ProtocolThreadPoolFilter threadPool2 = new ProtocolThreadPoolFilter();
 ProtocolThreadPoolFilter threadPool2 = new ProtocolThreadPoolFilter();
 threadPool2.start();
 threadPool2.start();
 
 
 ProtocolAcceptor acceptor2 = new IoProtocolAcceptor( acceptor );
 ProtocolAcceptor acceptor2 = new IoProtocolAcceptor( acceptor );
 acceptor2.getFilterChain().addLast( "threadPool", threadPool2 );
 acceptor2.getFilterChain().addLast( "threadPool", threadPool2 );
 
 
 
 
 
 
 threadPool2.stop();
 threadPool2.stop();
 threadPool.stop();
 threadPool.stop();
 === 更复杂的协议支持 ===
 
 ‘Reverser’示例相对于其他复杂的协议来说仍然过于简单。要想让一个server工作,仍然有许多message类型和它们的转换的工作需要作。MINA提供了一下工具类来提供帮助:
 
   * DemuxingProtocolHandler
   * DemuxingProtocolCodecFactory
 
 更多细节请参考 JavaDocs 。
 
 === VM 内部管道通讯 ===
 
 你一定已经知道协议层是建立在IO层之上的,但是有时也不一定。虽然我们通常使用协议层来包装IO层,但仍有一种特殊的协议层实现,称作:’
inVM pipe communication’
 让我们假设你需要使用MINA实现一个SMTP server和一个Spam Filter server。SMTP
server可能需要同Spam Filter server通讯以便发现spam message或者RBL中列出的客户端。如果这两个server是在同一个java虚拟机中,一个IO层是多余的,你可以绕过message对象的编解码的过程。InVM
pipe communication可以使你使用同样的代码而不管spam filter server是否在同一个虚拟机中。
 请查看随源码分发的’ Tennis’示例。