简述IO(BIO NIO IO多路复用)

在unix网络变成中的五种IO模型:

Blocking IO(阻塞IO)
NoneBlocking IO (非阻塞IO)
IO mulitplexing(IO多路复用)
signal driven IO (信号驱动IO)
asynchronous IO (异步IO)

BIO

BIO(Blocking IO)是一种阻塞IO模型,也是传统的IO操作模型之一。
在BIO模型中,当一个IO操作被调用时,进程会被阻塞,直到当前操作完成或发生超时。这意味着当一个IO操作进行时,进程无法执行其他任务,必须等待操作完成后才能继续。
在BIO模型中,通常通过调用系统提供的阻塞IO函数(如read和write)来进行IO操作。这些函数会一直等待,直到操作完成或发生阻塞。例如,当读取数据时,如果没有数据可用,读取操作将一直等待,直到有数据到达。
serverBIOdemo:

public class RedisServerBIO{public static void main(String[] args) throws IOException{ServerSocket serverSocket = new ServerSocket(6379);while(true) {System.out.println("-----111 等待连接");Socket socket = serverSocket.accept();//阻塞1 ,等待客户端连接System.out.println("-----222 成功连接");InputStream inputStream = socket.getInputStream();int length = -1;byte[] bytes = new byte[1024];System.out.println("-----333 等待读取");while((length = inputStream.read(bytes)) != -1)//阻塞2 ,等待客户端发送数据{System.out.println("-----444 成功读取"+new String(bytes,0,length));System.out.println("====================");System.out.println();}inputStream.close();socket.close();}}
}

BIOclient1 和client2 代码一致,如下:

public class RedisClient01{public static void main(String[] args) throws IOException{Socket socket = new Socket("127.0.0.1",6379);OutputStream outputStream = socket.getOutputStream();while(true){Scanner scanner = new Scanner(System.in);String string = scanner.next();if (string.equalsIgnoreCase("quit")) {break;}socket.getOutputStream().write(string.getBytes());System.out.println("------input quit keyword to finish......");}outputStream.close();socket.close();}
}

程序启动后,发现如果客户端安与服务端建立了连接,如果这个链接的客户端迟迟不发数据,线程就会一直堵在read方法上,其他客户端也不能进行连接,也就是说,一次只能处理一个客户端,这显然不符合实际场景.那么,如何解决呢?
改用多线程方式,server代码如下:

public class RedisServerBIOMultiThread{public static void main(String[] args) throws IOException {ServerSocket serverSocket = new ServerSocket(6379);while(true) {//System.out.println("-----111 等待连接");Socket socket = serverSocket.accept();//阻塞1 ,等待客户端连接//System.out.println("-----222 成功连接");new Thread(() -> {try {InputStream inputStream = socket.getInputStream();int length = -1;byte[] bytes = new byte[1024];System.out.println("-----333 等待读取");while((length = inputStream.read(bytes)) != -1)//阻塞2 ,等待客户端发送数据{System.out.println("-----444 成功读取"+new String(bytes,0,length));System.out.println("====================");System.out.println();}inputStream.close();socket.close();} catch (IOException e) {e.printStackTrace();}},Thread.currentThread().getName()).start();System.out.println(Thread.currentThread().getName());}}
}

多线程模型
每来一个客户端,就要开辟一个线程,如果来1万个客户端,那就要开辟1万个线程。在操作系统中用户态不能直接开辟线程,需要调用内核来创建的一个线程,这其中还涉及到用户状态的切换(上下文的切换),十分耗资源.

总结:

BIO模型的优点是简单易用,对于一些IO操作不频繁的应用来说,可以满足需求。然而,由于阻塞特性,BIO模型在高并发环境下可能存在性能瓶颈。当有多个IO操作需要处理时,每个操作都需要等待上一个操作完成,效率相对较低。 如果改用多线程模型,那么在并发搞得情况下,十分耗费资源.

NIO

NIO(Non-blocking IO)是一种非阻塞IO模型,它是Java中提供的一套IO操作的新API.NIO在处理IO操作时采用了事件驱动的方式,不再像BIO模型那样使用阻塞IO函数。相反,NIO使用一组新的抽象概念,如通道(Channel)和缓冲区(Buffer),以及选择器(Selector)来实现非阻塞IO。在NIO模型中,通过将IO操作注册到选择器上,进程可以继续执行其他任务,在IO操作就绪时,通过处理事件来完成相应的操作。与BIO模型不同,NIO模型允许同时处理多个IO操作,而不需要为每个操作创建一个线程或进程。
在NIO模式中,一切都是非阻塞的.accept()方法是非阻塞的,如果没有客户端连接,就返回无连接标识, read()方法是非阻塞的,如果read()方法读取不到数据就返回空闲中标识,如果读取到数据时只阻塞read()方法读数据的时间.
在NIO模式中,只有一个线程:当一个客户端与服务端进行连接,这个socket就会加入到一个数组中,隔一段时间遍历一次,看这个socket的read()方法能否读到数据,这样一个线程就能处理多个客户端的连接和读取了.
NIO的主要组件包括:
通道(Channel):负责传输数据,可以是文件、网络连接等。通道是双向的,可以同时进行读取和写入操作。
缓冲区(Buffer):存储数据的区域,用于读取和写入数据。
选择器(Selector):用于监听通道的事件,当一个或多个通道就绪时,可以通过选择器选取就绪的通道进行处理。
ServerNIOdemo:

public class RedisServerNIO{static ArrayList<SocketChannel> socketList = new ArrayList<>();static ByteBuffer byteBuffer = ByteBuffer.allocate(1024);public static void main(String[] args) throws IOException{System.out.println("---------RedisServerNIO 启动等待中......");ServerSocketChannel serverSocket = ServerSocketChannel.open();serverSocket.bind(new InetSocketAddress("127.0.0.1",6379));serverSocket.configureBlocking(false);//设置为非阻塞模式while (true){for (SocketChannel element : socketList){int read = element.read(byteBuffer);if(read > 0) {System.out.println("-----读取数据: "+read);byteBuffer.flip();byte[] bytes = new byte[read];byteBuffer.get(bytes);System.out.println(new String(bytes));byteBuffer.clear();}}SocketChannel socketChannel = serverSocket.accept();if(socketChannel != null) {System.out.println("-----成功连接: ");socketChannel.configureBlocking(false);//设置为非阻塞模式socketList.add(socketChannel);System.out.println("-----socketList size: "+socketList.size());}}}
}

NIOclient1 和client2 代码一致,如下:

public class NIOClient01{public static void main(String[] args) throws IOException{System.out.println("------RedisClient01 start");Socket socket = new Socket("127.0.0.1",6379);OutputStream outputStream = socket.getOutputStream();while(true){Scanner scanner = new Scanner(System.in);String string = scanner.next();if (string.equalsIgnoreCase("quit")) {break;}socket.getOutputStream().write(string.getBytes());System.out.println("------input quit keyword to finish......");}outputStream.close();socket.close();}
}

NIO成功的解决了BIO需要开启多线程的问题,NIO中一个线程就能解决多个socket,但是还存在2个问题:
问题一:
这个模型在客户端少的时候十分好用,但是客户端如果很多,比如有1万个客户端进行连接,那么每次循环就要遍历1万个socket,如果一万个socket中只有10个socket有数据,也会遍历一万个socket,就会做很多无用功,每次遍历遇到 read 返回 -1 时仍然是一次浪费资源的系统调用。
问题二:
而且这个遍历过程是在用户态进行的,用户态判断socket是否有数据还是调用内核的read()方法实现的,这就涉及到用户态和内核态的切换,每遍历一个就要切换一次,开销很大因为这些问题的存在

优点:不会阻塞在内核的等待数据过程,每次发起的 I/O 请求可以立即返回,不用阻塞等待,实时性较好。
缺点:轮询将会不断地询问内核,这将占用大量的 CPU 时间,系统资源利用率较低,所以一般 Web 服务器不使用这种 I/O 模型。
结论:NIO模型在高并发环境下有很好的性能和可伸缩性,因为它可以通过一个线程处理多个IO操作。相比于BIO模型,NIO模型能够更高效地处理大量的并发连接,适用于构建高性能的网络服务器。
让Linux内核搞定上述需求,我们将一批文件描述符通过一次系统调用传给内核由内核层去遍历,才能真正解决这个问题。IO多路复用应运而生,也即将上述工作直接放进Linux内核,不再两态转换而是直接从内核获得结果,因为内核是非阻塞的。

IO多路复用

I/O multiplexing 这里面的 multiplexing 指的其实是在单个线程通过记录跟踪每一个Sock(I/O流)的状态来同时管理多个I/O流. 目的是尽量多的提高服务器的吞吐能力。
大家都用过nginx,nginx使用epoll接收请求,ngnix会有很多链接进来, epoll会把他们都监视起来,然后像拨开关一样,谁有数据就拨向谁,然后调用相应的代码处理。redis类似同理.

IO multiplexing就是我们说的select,poll,epoll,有些技术书籍也称这种IO方式为event driven IO事件驱动IO。就是通过一种机制,一个进程可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。可以基于一个阻塞对象并同时在多个描述符上等待就绪,而不是使用多个线程(每个文件描述符一个线程,每次new一个线程),这样可以大大节省系统资源。所以,I/O 多路复用的特点是通过一种机制一个进程能同时等待多个文件描述符而这些文件描述符(套接字描述符)其中的任意一个进入读就绪状态,select,poll,epoll等函数就可以返回。

redis的IO多路复用

Redis利用epoll来实现IO多路复用,将连接信息和事件放到队列中,依次放到事件分派器,事件分派器将事件分发给事件处理器。

Redis 服务采用 Reactor 的方式来实现文件事件处理器(每一个网络连接其实都对应一个文件描述符) 所谓 I/O 多路复用机制,就是说通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或写就绪),能够通知程序进行相应的读写操作。这种机制的使用需要 select 、 poll 、 epoll 来配合。多个连接共用一个阻塞对象,应用程序只需要在一个阻塞对象上等待,无需阻塞等待所有连接。当某条连接有新的数据可以处理时,操作系统通知应用程序,线程从阻塞状态返回,开始进行业务处理。

reactor设计模式

基于 I/O 复用模型:多个连接共用一个阻塞对象,应用程序只需要在一个阻塞对象上等待,无需阻塞等待所有连接。当某条连接有新的数据可以处理时,操作系统通知应用程序,线程从阻塞状态返回,开始进行业务处理。
Reactor 模式,是指通过一个或多个输入同时传递给服务处理器的服务请求的事件驱动处理模式。服务端程序处理传入多路请求,并将它们同步分派给请求对应的处理线程,Reactor 模式也叫 Dispatcher 模式。即 I/O 多了复用统一监听事件,收到事件后分发(Dispatch 给某进程),是编写高性能网络服务器的必备技术。

Reactor 模式中有 2 个关键组成:
1)Reactor:Reactor 在一个单独的线程中运行,负责监听和分发事件,分发给适当的处理程序来对 IO 事件做出反应。 它就像公司的电话接线员,它接听来自客户的电话并将线路转移到适当的联系人;
2)Handlers:处理程序执行 I/O 事件要完成的实际事件,类似于客户想要与之交谈的公司中的实际办理人。Reactor 通过调度适当的处理程序来响应 I/O 事件,处理程序执行非阻塞操作。
Reactor模式的优点包括:
高效的并发处理:通过使用IO多路复用,Reactor模式能够并发处理多个IO事件,提高系统的吞吐量和响应性能。
可伸缩性:Reactor模式能够轻松地扩展到大量的并发连接,处理更多的请求。
结构化的设计:Reactor模式通过将IO事件和处理程序分离,使得系统设计更加结构化,易于理解和维护。

select poll epoll

select

select 其实就是把NIO中用户态要遍历的fd数组(我们的每一个socket链接,安装进ArrayList里面的那个)拷贝到了内核态,让内核态来遍历,因为用户态判断socket是否有数据还是要调用内核态的,所有拷贝到内核态后,这样遍历判断的时候就不用一直用户态和内核态频繁切换了.
select 函数执行流程:
1.select是一个阻塞函数,当没有数据时,会一直阻塞在select那一行
2.当有数据时会将rset中对应的那一位置为1
3.select函数返回,不在阻塞
4.便利文件描述符数组,判断那个fd被置位了
5.读取数据,然后处理

从代码中可以看出,select系统调用后,返回了一个置位后的&rset,这样用户态只需进行很简单的二进制比较,就能很快知道哪些socket需要read数据,有效提高了效率.
缺点:
1、bitmap最大1024位,一个进程最多只能处理1024个客户端
2、&rset不可重用,每次socket有数据就相应的位会被置位
3、文件描述符数组拷贝到了内核态(只不过无系统调用切换上下文的开销。(内核层可优化为异步事件通知)),仍然有开销。select 调用需要传入 fd 数组,需要拷贝一份到内核,高并发场景下这样的拷贝消耗的资源是惊人的。(可优化为不复制)
4、select并没有通知用户态哪一个socket有数据,仍然需要O(n)的遍历。select 仅仅返回可读文件描述符的个数,具体哪个可读还是要用户自己遍历。(可优化为只返回给用户就绪的文件描述符,无需用户做无效的遍历)
总结:
select方式,既做到了一个线程处理多个客户端连接(文件描述符),又减少了系统调用的开销(多个文件描述符只有一次 select 的系统调用 + N次就绪状态的文件描述符的 read 系统调用)

poll

poll的执行流程:

  • 1.将5个fd从用户带拷贝到内核态
  • 2.poll为阻塞方法,执行poll方法,如果有数据会将fd对应的revents置位pollin
  • 3.poll方法返回
  • 4.循环遍历,查找哪个fd被置位pollin了
  • 5.将revents充值为0 便于复用
  • 6.对置位的fd进行读取和处理

     

     

    poll解决了bitmap大小限制,也解决了rset不可重复用的情况.

优点

  • poll使用pollfd数组来代替select中的bitmap,数组没有1024的限制,可以一次管理更多的client。它和 select 的主要区别就是,去掉了 select 只能监听 1024 个文件描述符的限制
  • 当pollfds数组中有事件发生,相应的revents置位为1,遍历的时候又置位回零,实现了pollfd数组的重用

缺点

  • pollfds数组拷贝到了内核态,仍然有开销
  • poll并没有通知用户态哪一个socket有数据,仍然需要O(n)的遍历
epoll

在2002年呗大神David Libenzi(戴维德·利本兹)发明出来的

int epoll_create(int size);//创建一个epoll句柄; 参数size并不是限制了epoll所能监听的描述符最大个数,只是对内核初始分配内部数据结构的一个建议;
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);//向内核添加 修改 或者删除要监控的文件描述符
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);//等待epfd上的io事件,最多返回maxevents个事件。参数events用来从内核得到事件的集合,maxevents告之内核这个events有多大。 类似发起select()调用

