All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.netty.channel.socket.aio.AioSocketChannel Maven / Gradle / Ivy

There is a newer version: 5.0.0.Alpha2
Show newest version
/*
 * Copyright 2012 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:
 *
 *   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 io.netty.channel.socket.aio;

import io.netty.buffer.BufType;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelException;
import io.netty.channel.ChannelFlushPromiseNotifier;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelMetadata;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelPromise;
import io.netty.channel.EventLoop;
import io.netty.channel.FileRegion;
import io.netty.channel.aio.AbstractAioChannel;
import io.netty.channel.aio.AioCompletionHandler;
import io.netty.channel.aio.AioEventLoopGroup;
import io.netty.channel.socket.ChannelInputShutdownEvent;
import io.netty.channel.socket.ServerSocketChannel;
import io.netty.channel.socket.SocketChannel;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousChannelGroup;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.CompletionHandler;
import java.nio.channels.InterruptedByTimeoutException;
import java.nio.channels.WritableByteChannel;
import java.util.concurrent.TimeUnit;


/**
 * {@link SocketChannel} implementation which uses NIO2.
 *
 * NIO2 is only supported on Java 7+.
 */
public class AioSocketChannel extends AbstractAioChannel implements SocketChannel {

    private static final ChannelMetadata METADATA = new ChannelMetadata(BufType.BYTE, false);

    private static final CompletionHandler CONNECT_HANDLER  = new ConnectHandler();
    private static final CompletionHandler WRITE_HANDLER = new WriteHandler();
    private static final CompletionHandler READ_HANDLER = new ReadHandler();
    private static final CompletionHandler GATHERING_WRITE_HANDLER = new WriteHandler();
    private static final CompletionHandler SCATTERING_READ_HANDLER = new ReadHandler();

    private static AsynchronousSocketChannel newSocket(AsynchronousChannelGroup group) {
        try {
            return AsynchronousSocketChannel.open(group);
        } catch (IOException e) {
            throw new ChannelException("Failed to open a socket.", e);
        }
    }

    private final DefaultAioSocketChannelConfig config;
    private volatile boolean inputShutdown;
    private volatile boolean outputShutdown;

    private boolean readInProgress;
    private boolean inDoBeginRead;
    private boolean readAgain;
    private boolean writeInProgress;
    private boolean inDoFlushByteBuffer;

    /**
     * Create a new instance which has not yet attached an {@link AsynchronousSocketChannel}. The
     * {@link AsynchronousSocketChannel} will be attached after it was this instance was registered to an
     * {@link EventLoop}.
     */
    public AioSocketChannel() {
        this(null, null, null);
    }

    /**
     * Create a new instance from the given {@link AsynchronousSocketChannel}.
     *
     * @param parent
     *        the parent of this channel. {@code null} if there's no parent.
     * @param id
     *        the unique non-negative integer ID of this channel.
     *        Specify {@code null} to auto-generate a unique negative integer
     *        ID.
     * @param ch
     *        the {@link AsynchronousSocketChannel} which is used by this instance
     */
    AioSocketChannel(
            AioServerSocketChannel parent, Integer id, AsynchronousSocketChannel ch) {
        super(parent, id, ch);
        config = new DefaultAioSocketChannelConfig(this, ch);
    }

    @Override
    public InetSocketAddress localAddress() {
        return (InetSocketAddress) super.localAddress();
    }

    @Override
    public InetSocketAddress remoteAddress() {
        return (InetSocketAddress) super.remoteAddress();
    }

    @Override
    public ServerSocketChannel parent() {
        return (ServerSocketChannel) super.parent();
    }

    @Override
    public boolean isActive() {
        return ch != null && javaChannel().isOpen() && remoteAddress0() != null;
    }

    @Override
    protected AsynchronousSocketChannel javaChannel() {
        return (AsynchronousSocketChannel) super.javaChannel();
    }

    @Override
    public ChannelMetadata metadata() {
        return METADATA;
    }

    @Override
    public boolean isInputShutdown() {
        return inputShutdown;
    }

    @Override
    public boolean isOutputShutdown() {
        return outputShutdown;
    }

    @Override
    public ChannelFuture shutdownOutput() {
        return shutdownOutput(newPromise());
    }

