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
/**
* @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
/**
* @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函數類:
/**
* @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加密)等。
當我們設置如下:
//創建接收數據的過濾器
DefaultIoFilterChainBuilder chain = acceptor.getFilterChain();
//設定這個過濾器將以對象爲單位讀取數據
ProtocolCodecFilter filter= new ProtocolCodecFilter(new ObjectSerializationCodecFactory());
chain.addLast("objectFilter",filter);
這樣以來我們server可以收發Object對象啦;
acceptor.setHandler(new ClientMinaServerHanlder());
這裏是設置消息處理器,綁定在我們的ClientMinaServerHanlder類上,其實mina對於收發處理已經完全交給開發者來進行處理,所以至於在消息處理器如何編寫和處理就放任不會管了;
OK,現在我們可以run一下啓動server端了;
當然我們現在也可以來測試了,當前我們還沒有書寫Client端的代碼,所以我們使用terminal終端進行測試,OK,打開你的terminal
然後輸入 telnet localhost 9988
觀察服務器打印:
OK,沒有任何問題;但是這時候大家不要在終端書寫內容給server,因爲我們server的消息處理器中的接受Client的函數中做了如下處理:
// 當客戶端發送的消息到達時:
@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. 設定消息處理器
代碼如下:
/**
* @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:
/**
* @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打印出來!