net.tomp2p.connection2.ChannelServer.java Source code

Java tutorial

Introduction

Here is the source code for net.tomp2p.connection2.ChannelServer.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.bootstrap.ServerBootstrap;
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.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioDatagramChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.util.concurrent.DefaultThreadFactory;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import net.tomp2p.futures.FutureDone;
import net.tomp2p.message.TomP2PCumulationTCP;
import net.tomp2p.message.TomP2POutbound;
import net.tomp2p.message.TomP2PSinglePacketUDP;
import net.tomp2p.peers.Number160;
import net.tomp2p.peers.PeerAddress;
import net.tomp2p.peers.PeerSocketAddress;
import net.tomp2p.peers.PeerStatusListener;

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

/**
 * The "server" part that accepts connections.
 * 
 * @author Thomas Bocek
 * 
 */
public final class ChannelServer {

    private static final Logger LOG = LoggerFactory.getLogger(ChannelServer.class);
    // private static final int BACKLOG = 128;

    // important to keep them low, since a too high value results in connection degradation
    private final EventLoopGroup bossGroup = new NioEventLoopGroup(Runtime.getRuntime().availableProcessors() / 2,
            new DefaultThreadFactory(ConnectionBean.THREAD_NAME + "boss - "));
    private final EventLoopGroup workerGroup = new NioEventLoopGroup(Runtime.getRuntime().availableProcessors() / 2,
            new DefaultThreadFactory(ConnectionBean.THREAD_NAME + "worker-server - "));

    private Channel channelUDP;
    private Channel channelTCP;

    private final FutureDone<Void> futureServerDone = new FutureDone<Void>();

    // setup
    private final Bindings bindings;
    private final int tcpPort;
    private final int udpPort;
    private final ChannelServerConficuration channelServerConfiguration;
    private final Dispatcher dispatcher;
    private final PeerStatusListener[] peerStatusListeners;

    /**
     * Sets parameters and starts network device discovery.
     * 
     * @param channelServerConfiguration
     *            The server configuration, that contains e.g. the handlers
     * @param dispatcher
     *            The shared dispatcher
     * @param peerStatusListeners
     *            The status listener for offline peers
     * @throws IOException
     *             If device discovery failed.
     */
    public ChannelServer(final ChannelServerConficuration channelServerConfiguration, final Dispatcher dispatcher,
            final PeerStatusListener[] peerStatusListeners) throws IOException {
        this.bindings = channelServerConfiguration.getBindings();
        this.tcpPort = channelServerConfiguration.getTcpPort();
        this.udpPort = channelServerConfiguration.getUdpPort();
        this.channelServerConfiguration = channelServerConfiguration;
        this.dispatcher = dispatcher;
        this.peerStatusListeners = peerStatusListeners;
        final String status = DiscoverNetworks.discoverInterfaces(bindings);
        if (LOG.isInfoEnabled()) {
            LOG.info("Status of interface search: " + status);
        }
    }

    /**
     * @return The binding that was used to setup the incoming connections
     */
    public Bindings bindings() {
        return bindings;
    }

    /**
     * @return The channel server configuration.
     */
    public ChannelServerConficuration channelServerConfiguration() {
        return channelServerConfiguration;
    }

