Android之Mina框架學習

lvxiangan發表於2018-11-15
  1. 什麼是Mina?
    Apache MINA(Multipurpose Infrastructure for Network Applications) 是 Apache 組織一個較新的專案,它為開發高效能和高可用性的網路應用程式提供了非常便利的框架。當前發行的 MINA 版本支援基於 Java NIO 技術的 TCP/UDP 應用程式開發、串列埠通訊程式(只在最新的預覽版中提供),MINA 所支援的功能也在進一步的擴充套件中。
    總之:我們簡單理解它是一個封裝底層IO操作,提供高階操作API的通訊框架
     
  2. 什麼是NIO技術?

    在JDK1.4以前,I/O輸入輸出處理,我們把它稱為舊I/O處理,在JDK1.4開始,java提供了一系列改進的輸入/輸出新特性,這些功能被稱為新I/O(NEW I/O),新添了許多用於處理輸入/輸出的類,這些類都被放在java.nio包及子包下,並且對原java.io包中的很多類以NIO為基礎進行了改寫,新添了滿足新I/O的功能。

    Java NIO和IO的主要區別

    面向緩衝(Buffer)

    在整個Java的心I/O中,所以操作都是以緩衝區進行的,使操作的效能大大提高。更多請參考這裡這裡

  3. 什麼是長連線、短連線、心跳包?請參考這裡

  4. 常見的Java NIO框架有哪些?
    Mina、Netty、Grizzly、xSocket等,區別如下:
     

    Mina

    Mina(Multipurpose Infrastructure for Network Applications) 是 Apache組織一個較新的專案,它為開發高效能和高可用性的網路應用程式提供了非常便利的框架。當前發行的 Mina 版本2.04支援基於 JavaNIO 技術的 TCP/UDP 應用程式開發、串列埠通訊程式,Mina 所支援的功能也在進一步的擴充套件中。目前,正在使用Mina的應用包括:Apache Directory Project、AsyncWeb、AMQP(Advanced MessageQueuing Protocol)、RED5 Server(Macromedia? FlashMedia RTMP)、ObjectRADIUS、 Openfire等等。

    Netty

    Netty是一款非同步的事件驅動的網路應用框架和工具,用於快速開發可維護的高效能、高擴充套件性協議伺服器和客戶端。也就是說,Netty是一個NIO客戶端/伺服器框架,支援快速、簡單地開發網路應用,如協議伺服器和客戶端。它極大簡化了網路程式設計,如TCP和UDP套接字伺服器。

    Grizzly

    Grizzly是一種應用程式框架,專門解決編寫成千上萬使用者訪問伺服器時候產生的各種問題。使用JAVANIO作為基礎,並隱藏其程式設計的複雜性。容易使用的高效能的API。帶來非阻塞socketd到協議處理層。利用高效能的緩衝和緩衝管理使用高效能的執行緒池。

    xSocket

    xSocket:是一個輕量級的基於nio的伺服器框架用於開發高效能、可擴充套件、多執行緒的伺服器。該框架封裝了執行緒處理、非同步讀/寫等方面。(只是對Java的NIO做了最簡單的封裝,以便於開發使用。)

    OK,我們現在可以看看三者的簡單對比了。

    首先,從設計的理念上來看,Mina的設計理念是最為優雅的。當然,由於Netty的主導作者與Mina的主導作者是同一人,出自同一人之手的Netty在設計理念上與Mina基本上是一致的。而Grizzly在設計理念上就較差了點,幾乎是JavaNIO的簡單封裝。

    其次,從專案的出身來看,Mina出身於開源界的大牛Apache組織,Netty出身於商業開源大亨Jboss,而Grizzly則出身於土鱉Sun公司。從其出身可以看到其應用的廣泛程式,到目前為止,我見到業界還是使用Mina多一些,而Netty也在慢慢的應用起來,而Grizzly則似乎只有Sun自已的專案使用了,如果還有其他的公司或開源專案在使用,那就算我孤陋寡聞。 最後,從入門的文件來說,由於Mina見世時間相對較長,官方以及民間的文件與入門示例都相當的多。Netty的官方文件也做得很好,而民間文件就要相對於Mina少一些了。至於Grizzly,不管是官方還是民間,都很少見到其文件。

  5. 常見的Mina使用場景有哪些?
    如:客戶端和服務端進行socket通訊時,服務端需要知道客戶端的是否線上,這就要求客戶端要一直定時傳送心跳請求告知服務端;通訊服務中斷後,客戶端自動發起重新連線等等。。。有同學說:這功能幾個普通函式就能實現,沒啥特別的啊?開始我也是這樣想的,但慢慢了解Mina框架後,發現很多底層操作都封裝好了,簡單易用,真的比自己砌磚爽多了……自己實現請參考
  6. Mina如何實現通訊?
    下面以Mina實現Android客戶端與伺服器端通訊為例,原文參考

    二、伺服器端程式碼

    MinaService.java
    
    package com.czhappy.mina;
     
    import java.net.InetSocketAddress;
    import java.util.Date;
     
    import org.apache.mina.core.service.IoAcceptor;
    import org.apache.mina.core.service.IoHandlerAdapter;
    import org.apache.mina.core.session.IdleStatus;
    import org.apache.mina.core.session.IoSession;
    import org.apache.mina.filter.codec.ProtocolCodecFilter;
    import org.apache.mina.filter.codec.serialization.ObjectSerializationCodecFactory;
    import org.apache.mina.filter.logging.LoggingFilter;
    import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
     
    public class MinaService {
     
        public static void main(String[] args) {
            IoAcceptor acceptor = new NioSocketAcceptor();
            //新增日誌過濾器
            acceptor.getFilterChain().addLast("logger", new LoggingFilter());
            acceptor.getFilterChain().addLast("codec", new ProtocolCodecFilter(new ObjectSerializationCodecFactory()));
            acceptor.setHandler(new DemoServerHandler());
            acceptor.getSessionConfig().setReadBufferSize(2048);
            acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, 10);
            try {
                acceptor.bind(new InetSocketAddress(9226));
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println("啟動服務");
        }
        
        /**  
         * @ClassName: DemoServerHandler  
         * @Description: 負責session物件的建立和監聽以及訊息的建立和接收監聽
         * @author chenzheng
         * @date 2016-12-9 下午3:57:11  
         */
        private static class DemoServerHandler extends IoHandlerAdapter{
            
            //伺服器與客戶端建立連線
            @Override
            public void sessionCreated(IoSession session) throws Exception {
                System.out.println("伺服器與客戶端建立連線...");
                super.sessionCreated(session);
            }
     
            
            @Override
            public void sessionOpened(IoSession session) throws Exception {
                System.out.println("伺服器與客戶端連線開啟...");
                super.sessionOpened(session);
            }
            
            //訊息的接收處理
            @Override
            public void messageReceived(IoSession session, Object message)
                    throws Exception {
                // TODO Auto-generated method stub
                super.messageReceived(session, message);
                String str = message.toString();
                Date date = new Date();
                session.write(date.toString());
                System.out.println("接收到的資料:"+str);
                
            }
            
            @Override
            public void messageSent(IoSession session, Object message)
                    throws Exception {
                // TODO Auto-generated method stub
                super.messageSent(session, message);
            }
            
            @Override
            public void sessionClosed(IoSession session) throws Exception {
                // TODO Auto-generated method stub
                super.sessionClosed(session);
            }
        }
    }

    三、Android客戶端程式碼
     

    ConnectionConfig.java
    
    
    package com.czhappy.minaclient;
     
    import android.content.Context;
     
    /**
     * Description:構建者模式
     */
    public class ConnectionConfig {
     
        private Context context;
        private String ip;
        private int port;
        private int readBufferSize;
        private long connectionTimeout;
     
        public Context getContext() {
            return context;
        }
     
        public String getIp() {
            return ip;
        }
     
        public int getPort() {
            return port;
        }
     
        public int getReadBufferSize() {
            return readBufferSize;
        }
     
        public long getConnectionTimeout() {
            return connectionTimeout;
        }
     
        public static class Builder{
            private Context context;
            private String ip = "192.168.168.20";
            private int port = 9226;
            private int readBufferSize = 10240;
            private long connectionTimeout = 10000;
     
            public Builder(Context context){
                this.context = context;
            }
     
            public Builder setIp(String ip){
                this.ip = ip;
                return this;
            }
     
            public Builder setPort(int port){
                this.port = port;
                return this;
            }
     
            public Builder setReadBufferSize(int readBufferSize){
                this.readBufferSize = readBufferSize;
                return this;
            }
     
            public Builder setConnectionTimeout(long connectionTimeout){
                this.connectionTimeout = connectionTimeout;
                return this;
            }
     
            private void applyConfig(ConnectionConfig config){
     
                config.context = this.context;
                config.ip = this.ip;
                config.port = this.port;
                config.readBufferSize = this.readBufferSize;
                config.connectionTimeout = this.connectionTimeout;
            }
     
            public ConnectionConfig builder(){
                ConnectionConfig config = new ConnectionConfig();
                applyConfig(config);
                return config;
            }
        }
    }
    
    ConnectionManager.java
    
    package com.czhappy.minaclient;
     
    import android.content.Context;
    import android.content.Intent;
    import android.support.v4.content.LocalBroadcastManager;
    import android.util.Log;
     
    import org.apache.mina.core.future.ConnectFuture;
    import org.apache.mina.core.service.IoHandlerAdapter;
    import org.apache.mina.core.session.IoSession;
    import org.apache.mina.filter.codec.ProtocolCodecFilter;
    import org.apache.mina.filter.codec.serialization.ObjectSerializationCodecFactory;
    import org.apache.mina.filter.logging.LoggingFilter;
    import org.apache.mina.transport.socket.nio.NioSocketConnector;
     
    import java.lang.ref.WeakReference;
    import java.net.InetSocketAddress;
     
    public class ConnectionManager {
     
        private static final String BROADCAST_ACTION = "com.commonlibrary.mina.broadcast";
        private static final String MESSAGE = "message";
        private ConnectionConfig mConfig;
        private WeakReference<Context> mContext;
     
        private NioSocketConnector mConnection;
        private IoSession mSession;
        private InetSocketAddress mAddress;
     
        public ConnectionManager(ConnectionConfig config){
     
            this.mConfig = config;
            this.mContext = new WeakReference<Context>(config.getContext());
            init();
        }
     
        private void init() {
            mAddress = new InetSocketAddress(mConfig.getIp(), mConfig.getPort());
            mConnection = new NioSocketConnector();
            mConnection.getSessionConfig().setReadBufferSize(mConfig.getReadBufferSize());
            mConnection.getFilterChain().addLast("logging", new LoggingFilter());
            mConnection.getFilterChain().addLast("codec", new ProtocolCodecFilter(new ObjectSerializationCodecFactory()));
            mConnection.setHandler(new DefaultHandler(mContext.get()));
            mConnection.setDefaultRemoteAddress(mAddress);
        }
     
        /**
         * 與伺服器連線
         * @return
         */
        public boolean connnect(){
            Log.e("tag", "準備連線");
            try{
                ConnectFuture future = mConnection.connect();
                future.awaitUninterruptibly();
                mSession = future.getSession();
     
                SessionManager.getInstance().setSeesion(mSession);
     
            }catch (Exception e){
                e.printStackTrace();
                Log.e("tag", "連線失敗");
                return false;
            }
     
            return mSession == null ? false : true;
        }
     
        /**
         * 斷開連線
         */
        public void disContect(){
            mConnection.dispose();
            mConnection=null;
            mSession=null;
            mAddress=null;
            mContext = null;
            Log.e("tag", "斷開連線");
        }
     
        private static class DefaultHandler extends IoHandlerAdapter{
     
            private Context mContext;
            private DefaultHandler(Context context){
                this.mContext = context;
     
            }
     
            @Override
            public void sessionOpened(IoSession session) throws Exception {
                super.sessionOpened(session);
            }
     
            @Override
            public void messageReceived(IoSession session, Object message) throws Exception {
                Log.e("tag", "接收到伺服器端訊息:"+message.toString());
                if(mContext!=null){
                    Intent intent = new Intent(BROADCAST_ACTION);
                    intent.putExtra(MESSAGE, message.toString());
                    LocalBroadcastManager.getInstance(mContext).sendBroadcast(intent);
                }
            }
        }
    }
    
    
    
    
    
    MinaService.java
    
    package com.czhappy.minaclient;
     
    import android.app.Service;
    import android.content.Context;
    import android.content.Intent;
    import android.os.HandlerThread;
    import android.os.IBinder;
    import android.support.annotation.Nullable;
    import android.util.Log;
    
    public class MinaService extends Service{
     
        private ConnectionThread thread;
     
     
        @Override
        public void onCreate() {
            super.onCreate();
            thread = new ConnectionThread("mina", getApplicationContext());
            thread.start();
            Log.e("tag", "啟動執行緒嘗試連線");
        }
     
        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            return super.onStartCommand(intent, flags, startId);
        }
     
        @Override
        public void onDestroy() {
            super.onDestroy();
            thread.disConnect();
            thread=null;
     
        }
     
        @Nullable
        @Override
        public IBinder onBind(Intent intent) {
            return null;
        }
     
        class ConnectionThread extends HandlerThread{
     
            private Context context;
            boolean isConnection;
            ConnectionManager mManager;
            public ConnectionThread(String name, Context context){
                super(name);
                this.context = context;
     
                ConnectionConfig config = new ConnectionConfig.Builder(context)
                        .setIp("192.168.168.20")
                        .setPort(9226)
                        .setReadBufferSize(10240)
                        .setConnectionTimeout(10000).builder();
     
                mManager = new ConnectionManager(config);
            }
     
            @Override
            protected void onLooperPrepared() {
                while(true){
                    isConnection = mManager.connnect();
                    if(isConnection){
                        Log.e("tag", "連線成功");
                        break;
                    }
                    try {
                        Log.e("tag", "嘗試重新連線");
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
     
            public void disConnect(){
                mManager.disContect();
            }
        }
    }
    
    
    
    
    
    SessionManager.java
    
    package com.czhappy.minaclient;
    import android.util.Log;
    import org.apache.mina.core.session.IoSession;
     
    public class SessionManager {
     
        private static SessionManager mInstance=null;
     
        private IoSession mSession;
        public static SessionManager getInstance(){
            if(mInstance==null){
                synchronized (SessionManager.class){
                    if(mInstance==null){
                        mInstance = new SessionManager();
                    }
                }
            }
            return mInstance;
        }
     
        private SessionManager(){}
     
        public void setSeesion(IoSession session){
            this.mSession = session;
        }
     
        public void writeToServer(Object msg){
            if(mSession!=null){
                Log.e("tag", "客戶端準備傳送訊息");
                mSession.write(msg);
            }
        }
     
        public void closeSession(){
            if(mSession!=null){
                mSession.closeOnFlush();
            }
        }
     
        public void removeSession(){
            this.mSession=null;
        }
    }
    
    
    
    
    
    
    
    MinaTestActivity.java
    
    package com.czhappy.minaclient;
    import android.content.BroadcastReceiver;
    import android.content.Context;
    import android.content.Intent;
    import android.content.IntentFilter;
    import android.os.Bundle;
    import android.support.v4.content.LocalBroadcastManager;
    import android.support.v7.app.AppCompatActivity;
    import android.util.Log;
    import android.view.View;
    import android.widget.TextView;
    
    public class MinaTestActivity extends AppCompatActivity implements View.OnClickListener{
     
        private TextView start_service_tv, send_tv, receive_tv;
     
        private MessageBroadcastReceiver receiver;
     
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_mina_test);
     
            initView();
            registerBroadcast();
     
        }
     
        private void registerBroadcast() {
            receiver = new MessageBroadcastReceiver();
            IntentFilter filter = new IntentFilter("com.commonlibrary.mina.broadcast");
            LocalBroadcastManager.getInstance(this).registerReceiver(receiver, filter);
        }
     
        private void initView() {
            receive_tv = (TextView) this.findViewById(R.id.receive_tv);
            start_service_tv = (TextView) this.findViewById(R.id.start_service_tv);
            start_service_tv.setOnClickListener(this);
            send_tv = (TextView) this.findViewById(R.id.send_tv);
            send_tv.setOnClickListener(this);
     
        }
     
        @Override
        public void onClick(View v) {
            switch(v.getId()){
                case R.id.start_service_tv:
                    Log.e("tag", "點選啟動服務");
                    Intent intent = new Intent(this, MinaService.class);
                    startService(intent);
                    break;
                case R.id.send_tv:
                    Log.e("tag", "點選傳送訊息");
                    SessionManager.getInstance().writeToServer("hello123");
                    break;
            }
        }
     
        private void unregisterBroadcast(){
            LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver);
        }
     
        @Override
        protected void onDestroy() {
            super.onDestroy();
            stopService(new Intent(this, MinaService.class));
            unregisterBroadcast();
     
        }
     
        private class MessageBroadcastReceiver extends BroadcastReceiver{
            @Override
            public void onReceive(Context context, Intent intent) {
     
                receive_tv.setText(intent.getStringExtra("message"));
            }
        }
    }

    四、執行結果
    1.點選啟動服務,觀察客戶端是否與伺服器端連線成功;
    2.點選傳送訊息,觀察訊息是否傳送成功,如果失敗判斷失敗在哪一步。客戶端執行結果:


    伺服器端執行結果:

    原始碼下載:http://download.csdn.net/detail/chenzheng8975/9707406

  7. 我使用Mina遇到的坑
    1)客戶端無法得到伺服器的返回值
    解決方案:一定要正確設定編碼、解碼器,千萬不要直接搬別人的程式碼。


    StringDecoder和StringEncoder報文內容格式,請與服務端約定好。


    2)客戶端頻繁傳送心跳包,解決方法:參考這裡

     
  8. 利用Mina實現聊天室,可以參考這裡
  9. 其他參考

 

 

 

  1.  

相關文章