将NIO与IO混合

时间:2010-03-31 22:49:19

标签: java io nio blocking

通常你有一个绑定的tcp端口和几个连接。至少通常有更多连接作为绑定端口。我的情况不同:我想绑定很多端口,通常没有(或至少很少)连接。

所以我想使用NIO来接受传入的连接。

但是,我需要将接受的连接传递给现有的jsch ssh库。这需要IO套接字而不是NIO套接字,它会为每个连接产生一个(或两个)线程。但那对我没问题。

现在,我认为以下几行会产生相同的结果:

Socket a = serverSocketChannel.accept().socket();
Socket b = serverSocketChannel.socket().accept();
SocketChannel channel = serverSocketChannel.accept();
channel.configureBlocking( true );
Socket c = channel.socket();
Socket d = serverSocket.accept();

但是,返回的套接字的getInputStream()getOutputStream()函数似乎有所不同。只有在使用最后一次调用接受套接字时,jsch才能使用它。在前三个案例中,它失败了(我很抱歉:我不知道为什么)。

有没有办法转换这样的套接字?

3 个答案:

答案 0 :(得分:3)

从SocketChannels获得的套接字返回的输入和输出流在某些点上在通道内部同步。因此,您不能将它们用于SSH等全双工协议,因为系统会锁定。同样适用于通过Channels类从频道转换的流(这是第一种情况相同)。

答案 1 :(得分:0)

我必须这样做,因为我们使用JSch并且有1000个线程在端口上侦听。我会留给你处理接受连接,但这适用于JSch从一个启动的线程来处理连接:

/**
 * Handler that allows for input and output streams of an NIO socket to be
 * read as blocking I/O
 * <p>
 * Reading and writing from the streams <b>must</b> be done on the same thread
 * @author Eric
 *
 */
public class NioBlockingSocketStreamer
{

    private static long NIO_SELECT_TIMEOUT = 0;
    private static long NIO_WRITE_TIMEOUT_SECONDS = 30;
    /** poison pill result that signals a disconnected channel */
    private static int NIO_READ_IOEXCEPTION = -2;

    private final SocketChannel socketChannel;

    private Selector selector;

    private final InputStream inputStream = new MyInputStream(this);
    private final OutputStream outputStream = new MyOutputStream(this);

    private final LinkedBlockingQueue<ByteBuffer> pendingWrites;
    private final MyReadQueue<ReadRequest> pendingRead;

    private volatile boolean disconnected = false;

    public NioBlockingSocketStreamer(SocketChannel socketChannel,
                                     int writeQueueSize)
    {
        pendingRead = new MyReadQueue<ReadRequest>( );
        pendingWrites = new LinkedBlockingQueue<ByteBuffer>(writeQueueSize);
        this.socketChannel = socketChannel;
    }

    public NioBlockingSocketStreamer start( )
            throws IOException
    {
        this.selector = Selector.open( );
        this.socketChannel.configureBlocking(false);

        final NioBlockingSocketStreamer streamer = this;
        Thread t = new Thread(new Runnable( )
        {
            @Override
            public void run( )
            {
                try
                {
                    streamer.nioServiceLoop( );
                }
                catch (ClosedChannelException e)
                {
                    //shutting down
                }
                catch (ClosedSelectorException e)
                {
                    //shutting down
                }
                catch (IOException e)
                {
                    e.printStackTrace( );
                }
            }
        });
        t.start( );
        return this;
    }

    public void nioServiceLoop( )
            throws IOException
    {
        ByteBuffer writeBuffer = null;
        ReadRequest readRequest = null;

        try
        {
            for (;;)
            {
                if (readRequest == null)
                    readRequest = pendingRead.poll( );
                int interestOps = readRequest != null
                        ? SelectionKey.OP_READ
                        : 0;
                if (writeBuffer == null && pendingWrites.isEmpty( ))
                    interestOps &= ~SelectionKey.OP_WRITE;
                else
                    interestOps |= SelectionKey.OP_WRITE;
                socketChannel.register(selector, interestOps);

                int n = selector.select(NIO_SELECT_TIMEOUT);
                if (n > 0)
                {
                    Set<SelectionKey> selectedKeys = selector.selectedKeys( );
                    for (SelectionKey selectedKey : selectedKeys)
                    {
                        if (selectedKey.isValid( ))
                        {
                            if (selectedKey.isReadable( ))
                            {
                                int cbRead;
                                try
                                {
                                    cbRead = socketChannel.read(readRequest.buffer);
                                }
                                catch (IOException e)
                                {
                                    //"java.io.IOException: An existing connection was forcibly closed by the remote host"
                                    //is the typical disconnection
                                    return;
                                }
                                interestOps &= ~SelectionKey.OP_READ;
                                readRequest.submitResult(cbRead);
                                readRequest = null;
                            }

                            if (selectedKey.isWritable( ))
                            {
                                if (writeBuffer == null)
                                    writeBuffer = pendingWrites.poll( );

                                if (writeBuffer != null)
                                {
                                    try
                                    {
                                        socketChannel.write(writeBuffer);
                                    }
                                    catch (IOException e)
                                    {
                                        //"java.io.IOException: An existing connection was forcibly closed by the remote host"
                                        //is the typical disconnection
                                        return;
                                    }
                                    if (!writeBuffer.hasRemaining( ))
                                        writeBuffer = null;
                                }
                            }
                        }
                    }
                    selectedKeys.clear( );
                }
            }
        }
        finally
        {
            disconnected = true;

            //submit poison pill result for outstanding read request
            if (readRequest != null)
            {
                readRequest.submitResult(NIO_READ_IOEXCEPTION);
            }

            //since the read request queue became empty before we disconnected, check for the next
            //read request that may have queued in the meantime
            readRequest = pendingRead.poll( );
            if (readRequest != null)
            {
                readRequest.submitResult(NIO_READ_IOEXCEPTION);
            }
        }
    }

