BIO到NIO、多路复用器, 从理论到实践, 结合实际案例对比各自效率与特点(下)

这篇具有很好参考价值的文章主要介绍了BIO到NIO、多路复用器, 从理论到实践, 结合实际案例对比各自效率与特点(下)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

本篇文章是BIO到NIO、多路复用器, 从理论到实践, 结合实际案例对比各自效率与特点(上)的下一篇, 如果没有看的小伙伴, 可以先看下, 不然可能会不连贯.

多路复用器简介

多路复用器是对于传统NIO的优化, 解决了传统NIO无法直接获取所有所有连接的状态, 需要挨个遍历所有连接查看是否准备就绪的问题, 这种方式会涉及到很多次系统调用, 用户态和内核态的切换,效率不高.

那多路复用器是怎样优化的呢?
首先要明白 多路的路是谁-------->其实就是每个IO连接

每个路有没有数据谁知道呢-------->内核知道, 那既然内核自己知道某一时刻有哪些连接是有连接的, 是不是我们直接调用对应功能方法即可, 所以这里就有个多路复用器, 你调用这个多路复用器, 它就会给你返回所有的路的IO状态.

这个就可以通过一次系统调用,获取所有连接的IO状态的结果集
然后程序自己对有状态的(准备好的)连接进行读写,这样才是高性能

这里注意,多说一句, 只要程序自己读写数据, 你的IO模型就是同步的
BIO到NIO、多路复用器, 从理论到实践, 结合实际案例对比各自效率与特点(下),计算机网络,nio,java,数据库

多路复用器的两个阶段

多路复用器有两个阶段, 或者说是内核的两类实现, 这两类实现的最终目的都是一样的, 就是帮你返回所有IO连接的IO状态(是否可读), 但是实现细节有些许差别, 可以理解为epoll是select poll的升级版.

这里还是再提示下, 以下的两种实现讲的操作系统中的实现, 并不是Java中的方法.

  • select poll
    需要把所有IO连接存到一个集合中, 把这个集合传递拷贝给内核, 也就是调用select或者poll, 内核会把集合中准备就绪的连接给个特殊标识, 然后返回.
    这样程序就可以直接知道哪些连接是有状态的, 从而直接进行读取数据
    弊端:
    假如有1w个连接, 每次都需要把这个1w个连接拷贝给内核, 这个拷贝就是损耗点, 每次需要重复拷贝数据给内核.

  • epoll
    正是因为select, poll 有自身的弊端, 这才催生了epoll.
    优化
    以空间换时间, 开辟了内核空间, 缓存了应用程序的连接信息. 这样就不需要重复的拷贝数据.无损耗才是高性能.

    实现步骤
    1. 在一个linux机器上, 有很多的应用程序, 所以一个应用程序想要使用epoll的话, 首先需要在内核中 开辟空间------对应epoll_create系统调用
    2. 然后当连接创建后, 把这个连接加入到该空间------对应epoll_ctl(add)系统调用
    3. 然后才是进行询问, 看看有哪些IO连接准备就绪------对应epoll_wait系统调用

Java中的多路复用器封装

在java.nio的包下,封装了对于多路复用的实习和使用,也就是Selector类

Java中的Seletor底层用的是哪种实现? select poll 还是epoll?
Java其实会在运行的时候会动态的决定使用哪种实现, 因为它会调用固定的方法去启动多路复用器,即Selector.open, 你的程序可能跑在不同的内核之上, jdk会优先选择好的epoll, 但是如果没有epoll这个多路复用器的话,只有select或者poll, 也是可以正常运行的

主要使用方法介绍:
这里有三个主要的方法, 不管底层使用的是哪种实现, 都会调用这三个方法, 但是根据不同实现, 具体做的事情又不一样,区别如下:

  1. Selector.open
    启动多路复用器, 优先选择epoll, 没有的话选择select或者poll.
    如果是epoll的话, 需要在内核中开辟空间, 即调用epoll_create.
  2. register
    select、poll: 会在jvm里建一个数组, 把每个连接对应的文件描述符(fd4)都放进去.
    epoll: 则相当于调用内核方法epoll_ctl(add), 将该连接加入到内核空间, 直接由内核管理.
  3. select
    select、poll: 则会将jvm中的数组传给内核, 即调用select(fd4)或者poll(fd4)
    epoll: 相当于直接调用内核方法epol_wait, 直接询问内核

测试代码

服务端

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;

/**
 * @ClassName:     
 * @Description:(描述这个类的作用)   
 * @author: 
 * @date:        
 *   
 */  
public class SelectorTest {

    private static ServerSocketChannel server=null;
    private static Selector selector;
    static int port=9090;
    static int count=5000;
    static long startTime;