    /**
     * Creates the {@link PeerAddress} based on the network discovery that was done in
     * {@link #ChannelServer(Bindings, int, int, ChannelServerConficuration)}.
     * 
     * @param peerId
     *            The id of this peer
     * @return The peer address of this peer
     * @throws IOException
     *             If the address could not be determined
     */
    public PeerAddress findPeerAddress(final Number160 peerId) throws IOException {
        InetAddress outsideAddress = bindings.getExternalAddress();
        final PeerAddress self;
        if (outsideAddress == null) {
            if (bindings.getFoundAddresses().size() == 0) {
                throw new IOException("Not listening to anything. Maybe your binding information is wrong.");
            }
            outsideAddress = bindings.getFoundAddresses().get(0);
            final PeerSocketAddress peerSocketAddress = new PeerSocketAddress(outsideAddress, tcpPort, udpPort);

            self = new PeerAddress(peerId, peerSocketAddress, channelServerConfiguration.isBehindFirewall(),
                    channelServerConfiguration.isBehindFirewall(), false, new PeerSocketAddress[] {});
        } else {
            final PeerSocketAddress peerSocketAddress = new PeerSocketAddress(outsideAddress,
                    bindings.getOutsideTCPPort(), bindings.getOutsideUDPPort());
            self = new PeerAddress(peerId, peerSocketAddress, channelServerConfiguration.isBehindFirewall(),
                    channelServerConfiguration.isBehindFirewall(), false, new PeerSocketAddress[] {});
        }
        return self;
    }

    /**
     * Starts to listen to UDP and TCP ports.
     * 
     * @throws IOException
     *             If the starup fails, e.g, ports already in use
     */
    public void startup() throws IOException {

        if (!channelServerConfiguration.disableBind()) {
            final boolean listenAll = bindings.isListenAll();
            if (listenAll) {
                if (LOG.isInfoEnabled()) {
                    LOG.info("Listening for broadcasts on port udp: " + udpPort + " and tcp:" + tcpPort);
                }
                if (!startupTCP(new InetSocketAddress(tcpPort), channelServerConfiguration)
                        || !startupUDP(new InetSocketAddress(udpPort), channelServerConfiguration)) {
                    throw new IOException("cannot bind TCP or UDP");
                }

            } else {
                for (InetAddress addr : bindings.getFoundAddresses()) {
                    if (LOG.isInfoEnabled()) {
                        LOG.info("Listening on address: " + addr + " on port udp: " + udpPort + " and tcp:"
                                + tcpPort);
                    }
                    if (!startupTCP(new InetSocketAddress(addr, tcpPort), channelServerConfiguration)
                            || !startupUDP(new InetSocketAddress(addr, udpPort), channelServerConfiguration)) {
                        throw new IOException("cannot bind TCP or UDP");
                    }
                }
            }
        }
    }

    /**
     * Start to listen on a UPD port.
     * 
     * @param listenAddresses
     *            The address to listen to
     * @param config
     *            Can create handlers to be attached to this port
     * @return True if startup was successful
     */
    boolean startupUDP(final InetSocketAddress listenAddresses, final ChannelServerConficuration config) {
        Bootstrap b = new Bootstrap();
        b.group(workerGroup);
        b.channel(NioDatagramChannel.class);
        b.option(ChannelOption.SO_BROADCAST, true);
        b.option(ChannelOption.RCVBUF_ALLOCATOR, new FixedRecvByteBufAllocator(ConnectionBean.UDP_LIMIT));

        b.handler(new ChannelInitializer<Channel>() {
            @Override
            protected void initChannel(final Channel ch) throws Exception {
                for (Map.Entry<String, ChannelHandler> entry : handlers(false).entrySet()) {
                    ch.pipeline().addLast(entry.getKey(), entry.getValue());
                }
            }
        });

        ChannelFuture future = b.bind(listenAddresses);
        channelUDP = future.channel();
        return handleFuture(future);
    }

    /**
     * Start to listen on a TCP port.
     * 
     * @param listenAddresses
     *            The address to listen to
     * @param config
     *            Can create handlers to be attached to this port
     * @return True if startup was successful
     */
    boolean startupTCP(final InetSocketAddress listenAddresses, final ChannelServerConficuration config) {
        ServerBootstrap b = new ServerBootstrap();
        b.group(bossGroup, workerGroup);
        b.channel(NioServerSocketChannel.class);
        b.childHandler(new ChannelInitializer<Channel>() {
            @Override
            protected void initChannel(final Channel ch) throws Exception {
                for (Map.Entry<String, ChannelHandler> entry : handlers(true).entrySet()) {
                    ch.pipeline().addLast(entry.getKey(), entry.getValue());
                }
            }
        });
        // b.option(ChannelOption.SO_BACKLOG, BACKLOG);
        b.childOption(ChannelOption.SO_LINGER, 0);
        b.childOption(ChannelOption.TCP_NODELAY, true);

        ChannelFuture future = b.bind(listenAddresses);
        channelTCP = future.channel();
        return handleFuture(future);
    }

