Package net.tomp2p.connection

Source Code of net.tomp2p.connection.ChannelServer

/*
* 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.connection;

import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelException;
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.socket.nio.NioDatagramChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.util.concurrent.EventExecutorGroup;
import io.netty.util.concurrent.GenericFutureListener;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.atomic.AtomicInteger;

import net.tomp2p.futures.FutureDone;
import net.tomp2p.message.TomP2PCumulationTCP;
import net.tomp2p.message.TomP2POutbound;
import net.tomp2p.message.TomP2PSinglePacketUDP;
import net.tomp2p.peers.PeerStatusListener;
import net.tomp2p.utils.Pair;

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

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

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

  private final EventLoopGroup bossGroup;
  private final EventLoopGroup workerGroup;
 
  private final Map<InetAddress, Channel> channelsTCP = Collections.synchronizedMap(new HashMap<InetAddress, Channel>());
  private final Map<InetAddress, Channel> channelsUDP = Collections.synchronizedMap(new HashMap<InetAddress, Channel>());

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

  private final ChannelServerConfiguration channelServerConfiguration;
  private final Dispatcher dispatcher;
  private final List<PeerStatusListener> peerStatusListeners;
 
  private final DropConnectionInboundHandler tcpDropConnectionInboundHandler;
  private final DropConnectionInboundHandler udpDropConnectionInboundHandler;
  private final ChannelHandler udpDecoderHandler;
  private final DiscoverNetworks discoverNetworks;
 
  private boolean shutdown = false;
  private boolean broadcastAddressSupported = false;
  private boolean broadcastAddressTried = false;

    /**
     * Sets parameters and starts network device discovery.
     *
     * @param bossGroup
     *
     * @param workerGroup
     *
     * @param channelServerConfiguration
     *              The server configuration, that contains e.g. the handlers
     * @param dispatcher
     *              The shared dispatcher
     * @param peerStatusListeners
     *              The status listener for offline peers
     * @param timer
     *
     * @throws IOException
     *               If device discovery failed.
     */
  public ChannelServer(final EventLoopGroup bossGroup, final EventLoopGroup workerGroup, final ChannelServerConfiguration channelServerConfiguration, final Dispatcher dispatcher,
          final List<PeerStatusListener> peerStatusListeners, final ScheduledExecutorService timer) throws IOException {
    this.bossGroup = bossGroup;
    this.workerGroup = workerGroup;
    this.channelServerConfiguration = channelServerConfiguration;
    this.dispatcher = dispatcher;
    this.peerStatusListeners = peerStatusListeners;
   
    this.discoverNetworks = new DiscoverNetworks(5000, channelServerConfiguration.bindings(), timer);
   
    this.tcpDropConnectionInboundHandler = new DropConnectionInboundHandler(channelServerConfiguration.maxTCPIncomingConnections());
    this.udpDropConnectionInboundHandler = new DropConnectionInboundHandler(channelServerConfiguration.maxUDPIncomingConnections());
    this.udpDecoderHandler = new TomP2PSinglePacketUDP(channelServerConfiguration.signatureFactory());
   
    discoverNetworks.addDiscoverNetworkListener(this);
    if(timer!=null) {
      discoverNetworks.start().awaitUninterruptibly();
    }
  }
 
  public DiscoverNetworks discoverNetworks() {
    return discoverNetworks;
  }

  /**
   * @return The channel server configuration.
   */
  public ChannelServerConfiguration channelServerConfiguration() {
    return channelServerConfiguration;
  }
 
  @Override
    public void discoverNetwork(DiscoverResults discoverResults) {
    if (!channelServerConfiguration.isDisableBind()) {
      synchronized (ChannelServer.this) {
        if (shutdown) {
          return;
        }

        if(discoverResults.isListenAny()) {
          listenAny();
        } else {
          listenSpecificInetAddresses(discoverResults);
        }
      }
    }
     
    }

  private void listenAny() {
    if (LOG.isInfoEnabled()) {
        LOG.info("Listening on any address on port udp: "
                + channelServerConfiguration.ports().udpPort() + " and tcp:"
                + channelServerConfiguration.ports().tcpPort());
      }
     
    InetSocketAddress tcpSocket = new InetSocketAddress(channelServerConfiguration.ports().tcpPort());
      boolean tcpStart = startupTCP(tcpSocket, channelServerConfiguration);
      if(!tcpStart) {
        LOG.warn("cannot bind TCP on socket {}",tcpSocket);
      }
     
      InetSocketAddress udpSocket = new InetSocketAddress(channelServerConfiguration.ports().udpPort());
      boolean udpStart = startupUDP(udpSocket, channelServerConfiguration, true);
      if(!udpStart) {
        LOG.warn("cannot bind UDP on socket {}",udpSocket);
      }
   
    }

  private void listenSpecificInetAddresses(DiscoverResults discoverResults) {
      for (InetAddress inetAddress : discoverResults.newAddresses()) {
        if (LOG.isInfoEnabled()) {
          LOG.info("Listening on address: " + inetAddress + " on port udp: "
                  + channelServerConfiguration.ports().udpPort() + " and tcp:"
                  + channelServerConfiguration.ports().tcpPort());
        }

        InetSocketAddress tcpSocket = new InetSocketAddress(inetAddress,
            channelServerConfiguration.ports().tcpPort());
        boolean tcpStart = startupTCP(tcpSocket, channelServerConfiguration);
        if(!tcpStart) {
          LOG.warn("cannot bind TCP on socket {}",tcpSocket);
        }
       
        InetSocketAddress udpSocket = new InetSocketAddress(inetAddress,
            channelServerConfiguration.ports().udpPort());
        boolean udpStart = startupUDP(udpSocket, channelServerConfiguration, false);
        if(!udpStart) {
          LOG.warn("cannot bind UDP on socket {}",udpSocket);
        }
      }
     
      for (InetAddress inetAddress : discoverResults.removedFoundAddresses()) {
        Channel channelTCP = channelsTCP.remove(inetAddress);
        if (channelTCP != null) {
          channelTCP.close().awaitUninterruptibly();
        }
        Channel channelUDP = channelsUDP.remove(inetAddress);
        if (channelUDP != null) {
          channelUDP.close().awaitUninterruptibly();
        }
      }
     
      for (InetAddress inetAddress : discoverResults.newBroadcastAddresses()) {
        if (LOG.isInfoEnabled()) {
          LOG.info("Listening on broadcast address: " + inetAddress + " on port udp: "
                  + channelServerConfiguration.ports().udpPort());
        }
        InetSocketAddress udpBroadcastSocket = new InetSocketAddress(inetAddress, channelServerConfiguration.ports()
                  .udpPort());
        broadcastAddressTried = true;
        boolean udpStartBroadcast = startupUDP(udpBroadcastSocket, channelServerConfiguration, false);
       
        if (udpStartBroadcast) {
          //if one broadcast address was found, then we don't need to bind to 0.0.0.0
          broadcastAddressSupported = true;
        } else {
          LOG.warn("cannot bind broadcast UDP {}", udpBroadcastSocket);
        }
      }
     
      for (InetAddress inetAddress : discoverResults.removedFoundBroadcastAddresses()) {
        Channel channelUDP = channelsUDP.remove(inetAddress);
        if (channelUDP != null) {
          channelUDP.close().awaitUninterruptibly();
        }
      }
     
      //if we tried but could not bind to a broadcast address
    if(!broadcastAddressSupported && broadcastAddressTried) {
      InetSocketAddress udpBroadcastSocket = new InetSocketAddress(channelServerConfiguration.ports().udpPort());
      LOG.info("Listening on wildcard broadcast address {}", udpBroadcastSocket);
      boolean udpStartBroadcast = startupUDP(udpBroadcastSocket, channelServerConfiguration, true);
      if(!udpStartBroadcast) {
        LOG.warn("cannot bind wildcard broadcast UDP on socket {}", udpBroadcastSocket);
      }
    } 
    }

  @Override
    public void exception(Throwable throwable) {
      LOG.error("discovery problem", throwable);
    }

  /**
   * 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
   * @param broadcastFlag
   * @return True if startup was successful
   */
  boolean startupUDP(final InetSocketAddress listenAddresses, final ChannelServerConfiguration config, boolean broadcastFlag) {
    Bootstrap b = new Bootstrap();
    b.group(workerGroup);
    b.channel(NioDatagramChannel.class);
    //option broadcast only required as we not listen to the broadcast address directly
    if(broadcastFlag) {
      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, Pair<EventExecutorGroup, ChannelHandler>> entry : handlers(false).entrySet()) {
          if (!entry.getValue().isEmpty()) {
            ch.pipeline().addLast(entry.getValue().element0(), entry.getKey(), entry.getValue().element1());
          } else if (entry.getValue().element1() != null) {
            ch.pipeline().addLast(entry.getKey(), entry.getValue().element1());
          }
        }
      }
    });

    ChannelFuture future = b.bind(listenAddresses);
    channelsUDP.put(listenAddresses.getAddress(), 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 ChannelServerConfiguration 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 {
        // b.option(ChannelOption.SO_BACKLOG, BACKLOG);
        bestEffortOptions(ch, ChannelOption.SO_LINGER, 0);
        bestEffortOptions(ch, ChannelOption.TCP_NODELAY, true);
        for (Map.Entry<String, Pair<EventExecutorGroup, ChannelHandler>> entry : handlers(true).entrySet()) {
          if (!entry.getValue().isEmpty()) {
            ch.pipeline().addLast(entry.getValue().element0(), entry.getKey(), entry.getValue().element1());
          } else if (entry.getValue().element1() != null) {
            ch.pipeline().addLast(entry.getKey(), entry.getValue().element1());
          }
        }
      }
    });
    ChannelFuture future = b.bind(listenAddresses);
    channelsTCP.put(listenAddresses.getAddress(), future.channel());
    return handleFuture(future);
  }

  private static <T> void bestEffortOptions(final Channel ch, ChannelOption<T> option, T value) {
    try {
      ch.config().setOption(option, value);
    } catch (ChannelException e) {
      // Ignore
    }
  }

  /**
   * 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, Pair<EventExecutorGroup, ChannelHandler>> handlers(final boolean tcp) {
    TimeoutFactory timeoutFactory = new TimeoutFactory(null, channelServerConfiguration.idleTCPSeconds(),
            peerStatusListeners, "Server");
    final Map<String, Pair<EventExecutorGroup, ChannelHandler>> handlers;
    if (tcp) {
      final int nrTCPHandlers = 8; // 6 / 0.75 = 7;
      handlers = new LinkedHashMap<String, Pair<EventExecutorGroup, ChannelHandler>>(nrTCPHandlers);
      handlers.put("dropconnection", new Pair<EventExecutorGroup, ChannelHandler>(null, tcpDropConnectionInboundHandler));
      handlers.put("timeout0",
              new Pair<EventExecutorGroup, ChannelHandler>(null, timeoutFactory.idleStateHandlerTomP2P()));
      handlers.put("timeout1", new Pair<EventExecutorGroup, ChannelHandler>(null, timeoutFactory.timeHandler()));
      handlers.put("decoder", new Pair<EventExecutorGroup, ChannelHandler>(null, 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 = 6; // 4 = 0.75 = 4
      handlers = new LinkedHashMap<String, Pair<EventExecutorGroup, ChannelHandler>>(nrUDPHandlers);
      handlers.put("dropconnection", new Pair<EventExecutorGroup, ChannelHandler>(null, udpDropConnectionInboundHandler));
      handlers.put("decoder", new Pair<EventExecutorGroup, ChannelHandler>(null, udpDecoderHandler));
    }
    handlers.put("encoder", new Pair<EventExecutorGroup, ChannelHandler>(null, new TomP2POutbound(false,
            channelServerConfiguration.signatureFactory())));
    handlers.put("dispatcher", new Pair<EventExecutorGroup, ChannelHandler>(null, dispatcher));
    return channelServerConfiguration.pipelineFilter().filter(handlers, tcp, false);
  }

  /**
   * 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() {
    synchronized (this) {
          shutdown = true;
        }
    discoverNetworks.stop();
    final int maxListeners = channelsTCP.size() + channelsUDP.size();
    if(maxListeners == 0) {
      shutdownFuture().done();
    }
    // we have two things to shut down: UDP and TCP
    final AtomicInteger listenerCounter = new AtomicInteger(0);
    LOG.debug("shutdown servers");
    synchronized (channelsUDP) {
      for (Channel channelUDP : channelsUDP.values()) {
        channelUDP.close().addListener(new GenericFutureListener<ChannelFuture>() {
          @Override
          public void operationComplete(final ChannelFuture future) throws Exception {
            LOG.debug("shutdown TCP server");
            if (listenerCounter.incrementAndGet() == maxListeners) {
              futureServerDone.done();
            }
          }
        });
      }
    }
    synchronized (channelsTCP) {
      for (Channel channelTCP : channelsTCP.values()) {
        channelTCP.close().addListener(new GenericFutureListener<ChannelFuture>() {
          @Override
          public void operationComplete(final ChannelFuture future) throws Exception {
            LOG.debug("shutdown TCP channels");
            if (listenerCounter.incrementAndGet() == maxListeners) {
              futureServerDone.done();
            }
          }
        });
      }
    }
    return shutdownFuture();
  }

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

Related Classes of net.tomp2p.connection.ChannelServer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.