Java tutorial
/** * This file is part of the Kompics component model runtime. * * Copyright (C) 2009 Swedish Institute of Computer Science (SICS) Copyright (C) * 2009 Royal Institute of Technology (KTH) * * Kompics 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., 59 Temple * Place - Suite 330, Boston, MA 02111-1307, USA. */ package se.sics.gvod.net; import io.netty.bootstrap.Bootstrap; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.*; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.DatagramChannel; import io.netty.channel.socket.DatagramPacket; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioDatagramChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.channel.udt.UdtChannel; import io.netty.channel.udt.nio.NioUdtProvider; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.GenericFutureListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import se.sics.gvod.address.Address; import se.sics.gvod.common.msgs.DirectMsgNettyFactory; import se.sics.gvod.common.msgs.Encodable; import se.sics.gvod.config.VodConfig; import se.sics.gvod.net.events.*; import se.sics.gvod.net.msgs.RewriteableMsg; import se.sics.gvod.net.util.UtilThreadFactory; import se.sics.gvod.timer.SchedulePeriodicTimeout; import se.sics.gvod.timer.Timeout; import se.sics.gvod.timer.Timer; import se.sics.kompics.*; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.util.*; import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicLong; import se.sics.gvod.common.msgs.NatReportMsg; import se.sics.gvod.common.util.ToVodAddr; import se.sics.gvod.config.BaseCommandLineConfig; /** * The <code>NettyNetwork</code> class. * * @author Jim Dowling <jdowling@sics.se> * @author Steffen Grohsschmiedt */ public final class NettyNetwork extends ComponentDefinition { private static final int CONNECT_TIMEOUT_MS = 5000; private static final int RECV_BUFFER_SIZE = 65536; private static final int SEND_BUFFER_SIZE = 65536; private static final Logger logger = LoggerFactory.getLogger(NettyNetwork.class); /** * The Network port. */ Negative<VodNetwork> net = negative(VodNetwork.class); /** * The NetworkControl port. */ Negative<NatNetworkControl> netControl = negative(NatNetworkControl.class); Positive<Timer> timer = positive(Timer.class); private int maxPacketSize; private Class<? extends MsgFrameDecoder> msgDecoderClass; /** * Locally bound socket */ private Random rand; private NettyNetwork component; private Map<Integer, InetSocketAddress> udpPortsToSockets = new HashMap<Integer, InetSocketAddress>(); private Map<InetSocketAddress, Bootstrap> udpSocketsToBootstraps = new HashMap<InetSocketAddress, Bootstrap>(); // private Map<InetSocketAddress, DatagramChannel> udpSocketsToChannels = new HashMap<InetSocketAddress, DatagramChannel>(); private Map<Integer, DatagramChannel> udpSocketsToChannels = new HashMap<Integer, DatagramChannel>(); private Map<Integer, InetSocketAddress> tcpPortsToSockets = new HashMap<Integer, InetSocketAddress>(); private Map<InetSocketAddress, ServerBootstrap> tcpSocketsToServerBootstraps = new HashMap<InetSocketAddress, ServerBootstrap>(); private Map<InetSocketAddress, Bootstrap> tcpSocketsToBootstraps = new HashMap<InetSocketAddress, Bootstrap>(); private Map<InetSocketAddress, SocketChannel> tcpSocketsToChannels = new HashMap<InetSocketAddress, SocketChannel>(); private Map<Integer, InetSocketAddress> udtPortsToSockets = new HashMap<Integer, InetSocketAddress>(); private Map<InetSocketAddress, ServerBootstrap> udtSocketsToServerBootstraps = new HashMap<InetSocketAddress, ServerBootstrap>(); private Map<InetSocketAddress, Bootstrap> udtSocketsToBootstraps = new HashMap<InetSocketAddress, Bootstrap>(); private Map<InetSocketAddress, UdtChannel> udtSocketsToChannels = new HashMap<InetSocketAddress, UdtChannel>(); // Bandwidth Measurement statistics private boolean enableBandwidthStats; private long prevTotalWritten; private long prevTotalRead; private static long totalWrittenBytes, totalReadBytes; private static AtomicLong lastSecRead = new AtomicLong(); private static AtomicLong lastSecWritten = new AtomicLong(); // 60 samples stored private static LinkedList<Integer> lastMinWrote = new LinkedList<Integer>(); private static LinkedList<Integer> lastHourWrote = new LinkedList<Integer>(); // 60 mins stored private static LinkedList<Integer> lastMinRead = new LinkedList<Integer>(); // 12 hours stored private static LinkedList<Integer> lastHourRead = new LinkedList<Integer>(); private int bwSampleCounter = 0; private class ByteCounterTimeout extends Timeout { public ByteCounterTimeout(SchedulePeriodicTimeout spt) { super(spt); } } /** * Instantiates a new Netty network component. */ public NettyNetwork(NettyInit init) { this.component = this; // IPv4 addresses over IPv6 addresses when querying DNS // For java, Linux is still a dual IPv6/v4 address stack, while windows // uses a single address. System.setProperty("java.net.preferIPv4Stack", "true"); subscribe(handleRewriteableMessage, net); subscribe(handlePortBindRequest, netControl); subscribe(handlePortAllocRequest, netControl); subscribe(handlePortDeleteRequest, netControl); subscribe(handleCloseConnectionRequest, netControl); subscribe(handleByteCounterTimeout, timer); subscribe(handleStart, control); subscribe(handleStop, control); doInit(init); } private void doInit(NettyInit init) { rand = new Random(init.getSeed()); maxPacketSize = init.getMTU(); if (maxPacketSize <= 0) { throw new IllegalArgumentException("Netty problem: max Packet Size must be set to greater than zero."); } msgDecoderClass = init.getMsgDecoderClass(); DirectMsgNettyFactory.Base.setMsgFrameDecoder(msgDecoderClass); enableBandwidthStats = init.isEnableBandwidthStats(); } public Handler<Start> handleStart = new Handler<Start>() { @Override public void handle(Start event) { if (enableBandwidthStats) { SchedulePeriodicTimeout spt = new SchedulePeriodicTimeout(0, 1000); ByteCounterTimeout bct = new ByteCounterTimeout(spt); spt.setTimeoutEvent(bct); trigger(spt, timer); } } }; Handler<ByteCounterTimeout> handleByteCounterTimeout = new Handler<ByteCounterTimeout>() { @Override public void handle(ByteCounterTimeout event) { lastSecWritten.set(totalWrittenBytes - prevTotalWritten); lastSecRead.set(totalReadBytes - prevTotalRead); prevTotalWritten = totalWrittenBytes; prevTotalRead = totalReadBytes; if (lastSecWritten.longValue() > VodConfig.getMaxUploadBwCapacity()) { VodConfig.setMaxUploadBwCapacity(lastSecWritten.longValue()); } if (lastMinRead.size() == 60) { lastMinRead.removeLast(); } lastMinRead.addFirst((int) lastSecRead.longValue()); if (lastMinWrote.size() == 60) { lastMinWrote.removeLast(); } lastMinWrote.addFirst((int) lastSecWritten.longValue()); if (bwSampleCounter == 60) { if (lastHourRead.size() == 24) { lastHourRead.removeLast(); } lastHourRead.addFirst(getLast(lastMinRead)); if (lastHourWrote.size() == 24) { lastHourWrote.removeLast(); } lastHourWrote.addFirst(getLast(lastMinWrote)); bwSampleCounter = 0; } else { bwSampleCounter++; } trigger(new BandwidthStats((int) lastSecRead.longValue(), (int) lastSecWritten.longValue(), (int) totalReadBytes), netControl); } }; private int getLast(LinkedList<Integer> last) { int count = 0; for (int i = 0; i < last.size(); i++) { count += last.get(i); } return count; } /** * Close all connections. */ Handler<Stop> handleStop = new Handler<Stop>() { @Override public void handle(Stop event) { for (InetSocketAddress address : udpSocketsToBootstraps.keySet()) { closeSocket(address, Transport.UDP); } for (InetSocketAddress address : tcpSocketsToServerBootstraps.keySet()) { closeServerSocket(address, Transport.TCP); } for (InetSocketAddress address : tcpSocketsToBootstraps.keySet()) { closeSocket(address, Transport.TCP); } for (InetSocketAddress address : udtSocketsToServerBootstraps.keySet()) { closeServerSocket(address, Transport.UDT); } for (InetSocketAddress address : udtSocketsToBootstraps.keySet()) { closeSocket(address, Transport.UDT); } } }; /** * Send the received message over the network using the specified protocol. */ Handler<RewriteableMsg> handleRewriteableMessage = new Handler<RewriteableMsg>() { @Override public void handle(RewriteableMsg msg) { if (msg.getDestination().getIp().equals(msg.getSource().getIp()) && msg.getDestination().getPort() == msg.getSource().getPort()) { // deliver locally logger.trace("Delivering locally " + msg.getClass().getCanonicalName() + " from {} to {} ", msg.getSource(), msg.getDestination()); trigger(msg, net); return; } if (!(msg instanceof Encodable)) { throw new Error("Netty can only serialize instances of Encodable. You need to " + "make this class implement Encodable: " + msg.getClass()); } Transport protocol = msg.getProtocol(); if (protocol == Transport.UDP) { sendUdp(msg); } else if (protocol == Transport.TCP) { sendTcp(msg); } else if (protocol == Transport.UDT) { sendUdt(msg); } else { throw new Error("Unknown Transport type"); } } }; /** * Start listening as a server on the given port. */ Handler<PortBindRequest> handlePortBindRequest = new Handler<PortBindRequest>() { @Override public void handle(PortBindRequest msg) { logger.debug("Received bind request for port : " + msg.getPort()); PortBindResponse response = msg.getResponse(); try { if (bindPort(msg.getIp(), msg.getPort(), msg.getTransport(), msg.isBindAllNetworkIfs())) { response.setStatus(PortBindResponse.Status.SUCCESS); } else { response.setStatus(PortBindResponse.Status.FAIL); } } catch (ChannelException e) { response.setStatus(PortBindResponse.Status.PORT_ALREADY_BOUND); } trigger(response, netControl); } }; /** * Used by HolePunchingClient to allocate a bunch of ports, where we don't * care what the ports actually are, just that they are free ports. It tries * to find ports in the 50,000+ range, as they are typically not used by as * many applications as other ports. * */ Handler<PortAllocRequest> handlePortAllocRequest = new Handler<PortAllocRequest>() { @Override public void handle(PortAllocRequest msg) { int numPorts = msg.getNumPorts(); logger.debug("Request to allocate " + numPorts + " ports for hole-punching."); Set<Integer> setPorts = udpPortsToSockets.keySet(); Set<Integer> addedPorts = new HashSet<Integer>(); for (int i = 0; i < numPorts; i++) { int randPort = -1; do { // Allocate a port in the 50,000+ range. randPort = 50000 + rand.nextInt(65535 - 50000); } while (setPorts.contains(randPort)); if (bindPort(msg.getIp(), randPort, msg.getTransport(), true) == true) { addedPorts.add(randPort); } } PortAllocResponse response = msg.getResponse(); if (response == null) { throw new IllegalStateException( "PortAllocResponse event was not set before " + "sending PortAllocRequest to Netty."); } response.setAllocatedPorts(addedPorts); trigger(response, netControl); } }; /** * Stop listening as server on the given ports. */ Handler<PortDeleteRequest> handlePortDeleteRequest = new Handler<PortDeleteRequest>() { @Override public void handle(PortDeleteRequest msg) { Map<Integer, InetSocketAddress> portsToSockets; Transport protocol = msg.getTransport(); if (protocol == Transport.UDP) { portsToSockets = tcpPortsToSockets; } else if (protocol == Transport.TCP) { portsToSockets = udpPortsToSockets; } else if (protocol == Transport.UDT) { portsToSockets = udtPortsToSockets; } else { throw new Error("Unknown Transport type"); } Set<Integer> portsDeleted = new HashSet<Integer>(); for (int i : msg.getPortsToDelete()) { InetSocketAddress address = portsToSockets.remove(i); if (address != null) { closeServerSocket(address, msg.getTransport()); removeServerSocket(address, msg.getTransport()); portsDeleted.add(i); } } // TODO this is triggered before they might have been closed if (msg.getResponse() != null) { // if a response is requested, send it PortDeleteResponse response = msg.getResponse(); response.setPorts(portsDeleted); trigger(response, netControl); } } }; /** * Close the client socket connected to the given remote address. */ Handler<CloseConnectionRequest> handleCloseConnectionRequest = new Handler<CloseConnectionRequest>() { @Override public void handle(CloseConnectionRequest msg) { if (msg.getTransport() == Transport.UDP) { throw new RuntimeException("Cannot close connection for connectionless UDP"); } closeSocket(address2SocketAddress(msg.getRemoteAddress()), msg.getTransport(), msg.getResponse()); removeSocket(address2SocketAddress(msg.getRemoteAddress()), msg.getTransport()); } }; /** * Start listening as a server at the given address with the given protocol. * * @param addr the address to listen at * @param port the port number to listen at * @param protocol the protocol to use * @return true if listening was started * @throws ChannelException in case binding failed */ private boolean bindPort(InetAddress addr, int port, Transport protocol, boolean bindAllNetworkIfs) { switch (protocol) { case TCP: return bindTcpPort(addr, port, bindAllNetworkIfs); case UDP: return bindUdpPort(addr, port, bindAllNetworkIfs); case UDT: return bindUdtPort(addr, port, bindAllNetworkIfs); default: throw new Error("Unknown Transport type"); } } /** * Start listening as a server at the given address.. * * @param addr the address to listen at * @param port the port number to listen at * @param bindAllNetworkIfs whether to bind on all network interfaces * @return true if listening was started * @throws ChannelException in case binding failed */ private boolean bindUdpPort(final InetAddress addr, final int port, final boolean bindAllNetworkIfs) { if (udpPortsToSockets.containsKey(port)) { return true; } EventLoopGroup group = new NioEventLoopGroup(); Bootstrap bootstrap = new Bootstrap(); bootstrap.group(group).channel(NioDatagramChannel.class) .handler(new NettyMsgHandler(component, Transport.UDP, msgDecoderClass)); // Allow packets as large as up to 1600 bytes (default is 768). // You could increase or decrease this value to avoid truncated packets // or to improve memory footprint respectively. // // Please also note that a large UDP packet might be truncated or // dropped by your router no matter how you configured this option. // In UDP, a packet is truncated or dropped if it is larger than a // certain size, depending on router configuration. IPv4 routers // truncate and IPv6 routers drop a large packet. That's why it is // safe to send small packets in UDP. bootstrap.option(ChannelOption.RCVBUF_ALLOCATOR, new FixedRecvByteBufAllocator(1500)); bootstrap.option(ChannelOption.SO_RCVBUF, RECV_BUFFER_SIZE); bootstrap.option(ChannelOption.SO_SNDBUF, SEND_BUFFER_SIZE); // bootstrap.setOption("trafficClass", trafficClass); // bootstrap.setOption("soTimeout", soTimeout); // bootstrap.setOption("broadcast", broadcast); bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, CONNECT_TIMEOUT_MS); bootstrap.option(ChannelOption.SO_REUSEADDR, true); try { DatagramChannel c; if (bindAllNetworkIfs) { c = (DatagramChannel) bootstrap.bind( // new InetSocketAddress(port) port).sync().channel(); } else { c = (DatagramChannel) bootstrap.bind(new InetSocketAddress(addr, port)).sync().channel(); } addLocalSocket(new InetSocketAddress(addr, port), c); logger.debug("Successfully bound to ip:port {}:{}", addr, port); } catch (InterruptedException e) { logger.warn("Problem when trying to bind to {}:{}", addr.getHostAddress(), port); trigger(new Fault(e.getCause()), control); return false; } udpSocketsToBootstraps.put(new InetSocketAddress(addr, port), bootstrap); return true; } /** * Start listening as a server at the given address.. * * @param addr the address to listen at * @param port the port number to listen at * @param bindAllNetworkIfs whether to bind on all network interfaces * @return true if listening was started * @throws ChannelException in case binding failed */ private boolean bindTcpPort(InetAddress addr, int port, boolean bindAllNetworkIfs) { if (tcpPortsToSockets.containsKey(port)) { return true; } EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); NettyTcpServerHandler handler = new NettyTcpServerHandler(component); ServerBootstrap bootstrap = new ServerBootstrap(); bootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class) .childHandler((new NettyInitializer<SocketChannel>(handler, msgDecoderClass))) .option(ChannelOption.SO_REUSEADDR, true); try { if (bindAllNetworkIfs) { bootstrap.bind(new InetSocketAddress(port)).sync(); } else { bootstrap.bind(new InetSocketAddress(addr, port)).sync(); } logger.debug("Successfully bound to ip:port {}:{}", addr, port); } catch (InterruptedException e) { logger.warn("Problem when trying to bind to {}:{}", addr.getHostAddress(), port); trigger(new Fault(e.getCause()), control); return false; } InetSocketAddress iAddr = new InetSocketAddress(addr, port); tcpPortsToSockets.put(port, iAddr); tcpSocketsToServerBootstraps.put(iAddr, bootstrap); return true; } /** * Start listening as a server at the given address.. * * @param addr the address to listen at * @param port the port number to listen at * @param bindAllNetworkIfs whether to bind on all network interfaces * @return true if listening was started * @throws ChannelException in case binding failed */ private boolean bindUdtPort(InetAddress addr, int port, boolean bindAllNetworkIfs) { if (udtPortsToSockets.containsKey(port)) { return true; } ThreadFactory bossFactory = new UtilThreadFactory("boss"); ThreadFactory workerFactory = new UtilThreadFactory("worker"); NioEventLoopGroup bossGroup = new NioEventLoopGroup(1, bossFactory, NioUdtProvider.BYTE_PROVIDER); NioEventLoopGroup workerGroup = new NioEventLoopGroup(1, workerFactory, NioUdtProvider.BYTE_PROVIDER); NettyUdtServerHandler handler = new NettyUdtServerHandler(component); ServerBootstrap bootstrap = new ServerBootstrap(); bootstrap.group(bossGroup, workerGroup).channelFactory(NioUdtProvider.BYTE_ACCEPTOR) .childHandler(new NettyInitializer<UdtChannel>(handler, msgDecoderClass)) .option(ChannelOption.SO_REUSEADDR, true); try { if (bindAllNetworkIfs) { bootstrap.bind(new InetSocketAddress(port)).sync(); } else { bootstrap.bind(new InetSocketAddress(addr, port)).sync(); } logger.debug("Successfully bound to ip:port {}:{}", addr, port); } catch (InterruptedException e) { logger.warn("Problem when trying to bind to {}:{}", addr.getHostAddress(), port); trigger(new Fault(e.getCause()), control); return false; } InetSocketAddress iAddr = new InetSocketAddress(addr, port); udtPortsToSockets.put(port, iAddr); udtSocketsToServerBootstraps.put(iAddr, bootstrap); return true; } /** * Connect to a TCP server. * * @param remoteAddress the remote address * @param localAddress the local address to bind to * @return true if connection succeeded * @throws ChannelException if connecting failed */ private boolean connectTcp(Address remoteAddress, Address localAddress) { InetSocketAddress remote = address2SocketAddress(remoteAddress); InetSocketAddress local = address2SocketAddress(localAddress); if (tcpSocketsToBootstraps.containsKey(remote)) { return true; } EventLoopGroup group = new NioEventLoopGroup(); NettyStreamHandler handler = new NettyStreamHandler(component, Transport.TCP); Bootstrap bootstrap = new Bootstrap(); bootstrap.group(group).channel(NioSocketChannel.class) .handler(new NettyInitializer<SocketChannel>(handler, msgDecoderClass)) .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, CONNECT_TIMEOUT_MS) .option(ChannelOption.SO_REUSEADDR, true); try { SocketChannel c = (SocketChannel) bootstrap.connect(remote, local).sync().channel(); addLocalSocket(remote, c); logger.debug("Successfully connected to ip:port {}", remote.toString()); } catch (InterruptedException e) { logger.warn("Problem when trying to connect to {}", remote); trigger(new Fault(e.getCause()), control); return false; } tcpSocketsToBootstraps.put(remote, bootstrap); return true; } /** * Connect to a UDT server. * * @param remoteAddress the remote address * @param localAddress the local address to bind to * @return true if connection succeeded * @throws ChannelException if connecting failed */ private boolean connectUdt(Address remoteAddress, Address localAddress) { InetSocketAddress remote = address2SocketAddress(remoteAddress); InetSocketAddress local = address2SocketAddress(localAddress); if (udtSocketsToBootstraps.containsKey(remote)) { return true; } ThreadFactory workerFactory = new UtilThreadFactory("clientWorker"); NioEventLoopGroup workerGroup = new NioEventLoopGroup(1, workerFactory, NioUdtProvider.BYTE_PROVIDER); NettyStreamHandler handler = new NettyStreamHandler(component, Transport.UDT); Bootstrap bootstrap = new Bootstrap(); bootstrap.group(workerGroup).channelFactory(NioUdtProvider.BYTE_CONNECTOR) .handler(new NettyInitializer<UdtChannel>(handler, msgDecoderClass)) .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, CONNECT_TIMEOUT_MS) .option(ChannelOption.SO_REUSEADDR, true); try { UdtChannel c = (UdtChannel) bootstrap.connect(remote, local).sync().channel(); addLocalSocket(remote, c); logger.debug("Successfully connected to ip:port {}", remote.toString()); } catch (InterruptedException e) { logger.warn("Problem when trying to connect to {}", remote.toString()); trigger(new Fault(e.getCause()), control); return false; } udtSocketsToBootstraps.put(remote, bootstrap); return true; } /** * Add a {@link DatagramChannel} to the local connections. * * @param localAddress the local address * @param channel the channel to be added */ private void addLocalSocket(InetSocketAddress localAddress, DatagramChannel channel) { udpPortsToSockets.put(localAddress.getPort(), localAddress); // udpSocketsToChannels.put(localAddress, channel); udpSocketsToChannels.put(localAddress.getPort(), channel); } /** * Add a {@link SocketChannel} to the local connections. * * @param remoteAddress the remote address * @param channel the channel to be added */ void addLocalSocket(InetSocketAddress remoteAddress, SocketChannel channel) { tcpSocketsToChannels.put(remoteAddress, channel); trigger(new NetworkSessionOpened(remoteAddress, Transport.TCP), netControl); } /** * Add a {@link UdtChannel} to the local connections. * * @param remoteAddress the remote address * @param channel the channel to be added */ void addLocalSocket(InetSocketAddress remoteAddress, UdtChannel channel) { udtSocketsToChannels.put(remoteAddress, channel); trigger(new NetworkSessionOpened(remoteAddress, Transport.UDT), netControl); } private void removeServerSocket(InetSocketAddress addr, Transport protocol) { switch (protocol) { case TCP: tcpSocketsToServerBootstraps.remove(addr); break; case UDP: removeSocket(addr, Transport.UDP); break; case UDT: udtSocketsToServerBootstraps.remove(addr); break; default: throw new Error("Transport type not supported"); } } private void closeServerSocket(InetSocketAddress addr, Transport protocol) { switch (protocol) { case TCP: closeSeverBootstrap(tcpSocketsToServerBootstraps.get(addr)); break; case UDP: closeSocket(addr, Transport.UDP); break; case UDT: closeSeverBootstrap(udtSocketsToServerBootstraps.get(addr)); break; default: throw new Error("Transport type not supported"); } } private void closeSeverBootstrap(ServerBootstrap serverBootstrap) { serverBootstrap.childGroup().shutdownGracefully(); serverBootstrap.group().shutdownGracefully(); } /** * Remove a channel from the local connections and triggers the given * response at the netControl port. * * @param addr the address of the channel to be removed * @param protocol the protocol of the channel to be removed */ private void removeSocket(final InetSocketAddress addr, final Transport protocol) { switch (protocol) { case TCP: tcpSocketsToChannels.remove(addr); tcpSocketsToBootstraps.remove(addr); break; case UDP: udpPortsToSockets.remove(addr.getPort()); // udpSocketsToChannels.remove(addr); udpSocketsToChannels.remove(addr.getPort()); udpSocketsToBootstraps.remove(addr); break; case UDT: udtSocketsToChannels.remove(addr); udtSocketsToBootstraps.remove(addr); break; default: throw new Error("Transport type not supported"); } } private void closeSocket(final InetSocketAddress addr, final Transport protocol) { closeSocket(addr, protocol, null); } private void closeSocket(final InetSocketAddress addr, final Transport protocol, final CloseConnectionResponse response) { Bootstrap bootstrap; switch (protocol) { case TCP: bootstrap = tcpSocketsToBootstraps.get(addr); break; case UDP: bootstrap = udpSocketsToBootstraps.get(addr); break; case UDT: bootstrap = udtSocketsToBootstraps.get(addr); break; default: throw new Error("Transport type not supported"); } // Has been removed before if (bootstrap == null) { return; } Future future = bootstrap.group().shutdownGracefully(); future.addListener(new GenericFutureListener<Future<?>>() { @Override public void operationComplete(Future<?> future) throws Exception { if (response != null) { // if a response is requested, send it trigger(response, netControl); } } }); } private InetSocketAddress address2SocketAddress(Address address) { return new InetSocketAddress(address.getIp(), address.getPort()); } /** * Send a message using UDP. * * @param msg the message to be sent * @throws ChannelException in case of connection problems */ private void sendUdp(RewriteableMsg msg) { InetSocketAddress src = address2SocketAddress(msg.getSource()); InetSocketAddress dest = address2SocketAddress(msg.getDestination()); if (src == null) { String strError = "Source for msg " + "of type " + msg.getClass() + " is not bound at network component: " + msg.getSource(); logger.error(strError); // throw new IllegalArgumentException(strError); return; } // use one channel per local socket // session-less UDP. This means that remoteAddresses cannot be found in // the channel object, but only in the MessageEvent object. // DatagramChannel channel = udpSocketsToChannels.get(src); DatagramChannel channel = udpSocketsToChannels.get(src.getPort()); if (channel == null) { String strError = "Source for msg " + "of type " + msg.getClass() + " . Port not bound at client, need to bind the port first: " + msg.getSource(); logger.error(strError); // throw new IllegalArgumentException(strError); return; } try { logger.trace("Sending " + msg.getClass().getCanonicalName() + " from {} to {} ", msg.getSource(), msg.getDestination()); channel.writeAndFlush(new DatagramPacket(((Encodable) msg).toByteArray(), dest)); totalWrittenBytes += msg.getSize(); } catch (Exception ex) { ex.printStackTrace(); logger.warn("Problem trying to send msg of type: " + msg.getClass().getCanonicalName() + " with src address: " + src.toString() + " and dest address: " + msg.getDestination() + " Exception: " + ex.getMessage() + " " + ex.getClass()); if (BaseCommandLineConfig.reportNettyExceptions()) { try { NatReportMsg.NatReport nr = new NatReportMsg.NatReport(msg.getSource().getPort(), ToVodAddr.systemAddr(msg.getDestination()), false, 0, "Netty error: " + ex.getMessage()); List<NatReportMsg.NatReport> nrs = new ArrayList<NatReportMsg.NatReport>(); nrs.add(nr); VodAddress reportServer = ToVodAddr.bootstrap(VodConfig.getBootstrapServer()); NatReportMsg evt = new NatReportMsg(ToVodAddr.systemAddr(msg.getSource()), reportServer, nrs); InetSocketAddress reportDest = address2SocketAddress(reportServer.getPeerAddress()); channel.writeAndFlush(new DatagramPacket(((Encodable) evt).toByteArray(), reportDest)); } catch (Throwable t) { logger.warn("Couldn't report exception to NatReportServer: " + t.getMessage()); } } } } /** * Send a message to using TCP. Connects to a server on demand. * * @param msg the message to be sent * @throws ChannelException in case of connection problems */ private void sendTcp(RewriteableMsg msg) { InetSocketAddress dst = address2SocketAddress(msg.getDestination()); SocketChannel channel = tcpSocketsToChannels.get(dst); if (channel == null) { if (connectTcp(msg.getDestination(), msg.getSource()) == false) { logger.warn("Channel was null when trying to write msg of type: " + msg.getClass().getCanonicalName() + " with dst address: " + dst.toString()); trigger(new Fault(new IllegalStateException( "Could not send message because connection could not be established to " + dst.toString())), control); return; } channel = tcpSocketsToChannels.get(dst); } try { logger.trace("Sending " + msg.getClass().getCanonicalName() + " from {} to {} ", msg.getSource().getId(), msg.getDestination().getId()); channel.writeAndFlush(msg); totalWrittenBytes += msg.getSize(); } catch (Exception ex) { ex.printStackTrace(); logger.warn("Problem trying to write msg of type: " + msg.getClass().getCanonicalName() + " with dst address: " + dst.toString()); throw new RuntimeException(ex.getMessage()); } } /** * Send a message to using UDT. Connects to a server on demand. * * @param msg the message to be sent * @throws ChannelException in case of connection problems */ private void sendUdt(RewriteableMsg msg) { InetSocketAddress dst = address2SocketAddress(msg.getDestination()); UdtChannel channel = udtSocketsToChannels.get(dst); if (channel == null) { if (connectUdt(msg.getDestination(), msg.getSource()) == false) { logger.warn("Channel was null when trying to write msg of type: " + msg.getClass().getCanonicalName() + " with dst address: " + dst.toString()); trigger(new Fault(new IllegalStateException( "Could not send messge because connection could not be established to " + dst.toString())), control); return; } channel = udtSocketsToChannels.get(dst); } try { logger.trace("Sending " + msg.getClass().getCanonicalName() + " from {} to {} ", msg.getSource().getId(), msg.getDestination().getId()); channel.writeAndFlush(msg); totalWrittenBytes += msg.getSize(); } catch (Exception ex) { ex.printStackTrace(); logger.warn("Problem trying to write msg of type: " + msg.getClass().getCanonicalName() + " with dst address: " + dst.toString()); throw new RuntimeException(ex.getMessage()); } } /** * Deliver a message to the upper components. * * @param msg the message to be delivered */ final void deliverMessage(RewriteableMsg msg) { logger.trace("Receiving " + msg.getClass().getCanonicalName() + " source {} dest {} ", msg.getSource(), msg.getDestination()); trigger(msg, net); totalReadBytes += msg.getSize(); } /** * Forward an exception to the upper components. * * @param ctx the channel handler context * @param e the caught exception */ public void exceptionCaught(ChannelHandlerContext ctx, Throwable e) { logger.warn("Fault for " + e.getCause().getMessage()); trigger(new Fault(e.getCause()), control); } /** * Network exception. * * @param event the event */ final void networkException(NetworkException event) { logger.trace("NetworkException for " + event.getRemoteAddress().toString()); trigger(event, netControl); } /** * Remove a connection after it was lost or closed remotely and inform the * upper components. * * @param ctx the channel handler context * @param protocol the protocol */ final void channelInactive(ChannelHandlerContext ctx, Transport protocol) { SocketAddress addr = ctx.channel().remoteAddress(); ; if (addr instanceof InetSocketAddress) { InetSocketAddress remoteAddress = (InetSocketAddress) addr; trigger(new NetworkSessionClosed(remoteAddress, protocol), netControl); // Schedule a thread secure close event for this component trigger(new CloseConnectionRequest(0, new Address(remoteAddress.getAddress(), remoteAddress.getPort(), 0), protocol), positive(NatNetworkControl.class)); logger.trace("Channel closed"); } } public static long getNumBytesReadLastSec() { return lastSecRead.longValue(); } public static long getNumBytesReadLastMin() { return lastSecRead.longValue(); } public static long getNumBytesWroteLastSec() { return lastSecWritten.longValue(); } }