    @Override
    public ChannelFuture shutdownOutput(final ChannelPromise promise) {
        EventLoop loop = eventLoop();
        if (loop.inEventLoop()) {
            try {
                javaChannel().shutdownOutput();
                outputShutdown = true;
                promise.setSuccess();
            } catch (Throwable t) {
                promise.setFailure(t);
            }
        } else {
            loop.execute(new Runnable() {
                @Override
                public void run() {
                    shutdownOutput(promise);
                }
            });
        }
        return promise;
    }

    @Override
    protected void doConnect(SocketAddress remoteAddress, SocketAddress localAddress, final ChannelPromise promise) {
        if (localAddress != null) {
            try {
                javaChannel().bind(localAddress);
            } catch (IOException e) {
                promise.setFailure(e);
                return;
            }
        }

        javaChannel().connect(remoteAddress, this, CONNECT_HANDLER);
    }

    @Override
    protected InetSocketAddress localAddress0() {
        if (ch == null) {
            return null;
        }
        try {
            return (InetSocketAddress) javaChannel().getLocalAddress();
        } catch (IOException e) {
            return null;
        }
    }

    @Override
    protected InetSocketAddress remoteAddress0() {
        if (ch == null) {
            return null;
        }
        try {
            return (InetSocketAddress) javaChannel().getRemoteAddress();
        } catch (IOException e) {
            return null;
        }
    }

    @Override
    protected Runnable doRegister() throws Exception {
        super.doRegister();
        if (ch == null) {
            ch = newSocket(((AioEventLoopGroup) eventLoop().parent()).channelGroup());
            config.assign(javaChannel());
        }

        return null;
    }

    @Override
    protected void doBind(SocketAddress localAddress) throws Exception {
        javaChannel().bind(localAddress);
    }

    @Override
    protected void doDisconnect() throws Exception {
        doClose();
    }

    @Override
    protected void doClose() throws Exception {
        javaChannel().close();
        inputShutdown = true;
        outputShutdown = true;
    }

    @Override
    protected boolean isFlushPending() {
        return false;
    }

    @Override
    protected void doFlushByteBuffer(ByteBuf buf) throws Exception {
        if (inDoFlushByteBuffer || writeInProgress) {
            return;
        }

        inDoFlushByteBuffer = true;

        try {
            if (buf.isReadable()) {
                for (;;) {
                    if (buf.refCnt() == 0) {
                        break;
                    }
                    // Ensure the readerIndex of the buffer is 0 before beginning an async write.
                    // Otherwise, JDK can write into a wrong region of the buffer when a handler calls
                    // discardReadBytes() later, modifying the readerIndex and the writerIndex unexpectedly.
                    buf.discardReadBytes();

                    writeInProgress = true;
                    if (buf.nioBufferCount() == 1) {
                        javaChannel().write(
                                buf.nioBuffer(), config.getWriteTimeout(), TimeUnit.MILLISECONDS, this, WRITE_HANDLER);
                    } else {
                        ByteBuffer[] buffers = buf.nioBuffers(buf.readerIndex(), buf.readableBytes());
                        if (buffers.length == 1) {
                            javaChannel().write(
                                    buffers[0], config.getWriteTimeout(), TimeUnit.MILLISECONDS, this, WRITE_HANDLER);
                        } else {
                            javaChannel().write(
                                    buffers, 0, buffers.length, config.getWriteTimeout(), TimeUnit.MILLISECONDS,
                                    this, GATHERING_WRITE_HANDLER);
                        }
                    }

                    if (writeInProgress) {
                        // JDK decided to write data (or notify handler) later.
                        buf.suspendIntermediaryDeallocations();
                        break;
                    }

                    // JDK performed the write operation immediately and notified the handler.
                    // We know this because we set asyncWriteInProgress to false in the handler.
                    if (!buf.isReadable()) {
                        // There's nothing left in the buffer. No need to retry writing.
                        break;
                    }

                    // There's more to write. Continue the loop.
                }
            } else {
                flushFutureNotifier.notifyFlushFutures();
            }
        } finally {
            inDoFlushByteBuffer = false;
        }
    }

    @Override
    protected void doFlushFileRegion(FileRegion region, ChannelPromise promise) throws Exception {
        region.transferTo(new WritableByteChannelAdapter(region, promise), 0);
    }

