MINA開發-1-Android客戶端開發

MINA客戶端的應用應用體系結構

在上一篇文章中我們介紹了MINA服務器端的開發流程,這一章我給大家介紹MINA的客戶端開發流程,客戶端開發我們基于Android來實現相應的開發過程,首先讓我們來看一下MINA客戶端開發的應用體系結構圖:


clientdiagram.png
  • 客戶端首先創建一個IOConnector(MINA Construct (結構) 中連接Socket服務端的接口 ),初始化綁定Server
  • 創建的一個會話,并且與Connection相關聯
  • 應用程序/客戶端寫入會話,遍歷過過濾器鏈之后將數據發送到服務器
  • 從服務器接收到的所有響應/消息都將遍歷過濾器鏈,回調給IoHandler進行處理

Android端TCP代碼示例

首先創建android項目,和普通的Android項目一致,引入對應的MINA客戶端jar包:


android-project.png

按照客戶端的體系結構,按順序創建對應的對象:

/**
     * 創建tcp客戶端的連接
     */
    public void createTcpConnect() throws InterruptedException {
        if (ioSession == null) {
            //首先創建對應的tcp連接
            IoConnector ioConnector = new NioSocketConnector();
            //設置超時時間
            ioConnector.setConnectTimeoutMillis(CONNECT_TIMEOUT);
            //添加過濾器
            ioConnector.getFilterChain().addLast("logger", new LoggingFilter());//添加日志過濾器
            ioConnector.getFilterChain().addLast("codec", new ProtocolCodecFilter(new TextLineCodecFactory()));//設置字節處理過濾器
            //添加IoHandler
            ioConnector.setHandler(new ClientSessionHandler());

//            IoSession ioSession;

            //通過ConnectFuture來連接服務器
            for (; ; ) {
                try {
                    ConnectFuture future = ioConnector.connect(new InetSocketAddress(HOSTNAME, PORT));
                    future.awaitUninterruptibly();
                    ioSession = future.getSession();
                    break;//連接成功跳出死循環
                } catch (Exception e) {
                    System.err.println("Failed to connect");
                    e.printStackTrace();
                    Thread.sleep(5000);//如果連接失敗,5秒后繼續連接直到連接成功
                }
            }
        }
        ioSession.write("tcp-ceshi");
    }

和服務端不同的是,客戶端創建了IoConnector用來連接服務器,其他設置屬性,比如過濾器,Handler,等等設置都很相似。
客戶端創建對應的IoSession來實現數據的發送,通過IoHandler來獲取對應的服務端的數據。這里我們主要分析一下ConnectFuture來實現對應的連接,首先看看通過awaitUninterruptibly()方法實現連接成功:

//通過ConnectFuture來連接服務器
            for (; ; ) {
                try {
                    ConnectFuture future = ioConnector.connect(new InetSocketAddress(HOSTNAME, PORT));
                    future.awaitUninterruptibly();
                    ioSession = future.getSession();
                    break;//連接成功跳出死循環
                } catch (Exception e) {
                    System.err.println("Failed to connect");
                    e.printStackTrace();
                    Thread.sleep(5000);//如果連接失敗,5秒后繼續連接直到連接成功
                }
            }

ConnectFuture是一個接口繼承自IoFuture,而在使用過程中實際上使用的是默認實現類,我們通過源碼來分析連接過程(AbstractPollingIoConnector.java):

/**
     * {@inheritDoc}
     */
    @Override
    @SuppressWarnings("unchecked")
    protected final ConnectFuture connect0(SocketAddress remoteAddress, SocketAddress localAddress,
            IoSessionInitializer<? extends ConnectFuture> sessionInitializer) {
        H handle = null;
        boolean success = false;
        try {
            handle = newHandle(localAddress);
            if (connect(handle, remoteAddress)) {
                ConnectFuture future = new DefaultConnectFuture();//(1)
                T session = newSession(processor, handle);
                initSession(session, future, sessionInitializer);
                // Forward the remaining process to the IoProcessor.
                session.getProcessor().add(session);
                success = true;
                return future;
            }

            success = true;
        } catch (Exception e) {
            return DefaultConnectFuture.newFailedFuture(e);
        } finally {
            if (!success && handle != null) {
                try {
                    close(handle);
                } catch (Exception e) {
                    ExceptionMonitor.getInstance().exceptionCaught(e);
                }
            }
        }

        ConnectionRequest request = new ConnectionRequest(handle, sessionInitializer);
        connectQueue.add(request);
        startupWorker();
        wakeup();

        return request;
    }

