当前位置: 首页 > news >正文

安徽网站开发辽宁世纪兴电子商务服务中心

安徽网站开发,辽宁世纪兴电子商务服务中心,中国品牌网是什么网站,做网站的公司简称什么行业文章目录 NIO基本介绍同步、异步、阻塞、非阻塞IO的分类NIO 和 BIO 的比较NIO 三大核心原理示意图NIO的多路复用说明 核心一#xff1a;缓存区 (Buffer)Buffer类及其子类Buffer缓冲区的分类MappedByteBuffer类说明#xff1a; 核心二#xff1a;通道 (Channel)Channel类及其… 文章目录 NIO基本介绍同步、异步、阻塞、非阻塞IO的分类NIO 和 BIO 的比较NIO 三大核心原理示意图NIO的多路复用说明 核心一缓存区 (Buffer)Buffer类及其子类Buffer缓冲区的分类MappedByteBuffer类说明 核心二通道 (Channel)Channel类及其子类SelectableChannel类说明SelctionKey 核心三Selector选择器Selector类 案例说明栗子一本地文件写数据栗子二本地文件写数据栗子三使用一个Buffer 完成文件读取、写入栗子四从目标通道中复制原通道数据栗子五把原通道数据复制到目标通道栗子六分散 (Scatter) 和聚集 (Gather)栗子七NIO非阻塞 网络编程入门案例 分析小结NIO-零拷贝用户态和内核态简介零拷贝简介传统IOMMAP 优化sendFile 优化sendfileDMA Scatter/GatherNIO的零拷贝 总结参考文献 NIO基本介绍 Java NIO全称 java non-blocking IO是指 JDK 提供的新 API。从JDK1.4 开始Java 提供了一系列改进的输入/输出的新特性被统称为 NIO即 New IO是同步非阻塞的。NIO的设计目标是在处理I/O操作时提供更好的性能和可扩展性。它在BIO功能的基础上实现了非阻塞的特性位于java.nio包下。 NIO 有三大核心部分Channel通道Buffer缓冲区Selector选择器NIO支持面向缓冲区面向块的、基于通道的IO操作。数据读取到一个它稍后处理的缓冲区需要时可在缓冲区中前后移动这就增加了处理过程中的灵活性使用它可以提供非阻塞式的高伸缩性网络通道和缓冲区的配合 通道从缓冲区读取数据或将数据写入缓冲区。通常数据首先被写入缓冲区然后通过通道传输到目标。同样从通道读取的数据也首先被读入缓冲区然后从缓冲区中提取。 通俗理解NIO是可以做到用一个线程来处理多个操作的。假设有10000个请求过来根据实际情况可以分配50或者100个线程来处理。不像之前的阻塞10那样非得分配10000个。 同步、异步、阻塞、非阻塞 对于同步、异步、阻塞、非阻塞来说其实很好理解这里只做简单的介绍 同步、异步、阻塞和非阻塞是与 I/O 操作相关的概念用于描述程序中的任务调度和执行方式。 同步Synchronous 同步指的是程序按照顺序执行一个任务完成后才能开始另一个任务。在同步模型中一个操作的完成会导致程序等待直到该操作完成后才能继续执行下一个操作。同步操作通常会阻塞程序的执行因为程序必须等待操作完成才能继续。 异步Asynchronous 异步指的是程序不必等待一个操作完成而是可以继续执行后续的操作。在异步模型中一个操作的启动不会导致程序阻塞而是可以继续执行其他任务。异步操作通常会通过回调、事件处理或者异步任务来处理操作的完成。 阻塞Blocking 阻塞指的是当一个任务执行时程序暂停执行直到该任务完成。这是同步操作的典型特征。阻塞会导致程序的资源被浪费因为在等待任务完成时程序无法执行其他任务。 非阻塞Non-blocking 非阻塞指的是在执行一个任务时程序不会暂停等待任务完成而是会立即返回执行其他任务。非阻塞操作通常需要使用轮询或者回调等机制来检查任务是否完成。 其实这样说还是很难理解下面让我们用实例说明 例子咖啡馆的服务员 同步 顾客在咖啡馆点了一杯咖啡后服务员开始制作咖啡。在咖啡制作完成之前服务员不会处理其他顾客的订单必须等待当前订单完成后再接受下一个订单。这就是同步操作一个任务完成后才能开始下一个。异步 现在服务员接受了顾客的订单并将订单传给咖啡师傅。而服务员并不等待咖啡制作完成而是继续接受其他顾客的订单。咖啡制作完成后咖啡师傅通过呼叫服务员或者使用订单号通知服务员。这就是异步操作服务员不必等待咖啡制作完成而是可以继续处理其他订单。阻塞 如果服务员在等待咖啡制作完成的期间什么也不做直到咖啡师傅通知制作完成那么这是阻塞操作。服务员一直被阻塞无法执行其他任务。非阻塞 现在服务员在等待咖啡的同时可以接受其他订单或者询问其他师傅是否需要帮助。即使在咖啡制作的过程中服务员也可以执行其他任务这就是非阻塞操作。 同步与阻塞异步与非阻塞很多人都会对这两组概念产生疑惑都会有些区分不清这是由于它们之间的确是存在关系的而且是相辅相成的关系从某种意义上来说“同步天生就是阻塞的异步天生就是非阻塞的”。 但实际上又有点不一样这是我没有画非阻塞的原因上面画的异步就可以理解成非阻塞的其实是相辅相成的关系 非阻塞操作可以是同步的例如非阻塞 I/O 操作也可以是异步的。 异步操作可以是阻塞的例如在异步操作的结果返回前一直等待也可以是非阻塞的因为可以去干别的事。 有点绕希望你们可以理解 IO的分类 根据上述情况IO总共可被分为四大类同步阻塞式IO、同步非阻塞式IO、异步阻塞式IO、异步非阻塞式IO当然由于异步执行在一定程度上而言天生就是非阻塞式的因此不存在异步阻塞式IO的说法也就对应着BIO同步阻塞、NIO同步非阻塞、AIO异步非阻塞。 BIO是常见的IO像我们平时写的接口大多都是BIO很好理解这里不做说明 这里通过烧水来举例说明什么是同步非阻塞以及异步非阻塞 烧水步骤打开烧水壶的开关— 烧水中 — 水开了 同步非阻塞 现在想象你是传统的烧水壶它没有任何功能当你打开烧水壶的开关后你可以去做别的事但你会一直隔一会就来看水好了吗水有没有开呀直到最终水开 解释 这类似于 NIO 模型其中你可以发起一个 I/O 操作启动开关然后继续执行其他任务定期检查状态以确定操作是否完成。 异步非阻塞现在想象你的水壶是那种响壶水开了它就会提醒你水开了当你打开烧水壶开关后你可以去做别的任何事直到水壶响了你就知道水开了中间你无需反复去检查水是否开 解释 这类似于 AIO 模型其中你发起一个 I/O 操作但不需要定期检查状态。相反系统会在操作完成时通知你。 NIO 和 BIO 的比较 NIO和BIO是两种不同的I/O模型它们在处理数据的方式和效率上有所不同。 处理方式BIO以流的方式处理数据而NIO以块的方式处理数据。这意味着在BIO中数据是按流逐个字节读取的而在NIO中数据是按块一次读取多个字节。效率由于块I/O的效率比流I/O高很多因此NIO的效率通常比BIO高。这是因为NIO可以一次性读取多个字节减少了CPU和内存的访问次数提高了数据的处理效率。阻塞与非阻塞BIO是阻塞的而NIO是非阻塞的。在BIO中当一个线程进行I/O操作时它会一直等待直到操作完成。这会导致线程被阻塞无法处理其他任务。而在NIO中I/O操作不会阻塞线程线程可以继续执行其他任务。数据传输在BIO中数据是从字节流或字符流中读取的。而在NIO中数据是从通道Channel和缓冲区Buffer之间传输的。这意味着在NIO中数据总是从通道读取到缓冲区中或者从缓冲区写入到通道中。选择器SelectorNIO使用选择器Selector来监听多个通道的事件如连接请求、数据到达等。因此使用单个线程就可以监听多个客户端通道。这提高了程序的效率和并发性。 总的来说NIO相对于BIO的优势在于更高的效率、非阻塞性以及使用选择器监听多个通道的能力。然而这也增加了编程的复杂性。因此在实际应用中需要根据具体需求和场景来选择合适的I/O模型。 NIO 三大核心原理示意图 三大核心原理包括通道Channel、缓冲区Buffer和选择器Selector。 通道Channel 通道是数据传输的路径类似于流。通道可以打开和关闭可以读取和写入数据。在 NIO 中数据通过通道进行传输通道是双向的既可以用于读取数据也可以用于写入数据。通道可以连接到文件、网络套接字等。 缓冲区Buffer 缓冲区是一个存储数据的区域它实际上是一个数组。在 NIO 中数据是从通道读取到缓冲区或者从缓冲区写入到通道。缓冲区提供了对数据的结构化访问可以轻松地读取、写入、或者处理数据。本质上是一个可以读写数据的内存块 选择器Selector 选择器是 NIO 的多路复用器它允许单个线程处理多个通道。选择器会不断地轮询注册在其上的通道如果某个通道有数据可读或者可写就会通知该通道。这种机制使得一个线程可以有效地管理多个通道提高了系统的性能和资源利用率。 记住这张图对后面理解NIO很有帮助不理解没事后面会详细说明 至于什么是多路复用下面有举例说明 每个Channel对应一个BufferBuffer是一个可以读写的内存块是双向通道既可以读也可以写。当Channel向Selector注册时都会创建一个SelectionKey可以根据SelectionKey找到对应ChannelSelector对应一个线程一个线程对应多个Channel连接Selector用于监听多个Channel的事件例如连接请求、数据到达等。每个线程可以处理多个Channel的连接和事件。每个channel都会注册到Selector选择器上Channel是NIO中的核心概念之一用于建立连接并传输数据。每个Channel都会注册到Selector上以便Selector能够监听该Channel的事件。 NIO的多路复用说明 想象一个大型餐厅其中有许多客人等待就餐。传统的方式是每个客人都要有一个服务员单独服务这样每个服务员只能为一个客人服务。但这种方式非常低效因为当一个服务员忙于服务一个客人时其他客人只能等待。 现在想象一个改进的餐厅其中只有一个多路复用器服务员。这个服务员可以在多个桌子之间巡回为每个客人提供服务。当一个客人需要点菜时多路复用器服务员会记下客人的需求然后继续为其他客人服务。当所有的客人都点完菜后多路复用器服务员会回到第一个客人那里为他上菜。 在这个例子中多路复用器服务员就像NIO中的Selector。它可以在多个Channel之间进行选择当其中一个Channel有数据可读或可写时它会通知相应的线程进行操作。这种方式极大地提高了餐厅的效率和服务质量因为服务员可以同时为多个客人服务而不是只能为一个客人服务。 核心一缓存区 (Buffer) 缓冲区本质上是一个可以读写数据的内存块可以理解成是一个容器对象含数组该对象提供了一组方法可以更轻松地使用内存块缓冲区对象内置了一些机制能够跟踪和记录缓冲区的状态变化情况。Channel 提供从文件、网络读取数据的渠道但是读取或写入的数据都必须经由 Buffer; Buffer类及其子类 ByteBuffer字节缓冲区用于处理字节数据。CharBuffer字符缓冲区用于处理字符数据。DoubleBuffer双精度浮点缓冲区用于处理双精度浮点数据。FloatBuffer单精度浮点缓冲区用于处理单精度浮点数据。IntBuffer整数缓冲区用于处理整数数据。LongBuffer长整型缓冲区用于处理长整型数据。ShortBuffer短整型缓冲区用于处理短整型数据。 这些缓冲区类都继承自Buffer类具有一些通用的方法和属性如position、limit、capacity等。它们的主要区别在于存储的数据类型不同因此在使用时需要根据具体需求选择合适的缓冲区类。 使用Buffer读写数据一般遵循以下四个步骤 写入数据到Buffer调用flip()方法转换为读取模式从Buffer中读取数据调用buffer.clear()方法或者buffer.compact()方法清除缓冲区 对于Java中缓冲区的定义首先要明白当缓冲区被创建出来后同一时刻只能处于读/写中的一个状态同一时间内不存在即可读也可写的情况。理解这点后再来看看它的成员变量重点理解下述三个成员 pasition表示当前操作的索引位置下一个要读/写数据的下标。capacity表示当前缓冲区的容量大小。limit表示当前可允许操作的最大元素位置不是下标是正常数字。 图示 Buffer缓冲区的分类 Java中的缓冲区也被分为了两大类本地直接内存缓冲区与堆内存缓冲区前面Buffer类的所有子实现类xxxBuffer本质上还是抽象类每个子抽象类都会有DirectXxxBuffer、HeapXxxBuffer两个具体实现类这两者的主要区别在于创建缓冲区的内存是位于堆空间之内还是之外。 堆内存缓冲区 堆内存缓冲区是在Java堆内存中分配的。它们是Java标准的一部分可以使用ByteBuffer类的静态工厂方法来创建如ByteBuffer.allocate()。堆内存缓冲区在垃圾回收时可能会被回收因此它们可能会在任何时候被释放。 直接内存缓冲区 直接内存缓冲区不是Java标准的一部分但它们是在Java堆外分配的内存。直接内存缓冲区是通过使用NIO的ByteBuffer类的allocateDirect()方法创建的。直接内存缓冲区不会受到Java垃圾回收的影响因为它们是在Java堆外分配的。这意味着它们在程序运行期间不会被释放除非显式地调用release()方法。 优缺点 堆内存缓冲区在创建和销毁时相对较轻量级但可能会受到垃圾回收的影响。 而直接内存缓冲区虽然不会受到垃圾回收的影响但创建和销毁时可能更消耗资源。 MappedByteBuffer类说明 MappedByteBuffer是Java NIONew I/O中引入的文件内存映射方案它允许Java程序直接从内存中读取文件内容。通过将整个或部分文件映射到内存由操作系统来处理加载请求和写入文件应用只需要和内存打交道这使得IO操作非常快。 MappedByteBuffer的设计使得它能够高效地处理大文件。传统的文件IO操作中我们需要调用操作系统提供的底层标准IO系统调用函数如read()、write()此时调用此函数的进程在JAVA中即java进程由当前的用户态切换到内核态然后OS的内核代码负责将相应的文件数据读取到内核的IO缓冲区然后再把数据从内核IO缓冲区拷贝到进程的私有地址空间中去这样便完成了一次IO操作。而通过MappedByteBuffer我们可以直接从内存中读取文件内容避免了上述的IO操作过程从而提高了IO操作的效率。 public class MappedByteBufferTest {public static void main(String[] args) throws Exception {RandomAccessFile randomAccessFile new RandomAccessFile(1.txt, rw);//获取对应的通道FileChannel channel randomAccessFile.getChannel();/*** 参数1: FileChannel.MapMode.READ_WRITE 使用的读写模式* 参数2 0 可以直接修改的起始位置* 参数3: 5: 是映射到内存的大小(不是索引位置) ,即将 1.txt 的多少个字节映射到内存* 可以直接修改的范围就是 0-5* 实际类型 DirectByteBuffer*/MappedByteBuffer mappedByteBuffer channel.map(FileChannel.MapMode.READ_WRITE, 0, 5);mappedByteBuffer.put(0, (byte) H);mappedByteBuffer.put(3, (byte) 9);mappedByteBuffer.put(5, (byte) Y);//IndexOutOfBoundsExceptionrandomAccessFile.close();System.out.println(修改成功~~);} }核心二通道 (Channel) NIO中的通道Channel是一个用于数据传输的双向通道既可以读也可以写。与流Stream相比通道具有更好的扩展性和灵活性能够更好地映射底层操作系统的API。 具体来说通道和流的主要区别在于通道是双向的而流只是在一个方向上移动。流必须是InputStream或OutputStream的子类而通道可以用于读、写或者同时用于读写。另外通道是全双工的可以同时进行读写操作而流只能是单向的。 通道可以分为两大类用于网络读写的SelectableChannel和用于文件操作的FileChannel。SelectableChannel可以被选择器Selector选择从而实现多路复用。FileChannel则主要用于文件的读写操作支持异步读写和文件区域操作等高级功能。 通道的使用方式也与流有所不同。从一个通道中读取数据很简单只需创建一个缓冲区然后让通道将数据读到这个缓冲区中。写入也相当简单创建一个缓冲区用数据填充它然后让通道用这些数据来执行写入操作。 与流相比通道的优势在于它们可以更好地映射底层操作系统的API具有更高的效率和更好的扩展性。另外通道还支持异步读写操作这使得它们在处理大量并发操作时更加高效。 Channel类及其子类 通道不是打开就是关闭。通道在创建时是开放的一旦关闭它就保持关闭状态。一旦通道关闭任何对其调用I/O操作的尝试都将导致抛出closechannelexception。通道是否打开可以通过调用它的isOpen方法来测试。 一般来说通道对于多线程访问是安全的这在扩展和实现该接口的接口和类的规范中有描述。 // NIO包中定义的Channel通道接口 public interface Channel extends Closeable {// 判断通道是否处于开启状态public boolean isOpen();// 关闭通道public void close() throws IOException; }其中常用的FileChannel以及ServerSocketChannel分别位于ReadableByteChannel和SelectableChannel下 常用的Channel类有 FileChannel用于读取、写入、映射和操作文件的通道。DatagramChannel通过 UDP 读写网络中的数据通道。SocketChannel通过 TCP 读写网络中的数据。ServerSocketChannel可以监听新进来的 TCP 连接对每一个新进来的连接都会创建一个 SocketChannel。 ServerSocketChanne 类似 ServerSocket , SocketChannel 类似 Socket FileChannel类常用方法 public abstract class FileChannelextends AbstractInterruptibleChannelimplements SeekableByteChannel, GatheringByteChannel, ScatteringByteChannel{// 从Channel 到 中读取数据到 ByteBufferpublic int read(ByteBuffer dst);//将Channel中的数据“分散”到 ByteBuffer[]public long read(ByteBuffer[] dsts);//将ByteBuffer中的数据写入到 Channelpublic int write(ByteBuffer src) ;//将ByteBuffer[] 到 中的数据“聚集”到 Channelpublic long write(ByteBuffer[] srcs);//返回此通道的文件位置public long position();//设置此通道的文件位置public FileChannel position(long p);//返回此通道的文件的当前大小public long size() ;//将此通道的文件截取为给定大小public FileChannel truncate(long s) ;//强制将所有对此通道的文件更新写入到存储设备中public void force(boolean metaData);}在案例一、二、三说明 SelectableChannel类说明 SelectableChannel其中存在两个重要的子类分别是ServerSockerChannel和SockerChannel SocketChannel和ServerSocketChannel的关系 SocketChannel是用于客户端的网络通信它可以通过建立与ServerSocket的连接来与服务器进行通信。而ServerSocketChannel则是用于服务器端的网络通信它可以通过监听新进来的连接请求来接受客户端的连接。 在传统的Socket编程中客户端需要建立一个Socket对象来与服务器建立连接。而在Java NIO中客户端可以使用SocketChannel来代替Socket进行网络通信。同样地服务器端也可以使用ServerSocketChannel来代替ServerSocket进行网络通信。 SocketChannel和ServerSocketChannel之间的主要区别在于它们的使用场景不同。SocketChannel主要用于客户端而ServerSocketChannel主要用于服务器端。但是它们都提供了异步、高效的网络I/O操作能力使得客户端和服务器之间的通信更加高效和可靠。 ServerSockerChannel类常用说明 // 服务端通道抽象类 public abstract class ServerSocketChannelextends AbstractSelectableChannelimplements NetworkChannel {// 构造方法需要传递一个选择器进行初始化构建protected ServerSocketChannel(SelectorProvider provider);// 打开一个ServerSocketChannel通道public static ServerSocketChannel open() throws IOException;// 绑定一个IP地址作为服务端public final ServerSocketChannel bind(SocketAddress local);// 绑定一个IP并设置并发连接数大小超出后的连接全部拒绝public abstract ServerSocketChannel bind(SocketAddress local, int backlog);// 监听客户端连接的方法会发生阻塞的方法public abstract SocketChannel accept() throws IOException;// 获取一个ServerSocket对象public abstract ServerSocket socket();// .....省略其他方法...... }ServerSocketChannel类似 ServerSocket。主要作用是接受客户端的连接请求并建立TCP连接。当有客户端尝试连接到服务器时ServerSocketChannel可以监听到这个连接请求并接受该连接。一旦连接建立ServerSocketChannel就可以将接收到的数据传递给对应的处理程序进行进一步处理。 可以这么理解ServerSocketChanel本生也是channel他也需要selector去监听事件发生有新客户端连接selector就监听到了该事件SockerChannel类常用说明 public abstract class SocketChannel extends AbstractSelectableChannelimplements ByteChannel, ScatteringByteChannel, GatheringByteChannel, NetworkChannel{// 打开一个通道public static SocketChannel open();// 根据指定的远程地址打开一个通道public static SocketChannel open(SocketAddress remote);// 如果调用open()方法时未给定地址可以通过该方法连接远程地址public abstract boolean connect(SocketAddress remote);// 将当前通道绑定到本地套接字地址上public abstract SocketChannel bind(SocketAddress local);// 把当前通道注册到Selector选择器上// sel要注册的选择器、ops事件类型、att共享属性。public final SelectionKey register(Selector sel,int ops,Object att);// 省略其他......// 关闭通道 public final void close();// 向通道中写入数据数据通过缓冲区的方式传递public abstract int write(ByteBuffer src);// 根据给定的起始下标和数量将缓冲区数组中的数据写入到通道中public abstract long write(ByteBuffer[] srcs,int offset,int length);// 向通道中批量写入数据批量写入一个缓冲区数组 public final long write(ByteBuffer[] srcs);// 从通道中读取数据读取的数据放入到dst缓冲区中public abstract int read(ByteBuffer dst);// 根据给定的起始下标和元素数据在通道中批量读取数据public abstract long read(ByteBuffer[] dsts,int offset,int length);// 从通道中批量读取数据结果放入dits缓冲区数组中public final long read(ByteBuffer[] dsts);// 返回当前通道绑定的本地套接字地址public abstract SocketAddress getLocalAddress();// 判断目前是否与远程地址建立上了连接关系public abstract boolean isConnected();// 判断目前是否与远程地址正在建立连接public abstract boolean isConnectionPending();// 获取当前通道连接的远程地址null代表未连接public abstract SocketAddress getRemoteAddress();// 设置阻塞模式true代表阻塞false代表非阻塞public final SelectableChannel configureBlocking(boolean block);// 判断目前通道是否为打开状态public final boolean isOpen(); }SocketChannel所提供的方法大体分为三类 管理类如打开通道、连接远程地址、绑定地址、注册选择器、关闭通道等。操作类读取/写入数据、批量读取/写入、自定义读取/写入等。查询类检查是否打开连接、是否建立了连接、是否正在连接等。 看到这里如果还有什么不清楚的话可以先去看看案例一、二、三、四 SelctionKey SelectionKey是Java NIO中的一个抽象类表示selectableChannel在Selector中注册的标识。每个Channel向Selector注册时都会创建一个SelectionKey将Channel与Selector建立了关系并维护了channel事件。 SelectionKey有四个操作类型OP_READ当操作系统读缓冲区有数据可读时、OP_WRITE当操作系统写缓冲区有数据可写时、OP_CONNECT当连接被成功建立时、OP_ACCEPT当新连接被接受时。 在编程时通过SelectionKey可以获得通道的IO事件类型比方说SelectionKey.OP_READ还可以获得发生IO事件所在的通道 public abstract class SelectionKey {//得到与之关联的通道public abstract SelectableChannel channel();//得到与之关联的Selector 对象public abstract Selector selector();//设置或改变监听事件public abstract SelectionKey interestOps(int ops);///是否可以读public final boolean isReadable();// 是否可以写public final boolean isWritable();// 是否接受新的连接public final boolean isAcceptable();//得到与之关联的共享数据(Buffer)public final Object attachment();}核心三Selector选择器 Selector是一个选择器它用于检测一个或者多个NIO通道的状态是否处于可读、可写。Selector的使用可以实现单线程管理多个Channel也就是可以管理多个网络链接。 使用Selector的好处在于只需要更少的线程就可以来处理通道避免了线程上下文切换带来的开销。但是不是所有的Channel都可以被Selector复用只有继承了SelectableChannel的Channel才能被Selector复用。 Selector是非阻塞IO的核心。 要使用Selector首先需要创建一个Selector对象然后通过Channel的register()方法将Channel注册到Selector上注册时需要指定监听的事件类型。注册成功后就可以通过Selector的select()方法来检测是否有事件发生如果有事件发生就可以通过Selector的selectedKeys()方法获取到发生事件的所有Channel然后进行处理。 通道一共支持4中事件 SelectionKey.OP_READ/1读取就绪事件通道内的数据已就绪可被读取。 SelectionKey.OP_WRITE/4写入就绪事件一个通道正在等待数据写入。 SelectionKey.OP_CONNECT/8连接就绪事件通道已成功连接到服务端。 SelectionKey.OP_ACCEPT/16接收就绪事件服务端通道已准备好接收新的连接。 当一个通道注册时会为其绑定对应的事件当该通道触发了一个事件就代表着该事件已经准备就绪可以被线程操作了。当然如果要为一条通道绑定多个事件那可通过位或操作符拼接 Selector类 public abstract class Selector implements Closeable {// 创建一个选择器public static Selector open() throws IOException;// 判断一个选择器是否已打开public abstract boolean isOpen();// 获取创建当前选择器的生产者对象public abstract SelectorProvider provider();// 获取所有注册在当前选择的通道连接public abstract SetSelectionKey keys();// 获取所有数据已准备就绪的通道连接public abstract SetSelectionKey selectedKeys();// 非阻塞式获取就绪的通道如若没有就绪的通道则会立即返回public abstract int selectNow() throws IOException;// 在指定时间内阻塞获取已注册的通道中准备就绪的通道数量public abstract int select(long timeout) throws IOException;// 获取已注册的通道中准备就绪的通道数量阻塞式public abstract int select() throws IOException;// 唤醒调用Selector.select()方法阻塞后的线程public abstract Selector wakeup();// 关闭创建的选择器不会关闭通道public abstract void close() throws IOException; }案例说明 栗子一本地文件写数据 需求使用前面学习后的 **ByteBuffer(缓冲)**和 FileChannel(通道) 将数据写入到 data.txt 中 public class NIOFileChannel01 {public static void main(String[] args) throws Exception{String str hello,邱俊杰;//创建一个输出流-channelFileOutputStream fileOutputStream new FileOutputStream(d:\\data.txt);//通过 fileOutputStream 获取 对应的 FileChannel//这个 fileChannel 真实 类型是 FileChannelImplFileChannel fileChannel fileOutputStream.getChannel();//创建一个缓冲区 ByteBufferByteBuffer byteBuffer ByteBuffer.allocate(1024);//将 str 放入 byteBufferbyteBuffer.put(str.getBytes());//对byteBuffer 进行flipbyteBuffer.flip();//将byteBuffer 数据写入到 fileChannelfileChannel.write(byteBuffer);fileOutputStream.close();} }栗子二本地文件写数据 使用前面学习后的ByteBuffer缓存和FileChannel通道将file01.txt中的数据读入到程序并显示在控制台屏幕 public class NIOFileChannel02 {public static void main(String[] args) throws Exception {//创建文件的输入流File file new File(d:\\file01.txt);FileInputStream fileInputStream new FileInputStream(file);//通过fileInputStream 获取对应的FileChannel - 实际类型 FileChannelImplFileChannel fileChannel fileInputStream.getChannel();//创建缓冲区(创建文件一样大小的Buffer)ByteBuffer byteBuffer ByteBuffer.allocate((int) file.length());//将 通道的数据读入到BufferfileChannel.read(byteBuffer);//将byteBuffer 的 字节数据 转成StringSystem.out.println(new String(byteBuffer.array()));fileInputStream.close();} }栗子三使用一个Buffer 完成文件读取、写入 使用 FileChannel通道和 方法 readwrite完成文件的拷贝将1.txt中的数据拷贝到2.txt public class NIOFileChannel03 {public static void main(String[] args) throws Exception {FileInputStream fileInputStream new FileInputStream(1.txt);FileChannel fileChannel01 fileInputStream.getChannel();FileOutputStream fileOutputStream new FileOutputStream(2.txt);FileChannel fileChannel02 fileOutputStream.getChannel();ByteBuffer byteBuffer ByteBuffer.allocate(512);//循环读取while (true) { //这里有一个重要的操作清空bufferbyteBuffer.clear(); int read fileChannel01.read(byteBuffer);System.out.println(read read);if(read -1) { //表示读完break;}//将buffer 中的数据写入到 fileChannel02 -- 2.txtbyteBuffer.flip();fileChannel02.write(byteBuffer);}//关闭相关的流fileInputStream.close();fileOutputStream.close();} }栗子四从目标通道中复制原通道数据 使用FileChannel通道和方法transferFrom完成文件的拷贝 public class NIOFileChannel04 {public static void main(String[] args) throws Exception {//创建相关流FileInputStream fileInputStream new FileInputStream(d:\\a.jpg);FileOutputStream fileOutputStream new FileOutputStream(d:\\a2.jpg);//获取各个流对应的filechannelFileChannel sourceCh fileInputStream.getChannel();FileChannel destCh fileOutputStream.getChannel();//使用transferForm完成拷贝destCh.transferFrom(sourceCh,0,sourceCh.size());//关闭相关通道和流sourceCh.close();destCh.close();fileInputStream.close();fileOutputStream.close();} }栗子五把原通道数据复制到目标通道 使用FileChannel通道和方法transferTo完成文件的复制 public class NIOFileChannel05 {public static void main(String[] args) throws Exception {// 1、字节输入管道FileInputStream is new FileInputStream(E:\\test\\Aurora-4k.jpg);FileChannel isChannel is.getChannel();// 2、字节输出流管道FileOutputStream fos new FileOutputStream(E:\\test\\Aurora-4knew4.jpg);FileChannel osChannel fos.getChannel();// 3、复制isChannel.transferTo(isChannel.position() , isChannel.size() , osChannel);isChannel.close();osChannel.close();} }栗子六分散 (Scatter) 和聚集 (Gather) Scattering将数据写入到buffer时可以采用buffer数组依次写入 [分散] Gathering从buffer读取数据时可以采用buffer数组依次读 public class ScatteringAndGatheringTest {public static void main(String[] args) throws Exception {//使用 ServerSocketChannel 和 SocketChannel 网络ServerSocketChannel serverSocketChannel ServerSocketChannel.open();InetSocketAddress inetSocketAddress new InetSocketAddress(7000);//绑定端口到socket 并启动serverSocketChannel.socket().bind(inetSocketAddress);//创建buffer数组ByteBuffer[] byteBuffers new ByteBuffer[2];byteBuffers[0] ByteBuffer.allocate(5);byteBuffers[1] ByteBuffer.allocate(3);//等客户端连接(telnet)SocketChannel socketChannel serverSocketChannel.accept();int messageLength 8; //假定从客户端接收8个字节//循环的读取while (true) {int byteRead 0;while (byteRead messageLength ) {long l socketChannel.read(byteBuffers);byteRead l; //累计读取的字节数System.out.println(byteRead byteRead);//使用流打印, 看看当前的这个buffer的position 和 limitArrays.asList(byteBuffers).stream().map(buffer - postion buffer.position() , limit buffer.limit()).forEach(System.out::println);}//将所有的buffer进行flipArrays.asList(byteBuffers).forEach(buffer - buffer.flip());//将数据读出显示到客户端long byteWirte 0;while (byteWirte messageLength) {long l socketChannel.write(byteBuffers); //byteWirte l;}//将所有的buffer 进行clearArrays.asList(byteBuffers).forEach(buffer- {buffer.clear();});System.out.println(byteRead: byteRead byteWrite byteWirte , messagelength messageLength);}} }栗子七NIO非阻塞 网络编程入门案例 需求服务端接收客户端的连接请求并接收多个客户端发送过来的事件。 Server端代码实现 public class NIOServer {public static void main(String[] args) throws Exception{//创建ServerSocketChannel - ServerSocketServerSocketChannel serverSocketChannel ServerSocketChannel.open();//得到一个Selecor对象Selector selector Selector.open();//绑定一个端口6666, 在服务器端监听serverSocketChannel.socket().bind(new InetSocketAddress(6666));//设置为非阻塞serverSocketChannel.configureBlocking(false);//把 serverSocketChannel 注册到 selector 关心 事件为 OP_ACCEPTserverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);System.out.println(注册后的selectionkey 数量 selector.keys().size()); // 1//循环等待客户端连接while (true) {//这里我们等待1秒如果没有事件发生, 返回if(selector.select(1000) 0) { //没有事件发生System.out.println(服务器等待了1秒无连接);continue;}//如果返回的0, 就获取到相关的 selectionKey集合//1.如果返回的0 表示已经获取到关注的事件//2. selector.selectedKeys() 返回关注事件的集合// 通过 selectionKeys 反向获取通道SetSelectionKey selectionKeys selector.selectedKeys();System.out.println(selectionKeys 数量 selectionKeys.size());//遍历 SetSelectionKey, 使用迭代器遍历IteratorSelectionKey keyIterator selectionKeys.iterator();while (keyIterator.hasNext()) {//获取到SelectionKeySelectionKey key keyIterator.next();//根据key 对应的通道发生的事件做相应处理if(key.isAcceptable()) { //如果是 OP_ACCEPT, 有新的客户端连接//该该客户端生成一个 SocketChannelSocketChannel socketChannel serverSocketChannel.accept();System.out.println(客户端连接成功 生成了一个 socketChannel socketChannel.hashCode());//将 SocketChannel 设置为非阻塞socketChannel.configureBlocking(false);//将socketChannel 注册到selector, 关注事件为 OP_READ 同时给socketChannel//关联一个BuffersocketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));System.out.println(客户端连接后 注册的selectionkey 数量 selector.keys().size()); //2,3,4..}if(key.isReadable()) { //发生 OP_READ//通过key 反向获取到对应channelSocketChannel channel (SocketChannel)key.channel();//获取到该channel关联的bufferByteBuffer buffer (ByteBuffer)key.attachment();channel.read(buffer);System.out.println(form 客户端 new String(buffer.array()));}//手动从集合中移动当前的selectionKey, 防止重复操作keyIterator.remove();}}} }Client端代码实现 public class NIOClient {public static void main(String[] args) throws Exception{//得到一个网络通道SocketChannel socketChannel SocketChannel.open();//设置非阻塞socketChannel.configureBlocking(false);//提供服务器端的ip 和 端口InetSocketAddress inetSocketAddress new InetSocketAddress(127.0.0.1, 6666);//连接服务器if (!socketChannel.connect(inetSocketAddress)) {while (!socketChannel.finishConnect()) {System.out.println(因为连接需要时间客户端不会阻塞可以做其它工作..);}}//...如果连接成功就发送数据String str hello, 邱俊杰~;//将字节数组包装到缓冲区中ByteBuffer buffer ByteBuffer.wrap(str.getBytes());//发送数据将 buffer 数据写入 channelsocketChannel.write(buffer);System.in.read();} }分析小结 现在返回来看以下这个图的话相信大家会有一个新的理解了 对上图说明 当客户端连接时会通过ServerSocketChannel 得到SocketChannelSelector进行监听 select方法返回有事件发生的通道的个数.将socketChannel注册到Selector上registerSelector selint ops一个selector上可以注册多个SocketChannel注册后返回一个SelectionKey会和该Selector关联集合进一步得到各个SelectionKey有事件发生在通过SelectionKey反向获取 SocketChannel方法 channel可以通过得到的channel完成业务处理 NIO-零拷贝 要弄清楚什么是零拷贝首先得理解两个重要的概念即用户态与内核态 那什么是用户态和内核态呢想看图片 用户态和内核态简介 操作系统中的用户态User Mode和内核态Kernel Mode是两种不同的运行模式涉及到程序执行时与操作系统内核的交互方式。这两种模式有不同的权限和特权级别。 用户态User Mode 在用户态执行的程序通常是应用程序这些程序运行在较低的权限级别。在用户态程序只能访问自己的地址空间和有限的系统资源而无法直接访问操作系统的核心部分或硬件资源。大多数应用程序都在用户态执行因为用户态提供了一种安全的环境防止应用程序直接操作核心系统资源从而提高了系统的稳定性和安全性。 内核态Kernel Mode 内核态是操作系统内核执行的特权级别也被称为特权模式。在内核态操作系统拥有对系统内所有资源的完全访问权限包括硬件、内存和其他关键系统资源。操作系统内核在内核态下运行它可以执行特权指令、直接访问硬件设备、处理中断和系统调用等。内核态具有更高的权限和更广泛的访问权限但也需要更小心地管理以确保不会破坏系统的稳定性。 切换模式 在操作系统中程序从用户态切换到内核态需要通过系统调用System Call或者中断Interrupt等方式。这是为了防止应用程序滥用对系统资源的访问权限。当应用程序需要进行一些特权操作时例如文件读写、网络通信等它会通过系统调用进入内核态执行相应的操作然后再返回用户态。 为什么OS要区分用户态和内核态 区分用户态和内核态的主要目的是保护操作系统程序并确保计算机系统的运行安全。在多道程序环境下为了保障计算机系统的运行安全将计算机系统中的指令分为两类特权指令和非特权指令。能引起系统损害的机器指令称为特权指令否则称为非特权指令。操作系统模式内核态下可执行特权指令和非特权指令用户模式用户态下只能执行非特权指令。当CPU处于用户态时只能执行非特权指令并且只能访问当前运行进程运行的用户程序的地址空间这样才能有效地保护操作系统内核及内存中其他用户程序不受该运行进程程序的侵害。 用户态和内核态的切换是由处理机状态寄存器中的上下文信息控制的。当处理机从一个状态切换到另一个状态时它会保存当前的上下文信息以便在需要时可以恢复到之前的状态。这种机制使得处理机可以在不同状态下执行不同的任务从而提高了系统的效率和可靠性。总之处理机区分内核态和用户态是为了保护系统的安全性和稳定性这种机制可以提高系统的效率和可靠性。摘取《操作系统概念》 举例说明 其实学过网络编程的应该知道Socket一般来说或者我们听到的都是Socket协议或者Socket连接 但Socket 其实并不是一个协议而是为了方便使用TCP或UDP而抽象出来的一层是位于应用层和传输控制层之间的一组接口。 Socket本身并不是一个协议它工作在OSI模型会话层是一个套接字TCP/IP网络的API是为了方便大家直接使用。 更底层协议而存在的一个抽象层。Socket其实就是一个门面模式用户态它把复杂的**TCP/IP协议族内核态**隐藏在Socket接口后面对用户来说一组简单的接口就是全部让Socket去组织数据以符合指定的协议。 而WebSocket则是一个典型的应用层协议。 这样可能还是不好理解我们重新用生活中的例子说明 用户态驾驶者 当你驾驶汽车时你处于用户态。你能够直接操控方向盘、刹车、油门等汽车的用户接口。这就好比用户态的应用程序它能够执行各种任务但受到一定的限制。 内核态汽车引擎控制系统 与此同时汽车的引擎控制系统工作在内核态。这个系统负责管理引擎的运行、燃油供应、排放控制等核心功能。这些功能对于汽车的正常运行至关重要就像操作系统内核管理系统的核心资源一样。 用户态和内核态的切换 当你需要进行某些高级操作比如调整引擎映射、查看车辆诊断信息等时你可能需要将汽车引擎控制系统切换到用户模式这就好比进行系统调用。但大多数时间你只需在用户态进行驾驶引擎控制系统在内核态默默地处理所有必要的事务。 保护核心功能 想象一下如果任何人都能够直接干预引擎的内部工作那么汽车的安全性和可靠性就会大大降低。引擎控制系统运行在内核态提供了对核心功能的保护防止不懂引擎工作原理的人随意操作。 到这里我们应该对用户态和内核态有了一个大概的了解那回到我们的主题什么是零拷贝 零拷贝简介 零拷贝是指计算机执行IO操作时CPU不需要将数据从一个存储区域复制到另一个存储区域进而减少上下文切换以及CPU的拷贝时间。它是一种IO操作优化技术。具体来说在数据传输过程中源节点到目的节点之间需要将数据从一个存储区复制到另一个存储区而这个过程会产生一些额外的CPU指令和上下文切换从而导致一定的性能损失。而零拷贝技术可以避免这个过程从而提高了数据传输的效率。 总结起来就是一句话零拷贝从操作系统角度就是没有cpu拷贝 在Java 程序中常用的零拷贝有 mmap内存映射和 sendFile。那么他们在 OS里到底是怎么样的一个的设计 传统IO Java传统IO和网络编程的一段代码 public class BIOModel {public static void main(String[] args) throws Exception{File file new File(1.txt);RandomAccessFile raf new RandomAccessFile(file, rw);byte[] bytes new byte[(int) file.length()];raf.read(bytes);Socket socket new ServerSocket(8080).accept();socket.getOutputStream().write(bytes);} }上面代码的图解 DMAdirect memory access直接内存拷贝不使用 CPU 在传统IO中一共经过了4次切换以及4次拷贝具体过程 用户进程调用 read 方法向操作系统发出 I/O 请求上下文从用户态转向内核态请求读取数据到自己的内存缓冲区中进程进入阻塞状态 操作系统收到请求后进一步将 I/O 请求发送 DMA然后让 CPU 执行其他任务 DMA 进一步将 I/O 请求发送给磁盘 磁盘收到 DMA 的 I/O 请求把数据从磁盘读取到磁盘控制器的缓冲区中当磁盘控制器的缓冲区被读满后向 DMA 发起中断信号告知自己缓冲区已满 DMA 收到磁盘的信号将磁盘控制器缓冲区中的数据拷贝到内核缓冲区中此时不占用 CPUCPU 可以执行其他任务 当 DMA 读取了足够多的数据就会发送中断信号给 CPU CPU 收到 DMA 的信号知道数据已经准备好于是将数据从内核拷贝到用户空间系统调用返回上下文从内核态转为用户态 用户进程调用 write方法向操作系统发出 I/O 请求上下文从用户态转为内核态CPU将读缓冲区中数据拷贝到socket缓冲区。 DMA控制器把数据从socket缓冲区拷贝到网卡上下文从内核态切换回用户态write()返回。 这里DMA拷贝2次CPU拷贝2次,所谓的零拷贝也可以理解成没有CPU参与的拷贝 MMAP 优化 mmap通过内存映射将文件映射到内核缓冲区同时用户空间可以共享内核空间的数据。这样在进行网络传输时就可以减少内核空间到用户空间的拷贝次数。如下图 mmap方式的零拷贝经过了4次上下文切换和3次数据拷贝。 具体流程如下 用户进程通过mmap()方法向操作系统发起调用上下文从用户态转向内核态。DMA控制器把数据从硬盘中拷贝到读缓冲区上下文从内核态转为用户态mmap调用返回。用户进程通过write()方法发起调用上下文从用户态转为内核态CPU将读缓冲区中数据拷贝到socket缓冲区。DMA控制器把数据从socket缓冲区拷贝到网卡上下文从内核态切换回用户态write()返回。 相比mmap传统IO多了一次CPU拷贝。 在传统IO中数据首先被读取到内核缓冲区然后再从内核缓冲区复制到用户程序缓冲区。而使用mmap技术数据可以直接从文件映射到内存中用户程序可以直接对内存进行读写操作避免了额外的数据复制。因此相比于传统的IOmmap方式少了一次CPU拷贝。 sendFile 优化 Linux 2.1 版本 提供了sendFile函数其基本原理如下数据根本不经过用户态直接从内核缓冲区进入到Socket Buffer同时由于和用户态完全无关就减少了一次上下文切换 整个过程发生了2次用户态和内核态的上下文切换和3次拷贝具体流程如下 用户进程通过sendfile()方法向操作系统发起调用上下文从用户态转向内核态DMA控制器把数据从硬盘中拷贝到读缓冲区CPU将读缓冲区中数据拷贝到socket缓冲区DMA控制器把数据从socket缓冲区拷贝到网卡上下文从内核态切换回用户态sendfile调用返回 sendfile方法IO数据对用户空间完全不可见所以只能适用于完全不需要用户空间处理的情况比如静态文件服务器。 sendfileDMA Scatter/Gather Linux2.4内核版本之后对sendfile做了进一步优化通过引入新的硬件支持这个方式叫做DMA Scatter/Gather 分散/收集功能。 整个过程发生了2次用户态和内核态的上下文切换和2次拷贝其中更重要的是完全没有CPU拷贝具体流程如下 用户进程通过sendfile()方法向操作系统发起调用上下文从用户态转向内核态DMA控制器利用scatter把数据从硬盘中拷贝到读缓冲区离散存储CPU把读缓冲区中的文件描述符和数据长度发送到socket缓冲区DMA控制器根据文件描述符和数据长度使用scatter/gather把数据从内核缓冲区拷贝到网卡sendfile()调用返回上下文从内核态切换回用户态 真正意义上的零拷贝 NIO的零拷贝 其实在上面的案例四和案例五中我们已经用到了零拷贝也就是NIO中的transferTo方法和transferFrom方法: public class NIOFileChannel05 {public static void main(String[] args) throws Exception {// 1、字节输入管道FileInputStream is new FileInputStream(E:\\test\\Aurora-4k.jpg);FileChannel isChannel is.getChannel();// 2、字节输出流管道FileOutputStream fos new FileOutputStream(E:\\test\\Aurora-4knew4.jpg);FileChannel osChannel fos.getChannel();// 3、复制isChannel.transferTo(isChannel.position() , isChannel.size() , osChannel);isChannel.close();osChannel.close();} }注意 在linux下一个transferTo 方法就可以完成传输在windows 下 一次调用 transferTo 只能发送8m , 就需要分段传输文件 如果 Linux 系统支持 sendfile() 系统调用那么 transferTo() 实际上最后就会使用到 sendfile() 系统调用函数。 总结 NIONon-blocking I/O非阻塞I/O是一种基于Channel和Buffer的I/O模型它支持异步和多路复用。在NIO中Channel是用于进行I/O操作的通道Buffer是用于存储数据的容器。NIO通过Selector来监听Channel的事件从而实现非阻塞的I/O操作。 NIO的核心是Channel和Buffer它们之间的关系是Channel通过Buffer进行数据的读写操作。在NIO中所有的I/O操作都是异步的这意味着不需要等待操作完成就可以继续执行其他任务。当操作完成后会通知相关线程进行后续处理。 NIO的优点包括 非阻塞性NIO使用异步I/O操作可以避免阻塞线程提高并发处理能力。多路复用NIO通过Selector监听多个Channel的事件可以同时处理多个I/O操作提高效率。高效性NIO使用零拷贝技术减少了数据在内存中的复制次数提高了数据传输效率。 NIO的实战场景包括 高并发网络通信NIO可以用于构建高性能的网络服务器和客户端支持大量的并发连接和数据传输。大数据处理NIO可以用于读取和写入大量数据例如日志文件、数据库等提高数据处理效率。实时系统NIO可以用于构建实时系统例如实时通信、实时监控等支持低延迟的数据传输和处理。 参考文献 [尚硅谷Netty教程](027_尚硅谷_SelectionKey API_哔哩哔哩_bilibili) 零拷贝
http://www.ho-use.cn/article/10823115.html

