netty5/transport-sctp/src/main/java/io/netty/channel/sctp/nio/NioSctpChannel.java

389 lines
12 KiB
Java

/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you 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:
*
* https://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 io.netty.channel.sctp.nio;
import com.sun.nio.sctp.Association;
import com.sun.nio.sctp.MessageInfo;
import com.sun.nio.sctp.NotificationHandler;
import com.sun.nio.sctp.SctpChannel;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.channel.Channel;
import io.netty.channel.ChannelException;
import io.netty.channel.ChannelMetadata;
import io.netty.channel.ChannelOutboundBuffer;
import io.netty.channel.EventLoop;
import io.netty.channel.RecvByteBufAllocator;
import io.netty.channel.nio.AbstractNioMessageChannel;
import io.netty.channel.sctp.DefaultSctpChannelConfig;
import io.netty.channel.sctp.SctpChannelConfig;
import io.netty.channel.sctp.SctpMessage;
import io.netty.channel.sctp.SctpNotificationHandler;
import io.netty.channel.sctp.SctpServerChannel;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.Promise;
import io.netty.util.internal.StringUtil;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
/**
* {@link io.netty.channel.sctp.SctpChannel} implementation which use non-blocking mode and allows to read /
* write {@link SctpMessage}s to the underlying {@link SctpChannel}.
*
* Be aware that not all operations systems support SCTP. Please refer to the documentation of your operation system,
* to understand what you need to do to use it. Also this feature is only supported on Java 7+.
*/
public class NioSctpChannel extends AbstractNioMessageChannel implements io.netty.channel.sctp.SctpChannel {
private static final ChannelMetadata METADATA = new ChannelMetadata(false);
private static final InternalLogger logger = InternalLoggerFactory.getInstance(NioSctpChannel.class);
private final SctpChannelConfig config;
private final NotificationHandler<?> notificationHandler;
private static SctpChannel newSctpChannel() {
try {
return SctpChannel.open();
} catch (IOException e) {
throw new ChannelException("Failed to open a sctp channel.", e);
}
}
/**
* Create a new instance
*/
public NioSctpChannel(EventLoop eventLoop) {
this(eventLoop, newSctpChannel());
}
/**
* Create a new instance using {@link SctpChannel}
*/
public NioSctpChannel(EventLoop eventLoop, SctpChannel sctpChannel) {
this(null, eventLoop, sctpChannel);
}
/**
* Create a new instance
*
* @param parent the {@link Channel} which is the parent of this {@link NioSctpChannel}
* or {@code null}.
* @param sctpChannel the underlying {@link SctpChannel}
*/
public NioSctpChannel(Channel parent, EventLoop eventLoop, SctpChannel sctpChannel) {
super(parent, eventLoop, sctpChannel, SelectionKey.OP_READ);
try {
sctpChannel.configureBlocking(false);
config = new NioSctpChannelConfig(this, sctpChannel);
notificationHandler = new SctpNotificationHandler(this);
} catch (IOException e) {
try {
sctpChannel.close();
} catch (IOException e2) {
if (logger.isWarnEnabled()) {
logger.warn(
"Failed to close a partially initialized sctp channel.", e2);
}
}
throw new ChannelException("Failed to enter non-blocking mode.", e);
}
}
@Override
public InetSocketAddress localAddress() {
return (InetSocketAddress) super.localAddress();
}
@Override
public InetSocketAddress remoteAddress() {
return (InetSocketAddress) super.remoteAddress();
}
@Override
public SctpServerChannel parent() {
return (SctpServerChannel) super.parent();
}
@Override
public ChannelMetadata metadata() {
return METADATA;
}
@Override
public Association association() {
try {
return javaChannel().association();
} catch (IOException ignored) {
return null;
}
}
@Override
public Set<InetSocketAddress> allLocalAddresses() {
try {
final Set<SocketAddress> allLocalAddresses = javaChannel().getAllLocalAddresses();
final Set<InetSocketAddress> addresses = new LinkedHashSet<>(allLocalAddresses.size());
for (SocketAddress socketAddress : allLocalAddresses) {
addresses.add((InetSocketAddress) socketAddress);
}
return addresses;
} catch (Throwable ignored) {
return Collections.emptySet();
}
}
@Override
public SctpChannelConfig config() {
return config;
}
@Override
public Set<InetSocketAddress> allRemoteAddresses() {
try {
final Set<SocketAddress> allLocalAddresses = javaChannel().getRemoteAddresses();
final Set<InetSocketAddress> addresses = new HashSet<>(allLocalAddresses.size());
for (SocketAddress socketAddress : allLocalAddresses) {
addresses.add((InetSocketAddress) socketAddress);
}
return addresses;
} catch (Throwable ignored) {
return Collections.emptySet();
}
}
@Override
protected SctpChannel javaChannel() {
return (SctpChannel) super.javaChannel();
}
@Override
public boolean isActive() {
SctpChannel ch = javaChannel();
return ch.isOpen() && association() != null;
}
@Override
protected SocketAddress localAddress0() {
try {
Iterator<SocketAddress> i = javaChannel().getAllLocalAddresses().iterator();
if (i.hasNext()) {
return i.next();
}
} catch (IOException e) {
// ignore
}
return null;
}
@Override
protected SocketAddress remoteAddress0() {
try {
Iterator<SocketAddress> i = javaChannel().getRemoteAddresses().iterator();
if (i.hasNext()) {
return i.next();
}
} catch (IOException e) {
// ignore
}
return null;
}
@Override
protected void doBind(SocketAddress localAddress) throws Exception {
javaChannel().bind(localAddress);
}
@Override
protected boolean doConnect(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception {
if (localAddress != null) {
javaChannel().bind(localAddress);
}
boolean success = false;
try {
boolean connected = javaChannel().connect(remoteAddress);
if (!connected) {
selectionKey().interestOps(SelectionKey.OP_CONNECT);
}
success = true;
return connected;
} finally {
if (!success) {
doClose();
}
}
}
@Override
protected void doFinishConnect() throws Exception {
if (!javaChannel().finishConnect()) {
throw new Error();
}
}
@Override
protected void doDisconnect() throws Exception {
doClose();
}
@Override
protected void doClose() throws Exception {
javaChannel().close();
}
@Override
protected int doReadMessages(List<Object> buf) throws Exception {
SctpChannel ch = javaChannel();
RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();
ByteBuf buffer = allocHandle.allocate(config().getAllocator());
boolean free = true;
try {
ByteBuffer data = buffer.internalNioBuffer(buffer.writerIndex(), buffer.writableBytes());
int pos = data.position();
MessageInfo messageInfo = ch.receive(data, null, notificationHandler);
if (messageInfo == null) {
return 0;
}
allocHandle.lastBytesRead(data.position() - pos);
buf.add(new SctpMessage(messageInfo,
buffer.writerIndex(buffer.writerIndex() + allocHandle.lastBytesRead())));
free = false;
return 1;
} finally {
if (free) {
buffer.release();
}
}
}
@Override
protected boolean doWriteMessage(Object msg, ChannelOutboundBuffer in) throws Exception {
SctpMessage packet = (SctpMessage) msg;
ByteBuf data = packet.content();
int dataLen = data.readableBytes();
if (dataLen == 0) {
return true;
}
ByteBufAllocator alloc = alloc();
boolean needsCopy = data.nioBufferCount() != 1;
if (!needsCopy) {
if (!data.isDirect() && alloc.isDirectBufferPooled()) {
needsCopy = true;
}
}
ByteBuffer nioData;
if (needsCopy) {
data = alloc.directBuffer(dataLen).writeBytes(data);
}
nioData = data.nioBuffer();
final MessageInfo mi = MessageInfo.createOutgoing(association(), null, packet.streamIdentifier());
mi.payloadProtocolID(packet.protocolIdentifier());
mi.streamNumber(packet.streamIdentifier());
mi.unordered(packet.isUnordered());
final int writtenBytes = javaChannel().send(nioData, mi);
return writtenBytes > 0;
}
@Override
protected final Object filterOutboundMessage(Object msg) throws Exception {
if (msg instanceof SctpMessage) {
SctpMessage m = (SctpMessage) msg;
ByteBuf buf = m.content();
if (buf.isDirect() && buf.nioBufferCount() == 1) {
return m;
}
return new SctpMessage(m.protocolIdentifier(), m.streamIdentifier(), m.isUnordered(),
newDirectBuffer(m, buf));
}
throw new UnsupportedOperationException(
"unsupported message type: " + StringUtil.simpleClassName(msg) +
" (expected: " + StringUtil.simpleClassName(SctpMessage.class));
}
@Override
public Future<Void> bindAddress(InetAddress localAddress) {
return bindAddress(localAddress, newPromise());
}
@Override
public Future<Void> bindAddress(final InetAddress localAddress, final Promise<Void> promise) {
if (executor().inEventLoop()) {
try {
javaChannel().bindAddress(localAddress);
promise.setSuccess(null);
} catch (Throwable t) {
promise.setFailure(t);
}
} else {
executor().execute(() -> bindAddress(localAddress, promise));
}
return promise;
}
@Override
public Future<Void> unbindAddress(InetAddress localAddress) {
return unbindAddress(localAddress, newPromise());
}
@Override
public Future<Void> unbindAddress(final InetAddress localAddress, final Promise<Void> promise) {
if (executor().inEventLoop()) {
try {
javaChannel().unbindAddress(localAddress);
promise.setSuccess(null);
} catch (Throwable t) {
promise.setFailure(t);
}
} else {
executor().execute(() -> unbindAddress(localAddress, promise));
}
return promise;
}
private final class NioSctpChannelConfig extends DefaultSctpChannelConfig {
private NioSctpChannelConfig(NioSctpChannel channel, SctpChannel javaChannel) {
super(channel, javaChannel);
}
@Override
protected void autoReadCleared() {
clearReadPending();
}
}
}