NIO模型

NIO:同步非阻塞IO,它是基于IO复用实现的,在JDK1.4后提供。在NIO中有以下几个核心的组成部分:缓存区(Buffer)、通道(Channel)、选择器(Selector)。

一、缓冲区(Buffer)

       缓冲区是包在一个对象内的基本数据数组。所有的缓冲区都具有四个属性来提供关于其包含的数据元素的信息。分别是:容量、上界、位置和标记。在NIO中,所有的缓冲区类型均继承于抽象类Buffer,最常用的类是ByteBuffer。

容量(Capacity):缓冲区能够容纳的数据元素的最大数量。这一容量在缓冲区创建时被设定,并且永远不能被改变。

上界(Limit):缓冲区的第一个不能被读或写的元素。或者说是,缓冲区现存元素的个数。

位置(Position):下一个要被读或写的元素的索引。位置会自动由相应get()和put()函数更新。

标记(Mark):一个备忘位置。使缓冲区能够记住一个位置并在之后将其返回。标记在设定前是未定义的,调用时标记被设为当前位置的值。

这是个属性之间总是遵循以下关系:

                                                          0<=mark<=position<=limit<=capacity

注意:所有的缓冲区都是可读的,但并非都是可写的。

Buffer中常用的方法:

put():指出下一个数据元素应被插入的位置

get():指出下一个元素从什么位置开始检索

flip():讲一个能够继续添加数据元素的填充状态的缓冲区翻转成一个准备读出元素的释放状态

remaining():从当前位置到上界还剩余的元素数目

clear():将缓冲区重置为空状态。并不改变缓冲区中的任何数据元素,而是仅仅将上界设为容量的值,并把位置设为0

compact():丢弃已经释放的数据,保留未释放的数据,并使缓冲区对重新填充容量准备就绪

mark():被调用之前是未定义的,调用时标记被设为当前位置的值

reset():将位置返回到一个先前设定的标记

主要的缓冲区类有:

CharBuffer、IntBuffer,DoubleBuffer,ShortBuffer,LongBuffer,FloatBuffer,和 ByteBuffer

二、通道(Channel)

       通道用于在字节缓冲区和位于通道另一侧的实体之间有效地传输数据,只能在字节缓冲区上操作。通道是一个对象,通过该对象可以读数据和写数据,所有的数据都是通过Buffer对象来处理的,不是直接将字节写入通道中,而是将数据写入包含一个或者多个字节的缓冲区中;读取数据时,不会直接从通道读取数据,而是先将数据从通道读入缓冲区中,然后在缓冲区中获取数据。

       与缓冲区不同的是,通道不能被重复使用。一个打开的通道即代表与一个特定I/O服务的特定连接并封装该链接的状态。当通道关闭时,连接会丢失,然后通道将不再连接任何东西。

三、选择器(Selector)

选择器(Selector):选择器管理着一个被注册的通道的信息和它们的就绪状态。通道是和选择器一起被注册的,并且使用选择器来更新通道的就绪状态。当这么做时,可以选择被激发的线程挂起,直到有就绪的通道。

可选择通道(SelectableChannel):这个抽象类提供了实现通道的可选择性所需要的公共方法。它是所有支持就绪检查的通道类的父类。SelectableChannel可以被注册到Selector对象上,同时可以指定对那个选择器而言,哪种操作是感兴趣的。一个通道可以被注册到多个选择器上,但对每个选择器而言只能被注册一次。

选择键(SelectionKey):选择键封装了特定的通道与特定的选择器的注册关系。选择键对象被SelectableChannel.register()返回并提供一个表示这种注册关系的标记。选择键包含了两个比特集(以整数的形式进行编码),指示了该注册关系所关心的通道操作,以及通道已经准备好的操作。

四、NIO编程流程

服务器端(Server):

1、先创建ServerSocketChannel实例,并且绑定端口

2、创建Selector实例

3、将ServerSocketChannel实例注册到选择器上,并监听accept事件

4、有事件发生(accept事件),就获取客户端的socketChannel的连接,将该连接实例注册到选择器上