    private ReadRequest readRequest = new ReadRequest( );

    public synchronized int read(ByteBuffer buffer)
            throws IOException
    {
        if (disconnected)
            throw new IOException("disconnected");

        readRequest.buffer = buffer;
        int result;
        try
        {
            pendingRead.offer(readRequest);
            selector.wakeup( );
            result = readRequest.getResult( );
        }
        catch (InterruptedException e)
        {
            throw new IOException(e);
        }

        if (result == NIO_READ_IOEXCEPTION)
            throw new IOException("disconnected");

        return result;
    }

    public void write(ByteBuffer buffer)
            throws IOException
    {
        if (disconnected)
            throw new IOException("disconnected");

        boolean queued = false;
        if (pendingWrites.remainingCapacity( ) > 0)
        {
            try
            {
                queued = pendingWrites.add(buffer);
            }
            catch (IllegalStateException e)
            {
                throw new IOException("write queue capacity exceeded");
            }
        }
        else
        {
            try
            {
                queued = pendingWrites.offer(buffer, NIO_WRITE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
            }
            catch (InterruptedException e)
            {
                throw new IOException(e);
            }
        }

        if (!queued)
            throw new IOException("unable to queue write");

        if (selector != null) //selector should never be null
        {
            selector.wakeup( );
        }
    }

    public InputStream getInputStream( )
    {
        return inputStream;
    }

    public OutputStream getOutputStream( )
    {
        return outputStream;
    }

    /**
     * Implements a simple blocking mechanism for getting the results of a read
     * <p>
     * <b>Note</b> This class must avoid autoboxing due to Retroweaver
     */
    static class ReadRequest
    {
        ByteBuffer buffer;
        LinkedBlockingQueue<Integer> result = new LinkedBlockingQueue<Integer>(1);

        void submitResult(int cb)
        {
            result.add(new Integer(cb));
        }

        int getResult( )
                throws InterruptedException
        {
            return (int) result.take( );
        }
    }

    static class MyInputStream
        extends InputStream
    {

        private final NioBlockingSocketStreamer streamer;

        MyInputStream(NioBlockingSocketStreamer streamer)
        {
            this.streamer = streamer;
        }

        @Override
        public int read(byte[] b,
                        int off,
                        int len)
                throws IOException
        {
            ByteBuffer buffer = ByteBuffer.allocate(len);
            int n = streamer.read(buffer);
            if (n > 0)
                System.arraycopy(buffer.array( ), 0, b, off, n);
            return n;
        }

        @Override
        public int read(byte[] b)
                throws IOException
        {
            return read(b, 0, b.length);
        }

        @Override
        public int read( )
                throws IOException
        {
            return read(new byte[1], 0, 1);
        }

        @Override
        public void close( )
                throws IOException
        {
            // TODO Auto-generated method stub
        }

    }

    static class MyOutputStream
        extends OutputStream
    {

        private final NioBlockingSocketStreamer streamer;

        MyOutputStream(NioBlockingSocketStreamer streamer)
        {
            this.streamer = streamer;
        }

        @Override
        public void write(byte[] b,
                          int off,
                          int len)
                throws IOException
        {
            ByteBuffer buffer = ByteBuffer.allocate(len);
            buffer.put(b, off, len);
            buffer.flip( );
            streamer.write(buffer);
        }

        @Override
        public void write(byte[] b)
                throws IOException
        {
            write(b, 0, b.length);
        }

        @Override
        public void write(int b)
                throws IOException
        {
            write(new byte[1], 0, 1);
        }

        @Override
        public void close( )
                throws IOException
        {
            // TODO Auto-generated method stub
        }

        @Override
        public void flush( )
                throws IOException
        {
            // TODO Auto-generated method stub
        }
    }

    static class MyReadQueue<T>
    {
        //FIXME using LinkedBlockingQueue is overkill
        private final LinkedBlockingQueue<T> queue = new LinkedBlockingQueue<T>(1);

        public T poll( )
        {
            return queue.poll( );
        }

        public void offer(T request)
                throws InterruptedException
        {
            queue.offer(request, Long.MAX_VALUE, TimeUnit.NANOSECONDS);
        }
    }

}

答案 2 :(得分:-1)

此序列适用于我们的生产环境:

final SocketAddress serverAddr =
    new InetSocketAddress(
        bind_address,
        server_port
    );

final ServerSocketChannel serverChannel = ServerSocketChannel.open( );

serverChannel.socket( ).bind(
    serverAddr,
    backlog
);

final SocketChannel socketChannel = serverChannel.accept( );

final Socket socket = socketChannel.socket( );

final OutputStream out = socket.getOutputStream( );

final InputStream in = socket.getInputStream( );

默认情况下,所有频道都将处于blocking模式。

请注意,我没有显示任何清理/异常处理代码。

此外,还有一些bugs related to NIO and socket streams