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

com.github.mrstampy.kitchensync.netty.Bootstrapper Maven / Gradle / Ivy

The newest version!
/*
 * KitchenSync-core Java Library Copyright (C) 2014 Burton Alexander
 * 
 * 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.github.mrstampy.kitchensync.netty;

import io.netty.bootstrap.AbstractBootstrap;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.epoll.EpollDatagramChannel;
import io.netty.channel.epoll.EpollEventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.oio.OioEventLoopGroup;
import io.netty.channel.socket.DatagramChannel;
import io.netty.channel.socket.nio.NioDatagramChannel;
import io.netty.channel.socket.oio.OioDatagramChannel;
import io.netty.util.concurrent.GenericFutureListener;

import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

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

import com.github.mrstampy.kitchensync.netty.channel.AbstractKiSyMulticastChannel;
import com.github.mrstampy.kitchensync.util.KiSyUtils;

/**
 * The Class Bootstrapper is the KitchenSync/Netty
 * core class. It instantiates and keeps track of all Netty Bootstraps created and provides the ability to customize options on a per-bootstrap basis. This allows for easy creation of
 * channels for separate tasks (ie. one for autonomous program state, one for
 * sending and receiving broadcast messages, one for receiving broadcasted video
 * etc.). The defaults chosen should be fine for small to medium numbers of
 * channels with low to medium traffic. An understanding of Netty and Java
 * Sockets is necessary to optimize the parameters for specific tasks.
*
* * Bootstraps are either bound to a port (next available or specified) on the * host machine or are bound to multicast addresses, both using the UDP protocol * for communication. On startup the default InetAddress and default network * interface are determined. */ public class Bootstrapper { private static final Logger log = LoggerFactory.getLogger(Bootstrapper.class); private static final int DEFAULT_BOOTSTRAP_KEY = -1; /** The Constant INSTANCE. */ public static final Bootstrapper INSTANCE = new Bootstrapper(); /** The default interface. */ public final NetworkInterface DEFAULT_INTERFACE; /** The default address. */ public final InetAddress DEFAULT_ADDRESS; private static Map channelBootstraps = new ConcurrentHashMap(); private static Map multicastBootstraps = new ConcurrentHashMap(); /** * The Constructor, use if subclassing, otherwise {@link #INSTANCE}. */ public Bootstrapper() { try { DEFAULT_ADDRESS = InetAddress.getLocalHost(); DEFAULT_INTERFACE = NetworkInterface.getByInetAddress(DEFAULT_ADDRESS); } catch (Exception e) { log.error("Could not determine network interface", e); throw new RuntimeException(e); } } /** * Checks for default bootstrap. * * @return true, if checks for default bootstrap */ public boolean hasDefaultBootstrap() { return containsBootstrap(DEFAULT_BOOTSTRAP_KEY); } /** * Returns true if a bootstrap for the specified port is available. * * @param port * the port * @return true, if contains bootstrap */ public boolean containsBootstrap(int port) { return channelBootstraps.containsKey(port); } /** * Returns true if the bootstrap exists for the specified multicast address. * * @param multicast * the multicast * @return true, if contains multicast bootstrap */ public boolean containsMulticastBootstrap(InetSocketAddress multicast) { String key = createMulticastKey(multicast); return containsMulticastBootstrap(key); } /** * Inits the default bootstrap. This bootstrap is used should no port-specific * bootstrap exist when the {@link #bind()} or {@link #bind(int)} methods are * called.
*
* * The channel initializer allows the many Netty handlers to be specified as * required for the bootstrap ie. one for SSL communication, one for message * encryption/decryption etc etc. Refer to the Netty documentation for more information. * * @param * the generic type * @param initializer * the initializer * @param clazz * the clazz */ public void initDefaultBootstrap(ChannelInitializer initializer, Class clazz) { initBootstrap(initializer, DEFAULT_BOOTSTRAP_KEY, clazz); } /** * Inits the bootstrap for a specified port. This allows different bootstraps * for different channels, all configured differently.
*
* * The channel initializer allows the many Netty handlers to be specified as * required for the bootstrap ie. one for SSL communication, one for message * encryption/decryption etc etc. Refer to the Netty documentation for more information. * * @param * the generic type * @param initializer * the initializer * @param port * the port * @param clazz * the clazz */ public void initBootstrap(ChannelInitializer initializer, int port, Class clazz) { if (containsBootstrap(port)) { log.warn("Bootstrap for port {} already initialized", port); return; } log.debug("Initializing bootstrap for port {}", port); Bootstrap b = bootstrap(initializer, port, clazz); channelBootstraps.put(port, b); } /** * Inits the multicast bootstrap for the specified address.
*
* * The channel initializer allows the many Netty handlers to be specified as * required for the bootstrap ie. one for SSL communication, one for message * encryption/decryption etc etc. Refer to the Netty documentation for more information. * * @param * the generic type * @param initializer * the initializer * @param multicast * the multicast * @param clazz * the clazz */ public void initMulticastBootstrap(ChannelInitializer initializer, InetSocketAddress multicast, Class clazz) { initMulticastBootstrap(initializer, multicast, DEFAULT_INTERFACE, clazz); } /** * Inits the multicast bootstrap for the specified address.
*
* * The channel initializer allows the many Netty handlers to be specified as * required for the bootstrap ie. one for SSL communication, one for message * encryption/decryption etc etc. Refer to the Netty documentation for more information. * * @param * the generic type * @param initializer * the initializer * @param multicast * the multicast * @param networkInterface * the network interface * @param clazz * the clazz */ public void initMulticastBootstrap(ChannelInitializer initializer, InetSocketAddress multicast, NetworkInterface networkInterface, Class clazz) { String key = createMulticastKey(multicast); if (containsMulticastBootstrap(key)) { log.warn("Multicast bootstrap for {} already initialized", multicast); return; } log.debug("Initializing multicast bootstrap for {} using network interface {}", multicast, networkInterface); Bootstrap b = multicastBootstrap(initializer, multicast, networkInterface, clazz); multicastBootstraps.put(key, b); } private String createMulticastKey(InetSocketAddress multicast) { return AbstractKiSyMulticastChannel.createMulticastKey(multicast); } /** * Removes the bootstrap. * * @param port * the port * @return the bootstrap */ public Bootstrap removeBootstrap(int port) { return channelBootstraps.remove(port); } /** * Removes the multicast bootstrap. * * @param address * the address * @return the bootstrap */ public Bootstrap removeMulticastBootstrap(InetSocketAddress address) { return removeMulticastBootstrap(createMulticastKey(address)); } /** * Removes the multicast bootstrap. * * @param key * the key * @return the bootstrap */ public Bootstrap removeMulticastBootstrap(String key) { return multicastBootstraps.remove(key); } /** * Gets the bootstrap keys. * * @return the bootstrap keys */ public Set getBootstrapKeys() { return channelBootstraps.keySet(); } /** * Gets the bootstraps. * * @return the bootstraps */ public Collection getBootstraps() { return channelBootstraps.values(); } /** * Gets the multicast bootstrap keys. * * @return the multicast bootstrap keys */ public Set getMulticastBootstrapKeys() { return multicastBootstraps.keySet(); } /** * Gets the multicast bootstraps. * * @return the multicast bootstraps */ public Collection getMulticastBootstraps() { return multicastBootstraps.values(); } /** * Gets the default bootstrap. * * @return the default bootstrap */ public Bootstrap getDefaultBootstrap() { return getBootstrap(DEFAULT_BOOTSTRAP_KEY); } /** * Gets the bootstrap. * * @param key * the key * @return the bootstrap */ public Bootstrap getBootstrap(int key) { return channelBootstraps.get(key); } /** * Gets the multicast bootstrap. * * @param address * the address * @return the multicast bootstrap */ public Bootstrap getMulticastBootstrap(InetSocketAddress address) { String key = createMulticastKey(address); return getMulticastBootstrap(key); } /** * Gets the multicast bootstrap. * * @param key * the key * @return the multicast bootstrap */ public Bootstrap getMulticastBootstrap(String key) { return multicastBootstraps.get(key); } /** * Clear bootstraps. */ public void clearBootstraps() { channelBootstraps.clear(); } /** * Clear multicast bootstraps. */ public void clearMulticastBootstraps() { multicastBootstraps.clear(); } /** * Bind to the next available port on the host machine using the default * bootstrap. * * @param * the generic type * @return the channel */ public CHANNEL bind() { return bind(0); } /** * Returns a channel using the bootstrap for the specified port. Should none * exist then the default bootstrap is used. * * @param * the generic type * @param port * the port * @return the channel */ @SuppressWarnings("unchecked") public CHANNEL bind(int port) { boolean contains = containsBootstrap(port); if (!contains && !hasDefaultBootstrap()) { log.error("Bootstrap for port {} not initialized", port); return null; } Bootstrap b = channelBootstraps.get(contains ? port : DEFAULT_BOOTSTRAP_KEY); ChannelFuture cf = b.bind(port); CountDownLatch latch = new CountDownLatch(1); cf.addListener(getBindListener(port, latch)); await(latch, "Channel creation timed out"); return cf.isSuccess() ? (CHANNEL) cf.channel() : null; } /** * Bind to the specified multicast address. * * @param * the generic type * @param multicast * the multicast * @return the channel */ @SuppressWarnings("unchecked") public CHANNEL multicastBind(InetSocketAddress multicast) { String key = createMulticastKey(multicast); if (!containsMulticastBootstrap(key)) { log.error("Multicast bootstrap for {} not initialized", multicast); return null; } Bootstrap b = multicastBootstraps.get(key); ChannelFuture cf = b.bind(); CountDownLatch latch = new CountDownLatch(1); cf.addListener(getMulticastBindListener(multicast, latch)); await(latch, "Multicast channel creation timed out"); return cf.isSuccess() ? (CHANNEL) cf.channel() : null; } private GenericFutureListener getBindListener(final int port, final CountDownLatch latch) { return new GenericFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { try { if (future.isSuccess()) { log.debug("Channel creation successful for {}", future.channel()); } else { Throwable cause = future.cause(); if (cause == null) { log.error("Could not create channel for {}", port); } else { log.error("Could not create channel for {}", port, cause); } } } finally { latch.countDown(); } } }; } private GenericFutureListener getMulticastBindListener(final InetSocketAddress multicast, final CountDownLatch latch) { return new GenericFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { try { if (future.isSuccess()) { log.debug("Multicast channel creation successful for {}", multicast); } else { Throwable cause = future.cause(); if (cause == null) { log.error("Could not create multicast channel for {}", multicast); } else { log.error("Could not create multicast channel for {}", multicast, cause); } } } finally { latch.countDown(); } } }; } private void await(CountDownLatch latch, String error) { boolean ok = KiSyUtils.await(latch, 5, TimeUnit.SECONDS); if (!ok) log.error(error); } /** * Creates bootstrap for all purposes. Sets default options, event loop group * and channel class. If the port > 0 (and it should be > 1024) then it * is set in the bootstrap (localAddress). * * @param * the generic type * @param initializer * the initializer * @param port * the port * @param clazz * the clazz * @return the bootstrap */ protected Bootstrap bootstrap(ChannelInitializer initializer, int port, Class clazz) { Bootstrap b = new Bootstrap(); setDefaultBootstrapOptions(b, initializer); if (port > 0) b.localAddress(port); b.group(getEventLoopGroup(clazz)); b.channel(clazz); return b; } /** * Gets the event loop group based upon the class name. If the class is * neither an NioDatagramChannel nor OioDatagramChannel (or on Linux, EpollDatagramChannel) then an exception is thrown. * * @param * the generic type * @param clazz * the clazz * @return the event loop group */ protected EventLoopGroup getEventLoopGroup(Class clazz) { if (NioDatagramChannel.class.equals(clazz)) return new NioEventLoopGroup(); if (OioDatagramChannel.class.equals(clazz)) return new OioEventLoopGroup(); // Linux only if (EpollDatagramChannel.class.equals(clazz)) return new EpollEventLoopGroup(); throw new UnsupportedOperationException("No default event loop group defined for " + clazz.getName()); } /** * Sets the default bootstrap options (SO_BROADCAST=true, SO_REUSEADDR=true) * and the initializer as the channel handler. * * @param b * the b * @param initializer * the initializer */ protected void setDefaultBootstrapOptions(AbstractBootstrap b, ChannelInitializer initializer) { b.option(ChannelOption.SO_BROADCAST, true); b.option(ChannelOption.SO_REUSEADDR, true); b.handler(initializer); } /** * Multicast bootstrap using the default network interface. * * @param * the generic type * @param initializer * the initializer * @param multicast * the multicast * @param clazz * the clazz * @return the bootstrap */ protected Bootstrap multicastBootstrap( ChannelInitializer initializer, InetSocketAddress multicast, Class clazz) { return multicastBootstrap(initializer, multicast, DEFAULT_INTERFACE, clazz); } /** * Multicast bootstrap, adding the IP_MULTICAST_IF=networkInterface option. * * @param * the generic type * @param initializer * the initializer * @param multicast * the multicast * @param networkInterface * the network interface * @param clazz * the clazz * @return the bootstrap */ protected Bootstrap multicastBootstrap( ChannelInitializer initializer, InetSocketAddress multicast, NetworkInterface networkInterface, Class clazz) { Bootstrap b = bootstrap(initializer, multicast.getPort(), clazz); b.option(ChannelOption.IP_MULTICAST_IF, networkInterface); return b; } private boolean containsMulticastBootstrap(String key) { return multicastBootstraps.containsKey(key); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy