这篇NIO文章,你看不懂来找我领红包

JAVA从万级QPS到亿级吞吐,如何用非阻塞模型突破传统架构的性能瓶颈 渐进式迁移:从边缘服务开始试点全链路响应式:数据库→缓存→MQ均需非阻塞客户端防御式编程:为每个流设置超时与重试容量规划:通过压力测试确定背压阈值混沌工程:模拟网络抖动验证系统弹性附录:响应式生态工具链数据库:R2DBC(MySQL/PostgreSQL)、ReactiveMongo消息队列:ReactiveKafka、RSocket缓存:Lettuce(Redis)、Caffeine监控:Micrometer + Prometheus + Grafana。 阅读详情

前言

只有光头才能变强

本来我预想是先来回顾一下传统的IO模式的,将传统的IO模式的相关类理清楚(因为IO的类很多)。

但是,发现在整理的过程已经有很多优秀的文章了,而我自己来整理的话可能达不到他们的水平。并且传统的IO估计大家都会用,而NIO就不一定了。

下面我就贴几张我认为整理比较优秀的思维导图(下面会给出图片来源地址,大家可前往阅读):

按操作方式分类结构图:

字节流的输入和输出对照图:

字符流的输入和输出对照图:

按操作对象分类结构图:

上述图片原文地址,知乎作者@小明:

还有阅读传统IO源码的优秀文章:

相信大家看完上面两个给出的链接+理解了包装模式就是这么简单啦,传统的IO应该就没什么事啦~~

而NIO对于我来说可以说是挺陌生的,在当初学的时候是接触过的。但是一直没有用它,所以停留认知:nio是jdk1.4开始有的,比传统IO高级。

相信很多初学者都跟我一样,对NIO是不太了解的。而我们现在jdk10都已经发布了,jdk1.4的nio都不知道,这有点说不过去了。

所以我花了几天去了解NIO的核心知识点,期间看了《Java 编程思想》和《疯狂Java 讲义》的nio模块。但是,会发现看完了之后还是很迷,不知道NIO这是干嘛用的,而网上的资料与书上的知识点没有很好地对应。

  • 网上的资料很多都以IO的五种模型为基础来讲解NIO,而IO这五种模型其中又涉及到了很多概念:同步/异步/阻塞/非阻塞/多路复用,而不同的人又有不同的理解方式。
  • 还有涉及到了unix的select/epoll/poll/pselect,fd这些关键字,没有相关基础的人看起来简直是天书
  • 这就导致了在初学时认为nio远不可及

我在找资料的过程中也收藏了好多讲解NIO的资料,这篇文章就是以初学的角度来理解NIO。也算是我这两天看NIO的一个总结吧。

  • 希望大家可以看了之后知道什么是NIO,NIO的核心知识点是什么,会使用NIO~

那么接下来就开始吧,如果文章有错误的地方请大家多多包涵,不吝在评论区指正哦~

声明:本文使用JDK1.8

一、NIO的概述

JDK 1.4中的java.nio.*包中引入新的Java I/O库,其目的是提高速度。实际上,“旧”的I/O包已经使用NIO重新实现过,即使我们不显式的使用NIO编程,也能从中受益。

  • nio翻译成 no-blocking io 或者 new io 都无所谓啦,都说得通~

在《Java编程思想》读到**“即使我们不显式的使用NIO编程,也能从中受益”的时候,我是挺在意的,所以:我们测试**一下使用NIO复制文件和传统IO复制文件的性能:


import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class SimpleFileTransferTest {

    private long transferFile(File source, File des) throws IOException {
        long startTime = System.currentTimeMillis();

        if (!des.exists())
            des.createNewFile();

        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(source));
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(des));

        //将数据源读到的内容写入目的地--使用数组
        byte[] bytes = new byte[1024 * 1024];
        int len;
        while ((len = bis.read(bytes)) != -1) {
            bos.write(bytes, 0, len);
        }

        long endTime = System.currentTimeMillis();
        return endTime - startTime;
    }

    private long transferFileWithNIO(File source, File des) throws IOException {
        long startTime = System.currentTimeMillis();

        if (!des.exists())
            des.createNewFile();

        RandomAccessFile read = new RandomAccessFile(source, "rw");
        RandomAccessFile write = new RandomAccessFile(des, "rw");

        FileChannel readChannel = read.getChannel();
        FileChannel writeChannel = write.getChannel();


        ByteBuffer byteBuffer = ByteBuffer.allocate(1024 * 1024);//1M缓冲区

        while (readChannel.read(byteBuffer) > 0) {
            byteBuffer.flip();
            writeChannel.write(byteBuffer);
            byteBuffer.clear();
        }

        writeChannel.close();
        readChannel.close();
        long endTime = System.currentTimeMillis();
        return endTime - startTime;
    }

    public static void main(String[] args) throws IOException {
        SimpleFileTransferTest simpleFileTransferTest = new SimpleFileTransferTest();
        File sourse = new File("F:\\电影\\[电影天堂www.dygod.cn]猜火车-cd1.rmvb");
        File des = new File("X:\\Users\\ozc\\Desktop\\io.avi");
        File nio = new File("X:\\Users\\ozc\\Desktop\\nio.avi");

        long time = simpleFileTransferTest.transferFile(sourse, des);
        System.out.println(time + ":普通字节流时间");

        long timeNio = simpleFileTransferTest.transferFileWithNIO(sourse, nio);
        System.out.println(timeNio + ":NIO时间");


    }

}

我分别测试了文件大小为13M,40M,200M的:

1.1为什么要使用NIO

可以看到使用过NIO重新实现过的传统IO根本不虚,在大文件下效果还比NIO要好(当然了,个人几次的测试,或许不是很准)

  • 而NIO要有一定的学习成本,也没有传统IO那么好理解。

那这意味着我们可以不使用/学习NIO了吗?

答案是否定的,IO操作往往在两个场景下会用到:

  • 文件IO
  • 网络IO

NIO的魅力:在网络中使用IO就可以体现出来了!

  • 后面会说到网络中使用NIO,不急哈~

二、NIO快速入门

首先我们来看看IO和NIO的区别:

  • 可简单认为:IO是面向流的处理,NIO是面向块(缓冲区)的处理
    • 面向流的I/O 系统一次一个字节地处理数据。
    • 一个面向块(缓冲区)的I/O系统以块的形式处理数据。

NIO主要有三个核心部分组成:

  • buffer缓冲区
  • Channel管道
  • Selector选择器

2.1buffer缓冲区和Channel管道

在NIO中并不是以流的方式来处理数据的,而是以buffer缓冲区和Channel管道配合使用来处理数据。

简单理解一下:

  • Channel管道比作成铁路,buffer缓冲区比作成火车(运载着货物)

而我们的NIO就是通过Channel管道运输着存储数据的Buffer缓冲区的来实现数据的处理!

  • 要时刻记住:Channel不与数据打交道,它只负责运输数据。与数据打交道的是Buffer缓冲区
    • Channel–>运输
    • Buffer–>数据

相对于传统IO而言,流是单向的。对于NIO而言,有了Channel管道这个概念,我们的读写都是双向的(铁路上的火车能从广州去北京、自然就能从北京返还到广州)!

2.1.1buffer缓冲区核心要点

我们来看看Buffer缓冲区有什么值得我们注意的地方。

Buffer是缓冲区的抽象类:

其中ByteBuffer是用得最多的实现类(在管道中读写字节数据)。

拿到一个缓冲区我们往往会做什么?很简单,就是读取缓冲区的数据/写数据到缓冲区中。所以,缓冲区的核心方法就是:

  • put()
  • get()

Buffer类维护了4个核心变量属性来提供关于其所包含的数组的信息。它们是:

  • 容量Capacity
    • 缓冲区能够容纳的数据元素的最大数量。容量在缓冲区创建时被设定,并且永远不能被改变。(不能被改变的原因也很简单,底层是数组嘛)
  • 上界Limit
    • 缓冲区里的数据的总数,代表了当前缓冲区中一共有多少数据。
  • 位置Position
    • 下一个要被读或写的元素的位置。Position会自动由相应的 get( )和 put( )函数更新。
  • 标记Mark
    • 一个备忘位置。用于记录上一次读写的位置。

2.1.2buffer代码演示

首先展示一下是如何创建缓冲区的,核心变量的值是怎么变化的。


    public static void main(String[] args) {

        // 创建一个缓冲区
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

        // 看一下初始时4个核心变量的值
        System.out.println("初始时-->limit--->"+byteBuffer.limit());
        System.out.println("初始时-->position--->"+byteBuffer.position());
        System.out.println("初始时-->capacity--->"+byteBuffer.capacity());
        System.out.println("初始时-->mark--->" + byteBuffer.mark());

        System.out.println("--------------------------------------");

        // 添加一些数据到缓冲区中
        String s = "Java3y";
        byteBuffer.put(s.getBytes());

        // 看一下初始时4个核心变量的值
        System.out.println("put完之后-->limit--->"+byteBuffer.limit());
        System.out.println("put完之后-->position--->"+byteBuffer.position());
        System.out.println("put完之后-->capacity--->"+byteBuffer.capacity());
        System.out.println("put完之后-->mark--->" + byteBuffer.mark());
    }