相关文章:

  • 阿里巴巴网站开发工具p2p网站怎么做
  • 网站根目录下网址导航网址大全
  • 洛阳网站建设制作多少钱线上推广的意义
  • 帝国cms制作网站地图asp.net 网站写好后如何运行
  • 高端品牌网站定制织梦网站首页幻灯片不显示
  • 做卖车网站需要什么手续企业数据哪里找
  • 当今做啥网站致富网络营销推广公司网站有哪些
  • 克拉玛依网站建设公司北辰网站建设
  • 网站服务器选购网站建设与发布
  • 怎么看网站建设时间网页版微信小程序
  • siteground建站教程重庆关键词自然排名
  • 天津网站建设 Wordpress宣传片拍摄制作流程
  • 南昌定制网站开发公司网页美工工资水平
  • 济宁建设网站制作做网站的公司是接入商吗
  • 聊城网站制作价格网站开发需要书籍
  • 深圳做h5网站公司wordpress相册编辑插件下载
  • 做效果图的网站有哪些软件新闻热点事件及评论
  • 中国建设部网站能查叉车证wordpress 中文附件
  • 哪些网站是可以做网络推广的查询域名备案
  • 苏州微网站建设公司哪家好在线代理网址
  • 做直播网站软件自己的电脑怎么做网站
  • 网站建设设计价格山西省城乡住房建设厅网站首页
  • 网站进行中英文转换怎么做dw网页制作在线编辑
  • 昆明云南微网站建设小困网络科技泰安有限公司
  • 可信网站 如何验证小程序店铺
  • 如何查看网站备案信息网站验证码体验
  • 网站建设收税简单一点的网站建设
  • phpcms二级栏目文章列表调用网站最新文章的方法天猫官方网站首页
  • 商城做网站好还是淘宝合肥网站开发需要多
  • 建站模板建网站个人个性网页界面设计