    @Override
    protected void doBeginRead() {
        if (inDoBeginRead) {
            readAgain = true;
            return;
        }

        if (readInProgress || inputShutdown) {
            return;
        }

        inDoBeginRead = true;
        try {
            for (;;) {
                if (inputShutdown) {
                    break;
                }

                ByteBuf byteBuf = pipeline().inboundByteBuffer();
                expandReadBuffer(byteBuf);

                readInProgress = true;
                if (byteBuf.nioBufferCount() == 1) {
                    // Get a ByteBuffer view on the ByteBuf
                    ByteBuffer buffer = byteBuf.nioBuffer(byteBuf.writerIndex(), byteBuf.writableBytes());
                    javaChannel().read(
                            buffer, config.getReadTimeout(), TimeUnit.MILLISECONDS, this, READ_HANDLER);
                } else {
                    ByteBuffer[] buffers = byteBuf.nioBuffers(byteBuf.writerIndex(), byteBuf.writableBytes());
                    if (buffers.length == 1) {
                        javaChannel().read(
                                buffers[0], config.getReadTimeout(), TimeUnit.MILLISECONDS, this, READ_HANDLER);
                    } else {
                        javaChannel().read(
                                buffers, 0, buffers.length, config.getReadTimeout(), TimeUnit.MILLISECONDS,
                                this, SCATTERING_READ_HANDLER);
                    }
                }

                if (readInProgress) {
                    // JDK decided to read data (or notify handler) later.
                    break;
                }

                if (readAgain) {
                    // User requested the read operation.
                    readAgain = false;
                    continue;
                }

                break;
            }
        } finally {
            inDoBeginRead = false;
        }
    }

    private static final class WriteHandler extends AioCompletionHandler {

        @Override
        protected void completed0(T result, AioSocketChannel channel) {
            channel.writeInProgress = false;

            ByteBuf buf = channel.unsafe().directOutboundContext().outboundByteBuffer();
            if (buf.refCnt() == 0) {
                return;
            }

            buf.resumeIntermediaryDeallocations();

            int writtenBytes = result.intValue();
            if (writtenBytes > 0) {
                // Update the readerIndex with the amount of read bytes
                buf.readerIndex(buf.readerIndex() + writtenBytes);
            }

            if (channel.inDoFlushByteBuffer) {
                // JDK performed the write operation immediately and notified this handler immediately.
                // doFlushByteBuffer() will do subsequent write operations if necessary for us.
                return;
            }

            // Update the write counter and notify flush futures only when the handler is called outside of
            // unsafe().flushNow() because flushNow() will do that for us.
            ChannelFlushPromiseNotifier notifier = channel.flushFutureNotifier;
            notifier.increaseWriteCounter(writtenBytes);
            notifier.notifyFlushFutures();

            // Stop flushing if disconnected.
            if (!channel.isActive()) {
                return;
            }

            if (buf.isReadable()) {
                channel.unsafe().flushNow();
            }
        }

        @Override
        protected void failed0(Throwable cause, AioSocketChannel channel) {
            channel.writeInProgress = false;
            channel.flushFutureNotifier.notifyFlushFutures(cause);

            // Check if the exception was raised because of an InterruptedByTimeoutException which means that the
            // write timeout was hit. In that case we should close the channel as it may be unusable anyway.
            //
            // See http://openjdk.java.net/projects/nio/javadoc/java/nio/channels/AsynchronousSocketChannel.html
            if (cause instanceof InterruptedByTimeoutException) {
                channel.unsafe().close(channel.unsafe().voidFuture());
            }
        }
    }

    private static final class ReadHandler extends AioCompletionHandler {

