0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

探究Redis网络模型究竟有多强大(下)

jf_78858299 来源:蝉沐风的码场 作者:蝉沐风 2023-03-03 09:50 次阅读

4. 非阻塞I/O(NonBlocking I/O)

上文花了太多的笔墨描述BIO,接下来的非阻塞IO我们只抓主要矛盾,其余参考BIO即可。

如果你看过其他介绍非阻塞IO的文章,下面这个图片你多少会有点眼熟。

图片

NIO模型

非阻塞IO指的是进程发起系统调用之后,内核不会将进程投入睡眠,而是会立即返回一个结果,这个结果可能恰好是我们需要的数据,又或者是某些错误。

你可能会想,这种非阻塞带来的轮询有什么用呢?大多数都是空轮询,白白浪费CPU而已,还不如让进程休眠来的合适。

4.1 Java的非阻塞实现

这个问题暂且搁置一下,我们先看Java在语法层面是如何提供非阻塞功能的,细节慢慢聊。

public class NoBlockingServer {

    public static List<SocketChannel> channelList = new ArrayList<>();

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

        try {
            // 相当于serverSocket
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            // 将监听socket设置为非阻塞
            serverSocketChannel.configureBlocking(false);
            serverSocketChannel.socket().bind(new InetSocketAddress(8099));
            while (true) {
                // 这里将不再阻塞
                SocketChannel socketChannel = serverSocketChannel.accept();

                if (socketChannel != null) {
                    // 将连接socket设置为非阻塞
                    socketChannel.configureBlocking(false);
                    channelList.add(socketChannel);
                } else {
                    System.out.println("没有客户端连接!!!");
                }

                for (SocketChannel client : channelList) {
                    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                    // read也不阻塞
                    int num = client.read(byteBuffer);
                    if (num > 0) {
                        System.out.println("收到客户端【" + client.socket().getPort() + "】数据:" + new String(byteBuffer.array()));
                    } else {
                        System.out.println("等待客户端【" + client.socket().getPort() + "】写数据");
                    }
                }

                // 加个睡眠是为了避免strace产生大量日志,否则不好追踪
                Thread.sleep(1000);

            }

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

Java提供了新的APIServerSocketChannel以及SocketChannel,相当于BIO中的ServerSocketSocket。此外,通过下面两行的配置,将监听socket和连接socket设置为非阻塞。

// 将监听socket设置为非阻塞
serverSocketChannel.configureBlocking(false);

// 将连接socket设置为非阻塞
socketChannel.configureBlocking(false);

我们上文强调过, Java自身并没有将socket设置为非阻塞的本事,一定是在某个时间点上,操作系统内核提供了这个功能,才使得Java设计出了新的API来提供非阻塞功能

之所以需要上面两行代码的显式设置,也恰好说明了内核是默认将socket设置为阻塞状态的,需要非阻塞,就得额外调用其他系统调用。我们通过man命令查看一下socket()这个方法(截图的中间省略了一部分内容):

man 2 socket

f336ca741589b768db4fecb030b6b432.png

image-20221225144028751

我们可以看到socket()函数提供了SOCK_NONBLOCK这个类型,可以通过fcntl()这个方法将socket从默认的阻塞修改为非阻塞,不管是对监听socket还是连接socket都是一样的。

4.2 Java的非阻塞解释

现在解释上面提到的问题:这种非阻塞带来的轮询有什么用?观察一下上面的代码就可以发现,我们全程只使用了1个main线程就解决了所有客户端的连接以及所有客户端的读写操作。

serverSocketChannel.accept();会立即返回调用结果。

返回的结果如果是一个SocketChannel对象(系统调用底层就是个socket描述符),说明有客户端连接,这个SocketChannel就表示了这个连接;然后利用socketChannel.configureBlocking(false);将这个连接socket设置为非阻塞。这个设置非常重要,设置之后对连接socket所有的读写操作都变成了非阻塞,因此接下来的client.read(byteBuffer);并不会阻塞while循环,导致新的客户端无法连接。再之后将该连接socket加入到channelList队列中。

如果返回的结果为空(底层系统调用返回了错误),就说明现在还没有新的客户端要连接监听socket,因此程序继续向下执行,遍历channelList队列中的所有连接socket,对连接socket进行读操作。而读操作也是非阻塞的,会理解返回一个整数,表示读到的字节数,如果>0,则继续进行下一步的逻辑处理;否则继续遍历下一个连接socket。

下面给出一张accept()返回一个连接socket情况下的动图,希望对大家理解整个流程有帮助。

4.3 掀开非阻塞IO的底裤

我将上面的程序在CentOS下再次用strace程序追踪一下,具体步骤不再赘述,下面是out日志文件的内容(我忽略了绝大多数没用的)。

图片

非阻塞IO的系统调用分析

4.4 非阻塞IO总结

图片

NIO模型

再放一遍这个图,有一个细节需要大家注意,系统调用向内核要数据时,内核的动作分成两步:

  1. 等待数据(从网卡缓冲区拷贝到内核缓冲区)
  2. 拷贝数据(数据从内核缓冲区拷贝到用户空间)

只有在第1步时,系统调用是非阻塞的,第2步进程依然需要等待这个拷贝过程,然后才能返回,这一步是阻塞的。

非阻塞IO模型仅用一个线程就能处理所有操作,对比BIO的一个客户端需要一个线程而言进步还是巨大的。但是他的致命问题在于会不停地进行系统调用,不停的进行accept(),不停地对连接socket进行read()操作,即使大部分时间都是白忙活。要知道,系统调用涉及到用户空间和内核空间的多次转换,会严重影响整体性能。

所以,一个自然而言的想法就是,能不能别让进程瞎轮询。

比如有人告诉进程监听socket是不是被连接了,有的话进程再执行accept();比如有人告诉进程哪些连接socket有数据从客户端发送过来了,然后进程只对有数据的连接socket进行read()

这个方案就是 I/O多路复用

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • IO
    IO
    +关注

    关注

    0

    文章

    434

    浏览量

    39056
  • 非阻塞
    +关注

    关注

    0

    文章

    13

    浏览量

    2164
  • Redis
    +关注

    关注

    0

    文章

    370

    浏览量

    10832
收藏 人收藏

    评论

    相关推荐

    小米5的那颗核心,究竟有多强

    小米5的那颗核心,究竟有多强自从小米在2011年崛起后,高通的骁龙系列处理器就逐渐成为了市面上旗舰手机的主流处理器,从蝎子核心到环蛇核心,再到现在的骁龙810,高通一直在进步。那么,这款骁龙810
    发表于 06-01 19:35

    Redis Stream应用案例

    今天介绍的主角——Redis Stream,本身就是起源于IRC中一个用户的idea。IRC的模型如下,在某个IRC频道中的用户,既可以向所有的其他用户自由的发送消息,也可以接收其他所有用户发送
    发表于 06-26 17:15

    液晶PC与液晶电视究竟有什么区别?

    为什么要选择液晶?液晶PC与液晶电视究竟有什么区别?如何选择液晶PC与液晶电视?
    发表于 06-07 06:13

    请问一RFID与NFC究竟有什么关系?

    RFID与NFC究竟有什么关系?
    发表于 06-15 07:06

    面向列的HBase存储结构究竟有什么样的不同之处呢?

    HBase是什么?HBase的存储结构究竟是怎样的呢?面向列的HBase存储结构究竟有什么样的不同之处呢?
    发表于 06-16 06:52

    请问一芯片制造究竟有多难?

    请问一芯片制造究竟有多难?
    发表于 06-18 06:53

    PCI-E4.0究竟有什么优势?

    PCI-E4.0究竟有什么优势?PCI-E究竟指的是什么呢?
    发表于 06-18 06:54

    内存时序究竟有多重要呢?究竟该如何去选择内存条呢?

    内存时序究竟有多重要呢?究竟该如何去选择内存条呢?DDR内存时序是高一些好还是低一些好?
    发表于 06-18 08:20

    定时器中断类型探究 精选资料分享

     一直在用的stm32定时器的中断都是TIM_IT_Update更新中断,也没问为什么,直到碰到有人使用TIM_IT_CC1中断,才想到这定时器的中断类型究竟有什么区别,都怪当时学习stm32的时候
    发表于 08-13 06:28

    OpenPLC开源工业控制器究竟有何用处

    OpenPLC开源工业控制器有哪些优点?OpenPLC开源工业控制器有哪些功能?OpenPLC开源工业控制器究竟有何用处?
    发表于 09-02 07:42

    华为荣耀Magic今日发布:“未来”手机究竟有多强

    华为荣耀即将在12月16日发布最新的“未来”手机magic,关于这款手机的爆料在今日已经铺天盖地,今天,小编将为大家整理一,给大家一个荣耀Magic的基本判断,看看这款旗舰究竟有多强力!
    发表于 12-16 09:34 3313次阅读

    ibm的2nm芯片究竟有多强 2nm芯片对续航的影响

    全球首颗2nm芯片的问世对半导体行业影响重大,IBM通过与AMD、三星及GlobalFoundries等多家公司的合作,最终抵达了2nm芯片制程的节点,推出了2nm的测试芯片。那么这颗芯片究竟有多强呢?它对续航的影响又有多大呢?
    的头像 发表于 06-23 09:35 2053次阅读

    Molex莫仕连接器的功能究竟有多强大?看他们的行业应用你就知道了!

    KOYUELEC光与电子:Molex莫仕连接器的功能究竟有多强大?看他们的行业应用你就知道了!
    的头像 发表于 12-31 12:30 1w次阅读

    探究Redis网络模型究竟有多强大(上)

    本文将从BIO开始介绍,经过NIO、多路复用,最终说回Redis的Reactor模型,力求详尽。本文与其他文章的不同点主要在于:
    的头像 发表于 03-03 09:46 423次阅读
    <b class='flag-5'>探究</b><b class='flag-5'>Redis</b><b class='flag-5'>网络</b><b class='flag-5'>模型</b><b class='flag-5'>究竟有多强大</b>(上)

    探究Redis网络模型究竟有多强大(中)

    创建socket这一步和客户端没啥区别,不同的是这个socket我们称之为 **等待连接socket(或监听socket)** 。 #### 3.2.2 绑定端口号 `bind()`函数会将端口号写入上
    的头像 发表于 03-03 09:49 331次阅读
    <b class='flag-5'>探究</b><b class='flag-5'>Redis</b><b class='flag-5'>网络</b><b class='flag-5'>模型</b><b class='flag-5'>究竟有多强大</b>(中)