介紹
本教程通過建構一個時間伺服器(time server),向你展示基于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平台上測試了這個程式,如果你在運作這個程式的過程中遇到了問題,請立即聯系我們的開發人員。當然,本教程盡量保持與開發環境(IDE, editors等等)無關,它可以在任何你熟悉的平台中運作。另外,為了簡化,編譯指令與運作腳本都沒有展現,如果你需要幫助了解如何編譯并運作java程式,請參考Java tutorial。
編寫MINA時間伺服器
首先建立一個名為MinaTimeServer.java的檔案,最初的代碼如下:
public class MinaTimeServer {
public static void main(String[] args) {
// code will go here next
}
}
對所有人來說,這段代碼應該很簡單,我們隻是簡單的定義了一個main方法啟動這個程式。從現在開始,我們将逐漸加入代碼實作時間伺服器。首先,我們需要一個對象用來監聽到來的連接配接,既然我們的程式是基于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之後,我們可以繼續定義一個handler類,并将NioSocketAcceptor綁定到一個端口上。
下面,我們在配置中添加一個filter,這個filter将記錄所有的日志資訊,例如session建立,消息接收,消息發送,消息關閉。下一個filter是ProtocolCodecFilter,其把二進制資料或是協定相關的資料轉換成為一個消息對象,反之亦然。我們使用一個現有的TextLine工廠類處理基于文本的消息(你不必自己實作編解碼部分)。
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" ))));
}
}
然後,我們定義handler,用來服務用戶端的連接配接和目前時間的請求。handler類必須實作 IoHandler接口。對于大多數基于MINA的應用程式,這是程式設計的重心,因為它将處理所有來自用戶端的請求。在本教程中,我們将繼承自IoHandlerAdapter類,這個類遵循擴充卡模式,其簡化實作IoHandler接口所帶來的代碼量。
import java.io.IOException;
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() );
}
}
現在,我們在NioSocketAcceptor中添加配置,這将應用于接收用戶端連接配接的socket設定。
import java.io.IOException;
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 );
}
}
在MinaTimeServer增加了兩行新内容,這些set方法分别設定了IoHandler、輸入buffer大小和session對象上的idle屬性。Buffer大小告訴底層作業系統應該配置設定多大空間放置到來的資料;第二行指明什麼時候應該檢測session空閑。在setIdleTime方法中,第一參數定義當session空閑時,檢測那些活動,第二個參數定義session變為空閑狀态時需要經過多長的時間。
handler的代碼如下:
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。在handler中,應該總是定義 exceptionCaught方法,該方法用來處理在處理遠端連接配接的過程中發生的各種異常,如果此方法沒有定義,可能無法獲得正确的異常報告。
exceptionCaught方法隻是簡單地列印出異常堆棧資訊并關閉連接配接,對于大多數程式來說,這是标準做法,除非連接配接可以在異常情況下恢複。
messageReceived方法接收用戶端的資料并傳回目前的系統時間,如果來自用戶端的消息為‘quit’,則session關閉。根據協定編解碼器的不同,傳入該方法的對象(第二個參數)也是不同的,該對象與調用session.write(Object)方法傳入的對象相同。如果你沒有指定協定編解碼器,你可能接收到一個IoBuffer對象,當然,必須輸出IoBuffer對象。
當session保持指定(acceptor.getSessionConfig().setIdleTime( IdleStatus.BOTH_IDLE, 10 )設定的)時間的空閑時,sessionIdle方法将被調用。
剩下的就是定義server監聽的位址和端口了,當然還需要啟動伺服器。代碼如下:
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) );
}
}
正如你看到的, acceptor.setLocalAddress( new InetSocketAddress(PORT) ),方法定義server将監聽哪個主機和端口。最後調用IoAcceptor.bind()方法,該方法綁定端口并啟動遠端用戶端的處理過程。
驗證Time server
現在編譯程式,編譯完成後就可以運作并檢視運作結果了。最簡單的測試方式就是啟動程式,并使用telnet與之建立連接配接:
[email protected]:~> 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. [email protected]:~> |