系统学习NIO

一、什么是NIO?

NIO是非阻塞IO,也就是说它是一种新的IO,在读写的时候不会造成堵塞。

二、那么什么是阻塞?什么是异步同步?

阻塞:对于CPU来说就是把当前线程挂起,其他程序等待。
非阻塞:就是无需挂起当前线程,可以直接执行其他程序。
同步:一个任务在完成之前不会有另一个任务执行。
异步:任务与任务之间不会影响,即一个任务执行可以与另一个任务同时执行。

三、那么它与传统的IO相比有什么区别呢?

1.IO是阻塞的,NIO是非阻塞的。
2.IO是面向流的,只能单向读写。NIO是面向缓冲的,可以双向读写。
在使用IO做Socket连接时,由于单向读写,在没有数据的时候,会挂起当前线程,进行阻塞等待。那么为了防止对其他连接有影响,只能给每个连接创建新的线程。因为资源是有限的,所以不能创建过多的线程,不然会带来较大的性能耗损。因此需要使用NIO进行IO的多路复用,用一个线程来监听所有的Socket连接,再使用本线程或则其他的线程处理连接。

四、UNIX的5种IO模型

5种IO模型均可分为两个阶段, 一个是等待数据到达内核缓冲区, 一个是将数据从内核空间拷贝到用户空间, 其中前4种为同步IO, 最后1种为异步IO, 前4种的第二阶段完全相同

1、I/O阻塞

阻塞等待数据到达内核空间, 最后将数据拷贝到用户空间

2、I/O非阻塞

轮训检查数据是否到达内核空间, 数据就绪后将数据拷贝到用户空间

3、I/O多路复用

1.IO多路复用, 主要思想是一个线程阻塞监听所有IO事件, 当事件到达时, 分发给对应处理线程, 将数据拷贝到用户空间进行处理
2.IO多路复用又分为3种实现方式 – select/poll/epoll,
3.select方式会将需要监听的文件描述符从用户空间拷贝到内核空间, 内核会通过轮询该文件描述符数组,来判断对应事件是否就绪, 当事件就绪时, 就会将会整个文件描述符数组拷贝到用户空间. 所以select存在几个缺点: 1) 由于采用了数组保存文件描述符, 所以一般最大限制数量为1024 2)两次文件描述符的拷贝 3) 用户空间和内核空间上下文切换
4.poll方式与select方式类似, 只不过没有了最大文件描述符数量的限制, 因为采用了链表保存.
5.epoll方式则不同, 采用了红黑树保存需要监听的文件描述符, 并为该文件描述符注册回调函数, 当事件到达时, 会调用回调函数, 将红黑树中对应的文件描述符, 保存到一个双向链表中. 最后通过判断该双向链表是否为空即可.

4、信号驱动I/O

在文件描述符就绪时发送SIGIO信号通知

5、异步I/O

两个阶段都不用等待, 由内核完成, 数据到达内核缓冲区之后, 内核将数据拷贝到用户空间, 完成之后再通知用户程序.
在这里插入图片描述

五丶Buffer的介绍

buffer是缓冲区, 本质与其他缓冲(如BufferInputStream)一样, 里面都是字节数组(byte[]), 它在于控制一次性从数据源(磁盘等)读取多个字节,减少与数据源的交互次数,从而提高性能。

1、buffer有几个重要属性:

capacity: 缓冲区数组的长度
position: 下一个要操作元素的位置
limit: 下一个不可操作元素的位置
mark: 用于记录当前position的前一个位置或者默认是0

2、ByteBuffer几个重要方法

// 创建缓冲区
ByteBuffer allocate(int capacity);  //在堆中创建缓冲区
ByteBuffer allocateDirect(int capacity); //在堆外内存中创建缓冲区

// 存数据进buffer, position位置会随着数据的增加而变化
ByteBuffer put(byte b);
ByteBuffer put(byte[] src);

// 读取buffer中的缓冲数据
byte get();  //读取position对应的位置的数据, 并"移动"position (+1)

// 将position设置为0, 一般用于读操作之后的写操作
Buffer flip();

// 判断是否还有元素可操作,(position<limit)
boolean hasRemaining();

// 清空数据
Buffer clear();

// 使用mark标记position的位置
Buffer mark();

// 重置position, 将position变为之前mark的操作
Buffer reset();

3、注意事项

