Netty笔记3:NIO编程

Netty笔记1:线程模型

Netty笔记2:零拷贝

Netty笔记3:NIO编程

Netty笔记4:Epoll

Netty笔记5:Netty开发实例

Netty笔记6:Netty组件

Netty笔记7:ChannelPromise通知处理

Netty笔记8:ByteBuf使用介绍

Netty笔记9:粘包半包

Netty笔记10:LengthFieldBasedFrameDecoder很简单

Netty笔记11:编解码器

Netty笔记12:模拟Web服务器

Netty笔记13:序列化

文章目录

  • 前言
  • 编程示例
  • 总结

前言

想要快速理解NIO编程,需要先理解上篇的零拷贝技术和线程模型,本篇是对这两个知识的实践,也是netty的过度。

编程示例

我们尝试写一个NIO程序:

需要注意的是:

  1. 进行网络传输时,涉及到的数据,必须要经过缓冲区,不管是发送还是接收,结合用户态和内核态的切换过程就可以明白;
  2. NIO中的缓冲可以使用堆内存缓存和直接内存缓冲,这个需要结合零拷贝技术可以理解;
  3. 多路复用使用selector模式,需要循环遍历socket

注:buf在堆上。在进行数据发送时,如果使用堆内存,在JVM之外创建一个DirectBuf,然后把堆上的数据拷贝的这个DirectBuf,再写到SendBuf中,因为JVM中存在GC机制,如果使用引用方式,在拷贝过程中出现GC,会重新分配地址,导致数据出现问题。

服务端:

public class ServerHandle implements Runnable{private Selector selector;private ServerSocketChannel serverSocketChannel;public ServerHandle(int port) {try {selector = Selector.open();serverSocketChannel = ServerSocketChannel.open();// channel必须处于非阻塞模式下,不然会报错,所以不能同FileChannel一起使用serverSocketChannel.configureBlocking(false);serverSocketChannel.socket().bind(new InetSocketAddress(port));// 注册对应的事件,这里注册的是accept事件,只要监听到就会调用对应的处理器// 注册还可以添加附加对象,也就是第三个参数,获取方式:key.attachment();// 如果注册了事件,需要取消,需要调用channel.cancel()serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);System.out.println("服务端已准备好:" + port);} catch (IOException e) {throw new RuntimeException(e);}}@Overridepublic void run() {while (true) {try {// 阻塞直到通道就绪,这边设置了超时时间// 返回值:有多少通道就绪selector.select(1000);// 在通道就绪时,获取对应的键,也就是事件Set<SelectionKey> selectionKeys = selector.selectedKeys();Iterator<SelectionKey> iterator = selectionKeys.iterator();while (iterator.hasNext()) {SelectionKey key = iterator.next();iterator.remove();// 根据事件的类型进行对应的处理handlerInput(key);}} catch (Exception e) {throw new RuntimeException(e);}}}private void handlerInput(SelectionKey key) throws IOException {// 在这个循环中,可能存在key.cancel() 或者移除,所以这里需要判断是否有效if (!key.isValid()) {return;}try {if (key.isAcceptable()) {// 这里处理客户端连接服务端的事件ServerSocketChannel channel = (ServerSocketChannel)key.channel();try {// 接收请求SocketChannel sc = channel.accept();System.out.println("-----建立连接------");// 设置该通道非阻塞sc.configureBlocking(false);// 并注册read事件,监听着sc.register(selector, SelectionKey.OP_READ);} catch (IOException e) {System.out.println("连接客户端失败!");key.cancel();channel.close();}}if (key.isReadable()) {SocketChannel sc = (SocketChannel)key.channel();ByteBuffer buffer = ByteBuffer.allocate(1024);int read = sc.read(buffer);if (read > 0) {// 反转:将这个缓冲中的数据从现在的位置变成从0开始buffer.flip();byte[] bytes = new byte[buffer.remaining()];buffer.get(bytes);String msg = new String(bytes, StandardCharsets.UTF_8);System.out.println("服务器收到消息:" + msg);doWrite(sc, "hello,收到消息:" + msg);}}} catch (IOException e) {System.out.println("数据处理失败!");// 再处理失败,或异常时要退出通道,不然每次循环都会检查通道导致一致报错key.channel().close();key.cancel();}}private void doWrite(SocketChannel sc, String msg) throws IOException {byte[] bytes = msg.getBytes(StandardCharsets.UTF_8);ByteBuffer buffer = ByteBuffer.allocate(bytes.length);buffer.put(bytes);buffer.flip();sc.write(buffer);}
}

服务端启动:

