NIO详解Channel、Buffer、Selector看这一篇就够了

NIO是同步阻塞队列——>IO复用模型很像,请仔细看这幅图
在这里插入图片描述
NIO和IO的到底有什么区别?有什么关系?
1、NIO是以块的方式处理数据,但是IO是以最基础的字节流形式去写入和读出的。所以在效率上的话,肯定是NIO效率比IO的效率高出很多。
2、NIO不在是和IO一样用OutputStream和InputStream输入流得到的形式来进行处理数据的,但是又是基于这种流的形式,而是采用了通道和缓冲区的形式来进行数据处理的。
3、还有一点就是NIO的通道是可以双向的,但是IO的流只能是单向的。
4、还有就是NIO的缓冲区(其实也就是有个字节数组)换可以进行分片,可以建立只读缓冲区、直接
缓冲区和间接缓冲区,只读缓冲区 就是字面意思,直接缓冲区是加快IO速度,而以一种特殊的方式分配其内存的缓冲区。
先了解一下什么是通道,什么是缓冲区的概念
**

通道:

1、通道是对原IO包中的流的模拟。到任何目的地(或来自任何地方)的所有数据必须通过有个Channel对象(通道)。一个Buffer实质上是一个容器的对象,发送给一个通道的所有对象都必须首先放到缓冲区中;同样地,从通道中读取的任何数据都要读到缓冲区中,channl适应对象,可以通过他的读取和写入数据,拿NIO与原来的IO做个比较,通道就像是流。
2、正如前面提到的,所有数据都通过BUffer对象来处理。你永远不会将字节直接写入通道中,相反,你是将数据写入包含一个或者多个字节的缓冲区,同样,你不会直接从通道中读取字节,而是将数通道读入缓冲区,再从缓冲区获取字节。

buffer

1、Buffer是一个对象,他包含一些要写入或者刚读取的数据。再NIO中加入Buffer对象,体现了新库与原IO的一个重要区别,在面向IO中,在将数据直接写入或者将数据直接读到Stream对象中
2、在NIO库中,所有数据都是用缓冲区处理,读取数据时,它是直接读取到缓冲区中的,在写入数据时,它是写入到缓冲区中的任何时候访问NIO中的数据,你都是将推塔放到缓冲区中。
3、缓冲区的实质是一个数组,通常它是一个节点的数组,但是也可以使用其他种类的数组。但是一个缓冲区不仅仅是一个数组缓冲区提供了对数据的结构化访问,而且换可以跟踪系统的读写进程

缓冲区的类型:
ByteBUffer
charBuffer
ShortBuffer
IntBuffer
LongBuffer
FloatBuffer
DoubleBuffer
标记、位置、限制和容量值遵守以下不变式:
0 <= 标记(mark) <= 位置(position) <= 限制(limit) <= 容量(capacity)

buffer实例的方法:
allocate(capacity);: 在堆上创建大小的对象
allocateDirect(capacity);在堆外空间上创建指定大小的对象
wrap(byte[])通过存在的数组创建对象
wrap(byte[],offerset,length)通过存在的数组创建对象

方法:
buffer.put() :往Buffer中写入数据 pos 位置移动
buffer.flip() :读写模式切换 -》lim指向pos,pos指向mark
buffer.get() :从Buffer中读取数据 ->pos 位置移动
buffer.clear(): 清空Buffer缓存 mark=-1,pos=0, lim=cap=capacity

第一步:
初始化的时候就是这样
在这里插入图片描述
第二步:
往里面写数据的时候position的位置变化如下图 ,position一直指向要写入元素的位置。
这时候,要将这些数字写入到通道中。这时就要用到buffer.flip(),进行读写切换

在这里插入图片描述

