All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.firenio.baseio.component.ChannelConnector Maven / Gradle / Ivy

There is a newer version: 3.2.9.beta11
Show newest version
/*
 * Copyright 2015 The Baseio Project
 *  
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *  
 *      http://www.apache.org/licenses/LICENSE-2.0
 *  
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.firenio.baseio.component;

import java.io.Closeable;
import java.io.IOException;
import java.net.InetAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;

import com.firenio.baseio.TimeoutException;
import com.firenio.baseio.collection.DelayedQueue.DelayTask;
import com.firenio.baseio.common.Assert;
import com.firenio.baseio.common.Util;
import com.firenio.baseio.component.NioEventLoop.EpollNioEventLoopUnsafe;
import com.firenio.baseio.component.NioEventLoop.JavaNioEventLoopUnsafe;
import com.firenio.baseio.concurrent.Callback;
import com.firenio.baseio.concurrent.Waiter;

/**
 * @author wangkai
 *
 */
public final class ChannelConnector extends ChannelContext implements Closeable {

    private volatile Callback callback;
    private volatile boolean           callbacked = true;
    private Channel                    ch;
    private NioEventLoop               eventLoop;
    private volatile DelayTask         timeoutTask;
    private ConnectorUnsafe            unsafe;

    public ChannelConnector(int port) {
        this("127.0.0.1", port);
    }

    public ChannelConnector(NioEventLoop eventLoop, String host, int port) {
        this(eventLoop.getGroup(), host, port);
        this.eventLoop = eventLoop;
    }

    public ChannelConnector(NioEventLoopGroup group) {
        this(group, "127.0.0.1", 0);
    }

    public ChannelConnector(NioEventLoopGroup group, String host, int port) {
        super(group, host, port);
        if (!group.isSharable() && !group.isRunning()) {
            group.setEventLoopSize(1);
        }
        if (Native.EPOLL_AVAIABLE) {
            unsafe = new EpollConnectorUnsafe();
        } else {
            unsafe = new JavaConnectorUnsafe();
        }
    }

    public ChannelConnector(String host, int port) {
        this(new NioEventLoopGroup(1), host, port);
    }

    @Override
    protected void channelEstablish(Channel ch, Throwable ex) {
        if (!callbacked) {
            if (ex != null) {
                unsafe.cancel(this, eventLoop);
            }
            this.ch = ch;
            this.callbacked = true;
            this.timeoutTask.cancel();
            try {
                this.callback.call(ch, ex);
            } catch (Throwable e) {
                if (e instanceof Error) {
                    e.printStackTrace(System.err);
                }
            }
        }
    }

    @Override
    public synchronized void close() throws IOException {
        Util.close(ch);
        if (ch == null && eventLoop != null) {
            unsafe.cancel(this, eventLoop);
        }
        Util.stop(this);
        if (!getProcessorGroup().isSharable()) {
            this.eventLoop = null;
        }
        this.ch = null;
    }

    public synchronized Channel connect() throws Exception {
        return connect(3000);
    }

    public synchronized void connect(Callback callback) throws Exception {
        connect(callback, 3000);
    }

    public synchronized void connect(Callback callback, long timeout) throws Exception {
        Assert.notNull(callback, "null callback");
        if (isConnected()) {
            callback.call(ch, null);
            return;
        }
        if (!callbacked) {
            throw new IOException("connect is pending");
        }
        this.callbacked = false;
        this.timeoutTask = new TimeoutTask(this, timeout);
        this.callback = callback;
        this.getProcessorGroup().setContext(this);
        Util.start(getProcessorGroup());
        Util.start(this);
        if (eventLoop == null) {
            eventLoop = getProcessorGroup().getNext();
        }
        boolean submitted = this.eventLoop.submit(new Runnable() {

            @Override
            public void run() {
                try {
                    ChannelConnector ctx = ChannelConnector.this;
                    unsafe.connect(ctx, ctx.eventLoop);
                } catch (Throwable e) {
                    channelEstablish(null, e);
                }
            }
        });
        if (!submitted) {
            channelEstablish(null, new IOException("task submit failed"));
        }
    }