运行结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-O3EyUXwu-1583471377929)(https://user-gold-cdn.xitu.io/2018/5/14/1635dae142362882?w=955&h=255&f=png&s=7721)]

现在我想要从缓存区拿数据,怎么拿呀??NIO给了我们一个flip()方法。这个方法可以改动position和limit的位置!

还是上面的代码,我们flip()一下后,再看看4个核心属性的值会发生什么变化:

很明显的是:

  • limit变成了position的位置了
  • 而position变成了0

看到这里的同学可能就会想到了:当调用完filp()时:limit是限制读到哪里,而position是从哪里读

一般我们称filp()为**“切换成读模式”**

  • 每当要从缓存区的时候读取数据时,就调用filp()“切换成读模式”。

切换成读模式之后,我们就可以读取缓冲区的数据了:


        // 创建一个limit()大小的字节数组(因为就只有limit这么多个数据可读)
        byte[] bytes = new byte[byteBuffer.limit()];

        // 将读取的数据装进我们的字节数组中
        byteBuffer.get(bytes);

        // 输出数据
        System.out.println(new String(bytes, 0, bytes.length));

随后输出一下核心变量的值看看:

读完我们还想写数据到缓冲区,那就使用clear()函数,这个函数会“清空”缓冲区:

  • 数据没有真正被清空,只是被遗忘掉了

2.1.3FileChannel通道核心要点

Channel通道只负责传输数据、不直接操作数据的。操作数据都是通过Buffer缓冲区来进行操作!


        // 1. 通过本地IO的方式来获取通道
        FileInputStream fileInputStream = new FileInputStream("F:\\3yBlog\\JavaEE常用框架\\Elasticsearch就是这么简单.md");

        // 得到文件的输入通道
        FileChannel inchannel = fileInputStream.getChannel();

        // 2. jdk1.7后通过静态方法.open()获取通道
        FileChannel.open(Paths.get("F:\\3yBlog\\JavaEE常用框架\\Elasticsearch就是这么简单2.md"), StandardOpenOption.WRITE);

使用FileChannel配合缓冲区实现文件复制的功能:

使用内存映射文件的方式实现文件复制的功能(直接操作缓冲区):

通道之间通过transfer()实现数据的传输(直接操作缓冲区):

2.1.4直接与非直接缓冲区

  • 非直接缓冲区是需要经过一个:copy的阶段的(从内核空间copy到用户空间)
  • 直接缓冲区不需要经过copy阶段,也可以理解成—>内存映射文件,(上面的图片也有过例子)。

使用直接缓冲区有两种方式:

  • 缓冲区创建的时候分配的是直接缓冲区
  • 在FileChannel上调用map()方法,将文件直接映射到内存中创建

2.1.5scatter和gather、字符集

这个知识点我感觉用得挺少的,不过很多教程都有说这个知识点,我也拿过来说说吧:

  • 分散读取(scatter):将一个通道中的数据分散读取到多个缓冲区中
  • 聚集写入(gather):将多个缓冲区中的数据集中写入到一个通道中

分散读取

聚集写入

字符集(只要编码格式和解码格式一致,就没问题了)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iuziJuLo-1583471377936)(https://user-gold-cdn.xitu.io/2018/5/14/1635db37f523fbf0?w=398&h=431&f=png&s=22732)]

三、IO模型理解

文件的IO就告一段落了,我们来学习网络中的IO~~~为了更好地理解NIO,我们先来学习一下IO的模型~

根据UNIX网络编程对I/O模型的分类,在UNIX可以归纳成5种I/O模型:

  • 阻塞I/O
  • 非阻塞I/O
  • I/O多路复用
  • 信号驱动I/O
  • 异步I/O

3.0学习I/O模型需要的基础

3.0.1文件描述符

Linux 的内核将所有外部设备都看做一个文件来操作,对一个文件的读写操作会调用内核提供的系统命令(api),返回一个file descriptor(fd,文件描述符)。而对一个socket的读写也会有响应的描述符,称为socket fd(socket文件描述符),描述符就是一个数字,指向内核中的一个结构体(文件路径,数据区等一些属性)。

  • 所以说:在Linux下对文件的操作是利用文件描述符(file descriptor)来实现的。

3.0.2用户空间和内核空间

为了保证用户进程不能直接操作内核(kernel),保证内核的安全,操心系统将虚拟空间划分为两部分

  • 一部分为内核空间。
  • 一部分为用户空间。

3.0.3I/O运行过程

我们来看看IO在系统中的运行是怎么样的(我们以read为例)

可以发现的是:当应用程序调用read方法时,是需要等待的—>从内核空间中找数据,再将内核空间的数据拷贝到用户空间的。

  • 这个等待是必要的过程!

下面只讲解用得最多的3个I/0模型:

  • 阻塞I/O
  • 非阻塞I/O
  • I/O多路复用

3.1阻塞I/O模型

在进程(用户)空间中调用recvfrom,其系统调用直到数据包到达且被复制到应用进程的缓冲区中或者发生错误时才返回,在此期间一直等待。

3.2非阻塞I/O模型

recvfrom从应用层到内核的时候,如果没有数据就直接返回一个EWOULDBLOCK错误,一般都对非阻塞I/O模型进行轮询检查这个状态,看内核是不是有数据到来。

3.3I/O复用模型

前面也已经说了:在Linux下对文件的操作是利用文件描述符(file descriptor)来实现的。

在Linux下它是这样子实现I/O复用模型的:

  • 调用select/poll/epoll/pselect其中一个函数,传入多个文件描述符,如果有一个文件描述符就绪,则返回,否则阻塞直到超时。

比如poll()函数是这样子的:int poll(struct pollfd *fds,nfds_t nfds, int timeout);

其中 pollfd 结构定义如下:


struct pollfd {
    int fd;         /* 文件描述符 */
    short events;         /* 等待的事件 */
    short revents;       /* 实际发生了的事件 */
};

  • (1)当用户进程调用了select,那么整个进程会被block;
  • (2)而同时,kernel会“监视”所有select负责的socket;
  • (3)当任何一个socket中的数据准备好了,select就会返回;
  • (4)这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程(空间)。
  • 所以,I/O 多路复用的特点是通过一种机制一个进程能同时等待多个文件描述符,而这些文件描述符其中的任意一个进入读就绪状态,select()函数就可以返回。

select/epoll的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接。

3.4I/O模型总结

正经的描述都在上面给出了,不知道大家理解了没有。下面我举几个例子总结一下这三种模型:

阻塞I/O:

  • Java3y跟女朋友去买喜茶,排了很久的队终于可以点饮料了。我要绿研,谢谢。可是喜茶不是点了单就能立即拿,于是我在喜茶门口等了一小时才拿到绿研。
    • 在门口干等一小时

非阻塞I/O:

  • Java3y跟女朋友去买一点点,排了很久的队终于可以点饮料了。我要波霸奶茶,谢谢。可是一点点不是点了单就能立即拿,同时服务员告诉我:你大概要等半小时哦。你们先去逛逛吧~于是Java3y跟女朋友去玩了几把斗地主,感觉时间差不多了。于是又去一点点问:请问到我了吗?我的单号是xxx。服务员告诉Java3y:还没到呢,现在的单号是XXX,你还要等一会,可以去附近耍耍。问了好几次后,终于拿到我的波霸奶茶了。
    • 去逛了下街、斗了下地主,时不时问问到我了没有

I/O复用模型:

  • Java3y跟女朋友去麦当劳吃汉堡包,现在就厉害了可以使用微信小程序点餐了。于是跟女朋友找了个地方坐下就用小程序点餐了。点餐了之后玩玩斗地主、聊聊天什么的。时不时听到广播在复述XXX请取餐,反正我的单号还没到,就继续玩呗。~~等听到广播的时候再取餐就是了。时间过得挺快的,此时传来:Java3y请过来取餐。于是我就能拿到我的麦辣鸡翅汉堡了。
    • 听广播取餐,广播不是为我一个人服务。广播喊到我了,我过去取就Ok了。

四、使用NIO完成网络通信

4.1NIO基础继续讲解

回到我们最开始的图:

NIO被叫为 no-blocking io,其实是在网络这个层次中理解的,对于FileChannel来说一样是阻塞。

我们前面也仅仅讲解了FileChannel,对于我们网络通信是还有几个Channel的~

所以说:我们通常使用NIO是在网络中使用的,网上大部分讨论NIO都是在网络通信的基础之上的!说NIO是非阻塞的NIO也是网络中体现的!

从上面的图我们可以发现还有一个Selector选择器这么一个东东。从一开始我们就说过了,nio的核心要素有:

  • Buffer缓冲区
  • Channel通道
  • Selector选择器

我们在网络中使用NIO往往是I/O模型的多路复用模型!

  • Selector选择器就可以比喻成麦当劳的广播。
  • 一个线程能够管理多个Channel的状态

4.2NIO阻塞形态

为了更好地理解,我们先来写一下NIO在网络中是阻塞的状态代码,随后看看非阻塞是怎么写的就更容易理解了。

  • 是阻塞的就没有Selector选择器了,就直接使用Channel和Buffer就完事了。

客户端:


public class BlockClient {

    public static void main(String[] args) throws IOException {

        // 1. 获取通道
        SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 6666));

        // 2. 发送一张图片给服务端吧
        FileChannel fileChannel = FileChannel.open(Paths.get("X:\\Users\\ozc\\Desktop\\新建文件夹\\1.png"), StandardOpenOption.READ);

        // 3.要使用NIO,有了Channel,就必然要有Buffer,Buffer是与数据打交道的呢
        ByteBuffer buffer = ByteBuffer.allocate(1024);

        // 4.读取本地文件(图片),发送到服务器
        while (fileChannel.read(buffer) != -1) {

            // 在读之前都要切换成读模式
            buffer.flip();

            socketChannel.write(buffer);

            // 读完切换成写模式,能让管道继续读取文件的数据
            buffer.clear();
        }

        // 5. 关闭流
        fileChannel.close();
        socketChannel.close();
    }
}