在调用#put(byte[])方法将数据存进buffer中后, 需要调用#flip()方法, 将position指针移到开头, 然后才能将数据写进其他地方, 因为position表示下一个要操作的元素的位置。

六、Channel的介绍

channel是一个通道,与io中的stream类似, 都是用于读取输出数据, 不同之处在于, channel是双向的, stream是单向的, channel是不能直接操作数据,需要将数据读取到缓冲区buffer之中,然后缓冲区中读取数据,或者将数据写入缓冲区buffer中,然后在将buffer中的数据写入通道中

七丶Selector的介绍

选择器,用于注册各种channel感兴趣的事件, 共有4种感兴趣的事件: OP_READ, OP_WRITE, OP_CONNECT, OP_ACCEPT

ServerSocketChannel ssc=ServerSocketChannel.open();
ssc.configureBlocking(false); // 设置非阻塞io
//绑定端口 backlog设为1024
ssc.socket().bind(new InetSocketAddress(8040),1024000000);
ssc.register(selector, SelectionKey.OP_ACCEPT);
SocketChannel sc=ssc.accept();
sc.configureBlocking(false);
sc.register(selector, SelectionKey.OP_READ);
selector.select(); 是一个阻塞方法, 当没有感兴趣的事件时,会阻塞等待。
selector.selectedKeys(); 返回感兴趣的所有事

SelectionKey#isAcceptable(), #isReadable(), #isWritable(), #isConnectable() 判断发生的是哪种事件。

八丶完整示例代码

public class NioServerTests {

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

        // channel , selector
        ByteBuffer buffer=ByteBuffer.allocate(5);
        Selector selector=Selector.open();


		// Path path=FileSystems.getDefault().getPath("test.txt");
		// Channel channel=FileChannel.open(path, StandardOpenOption.WRITE);

        ServerSocketChannel ssc=ServerSocketChannel.open();
        ssc.configureBlocking(false); // 设置非阻塞io
        //绑定端口 backlog设为1024
        ssc.socket().bind(new InetSocketAddress(8040),1024000000);
        ssc.register(selector, SelectionKey.OP_ACCEPT);


        while(true){
            System.out.println("阻塞等待...");
            selector.select(); //阻塞等待


            Set<SelectionKey> selectedKeys=selector.selectedKeys();

            Iterator<SelectionKey> iter=selectedKeys.iterator();

            StringBuilder sb=null;
            while (iter.hasNext()){
                SelectionKey selectionKey=iter.next();
                iter.remove(); //移除被选择的通道, 防止重复处理
                if(selectionKey.isAcceptable()){
                    ssc=(ServerSocketChannel) selectionKey.channel();

                    // 需要注册channel
                    SocketChannel sc=ssc.accept();
                    sc.configureBlocking(false);
                    sc.register(selector, SelectionKey.OP_READ);

                    System.out.println("服务端接受连接...");
                }else if(selectionKey.isReadable()){
                    SocketChannel sc=(SocketChannel) selectionKey.channel();

                    int len=-1;

                    ByteArrayOutputStream baos=new ByteArrayOutputStream();

                    while((len=sc.read(buffer))>0){
                        buffer.flip(); // 反转postion, position表示操作的下一个位置
                        
                        byte b=-1;
                        while(buffer.hasRemaining()){
                            b=buffer.get();
                            baos.write(b);
                        }

                        buffer.clear();
                    }
                    if(baos.size()>  0){
                        System.out.println("服务端接收消息为: "+ baos.toString());
                    }
                    if(len==-1){ // -1, 表示客户端主动关闭连接关闭,
                        System.out.println("关闭连接...");
                        sc.close(); // 服务端也需要关闭连接, 否则该链接仍然会可读
                    }
                }
            }
        }
    }
}
public class NioClientTests {
    public static void main(String[] args) throws IOException, InterruptedException {

        SocketChannel sc=SocketChannel.open(new InetSocketAddress(8040));

        sc.configureBlocking(false); // 设置成非阻塞io
        ByteBuffer buffer=ByteBuffer.allocate(102400000);



        int i=0;
        while(true){
            System.out.println("发送消息...");
            buffer.clear(); //清空消息
            buffer.put("timfruit, 您好...".getBytes());

            buffer.flip(); // 将position反转至开始的位置, 用于写
            sc.write(buffer);

            i++;
            if(i>1){
                break;
            }
            Thread.sleep(1000);
        }
        sc.socket().close();
		// sc.close();

    }
}

源码地址:点击查看

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

欧阳锋feng

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值