[译]Apache MINA 2.0 第二章 基础知识

来源:互联网 发布:网店数据化分析的内容 编辑:程序博客网 时间:2024/04/30 12:01

[译]Apache MINA 2.0 第二章 基础知识

只看楼主 发言
 
     在第一章中,我们有一个短暂的Apache MINA。在这一章里,我们将看看客户端/服务器体系结构和细节工作在一个基于MINA的服务器和客户端。
     我们也会基于TCP和UDP展示一些非常简单的服务器和客户端。
 
MINA的基础应用程序体系结构
     被问得最多的问题:“基于MINA的应用程序看起来像什么?在本文中可以看到MINA基于应用程序的体系架构。试图从演示基于MINA收集信息。
     架构鸟榄图
图片
     在这里,我们可以看到MINA是应用程序(客户端或服务器)和底部网络层之间的胶水。可以基于TCP、UDP、in-VM通讯或甚至一个对客户端的RS-232C串行协议。
     你只需要在MINA之上设计你的应用程序,而不必处理所有复杂性网络层。
     现在让我们深入讨论细节。下面的图片显示了一点MINA的内部,和MINA的每个组件在干什么:
图片
(上图来自 Emmanuel Lécharny 简报 现实中的 MINA (ApacheCon EU 2009)【http://mina.apache.org/staticresources/pdfs/Mina_in_real_life_ASEU-2009.pdf】)
     概括来讲,基于MINA的应用程序划分为三个层次:
     #)I/O 服务 - 执行实际的I/O
     #)I/O 过滤器链 - 过滤器/字节转换成所需的数据结构,反之亦然
     #)I/O 处理 - 在这里存放实际的业务逻辑
     所以,为了创建一个基于MINA的应用程序,你必须:
     1、创建一个I/O服务 - 选择已经可用的服务(*Acceptor)或创建你自己的
     2、创建一个过滤器链 - 选择已经存在的过滤器或创建一个自定义过滤器改变请求/响应
     3、创建一个I/O处理程序 - 编写业务逻辑,在处理不同的消息
     差不多就是这些。
 
     你可以通过阅读下面两点得到一些深入信息:
#)服务器架构
     我们已经在前一节中展示了MINA的应用程序架构。现在让我们关注服务器架构。基本上,服务器监听一个端口获得传入的请求,处理它们并发送回复。它还为每个客户端创建和处理会话(无论是基于TCP或UDP协议),这将在《Apache MINA 2.0 用户指南》第四章 Session 中进一步讲解。
图片
 
#)IOAcceptor监听网络用于获取连接或者包
#)对于一个新的连接,创建一个新会话,后续所有从IP地址/端口组合请求都会在同一个会话中处理
#)所有会话接收的数据包,通过上图所示指定的过滤器链。过滤器可以用来修改数据包的内容(如转换成对象,添加/删除信息等)。从原始字节转换为高级对象的功能,PacketEncoder/Decoder是特别有用。
#)最后在IOHandler中包或转换对象的地方。IOHandlers可以用来满足业务需求。
 
会话创建
     每当客户端连接MINA上的服务器时,我们将创建一个新的会话存储持久数据。即使协议还未连接上,这将创建会话。下面的模式显示了MINA如何处理传入的连接:(注:官方图裂了,后期在跟进补上)
 
传入的消息处理
     现在我们将解释MINA处理传入消息。
     假设已经创建一个会话,任何新传入消息将导致一个选择器被唤醒。
 
#)客户端架构
     我们对基于MINA服务器架构有了一个大概的了解,现在我们再来看看客户端的样子。客户端需要连接到服务器,发送消息并处理响应。
图片
 
#)客户端首先创建一个IOConnector(MINA构造连接套接字),启动一个服务器的绑定
#)在创建一个连接时,会创建一个会话并与连接相关联
#)应用程序/客户端写入会话,结果数据会被发送到服务器,之后遍历过滤器链
#)所有从服务器接收到的响应/消息都会先遍历过滤器链和IOHandler部分进行处理
     当然,MINA提供的不仅仅如此,而你很可能将不得不注意其他方面内容,像信息编码/解码、网络配置如何扩大,等等……我们将在以后得几章中进一步了解。
 
简单的TCP服务器
     本教程将指导你完成构建一个基于MINA的程序的过程。本教程将介绍构建一个时间服务器。本教程需要以下先决条件:
     #)MINA 2.x 核心库
     #)JDK 1.5 或者更高版本
     #)SLF4J 1.3.0 或者更高版本
          *)Log4J 1.2 使用者: slf4j-api.jar, slf4j-log4j12.jar, 以及 Log4J 1.2.x
          *)Log4J 1.3 使用者: slf4j-api.jar, slf4j-log4j13.jar, 以及 Log4J 1.3.x
          *)java.util.logging 使用者: slf4j-api.jar 和 slf4j-jdk14.jar
          *)重要: 请确保你使用的 slf4j-*.jar 能够正确的匹配你的日志框架
     例如: slf4j-log4j12.jar 和 log4j-1.3.x.jar 就不能一起使用,并且将会报错。
 
     这个程序已经在Window@ 2000 专业版 和 linux 下进行测试。如果你让这个程序工作还有任何问题,请毫不犹豫到MINA的[联系我们|通讯录]清单中直接与MINA开发者交流。此外,本教程一直试图保持独立的开发环境(IDE、编辑器...等等)。本教程将工作在你认为满意的任何环境中。编译命令和执行步骤简洁的程序已被移除。如果你需要帮助学习如何执行编译java程序,请参阅java教程:http://docs.oracle.com/javase/tutorial/
 