服务端:


public class BlockServer {

    public static void main(String[] args) throws IOException {

        // 1.获取通道
        ServerSocketChannel server = ServerSocketChannel.open();

        // 2.得到文件通道,将客户端传递过来的图片写到本地项目下(写模式、没有则创建)
        FileChannel outChannel = FileChannel.open(Paths.get("2.png"), StandardOpenOption.WRITE, StandardOpenOption.CREATE);

        // 3. 绑定链接
        server.bind(new InetSocketAddress(6666));

        // 4. 获取客户端的连接(阻塞的)
        SocketChannel client = server.accept();

        // 5. 要使用NIO,有了Channel,就必然要有Buffer,Buffer是与数据打交道的呢
        ByteBuffer buffer = ByteBuffer.allocate(1024);

        // 6.将客户端传递过来的图片保存在本地中
        while (client.read(buffer) != -1) {

            // 在读之前都要切换成读模式
            buffer.flip();

            outChannel.write(buffer);

            // 读完切换成写模式,能让管道继续读取文件的数据
            buffer.clear();

        }

        // 7.关闭通道
        outChannel.close();
        client.close();
        server.close();
    }
}

此时服务端保存完图片想要告诉客户端已经收到图片啦:

客户端接收服务端带过来的数据:

如果仅仅是上面的代码是不行的!这个程序会阻塞起来!

  • 因为服务端不知道客户端还有没有数据要发过来(与刚开始不一样,客户端发完数据就将流关闭了,服务端可以知道客户端没数据发过来了),导致服务端一直在读取客户端发过来的数据。
  • 进而导致了阻塞!