    public static void main(String[] args) {ServerHandle serverHandle = new ServerHandle(8080);new Thread(serverHandle).start();}

客户端:

public class ClientHandle implements Runnable{private String ip;private int port;private SocketChannel socketChannel;private Selector selector;public ClientHandle(String ip, int port) {try {this.ip = ip;this.port = port;selector = Selector.open();socketChannel = SocketChannel.open();// 非阻塞状态socketChannel.configureBlocking(false);} catch (IOException e) {throw new RuntimeException(e);}}@Overridepublic void run() {try {// 启动后,执行连接操作if (socketChannel.connect(new InetSocketAddress(ip, port))) {// 连接服务端成功后,注册读取事件socketChannel.register(selector, SelectionKey.OP_READ);} else {// 如果连接失败,则再注册连接事件,之后再进行处理socketChannel.register(selector, SelectionKey.OP_CONNECT);}} catch (IOException e) {throw new RuntimeException(e);}while (true) {try {// 阻塞1000秒直到有通道就绪selector.select(1000);Set<SelectionKey> selectionKeys = selector.selectedKeys();Iterator<SelectionKey> iterator = selectionKeys.iterator();while (iterator.hasNext()) {SelectionKey key = iterator.next();iterator.remove();handleInput(key);}} catch (IOException e) {throw new RuntimeException(e);}}}public void sendMsg(String msg) throws IOException {byte[] bytes = msg.getBytes(StandardCharsets.UTF_8);ByteBuffer buffer = ByteBuffer.allocate(bytes.length);buffer.put(bytes);buffer.flip();socketChannel.write(buffer);}private void handleInput(SelectionKey key) throws IOException {if (!key.isValid()) {return;}SocketChannel sc = (SocketChannel) key.channel();if (key.isConnectable()) {if (sc.finishConnect()) {socketChannel.register(selector, SelectionKey.OP_READ);} else {System.exit(1);}}if (key.isReadable()) {ByteBuffer buffer = ByteBuffer.allocate(1024);int read = sc.read(buffer);if (read > 0) {buffer.flip();byte[] bytes = new byte[buffer.remaining()];buffer.get(bytes);String msg = new String(bytes, StandardCharsets.UTF_8);System.out.println("客户端收到消息:" + msg);} else if (read < 0) {key.cancel();;sc.close();}}}
}

客户端启动:

 public static void main(String[] args) throws IOException {ClientHandle handle = new ClientHandle("localhost", 8080);new Thread(handle).start();Scanner scanner = new Scanner(System.in);// 死循环保持监听while (true) {// 每次控制台输入,就发送给服务端handle.sendMsg(scanner.nextLine());}}

image-20250110175427693image-20250110175437500

总结

该示例对应于reactor单线程模型,服务端是一个单线程,通过selector单线程循环接收客户端的请求,并识别客户端请求事件类型,进行分发处理,相对于BIO很明显的区别,就是它不会等到上一个请求处理完成。在消息接收与发送的过程中,我们需要对缓冲数据进行处理,也就是对应于零拷贝知识点中提到的缓冲区概念,实例中用到了ByteBuffer对象,它是NIO中一个比较重要的对象,下一篇会进行说明。

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

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

相关文章

【技术总结】一文读懂Transformer

【技术总结】一文读懂Transformer 0. 前言1. 从词袋模型到 Transformer2. Transformer 架构2.1 关键思想2.2 计算注意力2.3 编码器-解码器架构2.4 Transformer 架构2.5 模型训练 3. Transformer 类别3.1 解码器(自回归)模型3.2 编码器(自编码)模型3.3 编码器-解码器模型 4. 经典…

vector 面试点总结

ps&#xff1a;部分内容使用“AI”查询 一、入门 1、什么是vector 动态数组容器&#xff0c;支持自动扩容、随机访问和连续内存存储。 2、怎么创建-初始化vector std::vector<int> v; // 创建空vectorstd::vector<int> v {1, 2, 3}; // 直接初始化std::vec…

2024年中国城市统计年鉴(PDF+excel)

2024年中国城市统计年鉴&#xff08;PDFexcel&#xff09; 说明&#xff1a;包括地级县级市 格式&#xff1a;PDFEXCEL 《中国城市统计年鉴》是一部全面反映中国城市发展状况的官方统计出版物&#xff0c;包括各级城市的详细统计数据。这部年鉴自1985年开始出版&#xff0c;…

SSL证书和HTTPS:全面解析它们的功能与重要性

每当我们在互联网上输入个人信息、进行在线交易时&#xff0c;背后是否有一个安全的保障&#xff1f;这时&#xff0c;SSL证书和HTTPS便扮演了至关重要的角色。本文将全面分析SSL证书和HTTPS的含义、功能、重要性以及它们在网络安全中的作用。 一、SSL证书的定义与基本概念 S…

辉视融合服务器方案:为小酒店行业铺垫未来智能化布局

在数字化浪潮席卷全球的今天&#xff0c;酒店行业正面临着前所未有的机遇与挑战。辉视融合服务器方案以其经济高效、智能运维和灵活扩展的独特优势&#xff0c;为小酒店行业提供了一站式的数字化解决方案&#xff0c;助力其在激烈的市场竞争中脱颖而出。通过重塑高效运维模式&a…

[vue] 缩放比适配问题

在开发前端页面的时候经常会发生不同用户存在不同缩放比的问题. 解决方案为 第一步, 在html标签中添加缩放锚点,及隐藏对应的滑块 项目刚开始 对于lang是没有设置的 , 这里我们设置成zh-CN,后续的最关键内容为transform-origin: 0 0;这样就保证了在缩放的时候不会乱跑. <…

通过多线程分别获取高分辨率和低分辨率的H264码流

目录 一.RV1126 VI采集摄像头数据并同时获取高分辨率码流和低分辨率码流流程 ​编辑 1.1初始化VI模块&#xff1a; 1.2初始化RGA模块&#xff1a; 1.3初始化高分辨率VENC编码器、 低分辨率VENC编码器&#xff1a; 1.4 VI绑定高分辨率VENC编码器&#xff0c;VI绑定RGA模块…

postman请求后端接受List集合对象

后端集合 post请求&#xff0c;即前端请求方式

TMS320F28P550SJ9学习笔记1:CCS导入工程以及测试连接单片机仿真器

学习记录如何用 CCS导入工程以及测试连接单片机仿真器 以下为我的CCS 以及驱动库C2000ware 的版本 CCS版本&#xff1a; Code Composer Studio 12.8.1 C2000ware &#xff1a;C2000Ware_5_04_00_00 目录 CCS导入工程&#xff1a; 创建工程&#xff1a; 添加工程&#xff1a; C…

MySQL——DQL、多表设计

目录 一、DQL 1.基本查询 2.条件查询 3.分组查询 4.排序查询 5.分页查询 二、多表设计 1.一对多 2.一对一 3.多对多 一、DQL 1.基本查询 注意&#xff1a; *号代表查询所有字段&#xff0c;在实际开发中尽量少用&#xff08;不直观、影响效率&#xff09; 2.条件查询…

千里科技亮相吉利AI智能科技发布会,共启“AI+车”新纪元

今天&#xff0c;在三亚举行的吉利AI智能科技发布会上&#xff0c;千里科技董事长印奇发表了主题为《从“车AI”到“AI车”》的演讲。印奇重点分享了对于“AI车”未来趋势的判断&#xff0c;并重点介绍了在吉利AI科技生态体系下&#xff0c;围绕智驾、智舱等领域的创新合作。基…

rust学习笔记11-集合349. 两个数组的交集

rust除了结构体&#xff0c;还有集合类型&#xff0c;同样也很重要&#xff0c;常见的有数组&#xff08;Array&#xff09;、向量&#xff08;Vector&#xff09;、哈希表&#xff08;HashMap&#xff09; 和 集合&#xff08;HashSet&#xff09;字符串等&#xff0c;好意外呀…

.net8 使用 license 证书授权案例解析

创建 webapi 项目 使用 .NET CLI 创建一个 ASP.NET Core Web API 应用&#xff0c;并添加指定的 NuGet 包&#xff0c;可以按照以下步骤操作&#xff1a; 创建 ASP.NET Core Web API 项目&#xff1a; dotnet new webapi -n WebAppLicense cd WebAppLicense添加 Standard.Li…

QT-自定义参数设计框架软件

QT-自定义参数设计框架软件 Chapter1 QT-自定义参数设计框架软件前言一、演示效果二、使用步骤1.应用进行参数注册2.数据库操作单例对象3.参数操作单例对象 三、下载链接 Chapter2 Qt中管理配置参数&#xff08;QSettings、单例模式&#xff09;1 前言2 QSettings类ini文件写in…

vue实例

// vue应用通过createApp函数创建一个新的应用实例&#xff0c;相当于根组件 import { createApp } from vue import App from ./App.vue // 在一个vue项目当中&#xff0c;有且只有一个vue的实例对象 const appcreateApp(App) // App:根组件 // 实例必须调用了.mount&am…

数据库设计理论与实践

设计理论 数据库范式 与反范式 ER模型 最小值 与最大值 | | &#xff1a;最小一个&#xff0c;最多一个 设计数据库我们只需要关心&#xff1a;一对一&#xff0c;一对多&#xff0c;多对多 。** 多的最小值 可以 不关注** ** 多对多 &#xff1a;我们可以 建立第三张表&am…

不用写代码,批量下载今日头条文章导出excel和pdf

前几天有人问我怎么批量抓取今日头条某个号的所有文章数据&#xff0c;需要文章链接&#xff0c;标题和时间&#xff0c;但是不会写代码&#xff0c;于是我写了个简单的教程 这里以渤海小吏为例 首先用edge浏览器安装web-scraper浏览器扩展 然后打开浏览器控制台&#xff0c;找…

FakeApp 技术浅析(二):生成对抗网络

生成对抗网络&#xff08;Generative Adversarial Networks&#xff0c;简称 GANs&#xff09;是 FakeApp 等深度伪造&#xff08;deepfake&#xff09;应用的核心技术。GANs 由 生成器&#xff08;Generator&#xff09; 和 判别器&#xff08;Discriminator&#xff09; 两个…

DeepSeek本地接口调用(Ollama)

前言 上篇博文&#xff0c;我们通过Ollama搭建了本地的DeepSeek模型&#xff0c;本文主要是方便开发人员&#xff0c;如何通过代码或工具&#xff0c;通过API接口调用本地deepSeek模型 前文&#xff1a;DeepSeek-R1本地搭建_deepseek 本地部署-CSDN博客 注&#xff1a;本文不仅…

JavaWeb后端基础(4)

这一篇就开始是做一个项目了&#xff0c;在项目里学习&#xff0c;我主要记录在学习过程中遇到的问题&#xff0c;以及一些知识点 Restful风格 一种软件架构风格 在REST风格的URL中&#xff0c;通过四种请求方式&#xff0c;来操作数据的增删改查。 GET &#xff1a; 查询 …