三、非阻塞网络NIO

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_35241080/article/details/82562585
一、使用 NIO 完成网络通信的三个核心:
  1. 通道(Channel):负责连接

    java.nio.channels.Channel 接口:
    ​ |–SelectableChannel
    ​ |–SocketChannel
    ​ |–ServerSocketChannel
    ​ |–DatagramChannel

    ​ |–Pipe.SinkChannel
    ​ |–Pipe.SourceChannel

  2. 缓冲区(Buffer):负责数据的存取,前面介绍过的

  3. 选择器(Selector):是 SelectableChannel 的多路复用器。用于监控 SelectableChannel 的 IO 状况

二、阻塞式NIO
@Test
    public void test1() throws IOException{
        //1. 获取通道
         SocketChannel open = SocketChannel.open(new InetSocketAddress("127.0.0.2", 5151));
         FileChannel fileChannel = FileChannel.open(Paths.get("1.txt"), StandardOpenOption.READ);
        //2. 分配指定大小的缓冲区
         ByteBuffer buffer = ByteBuffer.allocate(1024);
        //3. 读取本地文件,并发送到服务端
         while(fileChannel.read(buffer) != -1){
             buffer.flip();
             open.write(buffer);
             buffer.clear();
         }
        //4. 关闭通道
         fileChannel.close();
         open.close();
    }
    @Test
    public void test2() throws IOException{
        //1.打开服务
        ServerSocketChannel server = ServerSocketChannel.open();
        //2.数据输出通道
        FileChannel fos = FileChannel.open(Paths.get("4.txt"), StandardOpenOption.WRITE, StandardOpenOption.CREATE);
        //3.绑定端口号
        server.bind(new InetSocketAddress("127.0.0.2",5151));
        //4.后去缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        //5.接受客户端通道
        SocketChannel accept = server.accept();
        //6.从客户端读取数据,由输出通道完成数据传输
        while(accept.read(buffer) != -1){
            buffer.flip();
            fos.write(buffer);
            buffer.clear();
        }
        //7.关闭服务
        accept.close();
        fos.close();
        server.close();
    }

服务端与客户端通信

//------------------阻塞式NIO,接受服务端传入消息
        @Test
        public void test3() throws IOException{
            SocketChannel client = SocketChannel.open(new InetSocketAddress("127.0.0.1",8000));
            FileChannel fis = FileChannel.open(Paths.get("1.txt"), StandardOpenOption.READ);
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            while(fis.read(buffer) != -1){
                buffer.flip();
                client.write(buffer);
                buffer.clear();
            }
            //停止输出数据,接受服务端的消息
            client.shutdownOutput();
            int len = 0;
            String str="";
            while((len = client.read(buffer))!=-1){
                buffer.flip();
                str +=new String(buffer.array(),0,len);
                buffer.clear();
            }
            System.out.println("接受服务端消息:"+str);
            fis.close();
            client.close();
        }
        @Test
        public void test4() throws IOException{
            ServerSocketChannel server = ServerSocketChannel.open();
            FileChannel fos = FileChannel.open(Paths.get("6.txt"), StandardOpenOption.WRITE, StandardOpenOption.CREATE);
            server.bind(new InetSocketAddress(8000));
            SocketChannel accept = server.accept();
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            while(accept.read(buffer)!=-1){
                buffer.flip();
                fos.write(buffer);
                buffer.clear();
            }
            //向客户端返回消息
            buffer.put("接受数据成功".getBytes());
            buffer.flip();
            accept.write(buffer);
            accept.close();
            fos.close();
            server.close();
        }
三、非阻塞式NIO
1.选择器:当调用 register(Selector sel, int ops) 将通道注册选择器时,选择器对通道的监听事件,需要通过第二个参数 ops 指定。

可以监听的事件类型(可使用 SelectionKey 的四个常量表示):
​ 读 : SelectionKey.OP_READ (1)
​ 写 : SelectionKey.OP_WRITE (4)
​ 连接 : SelectionKey.OP_CONNECT (8)
​ 接收 : SelectionKey.OP_ACCEPT (16)

2.SelectionKey: 表示 SelectableChannel 和 Selector 之间的注册关系。每次向选择器注册通道时就会选择一个事件(选择键)。 选择键包含两个表示为整数值的操作集。操作集的每一位都表示该键的通道所支持的一类可选择操

作。

SelectionKey方 法 描 述
int interestOps() 获取感兴趣事件集合
int readyOps() 获取通道已经准备就绪的操作的集合
SelectableChannel channel() 获取注册通道
Selector selector() 返回选择器
boolean isReadable() 检测 Channal 中读事件是否就绪
boolean isWritable() 检测 Channal 中写事件是否就绪
boolean isConnectable() 检测 Channel 中连接是否就绪
boolean isAcceptable() 检测 Channel 中接收是否就绪

Selector方 法 描 述