    public synchronized Channel connect(long timeout) throws Exception {
        Waiter callback = new Waiter<>();
        this.connect(callback, timeout);
        if (eventLoop.inEventLoop()) {
            throw new IOException("can not blocking connect in its event loop");
        }
        // If your application blocking here, check if you are blocking the io thread.
        // Notice that do not blocking io thread at any time.
        if (callback.await()) {
            Util.close(this);
            throw new TimeoutException("connect to " + getServerAddress() + " time out");
        }
        if (callback.isFailed()) {
            Util.close(this);
            Throwable ex = callback.getThrowable();
            if (ex instanceof Exception) {
                throw (Exception) callback.getThrowable();
            }
            throw new IOException("connect failed", ex);
        }
        return getChannel();
    }

    public Channel getChannel() {
        return ch;
    }

    public NioEventLoop getEventLoop() {
        return eventLoop;
    }

    protected ConnectorUnsafe getUnsafe() {
        return unsafe;
    }

    @Override
    public boolean isActive() {
        return isConnected();
    }

    public boolean isConnected() {
        Channel ch = this.ch;
        return ch != null && ch.isOpened();
    }

    @Override
    public String toString() {
        Channel ch = this.ch;
        if (ch == null) {
            return super.toString();
        }
        return ch.toString();
    }

    static abstract class ConnectorUnsafe {

        abstract void connect(ChannelConnector ctx, NioEventLoop el) throws IOException;

        abstract void cancel(ChannelConnector ctx, NioEventLoop el);

    }

    static final class EpollConnectorUnsafe extends ConnectorUnsafe {

        private int     fd = -1;
        private volatile boolean needCancel;
        private String remoteAddr;

        @Override
        void connect(ChannelConnector ctx, NioEventLoop el) throws IOException {
            this.cancel(ctx, el);
            EpollNioEventLoopUnsafe un = (EpollNioEventLoopUnsafe) el.getUnsafe();
            InetAddress host = InetAddress.getByName(ctx.getHost());
            this.remoteAddr = host.getHostAddress();
            int fd = Native.connect(host.getHostAddress(), ctx.getPort());
            Native.throwException(fd);
            this.needCancel = true;
            this.fd = fd;
            el.schedule(ctx.timeoutTask);
            un.ctxs.put(fd, ctx);
            int res = Native.epoll_add(un.epfd, fd, Native.EPOLLOUT);
            Native.throwException(res);
        }

        @Override
        void cancel(ChannelConnector ctx, NioEventLoop el) {
            if (needCancel) {
                EpollNioEventLoopUnsafe un = (EpollNioEventLoopUnsafe) el.getUnsafe();
                un.ctxs.remove(fd);
                Native.epoll_del(un.epfd, fd);
                Native.close(fd);
                fd = -1;
                needCancel = false;
            }
        }
        
        String getRemoteAddr() {
            return remoteAddr;
        }

        int getFd() {
            return fd;
        }

    }

    static final class JavaConnectorUnsafe extends ConnectorUnsafe {

        private SocketChannel javaChannel;

        @Override
        void connect(ChannelConnector ctx, NioEventLoop el) throws IOException {
            Util.close(javaChannel);
            this.javaChannel = SocketChannel.open();
            this.javaChannel.configureBlocking(false);
            if (!javaChannel.connect(ctx.getServerAddress())) {
                el.schedule(ctx.timeoutTask);
                JavaNioEventLoopUnsafe elUnsafe = (JavaNioEventLoopUnsafe) el.getUnsafe();
                javaChannel.register(elUnsafe.getSelector(), SelectionKey.OP_CONNECT, ctx);
            }
        }

        SocketChannel getSelectableChannel() {
            return javaChannel;
        }

        @Override
        void cancel(ChannelConnector ctx, NioEventLoop el) {
            final SocketChannel channel = this.javaChannel;
            final JavaNioEventLoopUnsafe un = (JavaNioEventLoopUnsafe) el.getUnsafe();
            if (channel != null) {
                SelectionKey key = channel.keyFor(un.getSelector());
                if (key != null) {
                    key.cancel();
                }
                Util.close(channel);
            }
        }

    }

    static final class TimeoutTask extends DelayTask {

        private ChannelConnector ctx;

        public TimeoutTask(ChannelConnector ctx, long delay) {
            super(delay);
            this.ctx = ctx;
        }

        @Override
        public void run() {
            ctx.channelEstablish(null, new TimeoutException("connect timeout"));
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy