TcpController.java :  » UnTagged » androidsipservice » com » colibria » android » sipservice » Android Open Source

Android Open Source » UnTagged » androidsipservice 
androidsipservice » com » colibria » android » sipservice » TcpController.java
/*
 *
 * Copyright (C) 2010 Colibria AS
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package com.colibria.android.sipservice;

import com.colibria.android.sipservice.logging.Logger;

import java.io.EOFException;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.CancelledKeyException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Set;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author Sebastian Dehne
 */
public class TcpController extends Thread {
    private static final String TAG = "TcpController";

    // todo implement a connect timeout

    private static final int CONNECT_TIMEOUT = 1000 * 10;
    private static final int READ_BUFFER_SIZE = 1024 * 8 * 4;

    public static TcpController createPreStartedController(ScheduledExecutorService threadPool) {
        TcpController result = null;
        try {
            result = new TcpController(threadPool);
            result.setDaemon(true);
            result.start();
            while(!result.isRunning) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    //
                }
            }
        } catch (IOException e) {
            Logger.e(TAG, "Could not create/start controller", e);
        }
        return result;
    }

    private final ScheduledExecutorService mThreadPool;
    private final Selector mSelector;
    private final AtomicInteger mConnectionIdCounter;
    private volatile boolean isRunning;

    private TcpController(ScheduledExecutorService threadPool) throws IOException {
        mSelector = Selector.open();
        this.mThreadPool = threadPool;
        isRunning = false;
        mConnectionIdCounter = new AtomicInteger(0);
        Logger.e(TAG, "Done constructing a new controller");
    }

    @Override
    public void run() {
        Logger.i(TAG, "Controller started");
        isRunning = true;

        try {
            while (isRunning) {

                // guard
                synchronized (this) {
                }

                try {
                    if (mSelector.select() <= 0) {
                        Logger.d(TAG, "selector returned with nothing");
                        continue;
                    }
                } catch (IOException e) {
                    Logger.i(TAG, "error during selection", e);
                }

                Set<SelectionKey> keys = mSelector.selectedKeys();
                for (SelectionKey key : keys) {

                    // I can read something
                    if ((key.readyOps() & SelectionKey.OP_READ) == SelectionKey.OP_READ) {
                        handleReadOperation(key);
                    }

                    // I can write something
                    else if ((key.readyOps() & SelectionKey.OP_WRITE) == SelectionKey.OP_WRITE) {
                        handleWriteOperation(key);
                    }

                    // I can connect something
                    else if ((key.readyOps() & SelectionKey.OP_CONNECT) == SelectionKey.OP_CONNECT) {
                        handleConnectOperation(key);
                    }

                    // I can accept something
                    else if ((key.readyOps() & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT) {
                        // not used
                        Logger.e(TAG, "Got an unexpected accept signal");
                    }
                }
                keys.clear();

            }
        } catch (Throwable t) {
            // todo what to do now?
            Logger.e(TAG, "Main looper died", t);
        }

        /*
         * Try to close all connections
         */
        try {
            if (mSelector.keys() != null) {
                for (SelectionKey k : mSelector.keys()) {
                    k.cancel();
                    k.channel().close();
                }
            }
            mSelector.close();
        } catch (IOException e) {
            Logger.e(TAG, "Could not close selector", e);
        }
    }

    public TcpConnection createNewManagedConnection(String remoteHostname, int remotePort, ITcpConnectionListener listener) {
        Logger.d(TAG, "createNewManagedConnection() - isRunning=" + isRunning + ", remoteHostname=" + remoteHostname + ", remotePort=" + remotePort);
        if (!isRunning) {
            return null;
        }

        return new TcpConnection(this, mConnectionIdCounter.getAndIncrement(), listener, remoteHostname, remotePort);
    }

    public void shutdown() {
        Logger.d(TAG, "shutdown() - " + isRunning);

        synchronized (this) {
            if (isRunning) {
                isRunning = false;
                mSelector.wakeup();
            }
        }
    }

    public void sendKeepAlives() {
        Logger.d(TAG, "sendKeepAlives() - " + isRunning);

        if (!isRunning) {
            return;
        }

        synchronized (this) {
            mSelector.wakeup();
            Set<SelectionKey> keys = mSelector.keys();
            SelectionKeyAttachment a;
            if (keys != null) {
                for (SelectionKey k : keys) {
                    if ((a = (SelectionKeyAttachment) k.attachment()) != null) {
                        a.connection.sendKeepAliveNow();
                    }
                }
            }
        }
    }

    protected void haveDataToBeWritten(SocketChannel sc) {
        Logger.d(TAG, "haveDataToBeWritten() - " + isRunning);

        if (!isRunning)
            return;

        synchronized (this) {
            mSelector.wakeup();
            SelectionKey sk = sc.keyFor(mSelector);
            if (sk != null) {
                try {
                    sk.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
                } catch (CancelledKeyException e) {
                    Logger.d(TAG, "key already canceled, ignoring this event");
                }
            }
        }
    }

    protected void openNewConnection(TcpConnection connection) {
        Logger.d(TAG, "openNewConnection() - " + isRunning);

        if (!isRunning)
            return;

        /*
         * Connect now
         */
        InetSocketAddress remoteSocketAddress = connection.getRemoteAddress();
        Logger.d(TAG, "About to establish a new connection to " + remoteSocketAddress);
        if (remoteSocketAddress.isUnresolved()) {
            Logger.i(TAG, "Could not resolve hostname - connect failed");
            connection.mHiddenListener.connectFailed();
            return;
        }

        SocketChannel sc = null;
        try {
            sc = SocketChannel.open();
            sc.configureBlocking(false);
            if (sc.connect(remoteSocketAddress)) {
                finishedConnect(sc, connection);
            } else {
                synchronized (this) {
                    mSelector.wakeup();
                    SelectionKeyAttachment ska = new SelectionKeyAttachment(sc, connection);
                    sc.register(mSelector, SelectionKey.OP_CONNECT, ska);
                    ska.startConnectTimeoutTask();
                }
            }
        } catch (IOException e) {
            Logger.i(TAG, "error during connect", e);
            closeChannel(sc);
            connection.mHiddenListener.connectFailed();
        }
    }

    protected void closeConnection(SocketChannel mSocketChannel) {
        Logger.d(TAG, "closing connection");
        closeChannel(mSocketChannel);
    }

    private void finishedConnect(final SocketChannel sc, final TcpConnection connectionListener) {
        Logger.d(TAG, "in finishedConnect()");

        synchronized (this) {
            mSelector.wakeup();

            SelectionKey existingKey = sc.keyFor(mSelector);
            SelectionKeyAttachment ska = null;
            if (existingKey != null) {
                ska = (SelectionKeyAttachment) existingKey.attachment();
            }

            try {
                if (sc.finishConnect()) {
                    if (ska != null) {
                        ska.cancelConnectTimeoutTask();
                    } else {
                        ska = new SelectionKeyAttachment(sc, connectionListener);
                    }

                    sc.configureBlocking(false);
                    sc.register(mSelector, SelectionKey.OP_READ, ska);
                    mThreadPool.execute(new Runnable() {
                        @Override
                        public void run() {
                            connectionListener.mHiddenListener.newSocketEstablished(sc);
                        }
                    });
                } else {
                    Logger.i(TAG, "finishedConnect() failed");
                    closeChannel(sc);
                    mThreadPool.execute(new Runnable() {
                        @Override
                        public void run() {
                            connectionListener.mHiddenListener.connectFailed();
                        }
                    });
                }
            } catch (IOException e) { // thrown by finishConnect()
                Logger.i(TAG, "Could not complete connect", e);
                closeChannel(sc);
                mThreadPool.execute(new Runnable() {
                    @Override
                    public void run() {
                        connectionListener.mHiddenListener.connectFailed();
                    }
                });
            }
        }
    }

    private void closeChannel(SocketChannel sc) {
        Logger.d(TAG, "closeChannel() " + isRunning);

        if (!isRunning || sc == null)
            return;

        synchronized (this) {
            try {
                mSelector.wakeup();
                SelectionKey key = sc.keyFor(mSelector);
                if (key != null) {
                    key.cancel();
                }
                sc.close();
            } catch (IOException e) {
                //ignore
            }
        }
    }

    private void handleReadOperation(SelectionKey key) {
        SocketChannel sc = (SocketChannel) key.channel();
        SelectionKeyAttachment attachment = (SelectionKeyAttachment) key.attachment();
        try {
            if (sc.read(attachment.readBuffer) != -1) {
                Logger.d(TAG, "read " + attachment.readBuffer.position() + " bytes");
                if (attachment.readBuffer.position() > 0) {
                    attachment.connection.mHiddenListener.dataReceived(attachment.readBuffer);
                } else {
                    Logger.d(TAG, "read ignore since 0 bytes was read");
                }
            } else {
                Logger.d(TAG, "read got -1, closing channel");
                closeChannel(sc);
                attachment.connection.mHiddenListener.connectionLost();
            }
        } catch (IOException e) {
            Logger.i(TAG, "Could not read from socket ", e);
            closeChannel(sc);
            attachment.connection.mHiddenListener.connectionLost();
        }
    }

    private void handleWriteOperation(SelectionKey key) {
        SocketChannel sc = (SocketChannel) key.channel();
        SelectionKeyAttachment attachment = (SelectionKeyAttachment) key.attachment();

        WriteTask writeTask;
        if ((writeTask = attachment.connection.mHiddenListener.getNextWriteTask()) != null) {
            try {
                performWrite(sc, writeTask);
                Logger.d(TAG, "wrote " + writeTask.getData().limit() + " bytes");
                if (writeTask.getWhenDone() != null) {
                    try {
                        writeTask.getWhenDone().run();
                    } catch (Exception e) {
                        Logger.e(TAG, "whenDone threw exception", e);
                    }
                }
            } catch (Exception e) {
                Logger.i(TAG, "exception during write", e);
                try {
                    writeTask.getWhenError().run();
                } catch (Exception e2) {
                    Logger.e(TAG, "whenError threw exception", e2);
                }
                attachment.connection.mHiddenListener.connectionLost();
            }
        } else {
            // finished writing, no more data to be written for this connection
            key.interestOps(SelectionKey.OP_READ);
        }
    }

    private void handleConnectOperation(SelectionKey key) {
        SocketChannel sc = (SocketChannel) key.channel();
        SelectionKeyAttachment attachment = (SelectionKeyAttachment) key.attachment();
        finishedConnect(sc, attachment.connection);
    }

    private void performWrite(SocketChannel socketChannel, WriteTask writeTask) throws IOException {
        ByteBuffer bb = writeTask.getData();
        Selector writeSelector = null;
        SelectionKey key = null;
        int attempts = 0;
        try {
            while (bb.hasRemaining()) {
                int len = socketChannel.write(bb);
                attempts++;
                if (len < 0) {
                    throw new EOFException();
                }
                if (len == 0) {
                    if (writeSelector == null) {
                        writeSelector = Selector.open();
                        if (writeSelector == null) {
                            // Continue using the main one.
                            continue;
                        }
                    }

                    key = socketChannel.register(writeSelector, SelectionKey.OP_WRITE);

                    if (writeSelector.select(5 * 1000) == 0) { // 5 seconds timeout
                        if (attempts > 2)
                            throw new IOException("Client disconnected");
                    } else {
                        attempts--;
                    }
                } else {
                    attempts = 0;
                }
            }
        } finally {
            if (key != null) {
                key.cancel();
            }

            if (writeSelector != null) {
                // Flush the key.
                writeSelector.selectNow();
                writeSelector.close();
            }
        }
    }

    private class SelectionKeyAttachment {
        final TcpConnection connection;
        final SocketChannel sc;
        final ByteBuffer readBuffer;
        volatile ScheduledFuture connectTimeoutTask;

        private SelectionKeyAttachment(SocketChannel sc, TcpConnection connection) {
            this.sc = sc;
            this.connection = connection;
            readBuffer = ByteBuffer.allocate(READ_BUFFER_SIZE);
        }

        void startConnectTimeoutTask() {
            connectTimeoutTask = mThreadPool.schedule(new Runnable() {
                @Override
                public void run() {
                    Logger.d(TAG, "Connect timeout timer fired");
                    if (connectTimeoutTask != null) {
                        connectTimeoutTask = null;

                        // remove connect and notify listener
                        synchronized (this) {
                            mSelector.wakeup();
                            SelectionKey k = sc.keyFor(mSelector);
                            if (k != null) {
                                k.cancel();
                                connection.mHiddenListener.connectFailed();
                            }
                        }
                    }
                }
            }, CONNECT_TIMEOUT, TimeUnit.MILLISECONDS);
            Logger.d(TAG, "Scheduled connect timeout timer");
        }

        void cancelConnectTimeoutTask() {
            ScheduledFuture sf = connectTimeoutTask;
            if (sf != null) {
                sf.cancel(false);
                connectTimeoutTask = null;
            }
        }
    }
}
java2s.com  | Contact Us | Privacy Policy
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.