Apache mina使用入門

  Apache mina是一個基於NIO(非阻塞IO)模型的網絡應用框架。詳細資料和下載地址爲:http://mina.apache.org/
  遠程客戶端通過IoService建立連接得到session,session將數據傳送到IoFilterChain進行過濾,最後客戶端在IoHandle中進行數據操作,其工作原理如圖所示:

mina

  此處記錄的是Android中Apache mina於服務器建立的長連接通信示例。長連接和http短連接底層都是基於TCP/IP協議,長連接通過Socket,ServerSocket與服務器保持連接,服務端一般使用ServerSocket建立監聽,監聽客戶端的連接,客戶端使用Socket,指定端口和IP地址與服務端進行連接。通過長連接,可以實現服務器主動向客戶端推送消息,可以減少客戶端對服務器的輪詢,減少服務器的壓力,而且通信效率高於http,okhttp等。

Apache mina框架核心類:

  • IoService接口相關類:監聽管理、session管理等
  • IoAcceptor及其相關的類:繼承於IoService接口,實現TCP或UDP協議的監聽器
  • IoConnector及其相關的類:繼承於IoService接口,支持TCP或UDP協議與服務器建立連接
  • LoggingFilter 記錄mina所有日誌
  • ProtocolCodecFilter 數據轉化過濾器
  • CompressionFilter 數據壓縮過濾器
  • SSLFilter 數據加密與解密傳輸過濾器
  • IoSession類:通信管理類
  • Handler類:session事件的監聽,消息收發監聽,異常監聽等

mina簡單服務端建立

  創建java項目,導入mina-core-2.0.21.jar和slf4j-api-1.7.26.jar兩個庫文件,如不加slf4j包則會出現java.lang.ClassNotFoundException: org.slf4j.LoggerFactory錯誤。

  1. MinaServer類
public class MinaServer {
    static final int PORT = 55555;
    public static void main(String[] args) {
        IoAcceptor acceptor = new NioSocketAcceptor();
        //設置過濾器,主要設置協議的編碼解碼
        acceptor.getFilterChain().addLast("codec", new ProtocolCodecFilter(
                new TextLineCodecFactory(
                        StandardCharsets.UTF_8,
                        LineDelimiter.WINDOWS.getValue(),
                        LineDelimiter.WINDOWS.getValue()
                )
        ));
        //添加日誌過濾器
        acceptor.getFilterChain().addLast("logger", new LoggingFilter());
        //設置讀緩存大小
        acceptor.getSessionConfig().setReadBufferSize(2048);
        //設置讀寫空閒時間相同,都爲10s
        acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, 10);
        //註冊處理器
        acceptor.setHandler(new MyHandler());
        // 綁定端口開始進行監聽
        try {
            acceptor.bind(new InetSocketAddress(PORT));
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println("端口 " + PORT + " 啓動成功");
    }
}
  1. MyHandler類
/**
 * session對象的創建、消息收發等事件監聽
 */
public class MyHandler extends IoHandlerAdapter {
    @Override
    public void sessionCreated(IoSession session) {
        System.out.println("session被創建,sessionId: " + session.getId());
    }

    @Override
    public void sessionOpened(IoSession session) {
        String address = session.getRemoteAddress().toString();
        System.out.println("session被打開,address is : " + address);
    }

    @Override
    public void sessionClosed(IoSession session) {
        System.out.println("session客戶端關閉,");
    }

    /**
     * 消息的接收
     *
     * @param session
     * @param message
     */
    @Override
    public void messageReceived(IoSession session, Object message) {
        //假如是string類型消息
        String strMsg = message.toString();
        //向客戶端返回消息
        session.write("服務端已收到消息: " + new Date().toString());
        System.out.println("接收到消息:" + strMsg);
    }

    /**
     * 消息的發送
     *
     * @param session
     * @param message
     * @throws Exception
     */
    @Override
    public void messageSent(IoSession session, Object message) {
    }
}

mina客戶端建立

  新建Android項目,拷貝mina-core-2.0.21.jar和slf4j-api-1.7.26.jar兩個庫文件到libs目錄並添加到dependencies,app\build.gradle如圖所示:
build.gradle

  1. ConnectManage類
/**
 * 封裝connection和disconnection方法給外部調用
 */
public class ConnectManage {
    private ConnectConfig mConfig;
    private WeakReference<Context> mContext;
    private NioSocketConnector mConnector;
    private IoSession mSession;
    private InetSocketAddress mAddress;
    private ConnectFuture mConnectFuture;

    public ConnectManage(ConnectConfig config) {
        this.mConfig = config;
        mContext = new WeakReference<>(mConfig.getContext());
        init();
    }
    /**
     * 初始化連接配置
     */
    private void init() {
        // 創建連接對象
        mConnector = new NioSocketConnector();
        // 設置連接地址
        mAddress = new InetSocketAddress(mConfig.getIp(), mConfig.getPort());
        mConnector.setDefaultRemoteAddress(mAddress);
        // 設置緩衝大小
        mConnector.getSessionConfig().setReadBufferSize(mConfig.getReadBufferSize());
        // 設置過濾
        mConnector.getFilterChain().addLast("logger", new LoggingFilter());
        mConnector.getFilterChain().addLast("codec", new ProtocolCodecFilter(new ObjectSerializationCodecFactory()));
        // 設置心跳包,斷線重連
        KeepAliveMessageFactory heartBeatFactory = new KeepAliveMessageFactoryImpl();
        KeepAliveRequestTimeoutHandler heartBeatHandler = new KeepAliveRequestTimeoutHandlerImpl();
        KeepAliveFilter heartBeat = new KeepAliveFilter(heartBeatFactory, IdleStatus.BOTH_IDLE, heartBeatHandler);
        // 是否回發
        heartBeat.setForwardEvent(true);
        // 設置當連接的讀取通道空閒的時候,心跳包請求時間間隔
        heartBeat.setRequestInterval(mConfig.getHeartInterval());
        // 設置心跳包請求後,若超過該時間後則調用KeepAliveRequestTimeoutHandler.CLOSE_NOW
        heartBeat.setRequestTimeout(mConfig.getHeartTimeOut());
        mConnector.getFilterChain().addLast("heartbeat", heartBeat);
        mConnector.getSessionConfig().setUseReadOperation(true);
        // 設置連接監聽
        mConnector.setHandler(new ConnectHandler(mContext.get(), this));
        // 超時設置
        mConnector.setConnectTimeoutMillis(mConfig.getConnectionTimeout());
    }
    /**
     * 是否關閉Session
     *
     * @return
     */
    public boolean isCloseSession() {
        if (mSession != null) return mSession.isClosing();
        return false;
    }
    /**
     * 與服務器建立連接
     *
     * @return
     */
    public boolean connect() {
        try {
            mConnectFuture = mConnector.connect(mAddress);
            //一直等到連接爲止
            mConnectFuture.awaitUninterruptibly();
            //獲取session對象
            mSession = mConnectFuture.getSession();
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return mSession != null;
    }
    /**
     * 斷開與服務器的連接
     */
    public void disConnect() {
        if (mConnector != null) mConnector.dispose();
        if (mConnectFuture != null) mConnectFuture.cancel();
        mConnector = null;
        mSession = null;
        mAddress = null;
        mContext = null;
        mConnectFuture = null;
    }
}
  1. ConnectConfig類
/**
 * 連接選項配置
 * 通過lombok+構建者模式實現config
 */
@Getter
@Setter
@Builder
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class ConnectConfig {
    private Context context;       //內容
    private String ip;             //IP地址
    private int port;              //端口
    private int readBufferSize;    // 緩存大小
    private int connectionTimeout; // 連接超時時間
    private int heartInterval;     // 心跳間隔時間
    private int heartTimeOut;      // 心跳請求超時時間
}
  1. ConnectHandler類
/**
 * 業務處理類
 */
public class ConnectHandler extends IoHandlerAdapter {
    private Context context;
    private ConnectManage connectionManage;
    private static final String BROADCAST_ACTION = "mina";
    private static final String MESSAGE = "Message";

