NIO入门

NIO:客户端发送的连接请求都会注册到多路复用器selector上,多路复用 器轮询到连接有IO请求就进行处理

一、nio非阻塞代码

 public static void main(String[] args) throws IOException {
        //1、打开ServerSocketChannel
        ServerSocketChannel ssc = ServerSocketChannel.open();

        ssc.configureBlocking(false);
        //2、绑定监听地址InetSocketAddress
        ssc.socket().bind(new InetSocketAddress(9000));
        //3、创建Selector,并启动线程
        Selector selector = Selector.open();
        //4、把ServerSocketChannel注册到Selector,监听
        ssc.register(selector, SelectionKey.OP_ACCEPT);

        while (true) {
            int select = selector.select();
            Iterator<SelectionKey> it = selector.selectedKeys().iterator();
            //5 Selector轮询指定的key
            while (it.hasNext()) {
                SelectionKey key = it.next();
                it.remove();
                handle(key);
            }
        }
    }

    private static void handle(SelectionKey key) throws IOException {
        if (key.isAcceptable()) {
            System.out.println("有客户端连接事件发生了。。");
            ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
            //6、处理新的客户端接入
            SocketChannel sc = ssc.accept();
            //7、设置新的socket参数
            sc.configureBlocking(false);
            //8、将新接入的客户端连接注册到Reactor线程的多路复用器上,读取客户端发送的网络消息
            sc.register(key.selector(), SelectionKey.OP_READ);
        } else if (key.isReadable()) {
            System.out.println("有客户端数据可读事件发生了。。");
            SocketChannel sc = (SocketChannel) key.channel();
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            //9、异步读取客户端请求消息到缓冲区
            int len = sc.read(buffer);
            if (len != -1) {
                System.out.println("读取到客户端发送的数据:" + new String(buffer.array(), 0, len));
            }
            ByteBuffer bufferToWrite = ByteBuffer.wrap("HelloClient".getBytes());
            //10 将消息异步发送给客户端
            sc.write(bufferToWrite);
            key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
        } else if (key.isWritable()) {
            SocketChannel sc = (SocketChannel) key.channel();
            System.out.println("write事件");
            key.interestOps(SelectionKey.OP_READ);
        }
    }

打开cmd窗口,输入以下命令,实现客户端和服务端交互:

telnet  localhost 9000
ctrl + ]
send 123

二、nio服务端代码时序图

上面服务端代码启动时序图:

三、核心代码底层调用

1 Selector.open() //创建多路复用器                    (底层调用的epoll_create)
2 socketChannel.register() //将channel注册到多路复用器上    (底层调用的 epoll_ctl)
3 selector.select() //阻塞等待需要处理的事件发生              (底层调用的epoll_wait)

几个核心函数

int epoll_create(int size);

1、创建一个epoll实例,并返回一个非负数作为文件描述符,size代表表可能会容纳size个描述符

 int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

2、使用文件描述符epfd引用的epoll实例,对目标文件描述符fd执行op操作。

epfd: epoll对应的文件描述符 fd: socket对应的文件描述符 参数op有以下几个值: EPOLL_CTL_ADD:注册新的fd到epfd中,并关联事件event; EPOLL_CTL_MOD:修改已经注册的fd的监听事件; EPOLL_CTL_DEL:从epfd中移除fd,并且忽略掉绑定的event,这时event可以为null; events有很多可选值,这里只举例最常见的几个: EPOLLIN :表示对应的文件描述符是可读的; EPOLLOUT:表示对应的文件描述符是可写的; EPOLLERR:表示对应的文件描述符发生了错误;

1 int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

3、等待文件描述符epfd上的事件。

epfd: Epoll对应的文件描述符,

events: 调用者所有可用事件的集合,

maxevents: 最多等到多少个事件就返回, timeout: 超时时间。

总结:nio就是通过操作系统内核函数创建socket,获取文件描述符,再创建一个Selector ,获取Epoll文件描述符,将socket文件描述符绑定到Selector的文件描述符上,进行事件的异步通知;就实现了使用一条线程,并且不需要太多的无效的遍历 ,将事件处理交给操作系统内核,事件通知无需遍历,大大提高了效率。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值