epoll的执行流程:(epoll是非阻塞的)

  • 1.当有数据的时候,会把相应的文件描述符"置位",但是epoll没有revent标志位,所以并不是真正的置位.这时候会把所有数据的文件描述符放到队首.
  • 2.epoll会返回有数据的文件描述符的个数
  • 3.根据返回的个数,读取前N个文件描述符即可
  • 4.读取,处理

     

总结
多路复用快的原因在于,操作系统提供了这样的系统调用,使得原来的 while 循环里多次系统调用,变成了一次系统调用 + 内核层遍历这些文件描述符。
epoll是现在最先进的IO多路复用器,Redis、Nginx,linux中的Java NIO都使用的是epoll。
这里“多路”指的是多个网络连接,“复用”指的是复用同一个线程。
1、一个socket的生命周期中只有一次从用户态拷贝到内核态的过程,开销小
2、使用event事件通知机制,每次socket中有数据会主动通知内核,并加入到就绪链表中,不需要遍历所有的socket

总结:

NIO是一种非阻塞IO模型,它使用事件驱动的方式处理IO操作,通过通道、缓冲区和选择器等组件提供高性能的IO操作能力。在Java中,NIO对应的类库为java.nio包。

在多路复用IO模型中,会有一个内核线程不断地去轮询多个 socket 的状态,只有当真正读写事件发送时,才真正调用实际的IO读写操作。因为在多路复用IO模型中,只需要使用一个线程就可以管理多个socket,系统不需要建立新的进程或者线程,也不必维护这些线程和进程,并且只有真正有读写事件进行时,才会使用IO资源,所以它大大减少来资源占用。多路I/O复用模型是利用 select、poll、epoll 可以同时监察多个流的 I/O 事件的能力,在空闲的时候,会把当前线程阻塞掉,当有一个或多个流有 I/O 事件时,就从阻塞态中唤醒,于是程序就会轮询一遍所有的流(epoll 是只轮询那些真正发出了事件的流),并且只依次顺序的处理就绪的流,这种做法就避免了大量的无用操作。 采用多路 I/O 复用技术可以让单个线程高效的处理多个连接请求(尽量减少网络 IO 的时间消耗),且 Redis 在内存中操作数据的速度非常快,也就是说内存内的操作不会成为影响Redis性能的瓶颈

