Java NIO原理分析

©   老男孩    /    2017-07-13

这里主要围绕着Java NIO展开,从Java NIO的基本使用,到介绍LinuxNIO API,再到Java Selector其底层的实现原理。

 

    Java NIO基本使用

    Linux下的NIO系统调用介绍

    Selector原理

    ChannelBuffer之间的堆外内存

 

Java NIO基本使用

JDK NIO文档里面可以发现,Java将其划分成了三大块:ChannelBuffer以及多路复用SelectorChannel的存在,封装了对什么实体的连接通道(如网络/文件);Buffer封装了对数据的缓冲存储,最后对于Selector则是提供了一种可以以单线程非阻塞的方式,来处理多个连接。

基本应用示例

 

NIO的基本步骤是,创建SelectorServerSocketChannel,然后注册channelACCEPT事件,调用select方法,等待连接的到来,以及接收连接后将其注册到Selector中。下面的为Echo Server的示例:

 

public class SelectorDemo {

 public static void main(String[] args) throws IOException {

 

 

        Selector selector = Selector.open();

        ServerSocketChannel socketChannel = ServerSocketChannel.open();

        socketChannel.bind(new InetSocketAddress(8080));

        socketChannel.configureBlocking(false);

        socketChannel.register(selector, SelectionKey.OP_ACCEPT);

 

        while (true) {

            int ready = selector.select();

            if (ready == 0) {

                continue;

            } else if (ready < 0) {

                break;

            }

 

            Set keys = selector.selectedKeys();

            Iterator iterator = keys.iterator();

            while (iterator.hasNext()) {

 

                SelectionKey key = iterator.next();

                if (key.isAcceptable()) {

 

                    ServerSocketChannel channel = (ServerSocketChannel) key.channel();

                    SocketChannel accept = channel.accept();

                    if (accept == null) {

                        continue;

                    }

                    accept.configureBlocking(false);

                    accept.register(selector, SelectionKey.OP_READ);

                } else if (key.isReadable()) {

                    // 读事件

                    deal((SocketChannel) key.channel(), key);

                } else if (key.isWritable()) {

                    // 写事件

                    resp((SocketChannel) key.channel(), key);

                }

                // 注:处理完成后要从中移除掉

                iterator.remove();

            }

        }

        selector.close();

        socketChannel.close();

    }

private static void deal(SocketChannel channel, SelectionKey key) throws IOException {

 

        ByteBuffer buffer = ByteBuffer.allocate(1024);

        ByteBuffer responseBuffer = ByteBuffer.allocate(1024);

 

        int read = channel.read(buffer);

 

        if (read > 0) {

            buffer.flip();

            responseBuffer.put(buffer);

        } else if (read == -1) {

            System.out.println("socket close");

            channel.close();

            return;

        }

 

        key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);

        key.attach(responseBuffer);

    }

 private static void resp(SocketChannel channel, SelectionKey key) throws IOException {

 

        ByteBuffer buffer = (ByteBuffer) key.attachment();

        buffer.flip();

 

        channel.write(buffer);

        if (!buffer.hasRemaining()) {

            key.attach(null);

            key.interestOps(SelectionKey.OP_READ);

        }

    }

}

Linux下的NIO系统调用介绍

 

Linux环境下,提供了几种方式可以实现NIO,如epollpollselect等。对于select/poll,每次调用,都是从外部传入FD和监听事件,这就导致每次调用的时候,都需要将这些数据从用户态复制到内核态,就导致了每次调用代价比较大,而且每次从select/poll返回回来,都是全量的数据,需要自行去遍历检查哪些是READY的。对于epoll,则为增量式的,系统内部维护了所需要的FD和监听事件,要注册的时候,调用epoll_ctl即可,而每次调用,不再需要传入了,返回的时候,只返回READY的监听事件和FD。下面作个简单的伪代码:

// 1. 创建server socket

// 2. 绑定地址

// 3. 监听端口

// 4. 创建epoll

int epollFd = epoll_create(1024);

// 5. 注册监听事件

struct epoll_event event;

event.events = EPOLLIN | EPOLLRDHUP | EPOLLET;

event.data.fd = serverFd;

epoll_ctl(epollFd, EPOLL_CTL_ADD, serverFd, &event);

while(true) {

    readyNums = epoll_wait( epollFd, events, 1024, -1 );

    

    if ( readyNums < 0 )

     {

         printf("epoll_wait error\n");

         exit(-1);

     }

 

     for ( i = 0; i <  readyNums; ++i)

     {

         if ( events[i].data.fd == serverFd )

         {

             clientFd = accept( serverFd, NULL, NULL );

             // 注册监听事件

             ...

         }else if ( events[i].events & EPOLLIN )

         {

            // 处理读事件

         }else if ( events[i].events & EPOLLRDHUP )


(0)

分享至