bt.peer.lan.AnnounceGroupChannel Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of bt-core Show documentation
Show all versions of bt-core Show documentation
BitTorrent Client Library (Core)
The newest version!
/*
* Copyright (c) 2016—2021 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 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 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;
}
}
}
}