本教程将引导创建一个基于MINA的程序实例,例子是一个时间服务器。
编写MINA时间服务器
以创建一个名为MinaTimeServer.java的源文件开始,初始代码如下:
public class MinaTimeServer{
public static void main(String[] args){
// code will go here next
}
}
这段代码对于所有人来说应该都是很明了的了,我们简单的定义了一个main方法用来启动这个程序,这时,我们就开始添加代码来构造我们的服务器。第一步,我们需要创建一个对象用来监听所有接入的连接。因为这个程序是基于TCP/UDP的,我们将添加一个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.bind( new InetSocketAddress(PORT) ),这个方法定义了这个服务将要监听的主机和端口,并开始接收传入的连接。
接下来,我们添加一个过滤器的配置,这个过滤器将记录所有的日志信息,如新创建了会话,接收消息,发送消息和会话关闭等。下一个过滤器是协议编解码过滤器ProtocolCodecFilter,这个过滤器可以将二进制文件或协议特定数据转换为消息对象,或进行反向转换。这里,我们使用了一个已存在的文本行转换工厂,它可以替你处理基于文本的消息转换(你不需要自己实现这个):
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进行一些特殊设置。
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的空闲时间属性。设置缓冲器的大小用来告诉底层操作系统在为传入的数据分配空间时,每次分配的大小。第二行的用来设置多久检查一次空闲session,其中,第一个参数定义了用什么策略来检查一个会话是否空闲,第二个参数定义了一个session闲置了多少秒时进行一次检查。
处理程序的代码如下:
import java.util.Date;importorg.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关闭,多数情况下,这是个标准的做法,除非处理程序可以做到从异常中恢复。
例子中的messageReceived方法将接收客户端数据进行处理并立刻返回给客户端数据。如果从客户端接收到单词“quit”,则会关闭该session,该方法依然会在客户端打印出系统当前时间。借助于所使用的协议编码解码器,该方法传递的对象(方法中的第二个参数Object message)和原来的相比会有所变化,同样,使用session.write(Object)方法进行传递的对象也会是不同的。如果不指定一个协议编解码器,你接收到的将很有可能是一个IoBuffer对象,同时,你也必须返回一个IoBuffer对象。
sessionIdle方法会在一个session闲置了acceptor.getSessionConfig().setIdleTime( IdleStatus.BOTH_IDLE, 10 )中定义的时间后被调用。
最后要做的是定义服务器要监听的socket地址,并通过调用主方法启动服务器,该代码如下所示:
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 Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is '^]'. hello Wed Oct 17 23:23:36 EDT 2007 quit Connection closed by foreign host. user@myhost:~> | MINA Time server started. Message written... |