Netty深入浅出(无处不在的IO)

为什么要有Netty

Netty是为了解决网络编程的复杂性和提供易于使用、高性能和可扩展的框架而开发的。它通过提供一组可重用的组件来处理网络通信的低级细节,例如套接字管理、线程和缓冲,简化了开发网络应用程序的过程。这使开发人员可以专注于应用程序逻辑而不是网络编程的复杂性。此外,Netty支持各种协议和传输机制,使其成为构建各种网络应用程序的多功能选择。

Java中的IO模型

Netty是一个Java编写的网络IO库,Netty在其底层仍然使用Java I/O库,如java.nio包。它使用了Java NIO(New I/O)的一些特性,例如非阻塞通道(Channel)、选择器(Selector)等,以实现高性能的网络通信。

三种通信模型

  • BIO (Blocking I/O): 同步阻塞I/O模式,数据的读取写入必须阻塞在一个线程内等待其完成。在活动连接数不是特别高(小于单机1000)的情况下,这种模型是比较不错的。线程池本身就是一个天然的漏斗,可以缓冲一些系统处理不了的连接或请求。

  • NIO (New I/O): NIO是一种同步非阻塞的I/O模型,在Java 1.4 中引入了NIO框架。NIO提供了与传统BIO模型中的 SocketServerSocket 相对应的 SocketChannelServerSocketChannel 两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反。

  • AIO (Asynchronous I/O): AIO 也就是 NIO 2。在 Java 7 中引入了 NIO 的改进版 NIO 2,它是异步非阻塞的IO模型。异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。AIO 是异步IO的缩写,虽然 NIO 在网络操作中,提供了非阻塞的方法,但是 NIO 的 IO 行为还是同步的。对于 NIO 来说,我们的业务线程是在 IO 操作准备好时,得到通知,接着就由这个线程自行进行 IO 操作,IO操作本身是同步的。

NIO