于是客户端在写完数据给服务端时,显式告诉服务端已经发完数据了!

4.3NIO非阻塞形态

如果使用非阻塞模式的话,那么我们就可以不显式告诉服务器已经发完数据了。我们下面来看看怎么写:

客户端:

public class NoBlockClient {

    public static void main(String[] args) throws IOException {

        // 1. 获取通道
        SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 6666));

        // 1.1切换成非阻塞模式
        socketChannel.configureBlocking(false);

        // 2. 发送一张图片给服务端吧
        FileChannel fileChannel = FileChannel.open(Paths.get("X:\\Users\\ozc\\Desktop\\新建文件夹\\1.png"), StandardOpenOption.READ);

        // 3.要使用NIO,有了Channel,就必然要有Buffer,Buffer是与数据打交道的呢
        ByteBuffer buffer = ByteBuffer.allocate(1024);

        // 4.读取本地文件(图片),发送到服务器
        while (fileChannel.read(buffer) != -1) {

            // 在读之前都要切换成读模式
            buffer.flip();

            socketChannel.write(buffer);

            // 读完切换成写模式,能让管道继续读取文件的数据
            buffer.clear();
        }

        // 5. 关闭流
        fileChannel.close();
        socketChannel.close();
    }
}
 

服务端:


public class NoBlockServer {

    public static void main(String[] args) throws IOException {

        // 1.获取通道
        ServerSocketChannel server = ServerSocketChannel.open();

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

        // 3. 绑定连接
        server.bind(new InetSocketAddress(6666));

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

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

        // 5. 轮训地获取选择器上已“就绪”的事件--->只要select()>0,说明已就绪
        while (selector.select() > 0) {
            // 6. 获取当前选择器所有注册的“选择键”(已就绪的监听事件)
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();

            // 7. 获取已“就绪”的事件,(不同的事件做不同的事)
            while (iterator.hasNext()) {

                SelectionKey selectionKey = iterator.next();

                // 接收事件就绪
                if (selectionKey.isAcceptable()) {

                    // 8. 获取客户端的链接
                    SocketChannel client = server.accept();

                    // 8.1 切换成非阻塞状态
                    client.configureBlocking(false);

                    // 8.2 注册到选择器上-->拿到客户端的连接为了读取通道的数据(监听读就绪事件)
                    client.register(selector, SelectionKey.OP_READ);

                } else if (selectionKey.isReadable()) { // 读事件就绪

                    // 9. 获取当前选择器读就绪状态的通道
                    SocketChannel client = (SocketChannel) selectionKey.channel();

                    // 9.1读取数据
                    ByteBuffer buffer = ByteBuffer.allocate(1024);

                    // 9.2得到文件通道,将客户端传递过来的图片写到本地项目下(写模式、没有则创建)
                    FileChannel outChannel = FileChannel.open(Paths.get("2.png"), StandardOpenOption.WRITE, StandardOpenOption.CREATE);

                    while (client.read(buffer) > 0) {
                        // 在读之前都要切换成读模式
                        buffer.flip();

                        outChannel.write(buffer);

                        // 读完切换成写模式,能让管道继续读取文件的数据
                        buffer.clear();
                    }
                }
                // 10. 取消选择键(已经处理过的事件,就应该取消掉了)
                iterator.remove();
            }
        }

    }
}

还是刚才的需求:服务端保存了图片以后,告诉客户端已经收到图片了。

在服务端上只要在后面写些数据给客户端就好了:

在客户端上要想获取得到服务端的数据,也需要注册在register上(监听读事件)!


public class NoBlockClient2 {