我們可以看到,ConnectFuture的實現過程是,首先判斷是否連接成功:

 if (connect(handle, remoteAddress)) {
                ConnectFuture future = new DefaultConnectFuture();//(1)
                T session = newSession(processor, handle);
                initSession(session, future, sessionInitializer);
                // Forward the remaining process to the IoProcessor.
                session.getProcessor().add(session);
                success = true;
                return future;
            }

如果連接成功,創建默認的ConnectFuture對象,初始化創建有效連接的Session會話直接返回即可。但是如果尚沒有連接成功,會創建ConnectionRequest 對象返回,我們該對象對應的源碼 (AbstractPollingIoConnector.java內部類):

public final class ConnectionRequest extends DefaultConnectFuture {
        /** The handle associated with this connection request */
        private final H handle;

        /** The time up to this connection request will be valid */
        private final long deadline;

        /** The callback to call when the session is initialized */
        private final IoSessionInitializer<? extends ConnectFuture> sessionInitializer;

        public ConnectionRequest(H handle, IoSessionInitializer<? extends ConnectFuture> callback) {
            this.handle = handle;
            long timeout = getConnectTimeoutMillis();

            if (timeout <= 0L) {
                this.deadline = Long.MAX_VALUE;
            } else {
                this.deadline = System.currentTimeMillis() + timeout;
            }

            this.sessionInitializer = callback;
        }

        public H getHandle() {
            return handle;
        }

        public long getDeadline() {
            return deadline;
        }

        public IoSessionInitializer<? extends ConnectFuture> getSessionInitializer() {
            return sessionInitializer;
        }

        @Override
        public boolean cancel() {
            if (!isDone()) {
                boolean justCancelled = super.cancel();

                // We haven't cancelled the request before, so add the future
                // in the cancel queue.
                if (justCancelled) {
                    cancelQueue.add(this);
                    startupWorker();
                    wakeup();
                }
            }

            return true;
        }
    }

我們可以看到,ConnectionRequest繼承自DefaultConnectFuture ;創建完ConnectionRequest這個對象之后,就會將對應的對象實例添加到隊列中去:

ConnectionRequest request = new ConnectionRequest(handle, sessionInitializer);
connectQueue.add(request);
 startupWorker();
 wakeup();
 return request;

通過方法startupWorker()實現請求隊列的輪詢操作,具體是創建對應的Connector(實現Runnable),然后通過調用線程池去完成對應的操作,我們來看看Connector的實現過程 (AbstractPollingIoConnector.java內部類):

private class Connector implements Runnable {

        public void run() {
            assert (connectorRef.get() == this);

            int nHandles = 0;

            while (selectable) {
                try {
                    // the timeout for select shall be smaller of the connect
                    // timeout or 1 second...
                    int timeout = (int) Math.min(getConnectTimeoutMillis(), 1000L);
                    int selected = select(timeout);

                    nHandles += registerNew();

                    // get a chance to get out of the connector loop, if we
                    // don't have any more handles
                    if (nHandles == 0) {
                        connectorRef.set(null);

                        if (connectQueue.isEmpty()) {
                            assert (connectorRef.get() != this);
                            break;
                        }

                        if (!connectorRef.compareAndSet(null, this)) {
                            assert (connectorRef.get() != this);
                            break;
                        }

                        assert (connectorRef.get() == this);
                    }

                    if (selected > 0) {
                        nHandles -= processConnections(selectedHandles());
                    }

                    processTimedOutSessions(allHandles());

                    nHandles -= cancelKeys();
                } catch (ClosedSelectorException cse) {
                    // If the selector has been closed, we can exit the loop
                    ExceptionMonitor.getInstance().exceptionCaught(cse);
                    break;
                } catch (Exception e) {
                    ExceptionMonitor.getInstance().exceptionCaught(e);

                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e1) {
                        ExceptionMonitor.getInstance().exceptionCaught(e1);
                    }
                }
            }

            if (selectable && isDisposing()) {
                selectable = false;
                try {
                    if (createdProcessor) {
                        processor.dispose();
                    }
                } finally {
                    try {
                        synchronized (disposalLock) {
                            if (isDisposing()) {
                                destroy();
                            }
                        }
                    } catch (Exception e) {
                        ExceptionMonitor.getInstance().exceptionCaught(e);
                    } finally {
                        disposalFuture.setDone();
                    }
                }
            }
        }
    }