    public static void initServer(){
        try {
            server = ServerSocketChannel.open();
            server.configureBlocking(false);
            server.bind(new InetSocketAddress(port));

            //这里会在编译期间自动选择 多路复用器的 实现
            //可能为select poll 也可能为epoll
            selector = Selector.open();
            server.register(selector, SelectionKey.OP_ACCEPT);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    public static void main(String[] args) {
        initServer();
        System.out.println("服务器启动了......");
        startTime = System.currentTimeMillis();
        try {
            flag:
            while (true){
                //select相当于询问内核有无数据可读取 或者 有无连接可建立
                //里面传入的参数是超时时间,传入0代表阻塞,一直等待有人建立连接或发送数据
                //如果传入的>0, 比如200, 则会最多等待200毫秒,有没有都会返回一个结果
                while(selector.select(0)>0){
                    //从多路复用器中取出所有有效的key
                    Set<SelectionKey> selectionKeys = selector.selectedKeys();
                    Iterator<SelectionKey> iterator = selectionKeys.iterator();
                    while(iterator.hasNext()){
                        SelectionKey key = iterator.next();
                        //获取之后要进行移除,否则会重复获取
                        iterator.remove();
                        //有新连接可建立
                        if(key.isAcceptable()){
                            acceptHander(key);
                        //可以进行读取
                        }else if(key.isReadable()){
                            readHander(key);
                        }
                    }
                    if(count <= 0){
                        System.out.println("处理5000个连接用时:"+(System.currentTimeMillis()-startTime)/1000+"s");
                        server.close();
                        selector.close();
                        break flag;
                    }
                }


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

    }

    private static void readHander(SelectionKey key) {
        //取出当前key所关联的客户端
        SocketChannel client = (SocketChannel) key.channel();
        //取出该客户端 对应的  buffer
        //这个buffer是我们建立连接时传进去和 channel一对一绑定的
        ByteBuffer buffer = (ByteBuffer) key.attachment();

        buffer.clear();
        int read=0;
        try {
            for(;;){
                //从channel中读取数据写入到buffer中
                read = client.read(buffer);
                if(read==0){
                    break;
                //这里可能有bug,客户端可能关掉,处理close_wait状态, 会一直监听到这个事件
                // 这里直接简单暴力的关掉
                }else if(read<0){
                    client.close();
                    break;
                }else{
                    //对于buffer,刚刚是写,现在进行读操作,调用flip
                    buffer.flip();

                    byte[] bytes = new byte[buffer.limit()];
                    buffer.get(bytes);

                    String str = new String(bytes);
                    System.out.println(client.socket().getRemoteSocketAddress()+" -->" +str);
                }

            }

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

        }


    }

    private static void acceptHander(SelectionKey key) {
        try {
            ServerSocketChannel channel = (ServerSocketChannel) key.channel();
            SocketChannel client = channel.accept();
            client.configureBlocking(false);
            ByteBuffer buffer = ByteBuffer.allocate(8192);
            //将这个新连接交给多路复用器去管理,后面多路复用器中才能监控这个连接, 在我们去获取的时候,给我们返回有状态的连接
            //同时这里将channel和buffer 一对一 进行绑定,可以很方便的往里写入, 或者 读出来
            client.register(selector, SelectionKey.OP_READ,buffer);
            System.out.println("add client port:"+client.socket().getPort());

            count--;


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

    }

}

测试使用的客户端代码还是和上篇文章中保持一致, 这里不再放了.

压测结果

以上所有说的都是理论, 而理论一定是需要实际结果来验证的, 我们这里就还是同样处理5000个连接, 并接收同样消息, 看看多路复用器的实际效果如何.
BIO到NIO、多路复用器, 从理论到实践, 结合实际案例对比各自效率与特点(下),计算机网络,nio,java,数据库

可以看到, 效果是非常非常明显的, 比BIO,NIO都要快太多了, 而且还代码还是单线程模型, 将其扩展成多线程, 效率将会更高.

总结

从BIO -> NIO -> 多路复用器, 我们分析了各自的缺点及演变过程, 并是实际结果对比了各自的效率, 相信你会更加印象深刻.

针对本文的测试结果总结如下:

BIO到NIO、多路复用器, 从理论到实践, 结合实际案例对比各自效率与特点(下),计算机网络,nio,java,数据库

今天的分享就到这里了,有问题可以在评论区留言,均会及时回复呀.
我是bling,未来不会太差,只要我们不要太懒就行, 咱们下期见.
BIO到NIO、多路复用器, 从理论到实践, 结合实际案例对比各自效率与特点(下),计算机网络,nio,java,数据库文章来源地址https://www.toymoban.com/news/detail-690022.html

到了这里,关于BIO到NIO、多路复用器, 从理论到实践, 结合实际案例对比各自效率与特点(下)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 阻塞非阻塞IO(BIO和NIO),IO多路复用

    1.概念 NIO(New Input/Output)和BIO(Blocking Input/Output)是Java中用于处理输入输出的两种不同的模型。   BIO 会 阻塞 ,等有了消息,立刻返回,一个线程处理一个recv(需要很多线程)。 NIO 有没有消息,都返回(但程序要自己判断,返回空就循环重复);一个线程可以处理多个

    2024年02月09日
    浏览(28)
  • 从Java BIO到NIO再到多路复用,看这篇就够了

    目录 从一次优化说起 IO模型分类 分类 举例 概念详解 阻塞和非阻塞 同步与异步 Java支持版本 实战 c10k问题 上代码 BIO服务端 NIO服务端​​​​​​​ 多路复用 概念 阶段一:selectpoll 阶段二epoll Java selector 后记         近期优化了一个老的网关系统,在dubbo调用接口rt100

    2024年02月08日
    浏览(32)
  • AI计算中的光学模块:波分复用器的应用前景

    在人工智能(AI)的计算领域,光学模块扮演着至关重要的角色。随着AI技术的飞速发展,对数据处理速度和带宽的需求日益增长。光学模块,特别是波分复用器(WDM),因其高速、大容量的数据传输能力而成为研究和应用的热点。 ADOP-DWDM40路波分 一、波分复用器在AI计算中的

    2024年04月29日
    浏览(22)
  • IO、NIO、IO多路复用

    IO是什么? 网络IO是如何连接的? 下面是一次网络读取内容的I/O示意图,数据先从外设(网卡)到内核空间,再到用户空间(JVM),最后到应用程序的一个过程。 上述一次I/O读取,所谓的阻塞和非阻塞体现在哪里呢? Java最早期的版本的I/O就是这样实现的。当程序调用到读取

    2024年01月20日
    浏览(29)
  • Java NIO原理 (Selector、Channel、Buffer、零拷贝、IO多路复用)

    系列文章目录和关于我 最近有很多想学的,像netty的使用、原理源码,但是苦于自己对于操作系统和nio了解不多,有点无从下手,遂学习之。 上图粗略描述了网络io的过程,了解其中的拷贝过程有利于我们理解非阻塞io,以及IO多路复用的必要性。 数据从网卡到内核缓冲区 网

    2024年02月08日
    浏览(29)
  • NIO与BIO

    当谈到 Java 网络编程时,经常会听到两个重要的概念:BIO(Blocking I/O,阻塞 I/O)和 NIO(Non-blocking I/O,非阻塞 I/O)。它们都是 Java 中用于处理 I/O 操作的不同编程模型。 BIO 是 Java 最早的 I/O 模型,也是最简单的一种。在 BIO 模型中,每个 I/O 操作都会阻塞当前线程,直到数据

    2024年04月10日
    浏览(29)
  • BIO、NIO线程模型

    BIO(Blocking IO): 同步阻塞模型,一个客户端连接对应一个处理线程,即:一个线程处理一个客户端请求。     单线程版本: 服务端在处理完第一个客户端的所有事件之前,无法为其他客户端提供服务。     多线程版本:如果出现大量只连接不发数据的话,那么就会一直占用

    2024年02月09日
    浏览(27)
  • 33. bio和nio

    1.1 bio网络模型示意图 单个客户端向服务器发起请求时,请求顺序如下: 多个客户端向一个服务器发起请求时,请求顺序如下: 1.2 bio网络模型缺点 1.阻塞式I/O 2.弹性伸缩能力差 3.多线程耗费资源 2.1 nio网络模型示意图 单个客户端向服务器发起请求时,请求顺序如下: 2.2 ni

    2024年02月17日
    浏览(25)
  • BIO、NIO和AIO

    目录 一.引言 何为IO IO的过程 Java的3种网络IO模型 阻塞和非阻塞IO IO多路复用 异步和同步IO 二.BIO 三.NIO 1. 三大组件 Channel Buffer Selector 2.ByteBuffer 2.1ByteBuffer的使用 2.2ByteBuffer 结构 ​2.3ByteBuffer的常用方法 分配空间   向 buffer 写入数据 从 buffer 读取数据 字符串与 ByteBuffer 互转 分

    2024年02月12日
    浏览(21)
  • BIO、NIO、AIO区别详解

    主线程发起io请求后,需要等待当前io操作完成,才能继续执行。 引入selector、channel、等概念,当主线程发起io请求后,轮询的查看系统是否准备好执行io操作,没有准备好则主线程不会阻塞会继续执行,准备好主线程会阻塞等待io操作完成。 主线程发起io请求后,不会阻塞,

    2024年02月07日
    浏览(29)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包