JavaIO网络模型——多路复用NIO

简介

  • 概念:在IO多路复用模型中,引入了一种新的系统调用select/epoll,查询IO的就绪状态。通过该系统调用可以监视多个文件描述符,一旦某个描述符就绪(一般是内核缓存区可读/可写),内核能够将就绪的状态返回给应用程序。随后,应用程序根据就绪的状态,进行相应的IO系统调用。在IO多路复用模型中通过select/epoll系统调用,单个应用程序的线程,可以不断轮询成百上千的socket连接,当某个或者某些socket网络连接有IO就绪的状态,就返回对应的可以执行的读写操作。
  • 理解:餐厅安装了电子屏幕用来显示点餐的状态,这样我和女友逛街一会,回来就不用去询问服务员了,直接看电子屏幕就可以了。这样每个人的餐是否好了,都直接看电子屏幕就可以了,这就是典型的IO多路复用,

过程

在这里插入图片描述

特点

  • 优点:使用一个查询就绪状态的线程就可以同时同时轮询成千上万个连接。系统不必要创建和维护大量的线程,大大减小了系统的开销。

  • 缺点:select/epoll调用都是阻塞式的,属于同步IO。都需要在读写事件就绪后,由系统调用本身负责进行读写,也就是这个读写过程时阻塞的。

应用

  • 连接数目比较多以及连接时间短的系统架构,例如聊天服务器

简单实现

  1. 服务器
public class MyNIOServer {
    static class SocketChannelHandler extends Thread{
        private SocketChannel socketChannel;

        public SocketChannelHandler(SocketChannel socketChannel){
            this.socketChannel = socketChannel;
        }