五种IO模型总结:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/12808.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Windows上安装和使用git到gitoschina和github上_亲测

Windows上安装和使用git到gitoschina和github上_亲测 git介绍与在windows上安装创建SSHkey在gitoschina使用 【git介绍与在windows上安装】 Git是一款免费、开源的分布式版本控制系统&#xff0c;用于敏捷高效地处理任何或小或大的项目。 相关介绍可以参考 <百度百科>…

【Vue3 + Element Plus】纯前端实现本地数据分页

先附上效果图 Vue3 Element Plus 实现本地分页 首页弹窗代码 <el-table :data"tableData" style"width: 100%" border stripe><el-table-column v-for"{ id, prop, label } in tableColumn" :prop"prop" :key"id"…

RocketMQ概论

目录 前言&#xff1a; 1.概述 2.下载安装、集群搭建 3.消息模型 4.如何保证吞吐量 4.1.消息存储 4.1.1顺序读写 4.1.2.异步刷盘 4.1.3.零拷贝 4.2.网络传输 前言&#xff1a; RocketMQ的代码示例在安装目录下有全套详细demo&#xff0c;所以本文不侧重于讲API这种死…

【Rust 基础篇】Rust默认泛型参数:简化泛型使用

导言 Rust是一种以安全性和高效性著称的系统级编程语言&#xff0c;其设计哲学是在不损失性能的前提下&#xff0c;保障代码的内存安全和线程安全。在Rust中&#xff0c;泛型是一种非常重要的特性&#xff0c;它允许我们编写一种可以在多种数据类型上进行抽象的代码。然而&…

