/* * Copyright (c) 2016—2017 Andrei Tomashpolskiy and individual contributors. * * 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 bt.peer.lan; import bt.net.InternetProtocolUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetSocketAddress; import java.net.NetworkInterface; import java.net.ProtocolFamily; import java.net.SocketAddress; import java.net.StandardProtocolFamily; import java.net.StandardSocketOptions; import java.nio.ByteBuffer; import java.nio.channels.DatagramChannel; import java.nio.channels.Selector; import java.util.Collection; import java.util.concurrent.atomic.AtomicBoolean; /** * Repairable datagram channel. * If the invocation of {@link #send(ByteBuffer)} or {@link #receive(ByteBuffer)} results in an exception, * then the caller can call {@link #closeQuietly()} and retry the original operation, which will result in the creation of a new channel. * * @since 1.6 */ public class AnnounceGroupChannel { private static final Logger LOGGER = LoggerFactory.getLogger(AnnounceGroupChannel.class); private final AnnounceGroup group; private final Collection<NetworkInterface> networkInterfaces; private final Selector selector; private DatagramChannel channel; private final AtomicBoolean shutdown; /** * @param group Target announce group * @param selector Selector to use for opening local channel * @param networkInterfaces Network interfaces, on which to listen to incoming messages * @since 1.6 */ public AnnounceGroupChannel(AnnounceGroup group, Selector selector, Collection<NetworkInterface> networkInterfaces) { this.group = group; this.selector = selector; this.networkInterfaces = networkInterfaces; this.shutdown = new AtomicBoolean(false); } /** * @since 1.6 */ public AnnounceGroup getGroup() { return group; } /** * @since 1.6 */ public synchronized void send(ByteBuffer buffer) throws IOException { if (buffer.remaining() == 0) { return; } int written; do { written = getChannel().send(buffer, group.getAddress()); } while (buffer.hasRemaining() && written > 0); } /** * @since 1.6 */ public synchronized SocketAddress receive(ByteBuffer buffer) throws IOException { if (buffer.remaining() == 0) { return null; } return getChannel().receive(buffer); } private synchronized DatagramChannel getChannel() throws IOException { if (channel == null || !channel.isOpen()) { if (shutdown.get()) { throw new IllegalStateException("Channel has been shut down"); } ProtocolFamily protocolFamily = InternetProtocolUtils.getProtocolFamily(group.getAddress().getAddress()); DatagramChannel _channel = selector.provider().openDatagramChannel(protocolFamily); _channel.setOption(StandardSocketOptions.SO_REUSEADDR, true); // bind to any-local before setting TTL int port = group.getAddress().getPort(); if (protocolFamily == StandardProtocolFamily.INET) { _channel.bind(new InetSocketAddress(Inet4Address.getByName("0.0.0.0"), port)); } else { _channel.bind(new InetSocketAddress(Inet6Address.getByName("[::]"), port)); } int timeToLive = group.getTimeToLive(); if (timeToLive != 1) { _channel.setOption(StandardSocketOptions.IP_MULTICAST_TTL, timeToLive); } for (NetworkInterface iface : networkInterfaces) { _channel.join(group.getAddress().getAddress(), iface); } _channel.configureBlocking(false); channel = _channel; } return channel; } /** * Close currently opened channel if present and prevent creation of new channels. * * @since 1.6 */ public synchronized void shutdown() { if (shutdown.compareAndSet(false, true)) { closeQuietly(); } } /** * @since 1.6 */ public synchronized void closeQuietly() { if (channel != null) { try { if (channel.isOpen()) { channel.close(); } } catch (IOException e) { LOGGER.error("Failed to close channel", e); } finally { channel = null; } } } }