编写MINA时间服务器
     我们将首先创建一个名为 MinaTimeServer.java的文件。最初的代码如下:
 public class MinaTimeServer{
     public static void main(String[] args) {
          // code will go here next
     }
 }
 
     这段代码应该是最简单的。我们只是定义一个主方法,将用于启动程序。在这此基础上,我们将开始添加代码来完成我们的服务器。首先,我们需要一个对象,该对象将用于监听传入的链接。因为这个项目将基于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类之后,我们可以继续定义处理类和将端口绑定到NioSocketAcceptor。
import java.net.InetSocketAddress;

import org.apache.mina.core.service.IoAcceptor;
import org.apache.mina.transport.socket.nio.NioSocketAcceptor;

public class MinaTimeServer
{
    private static final int PORT = 9123;
    public static void main( String[] args ) throws IOException
    {
        IoAcceptor acceptor = new NioSocketAcceptor();
        acceptor.bind( new InetSocketAddress(PORT) );
    }
}
     如你所见,有一个叫acceptor.setLocalAddress(new InetSocketAddress(PORT));。该方法定义了这个服务器将监听主机和端口。最后一个方法调用IoAcceptor.bind()。该方法将绑定到指定的端口,并开始处理远程客户端。
 
     接下来我们向配置添加一个过滤器。这个过滤器将记录所有信息,如新创建的会话,接收到的消息,消息发送,关闭的会话。下一个过滤器是ProtocolCodecFilter。这个过滤器将二进制或协议特定数据转化为消息对象,反之亦然。我们使用现有的TextLine 工厂,因为它将为你处理文本基础信息(你不必编写编解码器部分)
import java.io.IOException;
import java.net.InetSocketAddress;
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" ))));
        acceptor.bind( new InetSocketAddress(PORT) );
    }
}
 
     在这点上,我们将定义被用于客户端连接和请求当前时间的处理程序。这个处理程序类必须实现IoHandler接口。几乎所有的程序使用MINA,这将成为该计划的主力,因为它服务所有从客户端传入的请求。对于本教程,我们将拓展IoHandlerAdapter类。这是一个遵循适配器模式的类,简化了需要编写的代码量,以满足传递一个实现了IoHandler接口的类的要求。
 
import java.net.InetSocketAddress;
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() );
        acceptor.bind( new InetSocketAddress(PORT) );
    }
}
 
     现在,我们将在NioSocketAcceptor添加配置。这将允许我们做出套接字特殊设置的套接字,将被用来接受来自客户端的链接。
import java.net.InetSocketAddress;
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() );
        acceptor.getSessionConfig().setReadBufferSize( 2048 );
        acceptor.getSessionConfig().setIdleTime( IdleStatus.BOTH_IDLE, 10 );
        acceptor.bind( new InetSocketAddress(PORT) );
    }
}
 
     在MinaTimeServer类中有两个新行。这些方法设置IoHandler设置,输入缓冲区大小和空闲会话的属性。将告诉底层的操作系统为传入的数据分配多少空间,指定缓冲区大小。第二行来检查时将指定空闲会话。在调用setIdleTime方法中,第一个参数定义了确定是否空闲会话时的检查行为,第二个参数定义了以秒为单位的时间,必须发生在一个被认为是闲置的会话中。
 
     处理程序的代码如下所示:
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。exceptionCaught总是应该定义在一个远程连接的处理程序中来处理正常和异常。如果没有定义这个方法,异常可能不会得到正确的反馈。
     exceptionCaught方法只会打印堆栈中的错误和关闭会话。对于大多数程序,这将是标准做法,除非处理程序能有从异常处理中恢复的条件。
     messageReceived方法将接收来自客户端的数据和写回客户端当前时间。取决于你所使用的协议编解码器,该对象(第二个参数)被传递给这个方法会有所不同,以及你传入的对象session.write(Object)方法。如果你不指定协议编解码器,你很可能会收到一个IoBuffer对象,并且会被要求写出一个IoBuffer对象。
     sessionIdle 方法将指定调用一次会话保持空闲的时间