    public static void main(String[] args) throws IOException {

        // 1. 获取通道
        SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 6666));

        // 1.1切换成非阻塞模式
        socketChannel.configureBlocking(false);

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

        // 1.3将通道注册到选择器中,获取服务端返回的数据
        socketChannel.register(selector, SelectionKey.OP_READ);

        // 2. 发送一张图片给服务端吧
        FileChannel fileChannel = FileChannel.open(Paths.get("X:\\Users\\ozc\\Desktop\\新建文件夹\\1.png"), StandardOpenOption.READ);

        // 3.要使用NIO,有了Channel,就必然要有Buffer,Buffer是与数据打交道的呢
        ByteBuffer buffer = ByteBuffer.allocate(1024);

        // 4.读取本地文件(图片),发送到服务器
        while (fileChannel.read(buffer) != -1) {

            // 在读之前都要切换成读模式
            buffer.flip();

            socketChannel.write(buffer);

            // 读完切换成写模式,能让管道继续读取文件的数据
            buffer.clear();
        }


        // 5. 轮训地获取选择器上已“就绪”的事件--->只要select()>0,说明已就绪
        while (selector.select() > 0) {
            // 6. 获取当前选择器所有注册的“选择键”(已就绪的监听事件)
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();

            // 7. 获取已“就绪”的事件,(不同的事件做不同的事)
            while (iterator.hasNext()) {

                SelectionKey selectionKey = iterator.next();

                // 8. 读事件就绪
                if (selectionKey.isReadable()) {

                    // 8.1得到对应的通道
                    SocketChannel channel = (SocketChannel) selectionKey.channel();

                    ByteBuffer responseBuffer = ByteBuffer.allocate(1024);

                    // 9. 知道服务端要返回响应的数据给客户端,客户端在这里接收
                    int readBytes = channel.read(responseBuffer);

                    if (readBytes > 0) {
                        // 切换读模式
                        responseBuffer.flip();
                        System.out.println(new String(responseBuffer.array(), 0, readBytes));
                    }
                }

                // 10. 取消选择键(已经处理过的事件,就应该取消掉了)
                iterator.remove();
            }
        }
    }


}

测试结果:

下面就简单总结一下使用NIO时的要点:

  • 将Socket通道注册到Selector中,监听感兴趣的事件
  • 当感兴趣的时间就绪时,则会进去我们处理的方法进行处理
  • 每处理完一次就绪事件,删除该选择键(因为我们已经处理完了)

4.4管道和DataGramChannel

这里我就不再讲述了,最难的TCP都讲了,UDP就很简单了。

UDP:

管道:

五、总结

总的来说NIO也是一个比较重要的知识点,因为它是学习netty的基础~

想以一篇来完全讲解NIO显然是不可能的啦,想要更加深入了解NIO可以往下面的链接继续学习~

参考资料:

涵盖Java后端所有知识点的开源项目(已有5.8K star):https://github.com/ZhongFuCheng3y/3y

如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号「Java3y」。(微信搜Java3y即可关注)

  • 🔥Java精美脑图
  • 🔥Java学习路线
  • 🔥Java常用的开发工具
  • 🔥原创PDF电子书

PDF文档的内容均为手打,有任何的不懂都可以直接来问我(公众号有我的联系方式)。

在公众号下回复「888」即可获取!!

Docker一键部署Synopsys EDA全家桶(VCS/Verdi 2018版)保姆级教程 本文提供了一份详尽的Docker容器化部署Synopsys EDA工具链(VCS/Verdi 2018版)的保姆级教程。通过环境隔离与标准化,解决传统安装的依赖冲突问题,并重点攻克了容器内GUI图形界面显示的核心难题。教程涵盖从Docker基础安装、镜像获取、容器启动配置,到使用X11转发或VNC实现Verdi完美显示的完整实践,旨在帮助芯片设计工程师快速构建稳定、可移植的一键化EDA工作环境。 阅读详情

相关推荐

一个开源的AI数据分析工具,与数据对话,一键生成SQL、图表、表格、报告和智能商业分析。

Wren AI是一个开源的AI智能代理,让用户能够跟数据对话,实现从文本到SQL的转换,还能生成图表、电子表格、报告以及商业智能分析。它支持多种语言,能深入挖掘数据价值,提供智能数据探索功能,通过语义索引和精心设计的界面,让用户无需写代码即可获取洞察。Wren AI采用独特的架构,包含Wren UI、Wren AI Service和Wren Engine三大核心服务,可部署在本地或云端,助力团队高效、安全地解锁数据潜力。

AI_Charlotte的博客 4496

Java NIO底层原理

1.1. JavaIO读写原理 无论是Socket的读写还是文件的读写,在Java层面的应用开发或者是linux系统底层开发,都属于输入input和输出output的处理,简称为IO读写。在原理上和处理流程上,都是一致的。区别在于参数的不同。 用户程序进行IO的读写,基本上会用到read&write两大系统调用。可能不同操作系统,名称不完全一样,但是功能是一样的。 先强调一个基础知识:read系统调用,并不是把数据直接从物理设备,读数据到内存。write系统调用,也不是直接把数据,写入到物理.

sicaujh的博客 363

[漏洞剖析]JeecgBoot queryFieldBySql注入与JDBC连接滥用:从内存马植入到RCE的完整攻击链

本文深度剖析了JeecgBoot框架中queryFieldBySql接口的SQL注入漏洞及其引发的完整攻击链。攻击者利用该漏洞,通过Freemarker SSTI植入隐蔽的内存马,并进一步滥用JDBC连接测试功能,借助H2数据库特性实现远程命令执行(RCE),揭示了低代码平台中功能滥用与安全配置疏忽带来的严重风险。

weixin_29232121的博客 115

NIO原理详解

