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 |
[email protected]:~> 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