现在已经是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;
 
 
import org.apache.mina.common.ByteBuffer;
 
import org.apache.mina.common.IoHandlerAdapter;
 
import org.apache.mina.common.IoSession;
 
import org.apache.mina.common.TransportType;
 
import org.apache.mina.transport.socket.nio.SocketSessionConfig;
 
 
public class EchoProtocolHandler extends IoHandlerAdapter
 
{
     
public void sessionCreated( IoSession session )
     
{
         
if (session.getTransportType() == TransportType.SOCKET) {
           ((SocketSessionConfig)session.getConfig()).setReceiveBufferSize(
2048);
     }

 
     
public void exceptionCaught( IoSession session, Throwable cause )
     
{
         session.close();
     }

 
     
public void messageReceived( IoSession session, Object message )
     
{
         
if (!(message instanceof ByteBuffer))
             
return;
      
         ByteBuffer rb 
= (ByteBuffer)message;
         
// Write the received data back to remote peer
         ByteBuffer wb = ByteBuffer.allocate( rb.remaining() );
         wb.put( rb );
         wb.flip();
         session.write( wb );
     }

 }

  刚刚我们使用MINA实现echo协议,现在我们将handler绑定到一个server端口上。
import java.net.InetSocketAddress;
 
import org.apache.mina.transport.socket.nio.SocketAcceptor;
 
import org.apache.mina.transport.socket.nio.SocketAcceptorConfig;
 
 
public class Main
 
{
     
/** Choose your favorite port number. */
     
private static final int PORT = 8080;
     
 
     
public static void main( String[] args ) throws Exception
     
{
         SocketAcceptor acceptor 
= new SocketAcceptor();
         SocketAcceptorConfig defaultConfig 
= new SocketAcceptorConfig();
         defaultConfig.setReuseAddress(
true);
      
         
// Bind
         acceptor.bind(new InetSocketAddress(PORT), new EchoProtocolHandler(), defaultConfig);
 
         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();
 addLogger(chain);
 .
 
 
private static void addLogger( DefaultIoFilterChainBuilder chain ) throws Exception
 
{
     chain.addLast( 
"logger"new LoggingFilter() );
 }


 想使用SSL?MINA也提供了一个SSL的filter,但它需要JDK1.5。
 .
 DefaultIoFilterChainBuilder chain 
= config.getFilterChain();
 addLogger(chain);
 .
 
 
private static void addSSLSupport( DefaultIoFilterChainBuilder chain )
     
throws Exception
 
{
     SSLFilter sslFilter 
=
         
new SSLFilter( BogusSSLContextFactory.getInstance( true ) );
     chain.addLast( 
"sslFilter", sslFilter );
     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;
 
 
import org.apache.mina.common.IoHandler;
 
import org.apache.mina.common.IoHandlerAdapter;
 
import org.apache.mina.common.IoSession;
 
 
public class ReverseProtocolHandler extends IoHandlerAdapter
 
{
     
public void exceptionCaught( IoSession session, Throwable cause )
     
{
         
// Close connection when unexpected exception is caught.
         session.close();
     }

 
     
public void messageReceived( IoSession session, Object message )
     
{
         
// Reverse received string
         String str = message.toString();
         StringBuffer buf 
= new StringBuffer( str.length() );
         
forint i = str.length()  1; i >= 0; i )
         
{
             buf.append( str.charAt( i ) );
         }

 
         
// and write it back.
         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;
 
 
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: 20050502 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类似:
 attachment:Arch4.gif
 
 添加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" );
     }


=== 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();
 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提供了一下工具类来提供帮助:
 
   * 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’示例。