5、(IO操作)通过selector监听读事件,就进行相应的读事件,写事件类似,也要监听->Buffer

6、关闭ServerSocketChannel实例

客户端(Client):

1、创建SocketChannel实例

2、创建Selector实例

3、连接服务器

4、将SocketChannel注册到Selector选择器上

5、直接发送数据,通过Selector选择器监听读事件

6、关闭SocketChannel实例

代码实现:

服务端

public class NIOServer {
    public static void main(String[] args) throws IOException {
        //创建ServerSocketChannel实例
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        //绑定端口
        serverSocketChannel.bind(new InetSocketAddress(1234));
        System.out.println("服务端启动");
        //创建Selector选择器
        Selector selector = Selector.open();
        //将ServerSocketChannel实例设置为非阻塞状态
        serverSocketChannel.configureBlocking(false);
        //将ServerSocketChannel实例实例注册到选择器上
        serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);
        //选择器进行阻塞选择,直到有监听事件发生后才返回
        while (selector.select()>0){
            //对所有监听事件的集合进行遍历
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()){
                SelectionKey key = iterator.next();//其中一个监听事件
                iterator.remove();
                //处理的是accept事件
                if(key.isValid()&&key.isAcceptable()){
                    //监听事件的通道(通道是与IO相连接的)
                    ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
                    //接收新用户的连接,要进行IO操作
                    SocketChannel socketChannel = ssc.accept();
                    //将新用户的连接设置为非阻塞状态
                    socketChannel.configureBlocking(false);
                    //将socketChannel实例注册到选择器上
                    socketChannel.register(selector,SelectionKey.OP_READ);
                    key.cancel();
                }
                //处理读事件
                if(key.isValid()&&key.isReadable()){
                    SocketChannel socketChannel = (SocketChannel) key.channel();
                    //创建buffer实例,进行储存信息
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    //将数据读入buffer中
                    socketChannel.read(buffer);
                    //翻转
                    buffer.flip();
                    //创建一个和buffer有一样长度的数组
                    byte[] bytes = new byte[buffer.remaining()];
                    //将buffer中的缓存数据读入byte中
                    buffer.get(bytes);
                    String msg = new String(bytes);
                    System.out.println("客户发送消息"+msg);
                    key.cancel();
                    //关闭客户端
                    socketChannel.close();
                }
            }
        }
        System.out.println("服务端结束");
        //关闭操作
        selector.close();
        serverSocketChannel.close();
    }
}

客户端

public class NIOCline {
    public static void main(String[] args) throws IOException {
        //创建SocketChannel实例
        SocketChannel socketChannel = SocketChannel.open();
        //创建Selector选择器
        Selector selector = Selector.open();
        //将SocketChannel实例设置为非阻塞
        socketChannel.configureBlocking(false);
        //connect本身是阻塞的,将SocketChannel设置为非阻塞,则该connect无论是否连接成功都要立即返回
        if(!socketChannel.connect(new InetSocketAddress("127.0.0.1",123))){
            //当前连接服务端还未连接上,则注册到选择器上进行监听
            socketChannel.register(selector,SelectionKey.OP_CONNECT);
        }
        int num;
        while ((num=selector.select(1000))>0){
            System.out.println("select返回:"+num);
            //获取注册到选择器上的事件
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()){
                SelectionKey key = iterator.next();
                iterator.remove();
                //可连接事件完成
                if(key.isValid()&key.isConnectable()){
                    SocketChannel channel = (SocketChannel) key.channel();
                    key.cancel();
                    if(!channel.finishConnect()){
                        //连接未完成,则直接结束与该用户的连接
                        System.exit(1);
                    }
                    //连接完成,创建发送消息的缓冲区
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    buffer.put("hello".getBytes());
                    buffer.flip();
                    //给服务端发送消息
                    socketChannel.write(buffer);
                    System.out.println("给服务端发送消息完成");
                }
            }
        }
        //关闭操作
        selector.close();
        socketChannel.close();
        System.out.println("客户端进行关闭操作");
    }
}

猜你喜欢

转载自blog.csdn.net/ZQ_313/article/details/88067753