现在已经是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() );
for( int 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’示例。