    /**
     * Creates the Netty handlers. After it sends it to the user, where the handlers can be modified. We add a couple or
     * null handlers where the user can add its own handler.
     * 
     * @param tcp
     *            Set to true if connection is TCP, false if UDP
     * @return The channel handlers that may have been modified by the user
     */
    private Map<String, ChannelHandler> handlers(final boolean tcp) {
        final ChannelHandler[] c = new TimeoutFactory(null, channelServerConfiguration.idleTCPSeconds(),
                peerStatusListeners).twoTimeoutHandlers();
        final Map<String, ChannelHandler> handlers;
        if (tcp) {
            final int nrTCPHandlers = 5;
            handlers = new LinkedHashMap<String, ChannelHandler>(nrTCPHandlers);
            handlers.put("timeout-server0", c[0]);
            handlers.put("timeout-server1", c[1]);
            handlers.put("decoder", new TomP2PCumulationTCP(channelServerConfiguration.signatureFactory()));
        } else {
            // we don't need here a timeout since we receive a packet or nothing. It is different than with TCP where we
            // may get a stream and in the middle of it, the other peer goes offline. This cannot happen with UDP
            final int nrUDPHandlers = 3;
            handlers = new LinkedHashMap<String, ChannelHandler>(nrUDPHandlers);
            handlers.put("decoder", new TomP2PSinglePacketUDP(channelServerConfiguration.signatureFactory()));
        }
        handlers.put("encoder", new TomP2POutbound(false, channelServerConfiguration.signatureFactory()));
        handlers.put("dispatcher", dispatcher);
        channelServerConfiguration.pipelineFilter().filter(handlers, tcp, false);
        return handlers;
    }

    /**
     * Handles the waiting and returning the channel.
     * 
     * @param future
     *            The future to wait for
     * @return The channel or null if we failed to bind.
     */
    private boolean handleFuture(final ChannelFuture future) {
        try {
            future.await();
        } catch (InterruptedException e) {
            if (LOG.isWarnEnabled()) {
                LOG.warn("could not start UPD server", e);
            }
            return false;
        }
        boolean success = future.isSuccess();
        if (success) {
            return true;
        } else {
            future.cause().printStackTrace();
            return false;
        }

    }

    /**
     * Shuts down the server.
     * 
     * @return The future when the shutdown is complete. This includes the worker and boss event loop
     */
    public FutureDone<Void> shutdown() {
        LOG.debug("shutdown UPD server");
        channelUDP.close().addListener(new GenericFutureListener<ChannelFuture>() {
            @Override
            public void operationComplete(final ChannelFuture future) throws Exception {
                LOG.debug("shutdown TCP server");
                channelTCP.close().addListener(new GenericFutureListener<ChannelFuture>() {
                    @SuppressWarnings({ "unchecked", "rawtypes" })
                    @Override
                    public void operationComplete(final ChannelFuture future) throws Exception {
                        LOG.debug("shutdown TCP workergroup");
                        workerGroup.shutdownGracefully(0, 0, TimeUnit.SECONDS)
                                .addListener(new GenericFutureListener() {
                                    @Override
                                    public void operationComplete(final Future future) throws Exception {
                                        LOG.debug("shutdown TCP workergroup done!");
                                        bossGroup.shutdownGracefully(0, 0, TimeUnit.SECONDS)
                                                .addListener(new GenericFutureListener() {
                                                    @Override
                                                    public void operationComplete(final Future future)
                                                            throws Exception {
                                                        futureServerDone.setDone();
                                                    }
                                                });
                                    }
                                });
                    }
                });
            }
        });
        return shutdownFuture();
    }

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