    ConnectHandler(Context context, ConnectManage connectionManage) {
        this.context = context;
        this.connectionManage = connectionManage;
    }

    @Override
    public void sessionCreated(IoSession session) throws Exception {
        super.sessionCreated(session);
        System.out.println("---創建連接---");
    }

    /**
     * 將session保存到session manage中,從而可以發送消息到服務器端
     */
    @Override
    public void sessionOpened(IoSession session) throws Exception {
        // 當與服務器連接成功時,將我們的session保存到我們的session manager類中,從而可以發送消息到服務器
        SessionManage.getInstance().setIoSession(session);
        System.out.println("---打開連接---");
    }

    @Override
    public void sessionClosed(IoSession session) throws Exception {
        super.sessionClosed(session);
        System.out.println("---關閉連接---");
        if (!SessionManage.getInstance().isCloseSession()) {// 關閉後自動重連
            if (connectionManage != null && connectionManage.isCloseSession())
                connectionManage.connect();
        } else {
            if (connectionManage != null) connectionManage.disConnect();
            connectionManage = null;
        }
    }

    @Override
    public void sessionIdle(IoSession session, IdleStatus status) throws Exception {
        super.sessionIdle(session, status);
        System.out.println("---空閒---");
    }

    @Override
    public void messageReceived(IoSession session, Object message) throws Exception {
        if (context != null) {
            //通過Android局部廣播LocalBroadcastManager機制發送消息
            Intent intent = new Intent(BROADCAST_ACTION);
            intent.putExtra(MESSAGE, message.toString());
            LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
        }
        System.out.println("---接收消息--- " + message);
    }

    @Override
    public void messageSent(IoSession session, Object message) throws Exception {
        super.messageSent(session, message);
        System.out.println("---發送消息--- " + message);
    }
}
  1. ConnectService類
/**
 * 服務管理類
 */
public class ConnectService extends Service {

    //負責調用connect manage完成於服務器的連接
    private ConnectThread connectThread;

    @Override
    public void onCreate() {
        super.onCreate();
        //初始化線程並啓動
        connectThread = new ConnectThread("mina");
        connectThread.start();

    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        //斷開連接並釋放線程
        connectThread.disConnection();
        connectThread = null;
    }
}
  1. ConnectThread類
/**
 * 創建線程類來調用connection manage完成於服務器的連接
 */
public class ConnectThread extends HandlerThread {

    private ConnectManage mConnectManage;
    private boolean isConnection;

    ConnectThread(String name) {
        super(name);
        ConnectConfig connectConfig = new ConnectConfig.ConnectConfigBuilder()
                .ip("192.168.1.55")
                .port(55555)
                .readBufferSize(10240)
                .connectionTimeout(10000)
                .heartInterval(20)
                .heartTimeOut(5).build();

        mConnectManage = new ConnectManage(connectConfig);
    }

