Java中IO模型之NIO(同步非阻塞模型)

NIO:同步非阻塞模型

Java中IO模型之NIO(同步非阻塞模型)_第1张图片
NIO中提供了选择器(Selector 类似底层操作系统提供的IO复用器:select、poll、epoll),也叫做多路复用器,作用是检查一个或者多个NIO Channel(通道)的状态是否是可读、可写。。。可以实现单线程管理多个channel,也可以管理多个网络请求

**Channel:**通道,用于IO操作的连接,在Java.nio.channels包下定义的,对原有IO的一种补充,不能直接访问数据需要和缓冲区Buffer进行交互

通道主要实现类:
SocketChannel:通过TCP读写网络中的数据,一般客户端的实现
ServerSocketChannel:监听新进来的TCP连接,对每一个连接都需要创建一个SocketChannel。一般是服务端的实现

Buffer:缓冲区,IO流中的数据需要经过缓冲区交给Channel

Java中IO模型之NIO(同步非阻塞模型)_第2张图片

NIO的编程

服务端:

public class NIOServer {
    public static void main(String[] args) {
        ServerSocketChannel serverSocketChannel = null;
        try {
            //创建ServerSocketChannel通道实例
            serverSocketChannel = ServerSocketChannel.open();

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

            //将serverSocketChannel设置为非阻塞  configureBlocking设置阻塞非阻塞 false:非阻塞  true:阻塞
            serverSocketChannel.configureBlocking(false);

            //创建selector选择器
            Selector selector = Selector.open();

            //将通道serverSocketChannel注册到选择器selector,关注可接受事件
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

            //等待监听结果,调用选择器的select阻塞等待,直到有事件发生才返回
            while (selector.select() > 0) {
                Iterator <SelectionKey> iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()) {
                    SelectionKey selectionKey = iterator.next();
                    iterator.remove();
                    //是否是可接受事件
                    if (selectionKey.isAcceptable()) {
                        System.out.println("可接受事件");
                        //有新用户连接
                        ServerSocketChannel serverSocketChannel1 = (ServerSocketChannel) selectionKey.channel();

                        //接受客户端的连接,通过accept(不在阻塞)接受一个SocketChannel通道
                        SocketChannel socketChannel = serverSocketChannel1.accept();

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

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

                    //是否是可读事件
                    if (selectionKey.isReadable()) {
                        System.out.println("可读事件");

                        //获取SocketChannel通道
                        SocketChannel socketChannel = (SocketChannel) selectionKey.channel();

                        //创建Buffer
                        ByteBuffer buffer = ByteBuffer.allocate(100);
                        //进行读取操作
                        socketChannel.read(buffer);
                        //进行读写模式的切换
                        buffer.flip();
                        //将数据从Buffer中读取
                        byte[] bytes = new byte[buffer.remaining()];
                        buffer.get(bytes);

                        //打印结果
                        System.out.println("客户端:"+socketChannel.getRemoteAddress()+new String(bytes,0,bytes.length));

                    }
                }
            }


        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //关闭资源
            try {
                if (serverSocketChannel != null) {
                    serverSocketChannel.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

}

服务端的编程步骤:

1、实例化通道:ServerSocketChannel
2、绑定端口:通过ServerSocketChannel实例调用bindI()方法绑定端口
3、将ServerSocketChannel设置为非阻塞
4、实例化选择器(IO复用器)Selector
5、将ServerSocketChannel注册给选择器,并且关注accept事件
6、监听事件是否完成,selector.select,如果事件未完成则一直阻塞直到事件完成
7、获取已完成事件的集合并遍历,判断是否是accept事件,是,则调用accept方法,获取SocketChannel通道
8、设置SocketChannel为非阻塞,并将SocketChannel注册到选择器Selector,并关注read事件
9、监听事件是否完成,若有事件完成,则判断是否是read读事件
10、通过SocketChannel通道读取数据(Buffer中),读完数据循环事件监听,即步骤6
11、关闭资源:ServerSocketChannel,SocketChannel,Selector

客户端:

public class NIOClient {
    public static void main(String[] args) {
        SocketChannel socketChannel = null;
        try {
            //创建SocketChannel通道
            socketChannel = SocketChannel.open();

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

            //创建Selector选择器
            Selector selector = Selector.open();

            //主动的进行连接,connect操作不在会阻塞,会直接返回,如果连接成功返回true ,连接还未完成返回false
            if (!socketChannel.connect(new InetSocketAddress("127.0.0.1",9998))) {
                //当前连接操作未完成
                //将SocketChannel注册到选择器,并关注可连接事件
                socketChannel.register(selector, SelectionKey.OP_CONNECT);

                //等待连接完成
                selector.select();

                Iterator <SelectionKey> iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()) {
                    SelectionKey selectionKey = iterator.next();
                    iterator.remove();

                    //是否是可连接事件
                    if (selectionKey.isConnectable()) {
                        //可连接事件完成
                        SocketChannel channel = (SocketChannel) selectionKey.channel();
                        //连接操作完成
                        channel.finishConnect();
                    }
                }
            }

            //连接成功,给服务端发送消息
            ByteBuffer buffer = ByteBuffer.allocate(100);
            //将发送的数据写到Buffer中
            buffer.put("hello tulun\n".getBytes());
            //读写模式的切换
            buffer.flip();
            socketChannel.write(buffer);

            //关闭资源
            selector.close();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (socketChannel != null) {
                try {
                    socketChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

NIO的客户端编程流程:

1、实例化通道:SocketChannel
2、设置SocketChannel为非阻塞
3、实例化复用器:Selector
4、连接服务器connect()(该方法不会阻塞直接返回结果,返回为Boolean,是否连接成功)
5、若返回为false,则将SocketChannel注册到复用器中,并监听connect可读事件
6、监听复用器事件是否完成(Selector.select),判断完成集合中是否有可连接事件,将可连接事件完成(channel.finishConnet())
7、给服务端发送消息,channel.write()操作
8、关闭资源:selector、SocketChannel

客户端为什么主动connect连接?

Java中IO模型之NIO(同步非阻塞模型)_第3张图片
在BIO中connect是一个可阻塞方法,在NIO中设置为非阻塞,交给IO复用器来监听事件是否完成(connect可连接事件),内核帮助监听事件是否完成,必须先触发事件发生,发生之后内核才能监听事件是否完成。客户端来主动连接服务端(Connect),在NIO中将SocketChannel通道设置为非阻塞,当前connect操作会立即返回(true:连接已经完成 false:当前事件已经触发,当并没有完成连接,就需要将连接等待的过程交给内核来关注),即将为完成的连接注册到选择器上。

完成客户端多次给服务端发送消息,完成Echo命令

客户端代码改动

public static void main(String[] args) {
        SocketChannel socketChannel = null;
        try {
            //创建SocketChannel通道
            socketChannel = SocketChannel.open();

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

            //创建Selector选择器
            Selector selector = Selector.open();

            //主动的进行连接,connect操作不在会阻塞,会直接返回,如果连接成功返回true ,连接还未完成返回false
            if (!socketChannel.connect(new InetSocketAddress("127.0.0.1",9998))) {
                //当前连接操作未完成
                //将SocketChannel注册到选择器,并关注可连接事件
                socketChannel.register(selector, SelectionKey.OP_CONNECT);

                //等待连接完成
                selector.select();

                Iterator <SelectionKey> iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()) {
                    SelectionKey selectionKey = iterator.next();
                    iterator.remove();

                    //是否是可连接事件
                    if (selectionKey.isConnectable()) {
                        //可连接事件完成
                        SocketChannel channel = (SocketChannel) selectionKey.channel();
                        //连接操作完成
                        channel.finishConnect();
                    }
                }
            }

            //注册读事件
            socketChannel.register(selector, SelectionKey.OP_READ);


            Scanner scanner = new Scanner(System.in);

            //连接成功,给服务端发送消息
            ByteBuffer buffer = ByteBuffer.allocate(100);

            while (scanner.hasNext()) {
                String msg = scanner.nextLine();
                buffer.put((msg+"\n").getBytes());
                //读写模式的切换
                buffer.flip();
                socketChannel.write(buffer);
                //关注服务端的返回 read
                selector.select();
                Iterator <SelectionKey> iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()) {
                    SelectionKey selectionKey = iterator.next();
                    if (selectionKey.isReadable()) {
                        SocketChannel channel = (SocketChannel) selectionKey.channel();
                        //将数据读取到Buffer中
                        buffer.clear();
                        channel.read(buffer);
                        //读写模式切换
                        buffer.flip();
                        byte[] bytes = new byte[buffer.remaining()];
                        buffer.get(bytes);
                        String s = new String(bytes);
                        System.out.println(s);
                    }
                }

                if ("".equals(msg) || "exit".equals(msg)) break;
                //Buffer是重复使用,需要进行清空
                buffer.clear();
            }

            //关闭资源
            selector.close();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (socketChannel != null) {
                try {
                    socketChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

服务端改动代码:

Java中IO模型之NIO(同步非阻塞模型)_第4张图片

NIO+ 多线程形式

NIO中一个selector可以关注多个用户的连接(即一个线程可以同时处理多个用户的通信),为了并发用户量能够处理更多,可以使用NIO+多线程的形式来处理。

NIO+多线程的处理思路:主线程主要来接收客户端的连接(accept),子线程处理用户的IO操作
主线程接收到客户端连接socketchannel通道,将SocketChannel交给子线程。selector选择器需要给子线程嘛?

如果子线程和主线程共用一个选择器:主线程注册的是可接受事件。子线程注册可读事件,即主线程处理可接受事件,子线程处理可读事件,选择器有返回结果,假如当前子线程获取到就绪事件(可读事件、可连接事件),
主线程和子线程分别使用各自的选择器。

服务端是固定数量为2的线程池,当进行IO操作是,子线程还在一致执行,意味着没有空闲的线程,引起结果就是两个用户请求可以处理,新用户请求没法继续处理了。

服务端代码:

public static void main(String[] args) {
        ServerSocketChannel serverSocketChannel = null;
        try {
            //创建ServerSocketChannel通道实例
            serverSocketChannel = ServerSocketChannel.open();

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

            //将serverSocketChannel设置为非阻塞  configureBlocking设置阻塞非阻塞 false:非阻塞  true:阻塞
            serverSocketChannel.configureBlocking(false);

            //创建selector选择器
            Selector selector = Selector.open();

            //将通道serverSocketChannel注册到选择器selector,关注可接受事件
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

            //子线程以线程池的形式提供
            ExecutorService executorService = Executors.newFixedThreadPool(10);

            //等待监听结果,调用选择器的select阻塞等待,直到有事件发生才返回
            while (selector.select() > 0) {
                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()) {
                    SelectionKey selectionKey = iterator.next();
                    iterator.remove();
                    //是否是可接受事件
                    if (selectionKey.isAcceptable()) {
                        System.out.println("可接受事件");
                        //有新用户连接
                        ServerSocketChannel serverSocketChannel1 = (ServerSocketChannel) selectionKey.channel();
                        //获取新用户channel
                        SocketChannel socketChannel = serverSocketChannel1.accept();
                        System.out.println(Thread.currentThread().getName()+":客户端:"+socketChannel.getRemoteAddress()+" 连接上。。。");

                        //将通道交给子线程
                        executorService.submit(new NIOServerHandler(socketChannel));

                    }
                }
            }
        } catch (Exception e) {

        }
    }

public class NIOServerHandler implements Runnable {
    //通过主线程将socketChannel获取到
    private SocketChannel socketChannel;
    //创建selector实例
    private Selector selector = null ;


    public NIOServerHandler(SocketChannel socketChannel) {
        this.socketChannel = socketChannel;
        try {
            //在一个子线程中只需要创建一个selector实例
            if (selector == null)
                selector = Selector.open();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        try {
            //将socketChannel设置为非阻塞
            socketChannel.configureBlocking(false);

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

            int num;
            while (selector.select() > 0) {
                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()) {
                    SelectionKey selectionKey = iterator.next();
                    iterator.remove();

                    //是否是可读事件
                    if (selectionKey.isReadable()) {
                        //获取SocketChannel通道
                        SocketChannel socketChannel = (SocketChannel) selectionKey.channel();

                        //创建Buffer
                        ByteBuffer buffer = ByteBuffer.allocate(100);
                        //进行读取操作
                        socketChannel.read(buffer);
                        //进行读写模式的切换
                        buffer.flip();
                        //将数据从Buffer中读取
                        byte[] bytes = new byte[buffer.remaining()];
                        buffer.get(bytes);

                        String msg = new String(bytes, 0, bytes.length);
                        //给客户端回复消息
                        buffer.clear();
                        buffer.put(("echo:"+msg).getBytes());
                        //读写模式切换
                        buffer.flip();
                        //回复消息
                        socketChannel.write(buffer);

                        //打印结果
                        System.out.println(Thread.currentThread().getName()+"客户端:"+socketChannel.getRemoteAddress()+" 消息:"+msg);
                        if ("".equals(msg)|| "exit".equals(msg)){
                            System.out.println(Thread.currentThread().getName()+"客户端:"+socketChannel.getRemoteAddress()+" 下线");
                            //当前注册的感兴趣事件取消
                            selectionKey.cancel();
                            //关闭通道
                            socketChannel.close();
                        }

                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

你可能感兴趣的:(Java中IO模型之NIO(同步非阻塞模型))