1. 程式人生 > >Android MINA框架之實戰總結(一) Mina連線,斷開,重連

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客戶端

第一步.匯入下載後的客戶端專案

MINA客戶端專案結構圖

第二步.客戶端程式

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協議的應用程式啦!