net.tomp2p.connection2.ChannelCreator.java Source code

Java tutorial

Introduction

Here is the source code for net.tomp2p.connection2.ChannelCreator.java

Source

/*
 * Copyright 2013 Thomas Bocek
 *
 * 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 net.tomp2p.connection2;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.FixedRecvByteBufAllocator;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.ChannelGroupFuture;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.channel.socket.nio.NioDatagramChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.util.concurrent.GenericFutureListener;
import io.netty.util.concurrent.GlobalEventExecutor;

import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.Map;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import net.tomp2p.futures.FutureDone;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Creates the channels. This class is created by {@link ConnectionReservation} and should never be called directly.
 * With this class one can create TCP or UDP channels up to a certain extend. Thus it must be know beforehand how much
 * connection will be created.
 * 
 * @author Thomas Bocek
 */
public class ChannelCreator {
    private static final Logger LOG = LoggerFactory.getLogger(ChannelCreator.class);
    // statistics
    private static final AtomicInteger CREATED_TCP_CONNECTIONS = new AtomicInteger(0);
    private static final AtomicInteger CREATED_UDP_CONNECTIONS = new AtomicInteger(0);

    private final EventLoopGroup workerGroup;
    private final ChannelGroup recipients = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);

    private final int maxPermitsUDP;
    private final int maxPermitsTCP;

    private final Semaphore semaphoreUPD;
    private final Semaphore semaphoreTCP;

    // we should be fair, otherwise we see connection timeouts due to unfairness if busy
    private final ReadWriteLock readWriteLockUDP = new ReentrantReadWriteLock(true);
    private final Lock readUDP = readWriteLockUDP.readLock();
    private final Lock writeUDP = readWriteLockUDP.writeLock();

    private final ReadWriteLock readWriteLockTCP = new ReentrantReadWriteLock(true);
    private final Lock readTCP = readWriteLockTCP.readLock();
    private final Lock writeTCP = readWriteLockTCP.writeLock();

    private boolean shutdownUDP = false;
    private boolean shutdownTCP = false;

    private final FutureDone<Void> futureChannelCreationDone;

    private final ChannelClientConfiguration channelClientConfiguration;

    /**
     * Package private constructor, since this is created by {@link ConnectionReservation} and should never be called
     * directly.
     * 
     * @param workerGroup
     *            The worker group for netty that is shared between TCP and UDP. This workergroup is not shutdown if
     *            this class is shutdown
     * @param futureChannelCreationDone
     *            We need to set this from the outside as we want to attach listeners to it
     * @param maxPermitsUDP
     *            The number of max. parallel UDP connections.
     * @param maxPermitsTCP
     *            The number of max. parallel TCP connections.
     * @param channelClientConfiguration
     *            The configuration that contains the pipeline filter
     */
    ChannelCreator(final EventLoopGroup workerGroup, final FutureDone<Void> futureChannelCreationDone,
            final int maxPermitsUDP, final int maxPermitsTCP,
            final ChannelClientConfiguration channelClientConfiguration) {
        this.workerGroup = workerGroup;
        this.futureChannelCreationDone = futureChannelCreationDone;
        this.maxPermitsUDP = maxPermitsUDP;
        this.maxPermitsTCP = maxPermitsTCP;
        this.semaphoreUPD = new Semaphore(maxPermitsUDP);
        this.semaphoreTCP = new Semaphore(maxPermitsTCP);
        this.channelClientConfiguration = channelClientConfiguration;
    }

    /**
     * Creates a "channel" to the given address. This won't send any message unlike TCP.
     * 
     * @param recipient
     *            The recipient of the a message
     * 
     * @param broadcast
     *            Sets this channel to be able to broadcast
     * @param channelHandlers
     *            The handlers to set
     * @return The channel future object or null if we are shut down
     */
    public ChannelFuture createUDP(final SocketAddress recipient, final boolean broadcast,
            final Map<String, ChannelHandler> channelHandlers) {
        readUDP.lock();
        try {
            if (shutdownUDP) {
                return null;
            }
            if (!semaphoreUPD.tryAcquire()) {
                LOG.error("Tried to acquire more resources (UDP) than announced!");
                throw new RuntimeException("Tried to acquire more resources (UDP) than announced!");
            }
            final Bootstrap b = new Bootstrap();
            b.group(workerGroup);
            b.channel(NioDatagramChannel.class);
            b.option(ChannelOption.RCVBUF_ALLOCATOR, new FixedRecvByteBufAllocator(ConnectionBean.UDP_LIMIT));
            if (broadcast) {
                b.option(ChannelOption.SO_BROADCAST, true);
            }
            channelClientConfiguration.pipelineFilter().filter(channelHandlers, false, true);
            addHandlers(b, channelHandlers);
            // Here we need to bind, as opposed to the TCP, were we connect if we do a connect, we cannot receive
            // broadcast messages
            final ChannelFuture channelFuture;
            if (broadcast) {
                channelFuture = b.bind(new InetSocketAddress(0));
            } else {
                channelFuture = b.connect(recipient);
            }

            setupCloseListener(channelFuture, semaphoreUPD);
            CREATED_UDP_CONNECTIONS.incrementAndGet();
            return channelFuture;
        } finally {
            readUDP.unlock();
        }
    }

    /**
     * Creates a channel to the given address. This will setup the TCP connection
     * 
     * @param socketAddress
     *            The address to send future messages
     * @param connectionTimeoutMillis
     *            The timeout for establishing a TCP connection
     * @param channelHandlers
     *            The handlers to set
     * @return The channel future object or null if we are shut down.
     */
    public ChannelFuture createTCP(final SocketAddress socketAddress, final int connectionTimeoutMillis,
            final Map<String, ChannelHandler> channelHandlers) {
        readTCP.lock();
        try {
            if (shutdownTCP) {
                return null;
            }
            if (!semaphoreTCP.tryAcquire()) {
                LOG.error("Tried to acquire more resources (TCP) than announced!");
                throw new RuntimeException("Tried to acquire more resources (TCP) than announced!");
            }
            Bootstrap b = new Bootstrap();
            b.group(workerGroup);
            b.channel(NioSocketChannel.class);
            b.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectionTimeoutMillis);
            b.option(ChannelOption.TCP_NODELAY, true);
            b.option(ChannelOption.SO_LINGER, 0);
            b.option(ChannelOption.SO_REUSEADDR, true);
            // b.option(ChannelOption.SO_KEEPALIVE, Boolean.TRUE);
            channelClientConfiguration.pipelineFilter().filter(channelHandlers, true, true);
            addHandlers(b, channelHandlers);

            ChannelFuture channelFuture = b.connect(socketAddress);

            setupCloseListener(channelFuture, semaphoreTCP);
            CREATED_TCP_CONNECTIONS.incrementAndGet();
            return channelFuture;
        } finally {
            readTCP.unlock();
        }
    }

    /**
     * Since we want to add multiple handlers, we need to do this with the pipeline.
     * 
     * @param b
     *            The boostrap
     * @param channelHandlers
     *            The handlers to be added.
     */
    private void addHandlers(final Bootstrap b, final Map<String, ChannelHandler> channelHandlers) {
        b.handler(new ChannelInitializer<Channel>() {
            @Override
            protected void initChannel(final Channel ch) throws Exception {
                for (Map.Entry<String, ChannelHandler> entry : channelHandlers.entrySet()) {
                    ch.pipeline().addLast(entry.getKey(), entry.getValue());
                }
            }
        });
    }

    /**
     * When a channel is closed, the semaphore is released an other channel can be created.
     * 
     * @param channelFuture
     *            The channel future
     * @param semaphore
     *            The semaphore to decrease
     * @return The same future that was passed as an argument
     */
    private ChannelFuture setupCloseListener(final ChannelFuture channelFuture, final Semaphore semaphore) {
        channelFuture.channel().closeFuture().addListener(new GenericFutureListener<ChannelFuture>() {
            @Override
            public void operationComplete(final ChannelFuture future) throws Exception {
                semaphore.release();
            }
        });
        recipients.add(channelFuture.channel());
        return channelFuture;
    }

    /**
     * Shutdown this channel creator. This means that no TCP or UDP connection can be established.
     * 
     * @return The shutdown future.
     */
    public FutureDone<Void> shutdown() {
        // set shutdown flag for UDP and TCP, if we acquire a write lock, all read locks are blocked as well
        writeUDP.lock();
        writeTCP.lock();
        try {
            if (shutdownTCP || shutdownUDP) {
                shutdownFuture().setFailed("already shutting down");
                return shutdownFuture();
            }
            shutdownUDP = true;
            shutdownTCP = true;
        } finally {
            writeTCP.unlock();
            writeUDP.unlock();
        }

        recipients.close().addListener(new GenericFutureListener<ChannelGroupFuture>() {
            @Override
            public void operationComplete(final ChannelGroupFuture future) throws Exception {
                if (!semaphoreUPD.tryAcquire(maxPermitsUDP)) {
                    LOG.error("Cannot shutdown, as connections (UDP) are still alive");
                    shutdownFuture().setFailed("Cannot shutdown, as connections (UDP) are still alive");
                    throw new RuntimeException("Cannot shutdown, as connections (UDP) are still alive");
                }
                if (!semaphoreTCP.tryAcquire(maxPermitsTCP)) {
                    LOG.error("Cannot shutdown, as connections (TCP) are still alive");
                    shutdownFuture().setFailed("Cannot shutdown, as connections (TCP) are still alive");
                    throw new RuntimeException("Cannot shutdown, as connections (TCP) are still alive");
                }
                shutdownFuture().setDone();
            }
        });
        return shutdownFuture();
    }

    /**
     * @return The shutdown future that is used when calling {@link #shutdown()}
     */
    public FutureDone<Void> shutdownFuture() {
        return futureChannelCreationDone;
    }

    /**
     * @return The number of created TCP connections for *all* created TCP connection
     */
    public static int tcpConnectionCount() {
        return CREATED_TCP_CONNECTIONS.get();
    }

    /**
     * @return The number of created UDP connections for *all* created TCP connection
     */
    public static int udpConnectionCount() {
        return CREATED_UDP_CONNECTIONS.get();
    }

    /**
     * Resets the statistical data.
     */
    public static void resetConnectionCounts() {
        CREATED_TCP_CONNECTIONS.set(0);
        CREATED_UDP_CONNECTIONS.set(0);
    }
}