NIO 同步非阻塞IO

NIO2  重要
/**
 * IO模型
 * BIO   同步阻塞IO
 * NIO   同步非阻塞IO
 * IO多路复用   一个select/epoll系统调用
 * AIO   异步非阻塞IO
 *
 * Java中NIO
 * 提供了选择器(Selector)类似操作系统提供的select/epoll,也叫做IO多路复用器
 * 作用是检查一个channel(通道)的状态是否是可读、可写、可连接、可接收的状态,为了
 * 实现单线程管理多个channel,其实也就是管理多个网络请求
 *
 * Channel通道
 * java.nio.channels 即可以读,又可以写,不直接和数据源打交道,主要和缓冲区Buffer进行交互
 * ServerSocketChannel 服务器端
 * SocketChannel 客户端
 *
 * Buffer 缓冲区
 * IO流中的数据通过缓冲区交给Channel
 *
 * 服务器端:
 * 1、实例化 ServerSocketChannel
 * 2、绑定端口 通过ServerSocketChannel调用bind()方法
 * 3、设置ServerSocketChannel为非阻塞 configir....
 * 4、实例化Selector选择器
 * 5、将ServerSocketChannel注册到选择器上  ServerSocketChannel.register()
 * 6、监听是否有新的事件 接收(连接)事件/读写事件/     Selector.select()
 * 7、获取已完成事件的集合,对于这个集合进行遍历,如果发现是Accept事件
 * 8、则进行accept调用,获取SocketChannel,注册到Selector上,关注read事件
 * 9、监听是否有read读事件
 * 10、通过SocketChananel通道来读取数据,其中通过buffer作为传输介质
 * 11、关闭资源 SocketChannel Selector ServerSocketChannel
 *
 * 客户端:
 * 1、实例化 SocketChannel
 * 2、设置 SocketChannel 为非阻塞
 * 3、实例化 Selector
 * 4、连接服务器connect,在这个方法中提供ip地址和端口号,注意:这个方法不是一个阻塞方法,如果连接
 * 失败返回false,连接成功返回true
 * 5、如果是false,则将SocketChannel注册到Selector选择器中,监听connect可连接事件
 * 6、监听selector中是否可完成事件,遍历可完成事件的集合,判断该事件是否是可连接事件
 * 7、connect方法就会返回true
 * 8、给服务器端发送消息,channel.write
 * 9、关闭资源,selector SocketChannel
 *

NIO的实现

服务器端

public class MyNIOServer {
    public static void main(String[] args) {
        ServerSocketChannel serverSocketChannel = null;
        Selector selector = null;
        try {
            //创建ServerSocket实例
            serverSocketChannel = ServerSocketChannel.open();
            //绑定端口
            serverSocketChannel.bind(new InetSocketAddress(9999));
            System.out.println("服务器端启动了...");
            //设置当前serverSocketChannel为非阻塞模式
            serverSocketChannel.configureBlocking(false);
            //实例化Selector
            selector = Selector.open();
            //将serverSocketChannel注册到选择器上
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            //进行监听,调用selector.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("可接受请求...");
                        //这个返回当前事件所创建的channel
                        ServerSocketChannel channel = (ServerSocketChannel)selectionKey.channel();
                        //接收这个事件获取SocketChannel
                        SocketChannel socketChannel = channel.accept();
                        //设置socketChannel为非阻塞的
                        socketChannel.configureBlocking(false);
                        //将socketChannel注册到选择器中
                        socketChannel.register(selector, SelectionKey.OP_READ);
                    }

                    //判断是否是读事件
                    if(selectionKey.isReadable()){
                        System.out.println("读请求...");
                        //获取可读事件的channel
                        SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
                        //进行读取操作
                        ByteBuffer buffer = ByteBuffer.allocate(100);
                        //通过channel将数据读取到buffer中
                        socketChannel.read(buffer);
                        //进行读写模式切换
                        buffer.flip();
                        //将数据从buffer中读取
                        byte[] bytes = new byte[buffer.remaining()];
                        //获取buffer缓冲区中的数据到byte数组中
                        buffer.get(bytes);
                        System.out.println("客户端:"+socketChannel.getRemoteAddress()+" 发送的消息为: "+new String(bytes, 0, bytes.length));

                        buffer.clear();
                        if(socketChannel.read(buffer) == -1){
                            socketChannel.close();
                        }
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(serverSocketChannel != null){
                try {
                    serverSocketChannel.close();
                    selector.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

客户端

public class MyNIOClient {
    public static void main(String[] args) {
        SocketChannel socketChannel = null;
        try {
            //创建SocketChannel通道
            socketChannel = SocketChannel.open();
            //设置SocketChananel为非阻塞
            socketChannel.configureBlocking(false);
            //实例化Selector
            Selector selector = Selector.open();
            System.out.println("客户端启动了...");
            if(!socketChannel.connect(new InetSocketAddress("127.0.0.1", 9999))){
                //将这个通道注册到选择器上
                socketChannel.register(selector, SelectionKey.OP_CONNECT);
                //监听Selector选择器
                selector.select();
                //遍历已完成事件的集合
                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("连接建立成功了...");
            //连接成功,给服务器端发送信息
            ByteBuffer buffer  = ByteBuffer.allocate(100);
            //将所要的数据写入到buffer中
            //buffer.put(("hello NIOServer\n").getBytes());
            BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
            String info = reader.readLine();
            buffer.put((info+"\n").getBytes());
            //mark position limit capacity
            //进行读写模式切换
            buffer.flip();
            //使用通道发送buffer
            socketChannel.write(buffer);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(socketChannel != null){
                try {
                    socketChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

多线程实现NIO 服务器端

class NIOServerHandler implements Runnable{

    private SocketChannel socketChannel;
    //创建Selector实例
    private Selector selector;

    public NIOServerHandler(SocketChannel socketChannel){
        this.socketChannel = socketChannel;
        if(selector == null){
            try {
                selector = Selector.open();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public void run() {
        //关注socketChannel通道的读写就绪事件,进而处理
        try {
            socketChannel.configureBlocking(false);
            socketChannel.register(selector, SelectionKey.OP_READ);

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

                    if(key.isReadable()){
                        SocketChannel channel = (SocketChannel) key.channel();
                        ByteBuffer buffer = ByteBuffer.allocate(1024);
                        channel.read(buffer);
                        buffer.flip();

                        byte[] bytes = new byte[buffer.remaining()];
                        buffer.get(bytes);
                        String msg = new String(bytes, 0, bytes.length);
                        System.out.println(Thread.currentThread().getName()+", 客户端:"+channel.getRemoteAddress()+", 消息: "+msg);

                        buffer.clear();
                        buffer.put(("hello client\n").getBytes());
                        buffer.flip();
                        channel.write(buffer);
                        if("".equals(msg) || "exit".equals(msg)){
                            System.out.println(Thread.currentThread().getName()+"客户端:"+channel.getRemoteAddress()+"下了 ");
                            key.cancel();
                            channel.close();
                        }

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

public class MyMultiThreadNIOServer {
    public static void main(String[] args) {
        ServerSocketChannel serverSocketChannel = null;

        try {
            //创建ServerSocketChannel
            serverSocketChannel = ServerSocketChannel.open();
            //绑定端口
            serverSocketChannel.bind(new InetSocketAddress(9998));
            //将SerSocketChannel置为非阻塞
            serverSocketChannel.configureBlocking(false);
            //创建Selector选择器
            Selector selector = Selector.open();
            //将serverSocketChannel注册到选择器上,关注可接受事件
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

            //使用固定数量的线程池
            ExecutorService pool = Executors.newFixedThreadPool(3);
            while(selector.select() > 0){
                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                while(iterator.hasNext()){
                    SelectionKey key = iterator.next();
                    iterator.remove();

                    if(key.isAcceptable()){
                        //是可接受事件
                        System.out.println(Thread.currentThread().getName()+"关注可接受事件");
                        ServerSocketChannel serverSocketChannel1 = (ServerSocketChannel) key.channel();
                        SocketChannel channel = serverSocketChannel1.accept();
                        System.out.println("客户端:"+channel.getRemoteAddress()+"已连接");
                        //将SocketChannel channel提交给子线程
                        pool.submit(new NIOServerHandler(channel));
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

NIO中重要组件

* NIO中重要组件
 * Channel
 * 类似流,即可以从通道中读取数据,也可以写数据到通道中
 * 通道可以异步地读写,通道中的数据先要读取到一个Buffer中,或者从一个Buffer中写入
 * 读数据:将数据从channel读到buffer
 * 写数据:从buffer中写入到channel通道中
 * 常用实现类:
 * DatagramChannel 通过UDP的方式读写网络中的数据通道
 * SocketChannel  通过TCP的方式读写网络中的数据,一般用于客户端
 * ServerSocketChanneel  通过TCP的方式读写网络中的数据,一般用于服务器端
 * FileChannel 用于读写操作文件的通道
 *
 * Buffer
 * 缓冲区,与NIO Channel交互,数据是从通道读取进入缓冲区,从缓冲区写入到通道中
 *
 * 使用Buffer注意点:
 * 1、写数据到buffer
 * 2、调用buffer.flip
 * 3、从Buffer中读取数据
 * 4、buffer.clear/buffer.compact
 * 当从buffer中读取数据,要么调用clear方法清空buffer中的数据,要么调用compact方法啊清空已经读过
 * 的数据,任何未读到数据会被移动缓冲区的起始位置,新写入的数据将放到缓冲区未读数据的后面
 *
 * Buffer实现依赖3个指针  position limit capacity
 * position:取决于Buffer处于读模式还是写模式,
 * · 写数据到Buffer中,position表示当前位置,初始的值为0
 * · 读数据时,从某个额特定位置开始去读,需要将buffer从写模式切换为读模式,position会被重置为0
 * limit:
 * · 写模式下,表示最多能往里写多少数据
 * · 读模式下,表示最多能读到的数据
 * capacity:
 * 作为buffer内存块,有一个固定的大小
 *
 * 示例:ByteBuffer.allocate(100);
 * 1、position = 0  limit = capacity = 100
 * 2、buffer.put("hello\n")  position=6    limit = capacity = 100
 * 3、buffer.flip()    position=0  limit = position  capacity = 100
 * 4.buffer.get()  3   position=3   limit = 6   capacity = 100
 *
 * Buffer的方法:
 * ByteBuffer.allocate() 分配空间
 * ByteBuffer.allocateDirect() 在堆外分配空间
 * ByteBuffer.wrap(byte[] bytes) 通过byte数据创建一个缓冲区
 * flip()
 * capacity()
 * limit()
 * positio()
 *
 * Selector
 * 选择器,也叫做多路复用器,作用是检查一个或多个channel通道是否处于可读、可写、可连接(管理多个网络请求)
 * 优势:
 * 单线程管理多个网络连接,相比于之前的多线程使用了更多的线程,效率反而更高,减少了线程上下文切换
 *
 * Selector的使用
 * 1、Selector.open();
 * 2、channel.register(xxx, SelectionKey的四种事件)
 * 3、selector.select() 这个方法是一个阻塞方法,如果有事件就绪则返回
 *
 * Selector维护三种类型的selectionKey集合
 * selector.selectedKeys() 已选择键的集合
 * selector.keys() 已注册键的集合
 * selector.cancelKey() 已取消键的集合
 *
 * SelectionKey的四种事件
 * 一个通道可以注册多个事件 SelectionKey.OP_READ | SelectionKey.OP_WRITE
 *
 * 思考:NIO和BIO的区别
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值