Android MINA框架之實戰總結(一) Mina連線,斷開,重連
(一). 前言
Apache MINA(Multipurpose Infrastructure for Network Applications) 是 Apache 組織一個較新的專案,它為開發高效能和高可用性的網路應用程式提供了非常便利的框架。當前發行的 MINA 版本支援基於 Java NIO 技術的 TCP/UDP 應用程式開發、串列埠通訊程式(只在最新的預覽版中提供),MINA 所支援的功能也在進一步的擴充套件中。
總之:我們簡單理解它是一個封裝底層IO操作,提供高階操作API的通訊框架!
(二). MINA體系結構
Mina鳥瞰圖
Mina位於使用者程式和網路處理之間,將使用者從複雜的網路處理中解耦,我們就可以更加關注業務領域。
Minamina元件結構圖
Mina框架被分成了主要的3個元件部分:
•I/O Service,具體提供服務的元件。
•I/O Filter Chain,過濾器鏈,用於支援各種切面服務。
•I/O Handler,用於處理使用者的業務邏輯。
相對應的,為了建立一個基於Mina的應用程式,我們需要:
•建立I/O Service :可選擇Mina提供的Services如(*Acceptor)或實現自己的Service。
•建立I/O Filter :同樣可以選擇Mina提供的各類filter,也可以實現自己的編解碼過濾器等。
•實現I/O Handler,實現Handler介面,處理各種訊息。
Mina服務端結構
服務端的作用就是開啟監聽埠,等待請求的到來、處理他們、以及將傳送對請求的響應。同時,服務端會為每個連線建立session,在session週期內提供各種精度的服務,比如連線建立時(sessionCreated(IoSession session))、連線等待時(sessionIdle(IoSession session, IdleStatus status))、連線銷燬時(sessionClosed(IoSession session))等。mina的api為TCP/UDP提供的一致性Server端操作。
•IOAcceptor 監聽來自網路的請求。
•當新的連線建立時,一個新的session會被建立,該session用作對同一IP/埠組合的客戶端提供服務。
•資料包需經過一系列的過濾器,這些過濾器可用來修改資料包的內容(如轉換物件、新增或修改資訊等),其中將原始位元組流轉換成POJO物件是非常有用的。當然這需要解編碼器提供支援。
•最後這些資料包或轉化後的物件將交由IOHandler處理,我們將實現IOHandler用於處理具體的業務邏輯。
Mina客戶端結構
客戶端需要連線服務端,傳送請求並處理響應。實際上客戶端的結構和服務端極其相似。
•客戶端首先需要建立IOConnector物件,繫結服務端的IP和埠。
•一旦連線成功,一個於本次連線繫結的session物件將被建立。
•客戶端傳送給服務端的請求都需要經過一系列的fliter。
•同樣,響應訊息的接受也會經過一系列的filter再到IOHandler被處理。
所以整體上,mina提供良好的一致性呼叫和封裝結構。在使用mina建立基於網路的程式應用時,投入的學習成本比較低。
(三). 參考資料
(四). 下載資料
(五). 伺服器搭建
第一步.下載使用的Jar包:
登入http://mina.apache.org/downloads.html下載最新 mina壓縮包(我下的是apache-mina-2.0.13-bin.zip),解壓獲得mina-core-2.0.13.jar和slf4j-api-1.7.14.jar(注:slf4j-api-1.7.14.jar檔案在apache-mina-2.0.13-bin.zip\apache-mina-2.0.13\lib目錄下)
第二步.服務端程式
建立一個簡單的服務端程式:(服務端繫結3344埠)
public class DemoServer {
// 埠號,要求客戶端與伺服器端一致
private static int PORT = 3344;
public static void main(String[] args) {
IoAcceptor acceptor = null;
try {
// 建立一個非阻塞的server端的Socket
acceptor = new NioSocketAcceptor();
// 設定過濾器(使用mina提供的文字換行符編解碼器)
acceptor.getFilterChain().addLast("codec",
new ProtocolCodecFilter(new TextLineCodecFactory(Charset.forName("UTF-8"),
LineDelimiter.WINDOWS.getValue(), LineDelimiter.WINDOWS.getValue())));
// 自定義的編解碼器
// acceptor.getFilterChain().addLast("codec", new
// ProtocolCodecFilter(new CharsetCodecFactory()));
// 設定讀取資料的換從區大小
acceptor.getSessionConfig().setReadBufferSize(2048);
// 讀寫通道10秒內無操作進入空閒狀態
acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, 10);
// 為接收器設定管理服務
acceptor.setHandler(new DemoServerHandler());
// 繫結埠
acceptor.bind(new InetSocketAddress(PORT));
System.out.println("伺服器啟動成功... 埠號未:" + PORT);
} catch (Exception e) {
System.out.println("伺服器啟動異常...");
e.printStackTrace();
}
}
}
package com.changwu;
import java.util.Date;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.core.session.IoSession;
public class DemoServerHandler extends IoHandlerAdapter {
// 從埠接受訊息,會響應此方法來對訊息進行處理
@Override
public void messageReceived(IoSession session, Object message) throws Exception {
super.messageReceived(session, message);
String msg = message.toString();
if ("exit".equals(msg)) {
// 如果客戶端發來exit,則關閉該連線
session.close(true);
}
// 向客戶端傳送訊息
Date date = new Date();
session.write(date);
System.out.println("伺服器接受訊息成功..." + msg);
}
// 向客服端傳送訊息後會呼叫此方法
@Override
public void messageSent(IoSession session, Object message) throws Exception {
super.messageSent(session, message);
// session.close(true);//加上這句話實現短連線的效果,向客戶端成功傳送資料後斷開連線
System.out.println("伺服器傳送訊息成功...");
}
// 關閉與客戶端的連線時會呼叫此方法
@Override
public void sessionClosed(IoSession session) throws Exception {
super.sessionClosed(session);
System.out.println("伺服器與客戶端斷開連線...");
}
// 伺服器與客戶端建立連線
@Override
public void sessionCreated(IoSession session) throws Exception {
super.sessionCreated(session);
System.out.println("伺服器與客戶端建立連線...");
}
// 伺服器與客戶端連線開啟
@Override
public void sessionOpened(IoSession session) throws Exception {
System.out.println("伺服器與客戶端連線開啟...");
super.sessionOpened(session);
}
@Override
public void sessionIdle(IoSession session, IdleStatus status) throws Exception {
super.sessionIdle(session, status);
System.out.println("伺服器進入空閒狀態...");
}
@Override
public void exceptionCaught(IoSession session, Throwable cause) throws Exception {
super.exceptionCaught(session, cause);
System.out.println("伺服器傳送異常...");
}
}
第三步.執行DemoServer.java檔案,成功後的效果圖如下:
(六). Android客戶端
第一步.匯入下載後的客戶端專案
第二步.客戶端程式
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void click(View v) {
MinaThread mThread = new MinaThread();
mThread.start();
}
}
public class MinaThread extends Thread {
private IoSession session = null;
private IoConnector connector = null;
@Override
public void run() {
super.run();
// TODO Auto-generated method stub]
System.out.println("客戶端連結開始...");
connector = new NioSocketConnector();
System.out.println(101);
// 設定連結超時時間
connector.setConnectTimeoutMillis(10000);
System.out.println(102);
// 新增過濾器
// connector.getFilterChain().addLast("codec", new
// ProtocolCodecFilter(new CharsetCodecFactory()));
connector.getFilterChain().addLast("codec",
new ProtocolCodecFilter(new TextLineCodecFactory(Charset.forName("UTF-8"),
LineDelimiter.WINDOWS.getValue(), LineDelimiter.WINDOWS.getValue())));
System.out.println(110);
connector.setHandler(new MinaClientHandler());
System.out.println(111);
connector.setDefaultRemoteAddress(new InetSocketAddress(ConstantUtil.OUT_MATCH_PATH, ConstantUtil.WEB_MATCH_PORT));
// 監聽客戶端是否斷線
connector.addListener(new IoListener() {
@Override
public void sessionDestroyed(IoSession arg0) throws Exception {
// TODO Auto-generated method stub
super.sessionDestroyed(arg0);
try {
int failCount = 0;
while (true) {
Thread.sleep(5000);
System.out.println(((InetSocketAddress) connector.getDefaultRemoteAddress()).getAddress()
.getHostAddress());
ConnectFuture future = connector.connect();
System.out.println("斷線2");
future.awaitUninterruptibly();// 等待連線建立完成
System.out.println("斷線3");
session = future.getSession();// 獲得session
System.out.println("斷線4");
if (session != null && session.isConnected()) {
System.out.println("斷線5");
System.out.println("斷線重連["
+ ((InetSocketAddress) session.getRemoteAddress()).getAddress().getHostAddress()
+ ":" + ((InetSocketAddress) session.getRemoteAddress()).getPort() + "]成功");
session.write("start");
break;
} else {
System.out.println("斷線重連失敗---->" + failCount + "次");
}
}
} catch (Exception e) {
// TODO: handle exception
}
}
});
//開始連線
try {
System.out.println(112);
ConnectFuture future = connector.connect();
System.out.println(113);
future.awaitUninterruptibly();// 等待連線建立完成
System.out.println(114);
session = future.getSession();// 獲得session
System.out.println(115);
if (session != null && session.isConnected()) {
session.write("start");
} else {
System.out.println("寫資料失敗");
}
System.out.println(11);
} catch (Exception e) {
System.out.println("客戶端連結異常...");
}
System.out.println(118);
if (session != null && session.isConnected()) {
session.getCloseFuture().awaitUninterruptibly();// 等待連線斷開
System.out.println("客戶端斷開111111...");
// connector.dispose();//徹底釋放Session,退出程式時呼叫不需要重連的可以呼叫這句話,也就是短連線不需要重連。長連線不要呼叫這句話,註釋掉就OK。
}
}
}
public class MinaClientHandler extends IoHandlerAdapter {
@Override
public void exceptionCaught(IoSession session, Throwable cause) throws Exception {
Log.i("TEST", "客戶端發生異常");
super.exceptionCaught(session, cause);
}
@Override
public void messageReceived(IoSession session, Object message) throws Exception {
String msg = message.toString();
Log.i("TEST", "i客戶端接收到的資訊為:" + msg);
super.messageReceived(session, message);
}
@Override
public void messageSent(IoSession session, Object message) throws Exception {
// TODO Auto-generated method stub
super.messageSent(session, message);
}
}
public class IoListener implements IoServiceListener{
@Override
public void serviceActivated(IoService arg0) throws Exception {
// TODO Auto-generated method stub
}
@Override
public void serviceDeactivated(IoService arg0) throws Exception {
// TODO Auto-generated method stub
}
@Override
public void serviceIdle(IoService arg0, IdleStatus arg1) throws Exception {
// TODO Auto-generated method stub
}
@Override
public void sessionClosed(IoSession arg0) throws Exception {
// TODO Auto-generated method stub
}
@Override
public void sessionCreated(IoSession arg0) throws Exception {
// TODO Auto-generated method stub
}
@Override
public void sessionDestroyed(IoSession arg0) throws Exception {
// TODO Auto-generated method stub
}
}
public class ConstantUtil {
/** 本地區域網IP地址 **/
public final static String WEB_MATCH_PATH="192.168.1.102";
/** 用花生殼轉換本地區域網後的IP地址,可供外網訪問 **/
public final static String OUT_MATCH_PATH="15zr163427.iask.in";
/** 用花生殼轉換本地區域網後的埠號 **/
public final static int WEB_MATCH_PORT=25400;
}
(七). 連線
這裡直接呼叫了
ConnectFuture future = connector.connect();
future.awaitUninterruptibly();// 等待連線建立完成
,在前面已經設定了預設伺服器
//設定預設連線遠端伺服器的IP地址和埠
connector.setDefaultRemoteAddress(new InetSocketAddress(ConstantUtil.OUT_MATCH_PATH, ConstantUtil.WEB_MATCH_PORT));
(八). 斷開
在服務端DemoServerHandler.java執行session.close(true);如圖所示:
(九). 斷線重連
在客戶端MinaThread.java中給connector新增監聽Session關閉事件;如圖所示:
注意:因為我們在在服務端DemoServerHandler.java中messageSent()方法下執行了session.close(true);所以我們會不斷點開和連線伺服器;成功後的效果圖如下:
(十). 長連線和短連線
Mina本身的效果就是長連線,與長連線相對應的是短連線,比如常說的請求/響應模式(HTTP協議就是典型的請求/響應模式)—–客戶端向服務端傳送一個請求,建立連線後,服務端處理並響應成功,此時就主動斷開連線了!
短連線是一個簡單而有效的處理方式,也是應用最廣的。Mina是Java NIO實現的應用框架,更傾向於短連線的服務;問題是哪一方先斷開連線呢?可以在服務端,也可以在客戶端,但是提倡在服務端主動斷開;
Mina的服務端業務邏輯處理類中有一個方法messageSent,他是在服務端傳送資訊成功後呼叫的:
@Override
public void messageSent(IoSession session, Object message) throws Exception {
super.messageSent(session, message);
System.out.println("伺服器傳送訊息成功...");
}
修改後為
@Override
public void messageSent(IoSession session, Object message) throws Exception {
super.messageSent(session, message);
session.close(true);//加上這句話實現短連線的效果,向客戶端成功傳送資料後斷開連線
System.out.println("伺服器傳送訊息成功...");
}
這時候客戶端與服務端就是典型的短連線了;再次測試,會發現客戶端傳送請求,接收成功後就自動關閉了,程序只剩下服務端了!
到此為止,我們已經可以執行一個完整的基於TCP/IP協議的應用程式啦!