/* * Copyright (c) 2011-2013 The original author or authors * ------------------------------------------------------ * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * and Apache License v2.0 which accompanies this distribution. * * The Eclipse Public License is available at * http://www.eclipse.org/legal/epl-v10.html * * The Apache License v2.0 is available at * http://www.opensource.org/licenses/apache2.0.php * * You may elect to redistribute this code under either of these licenses. */ package io.vertx.core.datagram.impl; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelOption; import io.netty.channel.FixedRecvByteBufAllocator; import io.netty.channel.socket.DatagramChannel; import io.netty.channel.socket.DatagramPacket; import io.netty.channel.socket.InternetProtocolFamily; import io.netty.channel.socket.nio.NioDatagramChannel; import io.netty.handler.logging.LoggingHandler; import io.vertx.core.AsyncResult; import io.vertx.core.Future; import io.vertx.core.Handler; import io.vertx.core.buffer.Buffer; import io.vertx.core.datagram.DatagramSocket; import io.vertx.core.datagram.DatagramSocketOptions; import io.vertx.core.impl.Arguments; import io.vertx.core.impl.ContextImpl; import io.vertx.core.impl.VertxInternal; import io.vertx.core.net.SocketAddress; import io.vertx.core.net.impl.ConnectionBase; import io.vertx.core.net.impl.PartialPooledByteBufAllocator; import io.vertx.core.net.impl.SocketAddressImpl; import io.vertx.core.spi.metrics.DatagramSocketMetrics; import io.vertx.core.spi.metrics.Metrics; import io.vertx.core.spi.metrics.MetricsProvider; import io.vertx.core.streams.WriteStream; import java.net.*; import java.util.Objects; /** * @author <a href="mailto:nmaurer@redhat.com">Norman Maurer</a> */ public class DatagramSocketImpl extends ConnectionBase implements DatagramSocket, MetricsProvider { private Handler<io.vertx.core.datagram.DatagramPacket> packetHandler; private DatagramSocketMetrics metrics; public DatagramSocketImpl(VertxInternal vertx, DatagramSocketOptions options) { super(vertx, createChannel(options.isIpV6() ? io.vertx.core.datagram.impl.InternetProtocolFamily.IPv6 : io.vertx.core.datagram.impl.InternetProtocolFamily.IPv4, new DatagramSocketOptions(options)), vertx.getOrCreateContext()); ContextImpl creatingContext = vertx.getContext(); if (creatingContext != null && creatingContext.isMultiThreadedWorkerContext()) { throw new IllegalStateException("Cannot use DatagramSocket in a multi-threaded worker verticle"); } channel().config().setOption(ChannelOption.DATAGRAM_CHANNEL_ACTIVE_ON_REGISTRATION, true); context.nettyEventLoop().register(channel); if (options.getLogActivity()) { channel().pipeline().addLast("logging", new LoggingHandler()); } channel.pipeline().addLast("handler", new DatagramServerHandler(this)); channel().config().setMaxMessagesPerRead(1); channel().config().setAllocator(PartialPooledByteBufAllocator.INSTANCE); metrics = vertx.metricsSPI().createMetrics(this, (DatagramSocketOptions) options); } @Override public DatagramSocketMetrics metrics() { return metrics; } @Override public DatagramSocket listenMulticastGroup(String multicastAddress, Handler<AsyncResult<DatagramSocket>> handler) { try { addListener(channel().joinGroup(InetAddress.getByName(multicastAddress)), handler); } catch (UnknownHostException e) { notifyException(handler, e); } return this; } @Override public DatagramSocket listenMulticastGroup(String multicastAddress, String networkInterface, String source, Handler<AsyncResult<DatagramSocket>> handler) { try { InetAddress sourceAddress; if (source == null) { sourceAddress = null; } else { sourceAddress = InetAddress.getByName(source); } addListener(channel().joinGroup(InetAddress.getByName(multicastAddress), NetworkInterface.getByName(networkInterface), sourceAddress), handler); } catch (Exception e) { notifyException(handler, e); } return this; } @Override public DatagramSocket unlistenMulticastGroup(String multicastAddress, Handler<AsyncResult<DatagramSocket>> handler) { try { addListener(channel().leaveGroup(InetAddress.getByName(multicastAddress)), handler); } catch (UnknownHostException e) { notifyException(handler, e); } return this; } @Override public DatagramSocket unlistenMulticastGroup(String multicastAddress, String networkInterface, String source, Handler<AsyncResult<DatagramSocket>> handler) { try { InetAddress sourceAddress; if (source == null) { sourceAddress = null; } else { sourceAddress = InetAddress.getByName(source); } addListener(channel().leaveGroup(InetAddress.getByName(multicastAddress), NetworkInterface.getByName(networkInterface), sourceAddress), handler); } catch (Exception e) { notifyException(handler, e); } return this; } @Override public DatagramSocket blockMulticastGroup(String multicastAddress, String networkInterface, String sourceToBlock, Handler<AsyncResult<DatagramSocket>> handler) { try { InetAddress sourceAddress; if (sourceToBlock == null) { sourceAddress = null; } else { sourceAddress = InetAddress.getByName(sourceToBlock); } addListener(channel().block(InetAddress.getByName(multicastAddress), NetworkInterface.getByName(networkInterface), sourceAddress), handler); } catch (Exception e) { notifyException(handler, e); } return this; } @Override public DatagramSocket blockMulticastGroup(String multicastAddress, String sourceToBlock, Handler<AsyncResult<DatagramSocket>> handler) { try { addListener(channel().block(InetAddress.getByName(multicastAddress), InetAddress.getByName(sourceToBlock)), handler); } catch (UnknownHostException e) { notifyException(handler, e); } return this; } @Override public DatagramSocket listen(int port, String address, Handler<AsyncResult<DatagramSocket>> handler) { return listen(new SocketAddressImpl(port, address), handler); } @Override public synchronized DatagramSocket handler(Handler<io.vertx.core.datagram.DatagramPacket> handler) { this.packetHandler = handler; return this; } @Override public DatagramSocketImpl endHandler(Handler<Void> endHandler) { return (DatagramSocketImpl) super.closeHandler(endHandler); } @Override public DatagramSocketImpl exceptionHandler(Handler<Throwable> handler) { return (DatagramSocketImpl) super.exceptionHandler(handler); } private DatagramSocket listen(SocketAddress local, Handler<AsyncResult<DatagramSocket>> handler) { Objects.requireNonNull(handler, "no null handler accepted"); vertx.resolveAddress(local.host(), res -> { if (res.succeeded()) { ChannelFuture future = channel().bind(new InetSocketAddress(res.result(), local.port())); addListener(future, ar -> { if (ar.succeeded()) { ((DatagramSocketMetrics) metrics).listening(local.host(), localAddress()); } handler.handle(ar); }); } else { handler.handle(Future.failedFuture(res.cause())); } }); return this; } @SuppressWarnings("unchecked") final void addListener(ChannelFuture future, Handler<AsyncResult<DatagramSocket>> handler) { if (handler != null) { future.addListener(new DatagramChannelFutureListener<>(this, handler, context)); } } @SuppressWarnings("unchecked") public DatagramSocket pause() { doPause(); return this; } @SuppressWarnings("unchecked") public DatagramSocket resume() { doResume(); return this; } @Override @SuppressWarnings("unchecked") public DatagramSocket send(Buffer packet, int port, String host, Handler<AsyncResult<DatagramSocket>> handler) { Objects.requireNonNull(packet, "no null packet accepted"); Objects.requireNonNull(host, "no null host accepted"); InetSocketAddress addr = InetSocketAddress.createUnresolved(host, port); if (addr.isUnresolved()) { vertx.resolveAddress(host, res -> { if (res.succeeded()) { doSend(packet, new InetSocketAddress(res.result(), port), handler); } else { handler.handle(Future.failedFuture(res.cause())); } }); } else { // If it's immediately resolved it means it was just an IP address so no need to async resolve doSend(packet, addr, handler); } if (metrics.isEnabled()) { metrics.bytesWritten(null, new SocketAddressImpl(port, host), packet.length()); } return this; } private void doSend(Buffer packet, InetSocketAddress addr, Handler<AsyncResult<DatagramSocket>> handler) { ChannelFuture future = channel().writeAndFlush(new DatagramPacket(packet.getByteBuf(), addr)); addListener(future, handler); } @Override public WriteStream<Buffer> sender(int port, String host) { Arguments.requireInRange(port, 0, 65535, "port p must be in range 0 <= p <= 65535"); Objects.requireNonNull(host, "no null host accepted"); return new PacketWriteStreamImpl(this, port, host); } @Override public DatagramSocket send(String str, int port, String host, Handler<AsyncResult<DatagramSocket>> handler) { return send(Buffer.buffer(str), port, host, handler); } @Override public DatagramSocket send(String str, String enc, int port, String host, Handler<AsyncResult<DatagramSocket>> handler) { return send(Buffer.buffer(str, enc), port, host, handler); } @Override public void close(final Handler<AsyncResult<Void>> handler) { // make sure everything is flushed out on close endReadAndFlush(); metrics.close(); ChannelFuture future = channel.close(); if (handler != null) { future.addListener(new DatagramChannelFutureListener<>(null, handler, context)); } } @Override public boolean isMetricsEnabled() { return metrics != null && metrics.isEnabled(); } @Override public Metrics getMetrics() { return metrics; } protected DatagramChannel channel() { return (DatagramChannel) channel; } private static NioDatagramChannel createChannel(io.vertx.core.datagram.impl.InternetProtocolFamily family, DatagramSocketOptions options) { NioDatagramChannel channel; if (family == null) { channel = new NioDatagramChannel(); } else { switch (family) { case IPv4: channel = new NioDatagramChannel(InternetProtocolFamily.IPv4); break; case IPv6: channel = new NioDatagramChannel(InternetProtocolFamily.IPv6); break; default: channel = new NioDatagramChannel(); } } if (options.getSendBufferSize() != -1) { channel.config().setSendBufferSize(options.getSendBufferSize()); } if (options.getReceiveBufferSize() != -1) { channel.config().setReceiveBufferSize(options.getReceiveBufferSize()); channel.config().setRecvByteBufAllocator(new FixedRecvByteBufAllocator(options.getReceiveBufferSize())); } channel.config().setReuseAddress(options.isReuseAddress()); if (options.getTrafficClass() != -1) { channel.config().setTrafficClass(options.getTrafficClass()); } channel.config().setBroadcast(options.isBroadcast()); channel.config().setLoopbackModeDisabled(options.isLoopbackModeDisabled()); if (options.getMulticastTimeToLive() != -1) { channel.config().setTimeToLive(options.getMulticastTimeToLive()); } if (options.getMulticastNetworkInterface() != null) { try { channel.config().setNetworkInterface(NetworkInterface.getByName(options.getMulticastNetworkInterface())); } catch (SocketException e) { throw new IllegalArgumentException("Could not find network interface with name " + options.getMulticastNetworkInterface()); } } return channel; } private void notifyException(final Handler<AsyncResult<DatagramSocket>> handler, final Throwable cause) { context.executeFromIO(() -> handler.handle(Future.failedFuture(cause))); } @Override protected void finalize() throws Throwable { // Make sure this gets cleaned up if there are no more references to it // so as not to leave connections and resources dangling until the system is shutdown // which could make the JVM run out of file handles. close(); super.finalize(); } protected void handleClosed() { checkContext(); super.handleClosed(); } synchronized void handlePacket(io.vertx.core.datagram.DatagramPacket packet) { if (metrics.isEnabled()) { metrics.bytesRead(null, packet.sender(), packet.data().length()); } if (packetHandler != null) { packetHandler.handle(packet); } } @Override protected void handleInterestedOpsChanged() { } }