Mina文檔 02-基礎
基礎
在第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
Mina文檔 02-基礎