基础
在第1章中,我们简要介绍了Apache MINA。在本章中,我们将了解客户端/服务器体系结构以及有关基于MINA的服务器和客户端的详细信息。
我们还将基于TCP和UDP公开一些非常简单的服务器和客户端实现。
基于MINA的应用程序架构
最常问的问题是:“基于MINA的应用程序看起来如何”?在本文中,我们将了解基于MINA的应用程序的体系结构。试图从基于MINA的演示文稿中收集信息。
鸟瞰图:
在这里,我们可以看到MINA是您的应用程序(无论是客户端还是服务器)与底层网络层之间的粘合剂,它可以提供一个基于TCP,UDP,VM内通信甚至RS-232C串行协议的客户端。
您只需在MINA上设计应用程序,而无需处理newtork层的所有复杂性。
让我们现在深入了解细节。下图显示了MINA的内部结构,以及每个MINA组件的作用:
从广义上讲,基于MINA的应用程序分为3层
I / O服务 - 执行实际I / O.
I / O过滤器链 - 将字节过滤/转换为所需的数据结构,反之亦然
I / O处理程序 - 这里存在实际的业务逻辑
因此,为了创建基于MINA的应用程序,您必须:
创建I / O服务 - 从已有的服务(* Acceptor)中选择或创建自己的服务。
创建过滤器链 - 从现有过滤器中选择或创建自定义过滤器以转换请求/响应。
创建I / O处理程序 - 编写业务逻辑,处理不同的消息。
通过阅读下面这两页,您可以更深入了解:
当然,MINA提供的不仅仅是这些,而且您将需要处理许多方面,例如消息编码/解码,网络配置如何扩展等等......我们将进一步研究那些下一章的各个方面。
服务器架构
我们在上一节中介绍了MINA应用程序架构。现在让我们关注服务器架构。基本上,服务器在端口上侦听传入请求,处理它们并发送回复。它还为每个客户端创建和处理会话(每当我们有基于TCP或UDP的协议时),这将在第4章中进行更广泛的解释。
IOAcceptor在网络上侦听传入的连接/数据包
对于新连接,将创建一个新会话,并在该会话中处理来自IP地址/端口组合的所有后续请求
为会话接收的所有数据包都按照图中的指定遍历过滤器链。过滤器可用于修改数据包的内容(如转换为对象,添加/删除信息等)。为了转换为/从原始字节转换为高级对象,PacketEncoder / Decoder特别有用。
最后,数据包或转换后的对象登陆IOHandler。 IOHandlers可用于满足业务需求。
会话创建
每当客户端连接MINA服务器时,我们将创建一个新会话来将持久数据存储到其中。即使未连接协议,也会创建此会话。以下架构显示了MINA如何处理传入连接:
传入消息处理
我们现在将解释MINA如何处理传入的消息。
假设已创建会话,任何新的传入消息都将导致选择器被唤醒
客户端架构
我们简要介绍了基于MINA的服务器架构,让我们看看客户端的外观。客户端需要连接到服务器,发送消息并处理响应。
客户端首先创建一个IOConnector(用于连接到Socket的MINA Construct),启动与Server的绑定
创建连接后,将创建一个会话并与Connection关联
应用程序/客户端写入会话,导致数据在遍历过滤器链后发送到服务器
从服务器接收的所有响应/消息都遍历过滤器链并落在IOHandler处进行处理
简单的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不能一起使用,并且会出现故障。
我们已经在Windows©2000 professional和linux上测试了这个程序。如果您在使用此程序时遇到任何问题,请随时与我们联系,以便与MINA开发人员交流。此外,本教程还试图保持独立于开发环境(IDE,editors..etc)。本教程适用于您熟悉的任何环境。为简洁起见,已删除编译命令和执行程序的步骤。如果您需要帮助学习如何编译或执行Java程序,请参阅Java教程。
编写MINA时间服务器
我们将首先创建一个名为MinaTimeServer.java的文件。初始代码可以在下面找到:
public class MinaTimeServer {
public static void main(String[] args) {
// code will go here next
}
}
这段代码应该直截了当。我们只是定义了一个用于启动程序的主要方法。此时,我们将开始添加构成我们服务器的代码。首先,我们需要一个用于侦听传入连接的对象。由于该程序将基于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()。此方法将绑定到指定的端口并开始处理远程客户端。
接下来,我们在配置中添加一个过滤器。此过滤器将记录所有信息,例如新创建的会话,收到的消息,发送的消息,会话已关闭。下一个过滤器是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类中有2个新行。这些方法为会话设置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对象。
一旦会话在调用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到程序:
Client Output |
Server Output |
user@myhost:~> telnet 127.0.0.1 9123 |
MINA Time server started. |
简单的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();
}
要构建客户端,我们需要执行以下操作
创建一个Connector
创建过滤器链
创建IOHandler并添加到Connector
绑定到服务器
让我们详细检查每一个
创建连接器
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的一个实例,并将其设置为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);
}
}
这是最重要的东西。我们连接到远程服务器。因为,connect是一个异步任务,我们使用ConnectFuture类来了解连接何时完成。连接完成后,我们将获得相关的IoSession。要将任何消息发送到服务器,我们必须写入会话。来自服务器的所有响应/消息都应遍历Filter链,最后在IoHandler中处理。
简单的UDP服务端
我们将首先查看org.apache.mina.example.udp包中的代码。为了简化,我们只关注与MINA相关的结构。
要构建服务器,我们必须执行以下操作:
1.创建数据报套接字以侦听传入的客户端请求(请参阅MemoryMonitor.java)
2.创建一个IoHandler来处理MINA框架生成的事件(参见MemoryMonitorHandler.java)
这是解决第一点的代码片段:
NioDatagramAcceptor acceptor = new NioDatagramAcceptor();
acceptor.setHandler(new MemoryMonitorHandler(this));
在这里,我们创建一个NioDatagramAcceptor来监听传入的客户端请求,并设置IoHandler。变量'PORT'只是一个int。下一步是将日志记录筛选器添加到此DatagramAcceptor将使用的筛选器链中。 LoggingFilter是一个非常好的方式来查看MINA in Action。它在各个阶段生成日志语句,提供对MINA如何工作的深入了解。
DefaultIoFilterChainBuilder chain = acceptor.getFilterChain();
chain.addLast("logger", new LoggingFilter());
接下来,我们将介绍UDP流量的一些更具体的代码。我们将设置接受器以重用该地址
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()函数,该函数在内部向UI添加Tab
消息接收事件
@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);
}
在Session Closed,事件中,我们只是从UI中删除Client选项卡
简单的UDP客户端
让我们看一下上一节中UDP服务器的客户端代码。
要实现客户端,我们需要执行以下操作:
1.创建套接字并连接到服务器
2.设置IoHandler
3.收集空闲内存
4.将数据发送到服务器
我们将首先查看org.apache.mina.example.udp.client java包中的MemMonClient.java文件。代码的前几行简单明了。
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来保存一个长变量,然后将可用内存量放在缓冲区中。然后翻转此缓冲区并将其写入服务器。
我们的UDP客户端实现已完成。
总结
在本章中,我们研究了基于MINA的应用程序架构,用于客户端和服务器。我们还讨论了示例TCP服务器/客户端以及UDP服务器和客户端的实现。
在接下来的章节中,我们将讨论MINA核心构造和高级主题
参考 : http://mina.apache.org/mina-project/userguide/ch2-basics/ch2-basics.html
转载于:https://www.cnblogs.com/fubinhnust/p/9940956.html