由源碼我們可以看到具體的操作方法processConnections中,實現了主要操作:

/**
     * Process the incoming connections, creating a new session for each valid
     * connection.
     */
    private int processConnections(Iterator<H> handlers) {
        int nHandles = 0;

        // Loop on each connection request
        while (handlers.hasNext()) {
            H handle = handlers.next();
            handlers.remove();

            ConnectionRequest connectionRequest = getConnectionRequest(handle);

            if (connectionRequest == null) {
                continue;
            }

            boolean success = false;
            try {
                if (finishConnect(handle)) {
                    T session = newSession(processor, handle);
                    initSession(session, connectionRequest, connectionRequest.getSessionInitializer());
                    // Forward the remaining process to the IoProcessor.
                    session.getProcessor().add(session);
                    nHandles++;
                }
                success = true;
            } catch (Exception e) {
                connectionRequest.setException(e);
            } finally {
                if (!success) {
                    // The connection failed, we have to cancel it.
                    cancelQueue.offer(connectionRequest);
                }
            }
        }
        return nHandles;
    }

該方法判斷連接是否完成,如果完成,創建初始化的Session對象;執行方法 initSession(session, connectionRequest, connectionRequest.getSessionInitializer())之后,就會將ConnectFuture中的狀態碼ready設置為true(這一塊的代碼暫時沒有發現,但是通過打斷點得到執行完該方法,ready=true),修改狀態時,執行方法(ConnectFuture中的方法)setValue(...):

/**
     * Sets the result of the asynchronous operation, and mark it as finished.
     * 
     * @param newValue The result to store into the Future
     * @return {@code true} if the value has been set, {@code false} if
     * the future already has a value (thus is in ready state)
     */
    public boolean setValue(Object newValue) {
        synchronized (lock) {
            // Allowed only once.
            if (ready) {
                return false;
            }

            result = newValue;
            ready = true;
            
            // Now, if we have waiters, notify them that the operation has completed
            if (waiters > 0) {
                lock.notifyAll();
            }
        }

        // Last, not least, inform the listeners
        notifyListeners();
        
        return true;
    }

執行該方法,就會調用notifyListeners方法來實現回調函數,由于awaitUninterruptibly()方法一直在阻塞等待ready狀態變化來結束執行,接下來分別看看兩種方式的執行:
1.awaitUninterruptibly():

/**
     * {@inheritDoc}
     */
    @Override
    public ConnectFuture awaitUninterruptibly() {
        return (ConnectFuture) super.awaitUninterruptibly();
    }

awaitUninterruptibly方法最終實現方法:

/**
     * Wait for the Future to be ready. If the requested delay is 0 or
     * negative, this method immediately returns the value of the
     * 'ready' flag.
     * Every 5 second, the wait will be suspended to be able to check if
     * there is a deadlock or not.
     * 
     * @param timeoutMillis The delay we will wait for the Future to be ready
     * @param interruptable Tells if the wait can be interrupted or not
     * @return <tt>true</tt> if the Future is ready
     * @throws InterruptedException If the thread has been interrupted
     * when it's not allowed.
     */
    private boolean await0(long timeoutMillis, boolean interruptable) throws InterruptedException {
        long endTime = System.currentTimeMillis() + timeoutMillis;

        if (endTime < 0) {
            endTime = Long.MAX_VALUE;
        }

        synchronized (lock) {
            // We can quit if the ready flag is set to true, or if
            // the timeout is set to 0 or below : we don't wait in this case.
            if (ready||(timeoutMillis <= 0)) {
                return ready;
            }

            // The operation is not completed : we have to wait
            waiters++;

            try {
                for (;;) {
                    try {
                        long timeOut = Math.min(timeoutMillis, DEAD_LOCK_CHECK_INTERVAL);
                        
                        // Wait for the requested period of time,
                        // but every DEAD_LOCK_CHECK_INTERVAL seconds, we will
                        // check that we aren't blocked.
                        lock.wait(timeOut);
                    } catch (InterruptedException e) {
                        if (interruptable) {
                            throw e;
                        }
                    }

                    if (ready || (endTime < System.currentTimeMillis())) {
                        return ready;
                    } else {
                        // Take a chance, detect a potential deadlock
                        checkDeadLock();
                    }
                }
            } finally {
                // We get here for 3 possible reasons :
                // 1) We have been notified (the operation has completed a way or another)
                // 2) We have reached the timeout
                // 3) The thread has been interrupted
                // In any case, we decrement the number of waiters, and we get out.
                waiters--;
                
                if (!ready) {
                    checkDeadLock();
                }
            }
        }
    }