示例代码
  • 服务端
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;public class NIOServer {public static void main(String[] args) throws IOException {// 创建ServerSocketChannelServerSocketChannel serverSocketChannel = ServerSocketChannel.open();serverSocketChannel.socket().bind(new InetSocketAddress(8080));serverSocketChannel.configureBlocking(false); // 设置为非阻塞模式Selector selector = Selector.open();serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);System.out.println("Server is listening on port 8080...");while (true) {int readyChannels = selector.select();if (readyChannels == 0) {continue;}Set<SelectionKey> selectedKeys = selector.selectedKeys();Iterator<SelectionKey> keyIterator = selectedKeys.iterator();while (keyIterator.hasNext()) {SelectionKey key = keyIterator.next();if (key.isAcceptable()) {SocketChannel clientChannel = serverSocketChannel.accept();clientChannel.configureBlocking(false);clientChannel.register(selector, SelectionKey.OP_READ);System.out.println("Accepted connection from " + clientChannel.getRemoteAddress());} else if (key.isReadable()) {SocketChannel clientChannel = (SocketChannel) key.channel();ByteBuffer buffer = ByteBuffer.allocate(1024);int bytesRead = clientChannel.read(buffer);if (bytesRead == -1) {clientChannel.close();System.out.println("Client disconnected.");} else if (bytesRead > 0) {buffer.flip();while (buffer.hasRemaining()) {clientChannel.write(buffer); // 回显客户端发送的数据}buffer.clear();}}keyIterator.remove();}}}
}
  • 客户端
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;public class NIOClient {public static void main(String[] args) throws IOException {SocketChannel socketChannel = SocketChannel.open();socketChannel.connect(new InetSocketAddress("localhost", 8080));String message = "Hello, NIO Server!";ByteBuffer buffer = ByteBuffer.wrap(message.getBytes());socketChannel.write(buffer); // 发送消息给服务器ByteBuffer responseBuffer = ByteBuffer.allocate(1024);int bytesRead = socketChannel.read(responseBuffer); // 读取服务器的响应if (bytesRead != -1) {responseBuffer.flip();byte[] bytes = new byte[responseBuffer.remaining()];responseBuffer.get(bytes);String response = new String(bytes);System.out.println("Received from server: " + response);}socketChannel.close(); // 关闭客户端连接}
}

AIO

示例代码
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousChannelGroup;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.concurrent.Executors;public class AIOTimeServer {public static void main(String[] args) throws IOException {int port = 8080;AsynchronousChannelGroup group = AsynchronousChannelGroup.withThreadPool(Executors.newFixedThreadPool(10));final AsynchronousServerSocketChannel serverChannel = AsynchronousServerSocketChannel.open(group);serverChannel.bind(new InetSocketAddress(port));System.out.println("Server is listening on port " + port);serverChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Void>() {@Overridepublic void completed(AsynchronousSocketChannel clientChannel, Void attachment) {serverChannel.accept(null, this); // 接受下一个连接String response = "Current time: " + System.currentTimeMillis();ByteBuffer buffer = ByteBuffer.wrap(response.getBytes());clientChannel.write(buffer, null, new CompletionHandler<Integer, Void>() {@Overridepublic void completed(Integer result, Void attachment) {try {clientChannel.close();} catch (IOException e) {e.printStackTrace();}}@Overridepublic void failed(Throwable exc, Void attachment) {exc.printStackTrace();try {clientChannel.close();} catch (IOException e) {e.printStackTrace();}}});}@Overridepublic void failed(Throwable exc, Void attachment) {exc.printStackTrace();}});try {group.awaitTermination(Long.MAX_VALUE, java.util.concurrent.TimeUnit.SECONDS);} catch (InterruptedException e) {e.printStackTrace();}}
}

为什么Netty依旧使用NIO的API?

Netty 不看重 Windows 上的使用,在 Linux 系统上,AIO 的底层实现仍使用 EPOLL(后续会讲),没有很好实现 AIO,因此在性能上没有明显的优势,而且被 JDK 封装了一层不容易深度优化。

Linux的IO模型

Java的I/O模型是在Java编程语言层面的抽象,而Linux的I/O模型是操作系统内核层面的实现。因此,虽然它们有一些相似之处,但并不是完全相同的概念。

LINUX五种IO模型

同步和异步:同步和异步是针对应用程序和内核的交互而言的,同步指的是用户进程触发IO 操作并等待或者轮询的去查看IO 操作是否就绪,而异步是指用户进程触发IO 操作以后便开始做自己的事情,而当IO 操作已经完成的时候会得到IO 完成的通知。

阻塞和非阻塞:阻塞和非阻塞是针对于进程在访问数据的时候,根据IO操作的就绪状态来采取的不同方式,说白了是一种读取或者写入操作方法的实现方式,阻塞方式下读取或者写入函数将一直等待,而非阻塞方式下,读取或者写入方法会立即返回一个状态值。

  1. 阻塞式IO

  2. 非阻塞式IO

  3. IO多路复用

  4. 信号驱动

  5. 异步IO

前面四种IO模型实际上都属于同步IO,只有最后一种是真正的异步IO,因为无论是多路复用IO还是信号驱动模型,IO操作的第2个阶段都会引起用户线程阻塞,也就是内核进行数据拷贝的过程都会让用户线程阻塞。

从图中可以看出用户态与内核态的切换,那么什么是用户/内核态呢?这也是下面的内容。

用户态与内核态

I/O(Input/Output,输入/输出)和用户态与内核态之间存在密切的关系,特别是在操作系统中。用户态和内核态是操作系统中的两个不同特权级别,它们用于管理和保护计算机系统的资源。以下是关于I/O、用户态和内核态之间的关系的重要信息:

  1. I/O操作涉及用户态和内核态

    • I/O操作包括从应用程序到外部设备(如磁盘、网络、键盘、显示器等)的数据传输。这些操作通常涉及到用户态和内核态之间的切换。
    • 当应用程序需要执行I/O操作时,它会调用操作系统提供的I/O系统调用(例如,读取文件或发送数据包)。这些系统调用是在用户态执行的。
    • 操作系统内核负责管理系统资源和I/O设备,因此在执行I/O操作时,控制必须从用户态切换到内核态,以便内核可以直接访问硬件资源。
  2. 用户态和内核态的切换

    • 用户态和内核态是不同的CPU执行模式。在用户态下,应用程序只能访问有限的资源,而在内核态下,操作系统内核可以访问系统的全部资源。
    • 当应用程序需要执行需要特权访问的操作,例如执行系统调用或访问设备驱动程序,它必须通过软中断或异常将控制权切换到内核态。这个切换是由操作系统的内核来处理的。
    • 用户态到内核态的切换会涉及一些开销,因为需要保存和恢复CPU寄存器、切换堆栈等操作。因此,频繁的切换会影响性能。
  3. 内核态的I/O处理

    • 一旦控制切换到内核态,操作系统内核就可以执行I/O操作。它会管理设备驱动程序、缓冲区、中断处理等细节,以确保数据的正确传输。
    • 内核还会维护I/O请求队列,以有效地处理多个I/O请求。
  4. 异步I/O和用户态I/O

    • 异步I/O是一种I/O模型,允许应用程序继续执行其他任务,而不必等待I/O操作完成。在这种情况下,通常使用异步I/O库来管理I/O操作,而不需要频繁地切换用户态和内核态。
    • 异步I/O通常通过回调函数或事件通知机制来处理I/O完成事件。

I/O操作涉及用户态和内核态之间的切换,因为操作系统内核必须管理和控制I/O设备。这个切换是操作系统的核心功能之一,用于确保计算机系统的稳定性、安全性和性能。不同的I/O模型可以影响用户态和内核态之间的切换方式和频率。说到内核态切换,下面不得不介绍的就是零拷贝。

Zero Copy(零拷贝)

Netty与零拷贝(Zero-Copy)之间有密切的关系,因为Netty是一个网络应用框架,专门设计用于高性能的网络通信,而零拷贝是一项技术,可以用于提高数据传输的效率,特别是在网络通信中。

以下是Netty与零拷贝的关系和如何在Netty中利用零拷贝技术的一些重要信息:

Netty的高性能特性:Netty被设计为高性能的网络应用框架,它旨在处理大量并发连接和高吞吐量的网络通信。为了实现这一目标,Netty采用了多种性能优化技术,其中之一就是零拷贝。

零拷贝是一种优化技术,旨在减少数据在内存之间的复制次数。传统的数据传输通常涉及将数据从一个缓冲区复制到另一个缓冲区,这会引入额外的CPU和内存开销。零拷贝技术通过操作系统或硬件支持,允许数据在不复制的情况下从一个地方传输到另一个地方,从而提高了数据传输的效率。
   
`ByteBuf`是Netty的自定义缓冲区类型,它支持零拷贝和引用计数等特性。在Netty中,`ByteBuf`可以在数据传输时直接暴露底层数据,而不需要进行数据复制,从而减少了CPU和内存开销。

零拷贝的实现

传统IO

read:将数据从磁盘通过DMA读取到内核缓存区中,在拷贝到用户缓冲区

write: 先将数据写入到socket缓冲区中,经过DMA写入网卡设备

在这里插入图片描述

4次切换,4次拷贝

虚拟内存 mmap

1.虚拟内存空间可以远远大于物理内存空间
2.多个虚拟内存可以指向同一个物理地址


正是多个虚拟内存可以指向同一个物理地址,可以把内核空间和用户空间的虚拟地址映射到同一个物理地址,这样的话,就可以减少IO的数据拷贝次数。用户态可以直接访问内核态的数据。

4次切换,3次拷贝

sendFile

sendfile表示在两个文件描述符之间传输数据,它是在操作系统内核中操作的,避免了数据从内核缓冲区和用户缓冲区之间的拷贝操作。

在这里插入图片描述

2次切换,3次拷贝

sendfile + DMA scatter/gather实现的零拷贝

linux2.4版本后,对sendfile做了优化升级,引入SG-DMA技术,其实就是对DMA拷贝加入了scatter/gather操作,它可以直接从内核空间缓冲区中将数据读取到网卡,这样的话还可以省去CPU拷贝。

在这里插入图片描述

2次切换,2次拷贝,CPU全程不参与数据搬运

直接内存 Direct ByteBuf

Netty通常使用直接内存(Direct Memory)来提高性能。直接内存是一种特殊的内存分配方式,不同于Java堆内存。

  1. ByteBuf与直接内存:Netty中的ByteBuf是一个用于处理字节数据的缓冲区抽象。ByteBuf可以使用直接内存分配,这称为"Direct ByteBuf"。直接内存分配意味着ByteBuf中的数据存储在堆外内存,而不是在Java堆中。

  2. 减少内存复制:在进行网络数据传输时,数据通常需要从应用程序的缓冲区复制到操作系统内核缓冲区,然后再从内核缓冲区复制到网络适配器。使用直接内存,可以在这些步骤中减少或消除数据复制,提高了性能。

  3. 零拷贝:直接内存可以与零拷贝相结合,使数据可以在应用程序和操作系统之间进行高效的传输。

  4. 缓冲区池:Netty通常使用池化的ByteBuf来管理直接内存的分配和释放。这种方式可以避免频繁地分配和释放直接内存,提高了内存管理的效率。

  5. 内存管理控制:Netty提供了一些工具和机制,帮助开发者有效地管理直接内存,包括手动释放、自动回收等。

示例代码
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;public class NettyDirectMemoryExample {public static void main(String[] args) throws InterruptedException {// 创建两个EventLoopGroup,一个用于接受客户端连接,一个用于处理客户端请求NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);NioEventLoopGroup workerGroup = new NioEventLoopGroup();try {// 创建ServerBootstrapServerBootstrap serverBootstrap = new ServerBootstrap();serverBootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) {ch.pipeline().addLast(new EchoServerHandler());}});// 绑定端口并启动服务器serverBootstrap.bind(8080).sync().channel().closeFuture().sync();} finally {bossGroup.shutdownGracefully();workerGroup.shutdownGracefully();}}// 自定义ChannelHandler处理客户端消息static class EchoServerHandler extends ChannelInboundHandlerAdapter {@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) {ByteBuf in = (ByteBuf) msg;ByteBuf out = Unpooled.directBuffer(); // 创建Direct ByteBuftry {out.writeBytes(in); // 将接收到的数据写入Direct ByteBufctx.write(out); // 写入回应数据到客户端ctx.flush();} finally {in.release(); // 释放接收缓冲区out.release(); // 释放Direct ByteBuf}}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {cause.printStackTrace();ctx.close();}}
}

使用直接内存需要谨慎管理,以避免内存泄漏和其他问题。

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

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

相关文章

自然语言处理 | WordNet

WordNet是词汇数据库,即英语词典,专为自然语言处理而设计。 Synset是一种特殊的简单接口,存在于 NLTK 中, 用于在 WordNet 中查找单词。同义词集实例是表达相同概念的同义词的分组。有些单词只有一个同义词集,有些则有多个。

C#对字典容器Dictionary<TKey, TValue>内容进行XML序列化或反序列化报错解决方法

一、问题描述 在使用C#对字典容器Dictionary<TKey, TValue>内容进行XML序列化报错【System.Exception:“不支持类型 System.Collections.Generic.Dictionary2[[System.String, mscorlib, Version2.0.0.0, Cultureneutral, PublicKeyTokenb77a5c561934e089],[System.Strin…

基于matlab统计Excel文件一列数据中每个数字出现的频次和频率

一、需求描述 如上表所示&#xff0c;在excel文件中&#xff0c;有一列数&#xff0c;统计出该列数中&#xff0c;每个数出现的次数和频率。最后&#xff0c;将统计结果输出到新的excel文件中。 二、程序讲解 第一步&#xff1a;选择excel文件&#xff1b; [Filename, Pathn…

C++笔记之信号量、互斥量与PV操作

C笔记之信号量、互斥量与PV操作 文章目录 C笔记之信号量、互斥量与PV操作1.信号量概念2.信号量例程一3.信号量例程二4.信号量例程三5.互斥量6.PV操作概念7.PV操作详解——抄自&#xff1a;https://mp.weixin.qq.com/s/vvjhbzsWQNRkU7-b_dURlQ8.PV操作的英文全称 1.信号量概念 …

HTTPS建立连接的过程

HTTPS 协议是基于 TCP 协议的&#xff0c;因而要先建立 TCP 的连接。在这个例子中&#xff0c;TCP 的连接是在手机上的 App 和负载均衡器 SLB 之间的。 尽管中间要经过很多的路由器和交换机&#xff0c;但是 TCP 的连接是端到端的。TCP 这一层和更上层的 HTTPS 无法看到中间的包…

拼多多API接口的使用方针如下:

了解拼多多API接口 拼多多API接口是拼多多网提供的一种应用程序接口&#xff0c;允许开发者通过程序访问拼多多网站的数据和功能。通过拼多多API接口&#xff0c;开发者可以开发各种应用程序&#xff0c;如店铺管理工具、数据分析工具、购物比价工具等。在本章中&#xff0c;我…

Spring中的设计模式

目录 工厂模式 组合模式 适配器模式 代理模式 单例模式 观察者模式 模板方法模式 责任链模式 Spring有着非常优雅的设计&#xff0c;很多地方都遵循SOLID原则&#xff0c;里面的设计模式更是数不胜数大概有以下几种&#xff1a; 工厂模式 所谓的工厂模式&#xff0c;核…

在MySQL中使用!=还能走索引吗?

在MySQL中使用!还能走索引吗&#xff1f; 一般情况下&#xff0c;我们会在一个索引上较多的使用等值查询或者范围查询&#xff0c;此时索引大多可以帮助我们极快的查询出我们需要的数据。 那当我们在where条件中对索引列使用!查询&#xff0c;索引还能发挥他的作用吗&#xf…

vue3 中使用 echarts 图表——准备篇

我们常常在项目中使用图表来表示数据&#xff0c;而目前最常用的图标就是echarts&#xff0c;接下来我们就开始学习在vue中使用echarts图标。 一、准备一个vue项目&#xff08;一般通过vite来构建&#xff0c;而不是vue-cli&#xff09; 1.找到打开vite官网 2. 运行创建命令 …

英语字典的一些 关键字 解释:

1. specialized &#xff1a;专业术语 这里的specialized 后面还接了 finance & economics 含义是 在经济和金融的领域&#xff0c;principal 作为一个专业术语&#xff0c;含义是 “本金&#xff0c;可生息资本” 2. XXXXXX

Web1.0——Web2.0时代——Web3.0

Web1.0 Web1.0是互联网的早期阶段&#xff0c;也被称为个人电脑时代的互联网。在这个阶段&#xff0c;用户主要通过web浏览器从门户网站单向获取内容&#xff0c;进行浏览和搜索等操作。在这个时代&#xff0c;技术创新主导模式、基于点击流量的盈利共通点、门户合流、明晰的主…

Vue-1.8生命周期

Vue生命周期 一个Vue实例从创建到销毁的整个过程。 生命周期&#xff1a; 1&#xff09;创建&#xff1a;响应式数据 ->发送初始化渲染请求 2&#xff09;挂载&#xff1a;渲染数据->操作dom 3&#xff09;更新&#xff1a;数据修改&#xff0c;更新视图 4&#xf…

028.Python面向对象_类补充_元类

我 的 个 人 主 页&#xff1a;&#x1f449;&#x1f449; 失心疯的个人主页 &#x1f448;&#x1f448; 入 门 教 程 推 荐 &#xff1a;&#x1f449;&#x1f449; Python零基础入门教程合集 &#x1f448;&#x1f448; 虚 拟 环 境 搭 建 &#xff1a;&#x1f449;&…

【tomcat、java】

java&#xff1a;maven配置 1.安装插件 <build><plugins><plugin><groupId>org.apache.tomcat.maven</groupId><artifactId>tomcat7-maven-plugin</artifactId><version>2.1</version><configuration><port&…

一种重要的天然氨基酸L-Homopropargylglycine(HPG)|CAS:942518-19-6

产品简介&#xff1a;L-Homopropargylglycine是一种重要的天然氨基酸&#xff0c;具有多种生物活性和医学应用价值。它广泛应用于生物学、药学、化学等多个领域。在生物学中&#xff0c;HPG被用作蛋白质合成的标记物&#xff0c;可以通过其特殊的化学反应与蛋白质中的半胱氨酸残…

【SV中的多线程fork...join/join_any/join_none】

SV中fork_join/fork_join_any/fork_join_none 1 一目了然1.1 fork...join1.2 fork...join_any1.3 fork...join_none 2 总结 SV中fork_join和fork_join_any和fork_join_none; Note: fork_join在Verilog中也有&#xff0c;只有其他的两个是SV中独有的&#xff1b; 1 一目了然 1.…

操作系统 OS

本文章是学习《操作系统》慕课版 和 王道《2024年 操作系统 考研复习指导》后所做的笔记&#xff0c;其中一些图片来源于学习资料。 目录 概念&#xff08;定义&#xff09; 目标 方便性 有效性 可扩充性 开放性 作用 OS 作为用户与计算机硬件系统之间的接口 — 人机交…

仅用61行代码,你也能从零训练大模型

本文并非基于微调训练模型&#xff0c;而是从头开始训练出一个全新的大语言模型的硬核教程。看完本篇&#xff0c;你将了解训练出一个大模型的环境准备、数据准备&#xff0c;生成分词&#xff0c;模型训练、测试模型等环节分别需要做什么。AI 小白友好~文中代码可以直接实操运…

Puppeteer基础知识(一)

Puppeteer基础知识&#xff08;一&#xff09; Puppeteer基础知识&#xff08;一&#xff09;一、简介二、其他一些自动化测试工具三、安装与使用四、Puppeteer常用命令五、常见问题解决&#xff1a; 一、简介 Puppeteer 是一个强大而灵活的工具&#xff0c;可以用于网页爬虫、…

Transformer预测 | Pytorch实现基于Transformer 的锂电池寿命预测(CALCE数据集)

文章目录 效果一览文章概述模型描述程序设计参考资料效果一览 文章概述 Pytorch实现基于Transformer 的锂电池寿命预测,环境为pytorch 1.8.0,pandas 0.24.2 随着充放电次数的增加,锂电池的性能逐渐下降。电池的性能可以用容量来表示,故寿命预测 (RUL) 可以定义如下: SOH(t…