tcp keepalive

tcp keepalive用于检查两者之间的链路是否正常&#xff0c;或防止链路断开。 一旦建立了TCP连接&#xff0c;该连接被定义为有效&#xff0c;直到一方关闭它。一旦连接进入连接状态&#xff0c;它将无限期地保持连接状态。但实际上&#xff0c;这种联系不会无限期地持续下去。如…

数据结构:快速的Redis有哪些慢操作?

redis 为什么要这莫快&#xff1f;一个就是他是基于内存的&#xff0c;另外一个就是他是他的数据结构 说到这儿&#xff0c;你肯定会说&#xff1a;“这个我知道&#xff0c;不就是 String&#xff08;字符串&#xff09;、List&#xff08;列表&#xff09;、 Hash&#xff08…

1.Ansible

文章目录 Ansible概念作用特性总结 部署AnsibleAnsible模块commandshellcronusergroupcopyfilehostnamepingyumserice/systemdscriptmountarchiveunarchivereplacesetup inventory主机清单主机变量组变量组嵌套 Ansible 概念 Ansible是一个基于Python开发的配置管理和应用部署…

【Redis】面试题

1. 为什么要用缓存 1. 提高系统的读写性能。 2. 减轻数据库的压力&#xff0c;防止大量的请求到达数据库&#xff0c;让数据库压力剧增&#xff0c;拖垮数据库。redis数据存储在内存中&#xff0c;高效的数据结构&#xff0c;读写数据比数据库快。 将热点数据存储在redis当中&…

#P1004. [NOIP1998普及组] 三连击

题目描述 将 1, 2, \ldots , 91,2,…,9 共 99 个数分成 33 组&#xff0c;分别组成 33 个三位数&#xff0c;且使这 33 个三位数构成 1 : 2 : 31:2:3 的比例&#xff0c;试求出所有满足条件的 33 个三位数。 输入格式 无 输出格式 若干行&#xff0c;每行 33 个数字。按照…