因此我們可以知道,通過阻塞的方式實現最終的連接成功,接著獲取對應的IoSession;
2.通過回調來實現方法:notifyListeners():

/**
     * Notify the listeners, if we have some.
     */
    private void notifyListeners() {
        // There won't be any visibility problem or concurrent modification
        // because 'ready' flag will be checked against both addListener and
        // removeListener calls.
        if (firstListener != null) {
            notifyListener(firstListener);
            firstListener = null;

            if (otherListeners != null) {
                for (IoFutureListener<?> listener : otherListeners) {
                    notifyListener(listener);
                }
                
                otherListeners = null;
            }
        }
    }

    @SuppressWarnings("unchecked")
    private void notifyListener(IoFutureListener listener) {
        try {
            listener.operationComplete(this);
        } catch (Exception e) {
            ExceptionMonitor.getInstance().exceptionCaught(e);
        }
    }

ConnectFuture回調的方法來實現獲取Session的代碼:

connFuture.addListener( new IoFutureListener(){
            public void operationComplete(IoFuture future) {
                ConnectFuture connFuture = (ConnectFuture)future;
                if( connFuture.isConnected() ){
                    session = future.getSession();
                    try {
                        sendData();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } else {
                    log.error("Not connected...exiting");
                }
            }
        });

我們來看一下addListener的源碼:

/**
     * {@inheritDoc}
     */
    public IoFuture addListener(IoFutureListener<?> listener) {
        if (listener == null) {
            throw new IllegalArgumentException("listener");
        }

        synchronized (lock) {
            if (ready) {
                // Shortcut : if the operation has completed, no need to 
                // add a new listener, we just have to notify it. The existing
                // listeners have already been notified anyway, when the 
                // 'ready' flag has been set.
                notifyListener(listener);
            } else {
                if (firstListener == null) {
                    firstListener = listener;
                } else {
                    if (otherListeners == null) {
                        otherListeners = new ArrayList<IoFutureListener<?>>(1);
                    }
                    
                    otherListeners.add(listener);
                }
            }
        }
        
        return this;
    }

至此,MINA客戶端的核心連接IoFuture的連接操作流程已經結束;
參考官網以及對應的源碼包

Android端UDP代碼示例

MINA客戶端的UDP連接和TCP的連接在代碼方面沒有太大的區別,畢竟MINA實現方面使用的是統一的API接口,至于不同點,主要對于IoConnector的實現類不一樣:

/**
     * 創建udp客戶端的連接
     */
    public void createUdpConnect() throws InterruptedException {
        if (udpUoSession == null) {
            //首先創建對應的tcp連接
            udpIoConnector = new NioDatagramConnector();
            //設置超時時間
            udpIoConnector.setConnectTimeoutMillis(CONNECT_TIMEOUT);
            //添加過濾器
            udpIoConnector.getFilterChain().addLast("logger", new LoggingFilter());//添加日志過濾器
            udpIoConnector.getFilterChain().addLast("codec", new ProtocolCodecFilter(new TextLineCodecFactory()));//設置字節處理過濾器
            //添加IoHandler
            udpIoConnector.setHandler(new ClientSessionHandler());

            //通過ConnectFuture來連接服務器
            for (; ; ) {
                try {
                    ConnectFuture future = udpIoConnector.connect(new InetSocketAddress(HOSTNAME, PORT + 1));
                    future.awaitUninterruptibly();
                    udpUoSession = future.getSession();
                    break;//連接成功跳出死循環
                } catch (Exception e) {
                    System.err.println("Failed to connect");
                    e.printStackTrace();
                    Thread.sleep(5000);//如果連接失敗,5秒后繼續連接直到連接成功
                }
            }
        }
        udpUoSession.write("udp-ceshi");
    }

由代碼我們可以發現,API的調用方式基本一致;
注:Android使用過程中,Socket連接不應該在主線程中實現,否則會報錯

至此我們的MINA的ANDROID客戶端開發大體的流程已經完成,后續會繼續MINA各個單獨模塊進行使用以及源碼分析。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容