acceptor.getSessionConfig().setIdleTime( IdleStatus.BOTH_IDLE, 10 );
 
     剩下要做的就是定义服务器将监听的套接字地址,和实际上调用启动服务器。代码如下所示:
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.charset.Charset;

import org.apache.mina.core.service.IoAcceptor;
import org.apache.mina.core.session.IdleStatus;
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
{
    private static final int PORT = 9123;
    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() );
        acceptor.getSessionConfig().setReadBufferSize( 2048 );
        acceptor.getSessionConfig().setIdleTime( IdleStatus.BOTH_IDLE, 10 );
        acceptor.bind( new InetSocketAddress(PORT) );
    }
}
试用时间服务器
     在这一点上,我们可以继续编译程序。你将程序编译完成就可以运行程序以测试会发生什么。最简单的测试程序的方法就是启动程序,然后telnet程序:
客户端输出服务器输出user@myhost:~>telnet 127.0.0.1 9123
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
hello
Mon Apr 09 23:42:55 EDT 2007
quit
Connection closed by foreign host.
user@myhost:~>MINA Time server started.
Session created...
Message written...
 
下一步?
     请访问我们的文档页面找到更多资源。你也可以继续阅读其他教程。
简单的TCP客户端
     我们已经了解了客户端架构。可以探索一个示例客户端实现。
     我们将使用总结客户端[http://mina.apache.org/mina-project/xref/org/apache/mina/example/sumup/Client.html]作为一个参考实现。
     我们将删除生硬的样板代码并且专注于其重要的结构上。下面的客户端代码:
public static void main(String[] args) throws Throwable {
    NioSocketConnector connector = new NioSocketConnector();
    connector.setConnectTimeoutMillis(CONNECT_TIMEOUT);

    if (USE_CUSTOM_CODEC) {
    connector.getFilterChain().addLast("codec",
        new ProtocolCodecFilter(new SumUpProtocolCodecFactory(false)));
    } else {
        connector.getFilterChain().addLast("codec",
            new ProtocolCodecFilter(new ObjectSerializationCodecFactory()));
    }

    connector.getFilterChain().addLast("logger", new LoggingFilter());
    connector.setHandler(new ClientSessionHandler(values));
    IoSession session;

    for (;;) {
        try {
            ConnectFuture future = connector.connect(new InetSocketAddress(HOSTNAME, PORT));
            future.awaitUninterruptibly();
            session = future.getSession();
            break;
        } catch (RuntimeIoException e) {
            System.err.println("Failed to connect.");
            e.printStackTrace();
            Thread.sleep(5000);
        }
    }

    // wait until the summation is done
    session.getCloseFuture().awaitUninterruptibly();
    connector.dispose();
}
 
构建一个客户端,我们需要做的如下
     #)创建一个连接器
     #)创建一个过滤器链
     #)创建一个IOHandler并增加连接器
     #)绑定到服务器
让我们来检测每一个细节
创建一个连接器
     NioSocketConnector connector = new NioSocketConnector();
     这里我们创建一个NIO套接字链接器
 
创建一个过滤器链
if (USE_CUSTOM_CODEC) {
    connector.getFilterChain().addLast("codec",
        new ProtocolCodecFilter(new SumUpProtocolCodecFactory(false)));
} else {
    connector.getFilterChain().addLast("codec",
        new ProtocolCodecFilter(new ObjectSerializationCodecFactory()));
}
我们把过滤器添加到连接器的过滤器链。在这里我们已经添加了一个ProtocolCodec到过滤器链。
创建IOHandler
connector.setHandler(new ClientSessionHandler(values));
这里我们创建一个ClientSessionHandler的实例作为连接器处理程序。[http://mina.apache.org/mina-project/xref/org/apache/mina/example/sumup/ClientSessionHandler.html]
 
绑定到服务器
IoSession session;

for (;;) {
    try {
        ConnectFuture future = connector.connect(new InetSocketAddress(HOSTNAME, PORT));
        future.awaitUninterruptibly();
        session = future.getSession();
        break;
    } catch (RuntimeIoException e) {
        System.err.println("Failed to connect.");
        e.printStackTrace();
        Thread.sleep(5000);
    }
}
 
这是最重要的东西。我们连接到远程服务器。由于连接是一个异步任务,我们使用ConnectFuture[http://mina.apache.org/mina-project/xref/org/apache/mina/core/future/ConnectFuture.html]类知道连接完成。连接完成后,我们就得到了IoSession[http://mina.apache.org/mina-project/xref/org/apache/mina/core/session/IoSession.html]有关信息。向服务器发送任何消息,我们都得写入会话。所有从服务器发出的响应/消息都会遍历过滤器链,最后应在IoHandler处理。
 
简单的UDP服务器
     我们现在来看一下org.apache.mina.example.udp包[http://mina.apache.org/mina-project/xref/org/apache/mina/example/udp/package-summary.html]中的代码。简单来说,我们只专注于MINA相关构造。
构建服务器,我们必须做到以下几点:
     1)创建数据套接字监听客户端连接请求(参见MemoryMonitor.java [http://mina.apache.org/mina-project/xref/org/apache/mina/example/udp/MemoryMonitor.html])
     2)创建一个IoHandler处理MINA框架生成的事件(见MemoryMonitorHandler.java[http://mina.apache.org/mina-project/xref/org/apache/mina/example/udp/MemoryMonitorHandler.html])
这是第一点的片段
NioDatagramAcceptor acceptor = new NioDatagramAcceptor();
acceptor.setHandler(new MemoryMonitorHandler(this));
 
     在这里,我们创建一个NioDatagramAcceptor侦听传入的客户端请求,并设置IoHandler。变量“PORT”只是一个int。下一步是将要使用的DatagramAcceptor过滤器链添加一个日志过滤器。LoggingFilter在MINA运行中是一个非常好的方法。它在不同阶段生成的日志语句,可以用来观察MINA是如何工作的。
DefaultIoFilterChainBuilder chain = acceptor.getFilterChain();
chain.addLast("logger", new LoggingFilter());
 
接下来我们进入一些关于UDP流量更具体的代码。我们将设置acceptor可复用的地址
DatagramSessionConfig dcfg = acceptor.getSessionConfig();
dcfg.setReuseAddress(true);acceptor.bind(new InetSocketAddress(PORT));
当然这里所需要的最后一件事是调用bind()。
IoHandler实现
     对我们服务器的实现有三个主要的事件
     1)会话创建
     2)消息接收
     3)会话关闭
我们分别看看它们的具体细节
 
会话创建事件
@Override
public void sessionCreated(IoSession session) throws Exception {
    SocketAddress remoteAddress = session.getRemoteAddress();
    server.addClient(remoteAddress);
}
在会话创建事件中,我们调用addClient()函数,它为界面内增加了一个选项卡
 
消息接收事件
@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());
    }
}
 
在消息接收事件中,我们仅转换在消息中收到的数据。应用程序需要发送的响应,可以在这个方法中处理消息并写入响应到会话。
 
会话关闭事件
@Override
public void sessionClosed(IoSession session) throws Exception {
    System.out.println("Session closed...");
    SocketAddress remoteAddress = session.getRemoteAddress();
    server.removeClient(remoteAddress);
}
在会话关闭,我们只是从UI选项卡删除客户端
 
简单的UDP客户端
     了解UDP服务器后,接下来让我们看看UDP客户端代码。
     实现客户端我们需要做的:
     #)创建套接字并连接到服务器
     #)设置IoHandler
     #)收集空闲内存
     #)发送数据到服务器
     我们现在查看一下在org.apache.mina.example.udp.client包下面的MemMonClient.java[http://mina.apache.org/mina-project/xref/org/apache/mina/example/udp/client/MemMonClient.html]文件。前几行代码简单明了。
connector = new NioDatagramConnector();
connector.setHandler( this );
ConnectFuture connFuture = connector.connect( new InetSocketAddress("localhost", MemoryMonitor.PORT ));
 
     这里我们创建一个NioDatagramConnector,设置处理器并连接到服务器。我掉入的陷阱是,您必须在InetSocketAddress对象中设置主机,否则它将不会工作。这个例子主要是编写和测试在Windows XP机器上,所以事情可能会有所不同。接下来,我们将等待确认客户端已经连接到服务器。一旦我们知道我们连接了,我们可以开始写数据到服务器。这是代码:
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对象,并且当我们收到一个客户端连接的回调,我们将开始写数据。写入的数据到服务器将由一个名为sendData的方法处理。这种方法如下所示:
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秒内每一秒向服务器发送一次空闲内存的数量。在这里你可以看到,我们分配了一个足够大的IoBuffer来保存一个long类型变量,并将空闲内存数量放入缓存。这个缓存立即写入到服务器。
我们的UDP客户端实现完成。
总结
     在这一章,我们了解了基于MINA的应用程序架构,客户端以及服务器。我们还实现了简单的TCP服务器/客户端,和UDP服务器和客户端。

在接下来的章节我们将讨论MINA核心结构和高级的主题

上一章
【译】Apache MINA 2.0 第一章 入门指南
======================================================
欢迎转载,转载请保留出处!
:看完以上内容,请通帖子底部的三种方式表达你此时的感受:分享给你的小伙伴发言给评价。你的支持,我的动力。
译者:ヅ虫虫ゎ
翻译时间:2014-01-24 19:02
原译文:http://mina.apache.org/mina-project/userguide/ch2-basics/ch2-basics.html
======================================================
0 0