数据结构:分块查找

分块查找&#xff0c;也叫索引顺序查找&#xff0c;算法实现除了需要查找表本身之外&#xff0c;还需要根据查找表建立一个索引表。例如图 1&#xff0c;给定一个查找表&#xff0c;其对应的索引表如图所示&#xff1a; 图 1 查找表及其对应的索引表 图 1 中&#xff0c;查找表…

小程序 账号的体验版正式版的账号信息及相关配置

siteinfo.js // 正式环境 const releaseConfig {appID: "",apiUrl: "",imgUrl: "" }; // 测试环境&#xff08;包含开发环境和体验环境&#xff09; const developConfig {appID: "",apiUrl: "",imgUrl: "" }…

相机可用性变化监听AvailabilityCallback流程分析

相机可用性变化监听及流程分析 一、接口说明 ​ 相机可用性变化监听可以通过CameraManager中的接口registerAvailabilityCallback()来设置回调&#xff0c;接口如下&#xff1a; /** *注册一个回调以获得有关相机设备可用性的通知。 * *<p>再次注册相同的回调将用提供…

Nginx性能优化配置

一、全局优化 # 工作进程数 worker_processes auto; # 建议 CPU核心数|CPU线程数# 最大支持的连接(open-file)数量&#xff1b;最大值受限于 Linux open files (ulimit -n) # 建议公式&#xff1a;worker_rlimit_nofile > worker_processes * worker_connections…

vue指令-v-for

vue指令-v-for 1、目标2、语法语法 1、目标 列表渲染&#xff0c;所在标签结构&#xff0c;按照数据数量&#xff0c;循环生成 2、语法 v-for "(值变量&#xff0c;索引变量) in 目标结构"示例&#xff1a; <template><div id"app"><di…

安装Anaconda3和MiniConda3

MiniConda3官方版是一款优秀的Python环境管理软件。MiniConda3最新版只包含conda及其依赖项如果您更愿意拥有conda以及超过720个开源软件包&#xff0c;请安装Anaconda。MiniConda3官方版还是一个开源的软件包管理系统和环境管理系统&#xff0c;能够帮助用户安装多个版本的软件…

ChatGPT漫谈(三)

AIGC(AI Generated Content)指的是使用人工智能技术生成的内容,包括文字、图像、视频等多种形式。通过机器学习、深度学习等技术,AI系统可以学习和模仿人类的创作风格和思维模式,自动生成大量高质量的内容。AIGC被视为继用户生成内容(UGC)和专业生成内容(PGC)之后的下…

上传图片到腾讯云对象存储桶cos 【腾讯云对象存储桶】【cos】【el-upload】【vue3】【上传头像】【删除】

1、首先登录腾讯云官网控制台 进入对象存储页面 2、找到跨越访问CIRS设置 配置规则 点击添加规则 填写信息 3、书写代码 这里用VUE3书写 第一种用按钮出发事件形式 <template><div><input type="file" @change="handleFileChange" /&…

数值线性代数:奇异值分解SVD

本文记录计算矩阵奇异值分解SVD的原理与流程。 注1&#xff1a;限于研究水平&#xff0c;分析难免不当&#xff0c;欢迎批评指正。 零、预修 0.1 矩阵的奇异值 设列满秩矩阵&#xff0c;若的特征值为&#xff0c;则称为矩阵的奇异值。 0.2 SVD(分解)定理 设&#xff0c;则…

神经网络简单介绍

人工神经网络(artififial neural network) 简称神经网络&#xff0c;它是一种模仿生物神经网络结构和功能的非线性数学模型。 神经网络通过输入层接受原始特征信息&#xff0c;再通过隐藏层进行特征信息的加工和提取&#xff0c;最后通过输出层输出结果。 根据需要神经网络可以…

RTPSv2.2(中文版)

实时发布订阅协议 &#xff08;RTPS&#xff09; DDS互操作性 有线协议规范 V2.2 &#xff08;2014-09-01正式发布&#xff09; https://www.omg.org/spec/DDSI-RTPS/2.2/PDF 目 录 1 范围Scope 9 2 一致性Conformance 9 3 参考文献References 9 4 术语和定义Terms a…