        @Override
        protected void completed0(T result, AioSocketChannel channel) {
            channel.readInProgress = false;

            if (channel.inputShutdown) {
                // Channel has been closed during read. Because the inbound buffer has been deallocated already,
                // there's no way to let a user handler access it unfortunately.
                return;
            }

            final ChannelPipeline pipeline = channel.pipeline();
            final ByteBuf byteBuf = pipeline.inboundByteBuffer();

            boolean closed = false;
            boolean read = false;
            boolean firedChannelReadSuspended = false;
            try {
                int localReadAmount = result.intValue();
                if (localReadAmount > 0) {
                    // Set the writerIndex of the buffer correctly to the
                    // current writerIndex + read amount of bytes.
                    //
                    // This is needed as the ByteBuffer and the ByteBuf does not share
                    // each others index
                    byteBuf.writerIndex(byteBuf.writerIndex() + localReadAmount);

                    read = true;
                } else if (localReadAmount < 0) {
                    closed = true;
                }
            } catch (Throwable t) {
                if (read) {
                    read = false;
                    pipeline.fireInboundBufferUpdated();
                }

                if (!closed && channel.isOpen()) {
                    firedChannelReadSuspended = true;
                    pipeline.fireChannelReadSuspended();
                }

                pipeline.fireExceptionCaught(t);
            } finally {
                if (read) {
                    pipeline.fireInboundBufferUpdated();
                }

                // Double check because fireInboundBufferUpdated() might have triggered the closure by a user handler.
                if (closed || !channel.isOpen()) {
                    channel.inputShutdown = true;
                    if (channel.isOpen()) {
                        if (channel.config().isAllowHalfClosure()) {
                            pipeline.fireUserEventTriggered(ChannelInputShutdownEvent.INSTANCE);
                        } else {
                            channel.unsafe().close(channel.unsafe().voidFuture());
                        }
                    }
                } else if (!firedChannelReadSuspended) {
                    pipeline.fireChannelReadSuspended();
                }
            }
        }

        @Override
        protected void failed0(Throwable t, AioSocketChannel channel) {
            channel.readInProgress = false;
            if (t instanceof ClosedChannelException) {
                channel.inputShutdown = true;
                return;
            }

            channel.pipeline().fireExceptionCaught(t);

            // Check if the exception was raised because of an InterruptedByTimeoutException which means that the
            // write timeout was hit. In that case we should close the channel as it may be unusable anyway.
            //
            // See http://openjdk.java.net/projects/nio/javadoc/java/nio/channels/AsynchronousSocketChannel.html
            if (t instanceof IOException || t instanceof InterruptedByTimeoutException) {
                channel.unsafe().close(channel.unsafe().voidFuture());
            }
        }
    }

    private static final class ConnectHandler extends AioCompletionHandler {

        @Override
        protected void completed0(Void result, AioSocketChannel channel) {
            ((DefaultAioUnsafe) channel.unsafe()).connectSuccess();
            channel.pipeline().fireChannelActive();
        }

        @Override
        protected void failed0(Throwable exc, AioSocketChannel channel) {
            ((DefaultAioUnsafe) channel.unsafe()).connectFailed(exc);
        }
    }

    @Override
    public AioSocketChannelConfig config() {
        return config;
    }

    private final class WritableByteChannelAdapter implements WritableByteChannel {
        private final FileRegion region;
        private final ChannelPromise promise;
        private long written;

        public WritableByteChannelAdapter(FileRegion region, ChannelPromise promise) {
            this.region = region;
            this.promise = promise;
        }

        @Override
        public int write(final ByteBuffer src) {
            javaChannel().write(src, AioSocketChannel.this, new AioCompletionHandler() {

                @Override
                public void completed0(Integer result, Channel attachment) {
                    try {
                        if (result == 0) {
                            javaChannel().write(src, AioSocketChannel.this, this);
                            return;
                        }
                        if (result == -1) {
                            checkEOF(region, written);
                            promise.setSuccess();
                            return;
                        }
                        written += result;

                        if (written >= region.count()) {
                            region.release();
                            promise.setSuccess();
                            return;
                        }
                        if (src.hasRemaining()) {
                            javaChannel().write(src, AioSocketChannel.this, this);
                        } else {
                            region.transferTo(WritableByteChannelAdapter.this, written);
                        }
                    } catch (Throwable cause) {
                        region.release();
                        promise.setFailure(cause);
                    }
                }

                @Override
                public void failed0(Throwable exc, Channel attachment) {
                    region.release();
                    promise.setFailure(exc);
                }
            });
            return 0;
        }

        @Override
        public boolean isOpen() {
            return javaChannel().isOpen();
        }

        @Override
        public void close() throws IOException {
            javaChannel().close();
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy