在第一章中,我们对 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))
概况来讲,基于 MINA 的应用划分为三个层次:
服务器端架构
前面我们披露了基于 MINA 的应用架构。现在我们来关注一下服务器端架构。从根本上说,服务器端监听一个端口以获得连入的请求,将其进行处理然后发送回复。服务器端还会为每个客户端 (无论是基于 TCP 还是基于 UDP 协议的) 创建并维护一个 session,这在第四章 《Apache MINA 2.0 用户指南》第四章:会话 中将进行进一步解释。
客户端架构
前面我们对基于 MINA 的服务端架构有了一个大体认识,现在我们看一下客户端的情况。客户端需要连接到一个服务端,发送消息并处理响应。
TCP 服务器示例
接下来的教程介绍构建基于 MINA 的应用的过程。这个教程介绍的是构建一个时间服务器。本教程需要以下先决条件:
public class MinaTimeServer {
public static void main(String[] args) {
// code will go here next
}
}
这段程序对所有人来说都很简单明了。我们简单定义了一个用于启动程序的 main 方法。现在,我们开始添加组成我们服务器的代码。首先,我们需要一个用于监听连入的连接的对象。因为本程序基于 TCP/IP,我们在程序中添加了 SocketAcceptor。
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() 调用。这个方法将会绑定到指定端口并开始处理远程客户端请求。
接下来我们在配置中添加一个过滤器。这个过滤器将会日志记录所有信息,比如 session 的新建、接收到的消息、发送的消息、session 的关闭。接下来的过滤器是一个 ProtocolCodecFilter。这个过滤器将会把二进制或者协议特定的数据翻译为消息对象,反之亦然。我们使用一个现有的 TextLine 工厂因为它将为你处理基于文本的消息 (你无须去编写 codec 部分)。
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 中的配置进行添加。这将允许我们为用于接收客户端连接的 socket 进行socket 特有的设置。
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,为 session 设置了输入缓冲区大小以及 idle 属性。指定缓冲区大小以通知底层操作系统为传入的数据分配多少空间。第二行指定了什么时候检查空闲 session。在对 setIdleTime 的调用中,第一个参数定义了再断定 session 是否闲置时要检查的行为,第二个参数定义了在 session 被视为空闲之前以毫秒为单位的时间长度内必须发生。
处理器代码如下所示:
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 方法将会对错误和 session 关闭的 stack trace 进行简单打印。对于更多的程序,这将是常规,除非处理器能够从异常情况下进行恢复。
messageReceived 方法会从客户端接收数据并将当前时间回写给客户端。如果接收自客户端的消息是单词 "quit",那么当前 session 将被关闭。这一方法也会向客户端打印输出当前时间。取决于你所使用的协议编解码器,传递到这一方法的对象 (第二个参数) 会有所不同,就和你传给 session.write(Object) 方法的对象一样。如果你不定义一个协议编码器,你很可能会接收到一个 IoBuffer 对象,而且被要求写出一个 IoBuffer 对象。
一旦 session 保持空闲状态到达 acceptor.getSessionConfig().setIdleTime( IdleStatus.BOTH_IDLE, 10 ); 所定义的时间长度,sessionIdle 方法会被调用。
剩下的工作就是定义服务器端将要监听的套接字地址,并进行启动服务的调用。代码如下所示:
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 <br>Trying 127.0.0.1...<br>Connected to 127.0.0.1. <br>Escape character is '^]'. <br>hello <br>Wed Oct 17 23:23:36 EDT 2007 <br>quit <br>Connection closed by foreign host. <br>user@myhost:~> | MINA Time server started. <br>Message written... |
接下来是什么?
请访问我们的文档页面以查找更多资源。当然你也可以继续去阅读其他教程。
TCP 客户端示例
在上文中我们已经了解了客户端架构。现在我们将披露一个客户端实现的示例。
我们将使用 Sumup Client 作为一个参考实现。
我们将移除掉样板代码并专注于重要结构上。以下是为客户端代码:
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();
}
要构建一个客户端,我们需要做以下事情:
NioSocketConnector connector = new NioSocketConnector();
这里我们创建了一个 NIO 套接字 connector。
创建一个 Filter Chain
if (USE_CUSTOM_CODEC) {
connector.getFilterChain().addLast("codec",
new ProtocolCodecFilter(new SumUpProtocolCodecFactory(false)));
} else {
connector.getFilterChain().addLast("codec",
new ProtocolCodecFilter(new ObjectSerializationCodecFactory()));
}
我们为 Connector 的 Filter Chain 添加了一些过滤器。这里我们添加的是一个 ProtocolCodec。
创建 IOHandler
connector.setHandler(new ClientSessionHandler(values));
这里我们创建了一个 ClientSessionHandler 的实例并将其设置为 Connector 的处理器。
绑定到服务器
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 来了解何时连接结束。一旦连接结束,我们将得到相关联的 IoSession。要向服务器端发送任何消息,我们都要写入 session。所有来自服务器端的响应或者消息都将穿越 Filter chain 并最终由 IoHandler 处理。
UDP 服务器端示例
现在我们看一下 org.apache.mina.example.udp 包里的代码。简单起见,我们将只专注于 MINA 相关构建方面的东西。
要构建服务器我们需要做以下事情:
1. 创建一个 Datagram Socket 以监听连入的客户端请求 (参考 MemoryMonitor.java)
2. 创建一个 IoHandler 以处理 MINA 框架生成的事件 (参考 MemoryMonitorHandler.java)
这里是第 1 点提到的一些代码片段:
NioDatagramAcceptor acceptor = new NioDatagramAcceptor();
acceptor.setHandler(new MemoryMonitorHandler(this));
这里我们创建了一个 NioDatagramAcceptor 以监听连入的客户端请求,并设置了 IoHandler。"PORT" 是一整型变量。下一步将要为这一 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 实现
对于我们服务器实现有三个主要事件:
@Override
public void sessionCreated(IoSession session) throws Exception {
SocketAddress remoteAddress = session.getRemoteAddress();
server.addClient(remoteAddress);
}
在这个 session 创建事件中,我们调用了 addClient() 方法,它为界面添加了一个选项卡。
Message 收到事件
@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());
}
}
在这个消息接收到事件中,我们对接收到的消息中的数据进行了处理。需要发送返回的应用,可以在这个方法中处理消息并回写响应。
Session 关闭事件
@Override
public void sessionClosed(IoSession session) throws Exception {
System.out.println("Session closed...");
SocketAddress remoteAddress = session.getRemoteAddress();
server.removeClient(remoteAddress);
}
在 Session 关闭事件中,我们将客户端选项卡从界面中移除。
UDP 客户端示例
前面我们讲了 UDP 服务器端示例,现在我们来看一下与之对应的客户端代码。
客户端的实现需要做的事情:
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 类型变量,然后将空闲内存的数量放进缓存。缓冲随即写给服务器端。
本章总结
在本章中,我们了解了基于 MINA 的客户端以及服务器端的应用架构。我们还涉及到 TCP 客户端/服务器端、UDP 客户端和服务器端的演示例子。
在接下来的几章中我们将讨论 MINA 的核心结构以及一些高级主题。
原文链接: http://mina.apache.org/mina-project/userguide/ch2-basics/ch2-basics.html。
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://defonds.blog.csdn.net/article/details/17996123
内容来源于网络,如有侵权,请联系作者删除!