    @Override
    protected void onLooperPrepared() {
        // 利用循環請求連接
        while (!isConnection) {
            if (mConnectManage == null) break;
            //是否於服務器連接成功
            boolean isConnection = mConnectManage.connect();
            if (isConnection) {
                // 當請求成功的時候,跳出循環
                System.out.println("----------連接成功");
                break;
            }
            System.out.println("---------連接失敗");
            try {
                Thread.sleep(2500);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 銷燬服務器的連接
     */
    public void disConnection() {
        isConnection = true;
        if (getLooper() != null) getLooper().quit();
        if (mConnectManage != null) {
            mConnectManage.disConnect();
            mConnectManage = null;
        }
    }
}
  1. SessionManage類
/**
 * 會話管理類
 */
class SessionManage {
    private static SessionManage mSessionManage = null;
    private IoSession mSession;
    private boolean isCloseSession;

    //單例加鎖機制
    public static SessionManage getInstance() {
        if (mSessionManage == null) {
            synchronized (SessionManage.class) {
                if (mSessionManage == null)
                    mSessionManage = new SessionManage();
            }
        }
        return mSessionManage;
    }

    public void setIoSession(IoSession ioSession) {
        this.mSession = ioSession;
    }

    public boolean isCloseSession() {
        return isCloseSession;
    }

    private void setCloseSession(boolean closeSession) {
        isCloseSession = closeSession;
    }

    /**
     * 將對象寫到服務器
     */
    public void writeToServer(Object msg) {
        if (mSession != null) {
            mSession.write(msg.toString());
        }
    }

    /**
     * 關閉連接
     */
    public void closeSession() {
        if (mSession != null) {
            mSession.closeNow();
            mSession.closeOnFlush();
            mSession.getService().dispose();
        }
        mSession = null;
        setCloseSession(false);
    }
}
  1. KeepAliveRequestTimeoutHandlerImpl類
/**
 * 當心跳超時時的處理
 */
class KeepAliveRequestTimeoutHandlerImpl implements KeepAliveRequestTimeoutHandler {
    @Override
    public void keepAliveRequestTimedOut(KeepAliveFilter keepAliveFilter, IoSession ioSession) throws Exception {
        ioSession.closeNow();
    }
}
  1. KeepAliveMessageFactoryImpl類
/**
 * 我這裏沒做啥操作
 */
class KeepAliveMessageFactoryImpl implements KeepAliveMessageFactory {
    @Override
    public boolean isRequest(IoSession ioSession, Object o) {
        return false;
    }

    @Override
    public boolean isResponse(IoSession ioSession, Object o) {
        return false;
    }

    @Override
    public Object getRequest(IoSession ioSession) {
        return null;
    }

    @Override
    public Object getResponse(IoSession ioSession, Object o) {
        return null;
    }
}
  1. MainActivity測試類
public class MainActivity extends AppCompatActivity {
    
    private Button bt1, bt2;
    private TextView view3;
    //自定義廣播接收器
    private MessageBroadcastReceiver receiver;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
        receiver = new MessageBroadcastReceiver();
        registBroadcast();
    }

    private void initView() {
        bt1 = findViewById(R.id.view);
        bt2 = findViewById(R.id.view2);
        view3 = findViewById(R.id.textView3);

        bt1.setOnClickListener(v -> SessionManage.getInstance().writeToServer("這是客戶端發送的消息"));
        bt2.setOnClickListener(v -> {
            Intent intent = new Intent(MainActivity.this, ConnectService.class);
            startService(intent);
        });
    }

    private void registBroadcast() {
        IntentFilter filter = new IntentFilter("mina");
        LocalBroadcastManager.getInstance(this).registerReceiver(receiver, filter);
    }

    private void unregistBroadcast() {
        LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver);
    }
    /**
     * 接受數據並更新UI
     */
    private class MessageBroadcastReceiver extends BroadcastReceiver {

        @Override
        public void onReceive(Context context, Intent intent) {
            view3.setText(intent.getStringArrayExtra("Message").toString());
        }
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        stopService(new Intent(this, ConnectService.class));
        unregistBroadcast();
    }
}

若Android客戶端啓動時報錯:
error1
解決辦法是:在app\build.gradle中增加includeCompileClasspath true。如圖所示:
solu1
若報錯信息爲:
error2
解決辦法是:在app\build.gradle中增加java8的編譯。如圖所示:
solu2
通常爲防止客戶端連接不上服務端,可以關閉服務端的防火牆再試試。


References:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章