第三步:
当你调用完2中的方法时,这个时候就会变成下面的图了,这样的话其实就可以知道你刚刚写到buffer中的数据是在position—->limit之间,然后下一步调用clear();
在这里插入图片描述
第四步:
这时底层操作系统就可以从缓冲区中正确读取这 5 个字节数据发送出去了。在下一次写数据之前我们在调一下 clear() 方法。缓冲区的索引状态又回到初始位置。(其实这一步有点像IO中的把转运字节数组 char[] buf = new char[1024]; 不足1024字节的部分给强制刷新出去的意思)
**

selector(选择器):

**
可以同时管理同时来连接接,将关注的事件注册通过选择器来帮我们监听事件是否完成,在此期间用户可以做自己的事情

selector的使用步骤:
1、获取选择器实例
Selector selector = Selector.open();
2、将通道注册到选择器上,并确定关注事件channel.configureBloking(fals):通道设置为非阻塞
channel.register(selector SelectionKey.OP_READ);
3、选择器监听事件
selector.select();通过该方法监听事件是否完成

监听有三个方法:返回结果表示感兴趣的时间个数
int select() : 该方法会阻塞住,直至有感兴趣事件完成之后才会返回
int select (long timouts);该方法在指定的时间内返回,可能有时间返回,也可能没有事件返回
int selectNow();该方法不会阻塞。会立马返回,
4、遍历感兴趣的事件集合、判断那种事件完成
Iterator iterator = selector.selectedKeys().iterator();

5、关闭selector的资源
selector.close();

SelectionKey:
channel():将注册到选择器(selector)中的通道也会在SelectionKey维护中维护一个
selector():将当前的选择器实例也会在SelectionKey维护一个
boolen isValid(): //表示当前的SelectionKey是否是有效
cancel(); //取消对当前key的关注
boolen isConnectable();//可连接事件
boolen isWritable(); //可写事件
boolen isReadable(); //可读事件
boolen isAcceptable(); //可连接事件

SelectionKey下维护的事件:有4种
int OP_READ = 1 << 0; 读事件
int OP_WRITE = 1 << 2; 写事件
int OP_CONNECT = 1 << 3; 可连接事件
int OP_ACCEPT = 1 << 4; 可接受事件

编程步骤

服务端:
1、创建ServerChannel实例(open)
2、对实例绑定端口(bind)
3、将实例设置为非阻塞(configureBlocking)
4、实例化selector选择器(open)
5、将serverSocketChannel实例注册(register)到selector选择器,并关注可接受事件
6、选择器进行监听,有事件发生则返回
7、遍历感兴趣事件集合,判断是否有可接受事件
8、有可接受事件发生,获取对应通道,调用accept()方法获取SocketChannel实例
9、SocketChannel实例设置为非阻塞,将其注册到选择器,并关注读事件
10、循环第六步,遍历集合,判断是否有可读事件发生
11、通过Buffer从Channel读取数据
12、关闭打开的资源,包括selector选择器,关闭SocketChannel实例、ServerSocketChannel实例

客户端:
1、穿件SocketChannel实例
2、将SocketChannel实例设置为非阻塞
3、创建selector实例
4、连接服务端,立即返回结果不成功将channel注册到选择器中,并关注可连接事件
5、selector实例监听事件,有事件测发生返回
6、如该事件是可连接事件,则完成当前连接(finishConnect)
7、发送消息,接受消息
8、关闭资源

下面是代码:

客户端