1.阻塞与同步 2.BIO与NIO对比 3.NIO简介 4.缓冲区Buffer 5.通道Channel 6.反应堆 7.选择器 8.NIO源码分析 9.AIO 1.阻塞与同步 1)阻塞(Block)和非租塞(NonBlock): 阻塞和非阻塞是进程在访问数据的时候,数据是否准备就绪的一种处理方式,当数据没有准备的时候阻塞:往往需要等待缞冲区...

左耳听风的博客 7万+

YOLOv11模型剪枝实战:让检测速度翻倍,精度仅降1%的终极优化教程

模型剪枝是YOLOv11落地边缘场景的“关键一步”——它让模型在“瘦身”的同时,几乎不损失精度。从稀疏化训练到通道剪枝,再到工程部署,本教程已为你打通全流程。现在就动手尝试,让你的YOLOv11模型在嵌入式设备、移动端上也能“飞起来”——相信我,当你看到剪枝后的模型在资源受限设备上仍能精准检测时,你会明白这份技术的价值。

m0_64661770的博客 711

Coze智能体开发:如何搭建一个生成长文本的工作流

扣子工作流提供了流程定制化的能力,我们可以把长文生成的流程拆分为多个步骤,例如先通过大模型节点生成大纲、再使用循环节点依次扩写各个段落,最后将各个段落组合起来,从而实现长文生成流程。此工作流可适用于学术写作、商业报告、小说创作等多种场景。

王国平的博客 1789

html点击复制到剪贴板

