搭建Apache Mina框架并实现Server与Client端的简单消息传递
http://www.himigame.com/apache-mina/831.html
:(作者新浪微博: @李華明Himi )
轉載自【黑米GameDev街區】 原文鏈接: http://www.himigame.com/apache-mina/831.html
?
Hibernate系列學習階段到此結束了,那么緊接著進入Apache Mina的開發學習,很多童鞋在微薄和QQ中疑問Himi為什么突然脫離游戲開發了,嘿嘿,其實可能更多的童鞋已經看出來了,Himi在偏向服務器Server端開發了,Hibernate、MySQL等都是為了Server端Mina開發而做的鋪墊,當前的Apache Mina才是Himi真正的目的。哈哈。Himi的技術目標是“一個人能做出一個網游~”,OK.不多說其他的了,開始Himi的Apache mina開發之旅吧。
對于Apache Mina不太連接的童鞋,請移步到如下百度百科連接進行學習了解:
http://baike.baidu.com/view/2668084.htm?
首先建立一個new project(Server端),這里Himi使用IDE是 eclipse;
OK,首先我們這里先配置下環境:對于Mina的日志輸出使用的是slf4j,對于slf4j在開發Hibernate的時候已經很熟悉了,不需要再介紹了。另外一方面就是加入mina的core核心jar包;
1. mina-core.jar ? ? ? ? 2. slf4j-api.jar ? ? ? ? 3.slf4j-simple.jar
然后我們首先創建兩個類:
HimiObject.java
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | /** ?* @author Himi ?*/ ?? import java.io.Serializable; ?? public class HimiObject implements Serializable{ ?? ????public HimiObject(int id,String name){ ????????this.id=id; ????????this.name=name; ????} ?? ????private int id; ?? ????private String name; ?? ????public int getId() { ????????return id; ????} ????public void setId(int id) { ????????this.id = id; ????} ????public String getName() { ????????return name; ????} ????public void setName(String name) { ????????this.name = name; ????} ?? } |
這個類是個消息Object,它用于server與client端的交互的數據,它需要序列化,所以我們使用Serializable接口;至于在mina框架中起到什么作用這個后續來說;
?
ClientMinaServerHanlder.java
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | /** ?* @author Himi ?*/ ?? import org.apache.mina.core.service.IoHandlerAdapter; import org.apache.mina.core.session.IdleStatus; import org.apache.mina.core.session.IoSession; ?? public class ClientMinaServerHanlder extends IoHandlerAdapter { ?? ????private int count = 0; ?? ????// 當一個新客戶端連接后觸發此方法. ????public void sessionCreated(IoSession session) { ????????System.out.println("新客戶端連接"); ????} ?? ????// 當一個客端端連結進入時 @Override ????public void sessionOpened(IoSession session) throws Exception { ????????count++; ????????System.out.println("第 " + count + " 個 client 登陸!address: : " ????????????????+ session.getRemoteAddress()); ?? ????} ?? ????// 當客戶端發送的消息到達時: ????@Override ????public void messageReceived(IoSession session, Object message) ????????????throws Exception { ????????// // 我們己設定了服務器解析消息的規則是一行一行讀取,這里就可轉為String: ????????// String s = (String) message; ????????// // Write the received data back to remote peer ????????// System.out.println("收到客戶機發來的消息: " + s); ????????// // 測試將消息回送給客戶端 session.write(s+count); count++; ?? ????????HimiObject ho = (HimiObject) message; ????????System.out.println(ho.getName()); ?? ????????ho.setName("serverHimi"); ????????session.write(ho); ?? ????} ?? ????// 當信息已經傳送給客戶端后觸發此方法. ????@Override ????public void messageSent(IoSession session, Object message) { ????????System.out.println("信息已經傳送給客戶端"); ?? ????} ?? ????// 當一個客戶端關閉時 ????@Override ????public void sessionClosed(IoSession session) { ????????System.out.println("one Clinet Disconnect !"); ????} ?? ????// 當連接空閑時觸發此方法. ????@Override ????public void sessionIdle(IoSession session, IdleStatus status) { ????????System.out.println("連接空閑"); ????} ?? ????// 當接口中其他方法拋出異常未被捕獲時觸發此方法 ????@Override ????public void exceptionCaught(IoSession session, Throwable cause) { ????????System.out.println("其他方法拋出異常"); ????} ?? } |
本類主要是繼承IoHandlerAdapter并且重寫其類的一些函數,至于每個函數的作用Himi都已經在代碼中加以注視;本類的作用:
此類是用以處理消息的也可說是個消息處理器,當客戶端有消息傳給server端的時候,或者server端傳遞給Client端的時候(Client端也會有個消息處理器)都會通過消息處理器進行處理。
OK,下面我們來書寫server端的main函數類:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | /** ?* @author Himi ?*/ ?? import java.io.IOException; import java.net.InetSocketAddress; ?? import org.apache.mina.core.filterchain.DefaultIoFilterChainBuilder; import org.apache.mina.filter.codec.ProtocolCodecFilter; import org.apache.mina.filter.codec.serialization.ObjectSerializationCodecFactory; import org.apache.mina.transport.socket.SocketAcceptor; import org.apache.mina.transport.socket.nio.NioSocketAcceptor; ?? public class MinaServer { ?? ????/** ?????* @param args ?????*/ ?? ????public static void main(String[] args) { ????????//創建一個非阻塞的server端Socket ,用NIO ????????SocketAcceptor acceptor = new NioSocketAcceptor(); ?? ????????/*---------接收字符串---------*/ //????? //創建一個接收數據過濾器 //????? DefaultIoFilterChainBuilder chain = acceptor.getFilterChain(); //????? //設定過濾器一行行(/r/n)的讀取數據 //????? chain.addLast("mychin", new ProtocolCodecFilter(new TextLineCodecFactory()?? )); ????????/*---------接收對象---------*/ ????????//創建接收數據的過濾器 ????????DefaultIoFilterChainBuilder chain = acceptor.getFilterChain(); ????????//設定這個過濾器將以對象為單位讀取數據 ????????ProtocolCodecFilter filter= new ProtocolCodecFilter(new ObjectSerializationCodecFactory()); ????????chain.addLast("objectFilter",filter); ?? ????????//設定服務器消息處理器 ????????acceptor.setHandler(new ClientMinaServerHanlder()); ????????//服務器綁定的端口 ????????int bindPort = 9988; ????????//綁定端口,啟動服務器 ????????try { ????????????acceptor.bind(new InetSocketAddress(bindPort)); ????????} catch (IOException e) { ????????????System.out.println("Mina Server start for error!"+bindPort); ????????????e.printStackTrace(); ????????} ????????System.out.println("Mina Server run done! on port:"+bindPort); ????} } |
IoService 是負責底層通訊接入,而 IoHandler 是負責業務處理的。那么 MINA 架構圖中的 IoFilter 作何用途呢?答案是你想作何用途都可以。但是有一個用途卻是必須的,那就是作為 IoService 和 IoHandler 之間的橋梁。IoHandler 接口中最重要的一個方法是 messageReceived,這個方法的第二個參數是一個 Object 型的消息,總所周知,Object 是所有 Java 對象的基礎,那到底誰來決定這個消息到底是什么類型呢?這個取決于我們后面設定的過濾器!??
對于在mina中建立一個server,步驟如下:
?1. 建立一個SockerAcceptor ,除了啟動server之外它還可以為我們可以生成過濾器DefaultIoFilterChainBuilder、設置消息處理器等功能;
? ? ? ? 2.設置過濾器
? ? ? ? 3. 設置消息處理器
其實很容易不是么? 哈哈;
OK,這里多說一些:
對于消息處理器?DefaultIoFilterChainBuilder,它的作用是用于設定收發的形式,例如:
//創建一個接收數據過濾器DefaultIoFilterChainBuilder chain = acceptor.getFilterChain();//設定過濾器一行行(/r/n)的讀取數據chain.addLast("mychin", new ProtocolCodecFilter(new TextLineCodecFactory() ));這樣設置一個過濾器作用是將來自客戶端輸入的信息轉換成一行行的文本后傳遞給 IoHandler,因此我們可以在 messageReceived 中直接將 msg 對象強制轉換成 String 對象。
ps.而如果我們不提供任何過濾器的話,那么在 messageReceived 方法中的第二個參數類型就是一個 byte 的緩沖區,對應的類是 org.apache.mina.common.ByteBuffer。雖然你也可以將解析客戶端信息放在 IoHandler 中來做,但這并不是推薦的做法,使原來清晰的模型又模糊起來,變得 IoHandler 不只是業務處理,還得充當協議解析的任務。
mina自身帶有一些常用的過濾器,例如LoggingFilter(日志記錄)、BlackListFilter(黑名單過濾)、CompressionFilter(壓縮)、SSLFilter(SSL加密)等。
當我們設置如下:
| 1 2 3 4 5 | //創建接收數據的過濾器 ????????DefaultIoFilterChainBuilder chain = acceptor.getFilterChain(); ????????//設定這個過濾器將以對象為單位讀取數據 ????????ProtocolCodecFilter filter= new ProtocolCodecFilter(new ObjectSerializationCodecFactory()); ????????chain.addLast("objectFilter",filter); |
這樣以來我們server可以收發Object對象啦;
| 1 | acceptor.setHandler(new ClientMinaServerHanlder()); |
這里是設置消息處理器,綁定在我們的ClientMinaServerHanlder類上,其實mina對于收發處理已經完全交給開發者來進行處理,所以至于在消息處理器如何編寫和處理就放任不會管了;
OK,現在我們可以run一下啟動server端了;
當然我們現在也可以來測試了,當前我們還沒有書寫Client端的代碼,所以我們使用terminal終端進行測試,OK,打開你的terminal
然后輸入 ?telnet localhost 9988
觀察服務器打印:
OK,沒有任何問題;但是這時候大家不要在終端書寫內容給server,因為我們server的消息處理器中的接受Client的函數中做了如下處理:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | // 當客戶端發送的消息到達時: ????@Override ????public void messageReceived(IoSession session, Object message) ????????????throws Exception { ????????// // 我們己設定了服務器解析消息的規則是一行一行讀取,這里就可轉為String: ????????// String s = (String) message; ????????// // Write the received data back to remote peer ????????// System.out.println("收到客戶機發來的消息: " + s); ????????// // 測試將消息回送給客戶端 session.write(s+count); count++; ?? ????????HimiObject ho = (HimiObject) message; ????????System.out.println(ho.getName()); ?? ????????ho.setName("serverHimi"); ????????session.write(ho); ?? ????} |
Himi這里server處理client端發來的數據處理函數(如上代碼)中,當Client發送數據過來的時候,將消息message強制轉換成了一個HimiObject對象,然后改個name重寫發給Client端,但是由于Client端是使用終端模擬登陸根本無法接受這個對象,所以會出異常;
但是到這里大家應該懂得,HimiObject類的作用了;哈哈
下面我們來書寫Client客戶端,對于建立一個Client端,其實步驟雷同,步驟如下:
? ? ? ? 1 . 建立一個NioSocketConnector對象;?
? ? ? ? 2. 設定過濾器
? ? ? ? 3. 設定消息處理器
代碼如下:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | /** ?* @author Himi ?*/ import java.net.InetSocketAddress; ?? import org.apache.mina.core.filterchain.DefaultIoFilterChainBuilder; import org.apache.mina.core.future.ConnectFuture; import org.apache.mina.filter.codec.ProtocolCodecFilter; import org.apache.mina.filter.codec.serialization.ObjectSerializationCodecFactory; import org.apache.mina.transport.socket.nio.NioSocketConnector; ?? public class MainClient { ????public static void main(String[] args) { ????????// 創建一個tcp/ip 連接 ????????NioSocketConnector connector = new NioSocketConnector(); ?? ????????/*---------接收字符串---------*/ ????????// //創建接收數據的過濾器 ????????// DefaultIoFilterChainBuilder chain = connector.getFilterChain(); ????????// // 設定這個過濾器將一行一行(/r/n)的讀取數據 ????????// chain.addLast("myChin", new ProtocolCodecFilter( ????????// new TextLineCodecFactory())); ????????/*---------接收對象---------*/ ????????// 創建接收數據的過濾器 ????????DefaultIoFilterChainBuilder chain = connector.getFilterChain(); ????????// 設定這個過濾器將以對象為單位讀取數據 ????????ProtocolCodecFilter filter = new ProtocolCodecFilter( ????????????????new ObjectSerializationCodecFactory()); ????????// 設定服務器端的消息處理器:一個SamplMinaServerHandler對象, ????????chain.addLast("objectFilter",filter); ?? ????????// 設定服務器端的消息處理器:一個 SamplMinaServerHandler 對象, ????????connector.setHandler(new ClientMinaServerHanlder()); ????????// Set connect timeout. ????????connector.setConnectTimeoutCheckInterval(30); ????????// 連結到服務器: ????????ConnectFuture cf = connector.connect(new InetSocketAddress("localhost", ????????????????9988)); ????????// Wait for the connection attempt to be finished. ????????cf.awaitUninterruptibly(); ????????cf.getSession().getCloseFuture().awaitUninterruptibly(); ????????connector.dispose(); ?? ????} } |
不多說了,很eazy:那么我們繼續看Clent端的消息處理器以及HimiObject:
ClentMinaServerHanlder:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | /** ?* @author Himi ?*/ ?? import org.apache.mina.core.service.IoHandlerAdapter; import org.apache.mina.core.session.IoSession; ?? public class ClientMinaServerHanlder extends IoHandlerAdapter { ????// 當一個客端端連結到服務器后 ????@Override ????public void sessionOpened(IoSession session) throws Exception { //????? session.write("我來啦........"); ????????HimiObject ho = new HimiObject(1,"Himi"); ????????session.write(ho); ????} ?? ????// 當一個客戶端關閉時 ????@Override ????public void sessionClosed(IoSession session) { ????????System.out.println("I'm Client &&? I closed!"); ????} ?? ????// 當服務器端發送的消息到達時: ????@Override ????public void messageReceived(IoSession session, Object message) ????????????throws Exception { //????? // 我們己設定了服務器解析消息的規則是一行一行讀取,這里就可轉為 String: //????? String s = (String) message; //????? // Write the received data back to remote peer //????? System.out.println("服務器發來的收到消息: " + s); //????? // 測試將消息回送給客戶端 session.write(s); ?? ????????HimiObject ho = (HimiObject) message; ????????System.out.println(ho.getName()); ?? ????} } |
Client的HimiObject與服務器Server的HimiObejct類一模一樣!
可能有童鞋不理解為什么server端與client的HimiObject一模一樣,這里Himi說下,通過Client端的消息處理器可以看出,當我們Client端連接到服務器后,首先會寫給Server端一個HimiObject對象!那么服務器之前說過了,接受到Client端的消息后首先將消息強制轉換成HimiObject對象然后處理;
既然Client端發的是Object,那么當然我們的服務器也要有對應的此Object對象才可以,否則如何獲取這個Object? ?大家應該很容易理解;
OK,不多說直接運行Client端,觀察結果:
?
OK,結果正常。
? ? Client與Server消息邏輯如下:
? ? 1. Client->傳遞HimiObject給Server
? ? ?2. Server端接受message強制轉換HimiObject,并且設置其name為serverHimi,然后再傳遞給Client
? ? 3. 客戶端接受message強制轉換HimiObject,然后獲取此類的name打印出來!
?
總結
以上是生活随笔為你收集整理的搭建Apache Mina框架并实现Server与Client端的简单消息传递的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: FIXML and FpML - Bac
- 下一篇: QuickFIX/N入门