        @Override
        public void run(){
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            Scanner scanner = new Scanner(System.in);
            try {
                socketChannel.configureBlocking(false);
                //创建选择器
                Selector selector = Selector.open();
                //将socketChannel注册到选择器上,让其关注读事件
                socketChannel.register(selector,SelectionKey.OP_READ);
                //调用select方法去监听
                while (selector.select() > 0){
                    //通过SelectedKeys拿到选择器集合的迭代器
                    Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                    //遍历集合
                    while (iterator.hasNext()){
                        //获取当前事件
                        SelectionKey selectionKey = iterator.next();
                        //将此事件移除
                        iterator.remove();
                        //判断当前是事件是不是读事件
                        if (selectionKey.isReadable()){
                            //拿到当前channel
                            socketChannel = (SocketChannel) selectionKey.channel();
                            //进行读取操作
                            socketChannel.read(buffer);
                            //读写转换
                            buffer.flip();
                            //从缓冲区读取数据
                            byte[] bytes = new byte[buffer.remaining()];
                            buffer.get(bytes);
                            //清空缓冲区
                            buffer.clear();
                            //数据打印到控制台
                            System.out.println("客户端"+socketChannel.getRemoteAddress()+"发送的数据为: "+ new String(bytes));
                            //将channel注册到选择器上 让其关注写事件
                            socketChannel.register(selector,SelectionKey.OP_WRITE);
                        }
                        //判断当前是事件是不是读事件
                        if (selectionKey.isWritable()){
                            //拿到当前channel
                            socketChannel = (SocketChannel) selectionKey.channel();
                            //从控制台获取数据
                            String s = scanner.next();
                            //将数据装入到缓冲区
                            buffer.put(s.getBytes());
                            //读写转换
                            buffer.flip();
                            //发送数据
                            socketChannel.write(buffer);
                            //清空buffer
                            buffer.clear();
                            //将channel注册到选择器上 让其关注读事件
                            socketChannel.register(selector,SelectionKey.OP_READ);
                        }
                    }
                }

            } catch (IOException e) {
                e.printStackTrace();
            }finally {
                try {
                    socketChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        ServerSocketChannel serverSocketChannel = null;
        try {
            //创建ServerSocketChannel通道
            serverSocketChannel = ServerSocketChannel.open();
            //绑定端口
            serverSocketChannel.bind(new InetSocketAddress(2222));
            System.out.println("服务器已启动...");
            //将通道设置为非阻塞的
            serverSocketChannel.configureBlocking(false);
            //创建选择器
            Selector selector = Selector.open();
            //将ServerSocketChannel注册到选择器上,让其关注可接受连接事件
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            //调用选择器的select方法监听 select为阻塞方法,有事件发生时才会但会
            while (selector.select() > 0){
                //通过SelectedKeys拿到选择器集合的迭代器
                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                //遍历集合
                while (iterator.hasNext()){
                    //获取当前事件
                    SelectionKey selectionKey = iterator.next();
                    //将此事件移除
                    iterator.remove();
                    //判断当前是事件是不是 请求连接事件
                    if (selectionKey.isAcceptable()){
                        //有客户端的请求连接
                        //拿到注册到选择器中的ServerSocketChannel
                        ServerSocketChannel channel = (ServerSocketChannel) selectionKey.channel();
                        //建立连接 拿到连接的SocketChannel
                        SocketChannel socketChannel = channel.accept();
                        System.out.println("与客户端"+socketChannel.getRemoteAddress()+"建立连接成功...");
                        //将SocketChannel交给子线程 启动子线程去通信
                        new SocketChannelHandler(socketChannel).start();
                    }
                }
            }

        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                if (serverSocketChannel != null){
                    serverSocketChannel.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

  1. 客户端
public class MyNIOClient {
    public static void main(String[] args) {
        SocketChannel socketChannel = null;
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        try {
            //创建SocketChannel通道,绑定ip端口
            socketChannel = SocketChannel.open();
            //设置通道为非阻塞
            socketChannel.configureBlocking(false);
            System.out.println("客户端已启动...");
            //创建一个选择器
            Selector selector = Selector.open();
            //连接服务器
            if (!socketChannel.connect(new InetSocketAddress("127.0.0.1",2222))){
                //连接失败注册到选择器上关注连接时间
                socketChannel.register(selector,SelectionKey.OP_CONNECT);
                //监听
               while (selector.select() > 0) {
                   //遍历已完成事件的集合
                   Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                   while(iterator.hasNext()) {
                       SelectionKey selectionKey = iterator.next();
                       iterator.remove();
                       //判断是否存在可连接事件
                       if (selectionKey.isConnectable()) {
                           //获取selectionKey的channel
                           SocketChannel channel = (SocketChannel) selectionKey.channel();
                           //完成建立连接
                           channel.finishConnect();
                           System.out.println("与服务器"+socketChannel.getRemoteAddress()+"连接建立成功...");
                           //将channel注册到选择器上 让其关注写事件
                           socketChannel.register(selector,SelectionKey.OP_WRITE);
                       }
                       //判断当前是事件是不是写事件
                       if (selectionKey.isWritable()) {
                           //拿到当前channel
                           socketChannel = (SocketChannel) selectionKey.channel();
                           //从控制台获取数据,将数据装入到缓冲区
                           buffer.put(new Scanner(System.in).next().getBytes());
                           //读写转换
                           buffer.flip();
                           //发送数据
                           socketChannel.write(buffer);
                           //清空buffer
                           buffer.clear();
                           //将channel注册到选择器上 让其关注读事件
                           socketChannel.register(selector, SelectionKey.OP_READ);
                       }
                       //判断当前是事件是不是读事件
                       if (selectionKey.isReadable()) {
                           //拿到当前channel
                           socketChannel = (SocketChannel) selectionKey.channel();
                           //将数据读取到缓冲区中
                           socketChannel.read(buffer);
                           //读写转换
                           buffer.flip();
                           //从缓冲区读取数据
                           byte[] bytes = new byte[buffer.remaining()];
                           buffer.get(bytes);
                           //清空缓冲区
                           buffer.clear();
                           //数据打印到控制台
                           System.out.println("服务器" + socketChannel.getRemoteAddress() + "发送的数据为: " + new String(bytes));
                           //将channel注册到选择器上 让其关注写事件
                           socketChannel.register(selector, SelectionKey.OP_WRITE);
                       }
                   }
               }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try{
                if (socketChannel != null){
                    socketChannel.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值