Set keys() 所有的 SelectionKey 集合。代表注册在该Selector上的Channel
selectedKeys() 被选择的 SelectionKey 集合。返回此Selector的已选择键集
int select() 监控所有注册的Channel,当它们中间有需要处理的 IO 操作时,该方法返回,并将对应得的 SelectionKey 加入被选择的
SelectionKey 集合中,该方法返回这些 Channel 的数量。
int select(long timeout) 可以设置超时时长的 select() 操作
int selectNow() 执行一个立即返回的 select() 操作,该方法不会阻塞线程
Selector wakeup() 使一个还未返回的 select() 方法立即返回
void close() 关闭该选择器

3.代码体现
//客户端
    @Test
    public void client() throws IOException{
        //1. 获取通道
        SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));

        //2. 切换非阻塞模式
        sChannel.configureBlocking(false);

        //3. 分配指定大小的缓冲区
        ByteBuffer buf = ByteBuffer.allocate(1024);

        //4. 发送数据给服务端
        Scanner scan = new Scanner(System.in);

        while(scan.hasNext()){
            String str = scan.next();
            buf.put((new Date().toString() + "\n" + str).getBytes());
            buf.flip();
            sChannel.write(buf);
            buf.clear();
        }

        //5. 关闭通道
        sChannel.close();
    }

    //服务端
    @Test
    public void server() throws IOException{
        //1. 获取通道
        ServerSocketChannel ssChannel = ServerSocketChannel.open();

        //2. 切换非阻塞模式
        ssChannel.configureBlocking(false);

        //3. 绑定连接
        ssChannel.bind(new InetSocketAddress(9898));

        //4. 获取选择器
        Selector selector = Selector.open();

        //5. 将通道注册到选择器上, 并且指定“监听接收事件”
        ssChannel.register(selector, SelectionKey.OP_ACCEPT);

        //6. 轮询式的获取选择器上已经“准备就绪”的事件
        while(selector.select() > 0){

            //7. 获取当前选择器中所有注册的“选择键(已就绪的监听事件)”
            Iterator<SelectionKey> it = selector.selectedKeys().iterator();

            while(it.hasNext()){
                //8. 获取准备“就绪”的是事件
                SelectionKey sk = it.next();

                //9. 判断具体是什么事件准备就绪
                if(sk.isAcceptable()){
                    //10. 若“接收就绪”,获取客户端连接
                    SocketChannel sChannel = ssChannel.accept();

                    //11. 切换非阻塞模式
                    sChannel.configureBlocking(false);

                    //12. 将该通道注册到选择器上
                    sChannel.register(selector, SelectionKey.OP_READ);
                }else if(sk.isReadable()){
                    //13. 获取当前选择器上“读就绪”状态的通道
                    SocketChannel sChannel = (SocketChannel) sk.channel();

                    //14. 读取数据
                    ByteBuffer buf = ByteBuffer.allocate(1024);

                    int len = 0;
                    while((len = sChannel.read(buf)) > 0 ){
                        buf.flip();
                        System.out.println(new String(buf.array(), 0, len));
                        buf.clear();
                    }
                }

                //15. 取消选择键 SelectionKey
                it.remove();
            }
        }
    }

DatagramChannel

@Test
    public void send() throws IOException{
        DatagramChannel dc = DatagramChannel.open();

        dc.configureBlocking(false);

        ByteBuffer buf = ByteBuffer.allocate(1024);

        Scanner scan = new Scanner(System.in);

        while(scan.hasNext()){
            String str = scan.next();
            buf.put((new Date().toString() + ":\n" + str).getBytes());
            buf.flip();
            dc.send(buf, new InetSocketAddress("127.0.0.1", 9898));
            buf.clear();
        }

        dc.close();
    }

    @Test
    public void receive() throws IOException{
        DatagramChannel dc = DatagramChannel.open();

        dc.configureBlocking(false);

        dc.bind(new InetSocketAddress(9898));

        Selector selector = Selector.open();

        dc.register(selector, SelectionKey.OP_READ);

        while(selector.select() > 0){
            Iterator<SelectionKey> it = selector.selectedKeys().iterator();

            while(it.hasNext()){
                SelectionKey sk = it.next();

                if(sk.isReadable()){
                    ByteBuffer buf = ByteBuffer.allocate(1024);

                    dc.receive(buf);
                    buf.flip();
                    System.out.println(new String(buf.array(), 0, buf.limit()));
                    buf.clear();
                }
            }

            it.remove();
        }
    }

Pipe

    @Test
    public void test1() throws IOException{
        //1. 获取管道
        Pipe pipe = Pipe.open();

        //2. 将缓冲区中的数据写入管道
        ByteBuffer buf = ByteBuffer.allocate(1024);

        Pipe.SinkChannel sinkChannel = pipe.sink();
        buf.put("通过单向管道发送数据".getBytes());
        buf.flip();
        sinkChannel.write(buf);

        //3. 读取缓冲区中的数据
        Pipe.SourceChannel sourceChannel = pipe.source();
        buf.flip();
        int len = sourceChannel.read(buf);
        System.out.println(new String(buf.array(), 0, len));

        sourceChannel.close();
        sinkChannel.close();
    }

资料来源于尚硅谷NIO视频

猜你喜欢

转载自blog.csdn.net/qq_35241080/article/details/82562585