复制到剪贴板     $(document).ready(function(){ if ( window.clipboardData ) { $('.copy_btn').click(function() { window.clipboardData.setData("Text", $(this).prev('input').val())

qq_35048811的博客 3833

RuoYi-Cloud-Plus使用minio进行文件上传图片后无法预览解决_修改minio配置minio桶权限---SpringCloud工作笔记198

在文件管理的位置,发现刚刚上传的图片文件,会显示 预览图片失败 后来经过多方查看,发现是minio的配置的问题。可以看到首先登录RuoYi-Cloud-Plus系统然后,打开文件管理页面可以看到,当上传了图片文件以后。登录以后,选中左侧Buckets,可以看到有自己用的桶,找到以后点击打开。显示文件展示中,文件预览失败,那么这个时候,去修改minio的配置。可以看到,点击配置管理,这里我们启用的是minio对吧。默认是没有配置的这个时候,是允许下载,但不允许预览的.

添柴程序猿的专栏 5568

BIO、NIO、AIO 有什么区别?

BIO、NIO、AIO 有什么区别? BIO:线程发起IO请求,不管内核是否准备好IO操作,从发起请求起,线程一直阻塞,直到操作完成。 NIO:线程发起IO请求,立即返回;内核在做好IO操作的准备之后,通过调用注册的回调函数通知线程做IO操作,线程开始阻塞,直到操作完成。 AIO:线程发起IO请求,立即返回;内存做好IO操作的准备之后,做IO操作,直到操作完成或者失败,通过调用注册的回调...

ConstXiong 2万+

【VTK】VTK中的光标样式

本文分享VTK中的光标设置相关内容技术,希望对各位小伙伴有所帮助!

qq_40041064的博客 1931

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

史上最强 NIO 框架,高性能排名第一!!

作为当前最流行的NIO框架之一,Netty的健壮性、功能、性能、可定制性、可扩展性在同类框架中都是首屈一指的,在互联网领域、大数据分布式计算领域、游戏行业、通信行业等获得了广泛的应用,一些...

JAVA葵花宝典 599

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

基于分布式的抢红包服务器

抢红包服务器是我的一个课程设计,我在做的时候查了很多资料,但是有部分资料是很久之前的,我在这里将我的设计写一下,供大家参考,如果有写的不好的地方请留言指教,谢谢大家。 首先,我拿到题目的时候是,抢红包服务器(分布式锁),抢红包其实是类似于秒杀场景,而题目又提示分布式锁,到网上一查,嘿,很多关于redis秒杀场景的分布式锁。而且redis官方文档关于锁着部分的翻译也可以查到,我的工程中选择的是乐观

懒懒怪先生的博客 4039

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

抖音春晚红包互动为啥全程无宕机?看完字节内部高并发速成笔记我悟了!

今年的春晚,不知道你有没有看。虽然春晚的内容已经引不起我的兴趣,但我还是特意关注了下。 为啥?就冲着抖音花了 12 亿,这春晚如此高的并发流量,能不能顶住! 结果…… 抖音:703 亿次红包互动,全程没有宕机 哈哈,还是让我这个想看戏的看客失望了。(围笑) 703 亿次,想想,肯定是每秒数以亿计的高并发啊,抖音能顶住,而且,全程没有宕机,说明它专门打造的火山引擎牛,技术也确实牛。 就拿这个常见的抢红包来说,看似简单的红包模型,其实覆盖了很多并发点,比如: 拆包算法,是用线性切割法.

Java圈全能架构师的博客 421

字节Java高级岗:java微信红包

Java NIO 基本介绍 Java NIO 全称 java non-blocking IO,是指 JDK 提供的新 API。从 JDK1.4 开始,Java 提供了一系列改进的输入/输出的新特性,被统称为 NIO(即 New IO),是同步非阻塞的 NIO 相关类都被放在 java.nio 包及子包下,并且对原 java.io 包中的很多类进行改写。 NIO 有三大核心部分:Channel(通道),Buffer(缓冲区), Selector(选择器) NIO是 面向缓冲区 ,或者面向 块 编程的。数据读

m0_57768035的博客 128

抖音上春晚,每秒1亿次“高并发”订单的业务,你怎么实现?

今年的春晚,不知道你有没有看。虽然春晚的内容已经引不起我的兴趣,但我还是特意关注了下。 为啥?就冲着抖音花了 12 亿,这春晚如此高的并发流量,能不能顶住! 结果…… 抖音:703 亿次红包互动,全程没有宕机 哈哈,还是让我这个想看戏的看客失望了。(围笑) 703 亿次,想想,肯定是每秒数以亿计的高并发啊,抖音能顶住,而且,全程没有宕机,说明它专门打造的火山引擎牛,技术也确实牛。 就拿这个常见的抢红包来说,看似简单的红包模型,其实覆盖了很多并发点,比如: 拆包算法,是用线性切割..

Java0258的博客 686

软件架构系列文章

http://www.uml.org.cn/zjjs/zjjs-shj.asp 架构设计与实践   解密京东高并发抢购系统的核心逻辑与架构实现   Spring Cloud Netflix 概览和架构设计   TensorFlow架构与设计:会话生命周期   多形态MVC式Web架构:完成实时响应   处

csdn265的博客 1659

系列文章分类汇总

尤娜系列从前,有一个简单的通道系统叫尤娜……尤娜系统的第一次飞行中换引擎的架构垂直拆分改造四种常用的微服务架构拆分方式尤娜,我去面试了专业课回顾白话linux操作系统原理白话TCP/IP原...

xiexiaojing的博客 715

面试被问到微信钉钉的群人数为什么要设上限,懵了。。

作为当前最流行的NIO框架之一,Netty的健壮性、功能、性能、可定制性、可扩展性在同类框架中都是首屈一指的,在互联网领域、大数据分布式计算领域、游戏行业、通信行业等获得了广泛的应用,一些...

weixin_44421461的博客 492

业余草 2018 技术文章合集整理,适合入门、中级、高级、架构师进阶

阅读优秀的文章可以使你快速进步,本文收集了广受好评的优秀IT技术文章,在你有空时读一读,安静下来思考一下。 后端技术文章精选。 WebFlux 静态资源处理教程 简书故障,访问出现 502 Bad Gateway 极客时间《Java并发编程实战》购买返现24 尚硅谷_玩转Scala数据结构和算法视频教程免费下载 说说信号量限流和限流算法的区别 Java 中的 @Immutable...

xmt1139057136的专栏 6079

我撸了一个微信,有点大,请过目...

大家工作生活中可能经常使用微信、钉钉这样的即时通讯工具,作为技术开发的你有没有想过它们后端的技术架构是如何做的?要知道这种亿级海量用户的系统架构难度是非常大的,随便给你说几个架构难点,看下...

路人甲Java 587

吴签事件可以直接调取微信聊天记录吗?在这个技术面前瑟瑟发抖。。。

作为当前最流行的NIO框架之一,Netty的健壮性、功能、性能、可定制性、可扩展性在同类框架中都是首屈一指的,在互联网领域、大数据分布式计算领域、游戏行业、通信行业等获得了广泛的应用,一些...

程序员闪充宝 205

【网络】什么是RPC

RPC 是指计算机 A 上的进程,调用另外一台计算机 B 上的进程,其中 A 上的调用进程被挂起,而 B 上的被调用进程开始执行,当值返回给 A 时,A 进程继续执行。调用方可以通过使用参数将信息传送给被调用方,而后可以通过传回的结果得到信息。而这一过程,对于开发人员来说是透明的。就像后厨的例子一样,服务员把菜单传给后厨,厨师告诉备菜师和洗菜师开始工作,然后他等待他们完成工作。备菜师和洗菜师工作完之后,厨师开始炒菜。这个过程对于服务员来说其实是透明的,他不需要关心到底后厨是怎么做菜的。

weixin_40772077的博客 820

车载桌面开发实战:Activity嵌入与动态集成核心技术解析

Android Activity嵌入是一种在宿主界面中运行第三方应用界面的技术,其核心原理是通过DisplayManager创建虚拟显示,将目标Activity渲染到独立Surface并合成到宿主View中。该技术能实现非侵入式的应用集成,在车载桌面、分屏多任务等场景中具有重要价值。CarLauncher作为车机系统的核心入口,需要稳定承载导航、媒体、车辆状态等多类信息,并动态嵌入第三方应用界面。开发中需重点解决尺寸密度同步、生命周期管理、输入事件分发及性能优化等问题,确保系统稳定流畅运行。

weixin_34023982的博客 312
上一篇: 什么是HDFS?算了,告诉你也不懂。
下一篇: 没想到MySQL还会问这些...
Java3y
Java3y 领域专家: 后端开发技术领域 领域专家: 后端开发技术领域
博客等级 码龄9年 1万+粉丝 509原创
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值