public class NIOCilent {
    public static void main(String[] args) throws IOException {
//        创建实例SocketChannel.open();
        SocketChannel socketChannel = SocketChannel.open();

//        将SocketChannel实例设置为非阻塞
        socketChannel.configureBlocking(false);
//        创建selector实例
        Selector selector = Selector.open();

//        连接服务器
        if (!socketChannel.connect(new InetSocketAddress("127.0.0.1",5555))){
            System.out.println("连接为完成,注册到选择器当中");
            //        将Selector注册到选择器里面,并关注OP_CONNECT事件
            socketChannel.register(selector,SelectionKey.OP_CONNECT);
//            监听感兴趣的事件是否完成
            selector.select();

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

            while (iterator.hasNext()){
                System.out.println(">>>>"+selector.selectedKeys().size());
                SelectionKey key = iterator.next();
                iterator.remove();

                if (key.isValid()&&key.isConnectable()){
                    System.out.println("可连接事件完成");
                    SocketChannel channel = (SocketChannel) key.channel();

                    if (channel.isConnectionPending())
                        channel.finishConnect();
                    System.out.println("客户端连接成功");
                    channel.configureBlocking(false);

                    channel.register(selector,SelectionKey.OP_READ);
                }
            }
        }
//        连接完成进行读写操作
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        Scanner scanner = new Scanner(System.in);
        while (true){
            System.out.println("请输入:");
            String msg = scanner.nextLine();
            if ("exit".equals(msg)){
                break;
            }
//          向缓存写数据
            byteBuffer.put(msg.getBytes());
            byteBuffer.flip();
//        发送数据
            socketChannel.write(byteBuffer);
//        缓存清空
            byteBuffer.clear();
//        监听读事件,没有则阻塞
            selector.select();
            socketChannel.read(byteBuffer);
//            读写模式切换
            byteBuffer.flip();
//            通过缓冲区有效元素大小确定数组大小
            byte[] bytes1 = new byte[byteBuffer.remaining()];
//            从缓冲区读数据
            byteBuffer.get(bytes1);
            String msg1 = new String(bytes1);
            System.out.println("[recv]:"+msg1);
            byteBuffer.clear();

        }

        System.out.println("客户端关闭");
        selector.close();
        socketChannel.close();
    }
}

服务端

public class NIOServer {
    public static void main(String[] args) throws IOException {
//        穿建服务端的ServerSocketChannel.open()
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

//        绑定端口
        serverSocketChannel.bind(new InetSocketAddress(5555));
        System.out.println("服务端启动");

//        实例化选择器Selector.open();
        Selector selector = Selector.open();

//        设置 ServerSocketChannel实例为非阻塞的形式,因为默认为阻塞的
        serverSocketChannel.configureBlocking(false);

//        将该实例注册到选择器上,并设置关注accept事件
        serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);

//        让选择器帮监听关注的事件 selector.select()会阻塞,之中有关注事件发生才返回
//        返回值表示关注的事件有多少个发生了

        while (selector.select()>0){
            System.out.println("有关注的事件发生");
//          selector.selectedKeys().表示关注事件的集合
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()){
                SelectionKey key = iterator.next();
                if (key.isValid()&&key.isAcceptable()){
                    System.out.println("有可接受事件发生");
//                表示可接受事件完成
                    ServerSocketChannel sec = (ServerSocketChannel) key.channel();

//                接受客户端的连接accept操作
                    SocketChannel socketChannel = sec.accept();

//               设置SocketChannel实例为非阻塞
                    socketChannel.configureBlocking(false);

//                 将SocketChannel注册到选择器中,并关注读事件
                    socketChannel.register(selector,SelectionKey.OP_READ);

                }
                if (key.isValid()&&key.isReadable()){
//                    可读事件完成
                    SocketChannel socketChannel = (SocketChannel) key.channel();

//                    创建Buffer缓存
                    ByteBuffer buffer =  ByteBuffer.allocate(1024);

//                    将数据通过channel读到Buffer里
                    int cont = 0;
                    while ((cont = socketChannel.read(buffer))>0){

//                    读写模式的切换
                        buffer.flip();
//                  将数据读取Byte[] 数组
                        byte[] bytes = new byte[buffer.remaining()];

                        String msg = new String(bytes);
                        System.out.println(msg);

//                    清空缓存
                        buffer.clear();
                        buffer.put(msg.getBytes());

                        buffer.flip();
//                    给客户端回复
                        socketChannel.write(buffer);
                        buffer.clear();
                    }
                    System.out.println("Client->send:  "+buffer.toString());
                    if (cont<0){
                        socketChannel.close();
                    }
                   iterator.remove();
                    System.out.println("集合中剩余的事件个数"+selector.selectedKeys().size());

                }
            }
        }

    }
}

图片均为网上下载:侵删

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值