Netty入门指南之NIO Selector写操作

作者简介:☕️大家好,我是Aomsir,一个爱折腾的开发者!
个人主页:Aomsir_Spring5应用专栏,Netty应用专栏,RPC应用专栏-CSDN博客
当前专栏:Netty应用专栏_Aomsir的博客-CSDN博客

文章目录

  • 参考文献
  • 前言
  • 操作演示
    • 第一版
    • 第二版
  • 总结

参考文献

  • 孙哥suns说Netty
  • Netty官方文档

前言

在我们之前的学习中,我们主要关注了服务端处理的两种事件:ServerSocketChannel的ACCEPT事件SocketChannel的READ事件。然而,除了这两种事件外,还存在另一种重要的事件类型,即WRITE事件。在今天的学习中,我将专门对WRITE事件进行探讨。基于我们已经掌握的知识,我们将继续深入理解并掌握如何有效地处理和利用WRITE事件,以提高我们服务端程序的性能和效率。

操作演示

为了避免混淆,这里就只演示ACCEPT和WRITE,不演示READ事件

第一版

如下是第一版的代码和演示,服务端使用了Selector来避免无效的CPU空转。当服务端的ServerSocketChannel接收到ACCEPT事件并获得与客户端的SocketChannel连接后,它会立即向客户端发送大量的数据。然而,我们观察到一个问题:服务端总共发送了9个数据包,但其中有5个是空包。这是因为客户端处理接收数据的速度无法跟上服务端发送数据的速度,所以TCP为了进行流量控制,发送了几个空包。然而,这种情况并不理想,因为我们的服务端是单线程的。在向客户端发送数据的过程中,CPU资源被持续占用,但是这个线程却在发送空包,这无疑是对资源的浪费。因此,我们需要对代码进行修改,以解决这个问题
在这里插入图片描述

public class MyServer5 {
    public static void main(String[] args) throws Exception {
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);
        serverSocketChannel.bind(new InetSocketAddress(8000));

        Selector selector = Selector.open();
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        while (true) {
            selector.select();
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()) {
                SelectionKey sscKey = iterator.next();
                iterator.remove();

                if (sscKey.isAcceptable()) {
                    ServerSocketChannel server = (ServerSocketChannel) sscKey.channel();
                    SocketChannel socketChannel = server.accept();
                    socketChannel.configureBlocking(false);
                    socketChannel.register(selector, SelectionKey.OP_READ);

                    // 准备数据写回
                    StringBuilder sb = new StringBuilder();
                    for (int i = 0; i < 3000000; i++) {
                        sb.append("s");
                    }

                    ByteBuffer buffer = Charset.defaultCharset().encode(sb.toString());
                    while (buffer.hasRemaining()) {
                        int write = socketChannel.write(buffer);

                        // 实际每一次写了多少
                        System.out.println("write = " + write);
                    }
                }
            }
        }
    }
}
public class MyClient1 {
    public static void main(String[] args) throws Exception{
        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.connect(new InetSocketAddress(8000));

        // 读取服务端数据,输出每次读取的字节数
        while (true) {
            ByteBuffer buffer = ByteBuffer.allocate(1024 * 1024);
            int read = socketChannel.read(buffer);
            System.out.println("read = " + read);
        }
    }
}

第二版

在第一版的代码中,当客户端无法及时处理服务端发送的数据时,服务端会发送空包。这种情况下,服务端的线程资源被浪费,因为它们被用来发送无实质内容的空包。为了解决这个问题,我进行了以下优化:

在服务端的ServerSocketChannel接收到与客户端的SocketChannel连接后,我首先将准备好的数据存入buffer。只有当buffer中有数据需要发送时,我才会注册SocketChannel的WRITE事件,并将buffer作为附件附加到SocketChannel上。这样,我们在服务端有数据需要发送给客户端时,我们会监听到WRITE事件并且数据在附件中,多次发送也不会丢掉。

当客户端已经接收并处理完一部分数据,并且需要继续接收新的数据时,服务端的Selector#select()方法会被触发,然后我们可以继续处理WRITE事件,将buffer中的数据发送给客户端。在所有数据被成功发送后,我将取消SocketChannel的附件,并停止监听WRITE事件。

这样,这种优化方法使得我们的服务端程序更加高效,因为我们只在真正需要发送数据时,才会使用CPU资源。同时,通过动态地注册和注销WRITE事件,我们还可以更好地控制我们的服务端程序的行为,使其更加符合我们的需求。

public class MyClient1 {
    public static void main(String[] args) throws Exception{
        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.connect(new InetSocketAddress(8000));


        ByteBuffer buffer = ByteBuffer.allocate(1024 * 1024);
        // 读取服务端数据
        int read = 0;
        while (true) {
            read += socketChannel.read(buffer);
            System.out.println("read = " + read);
            buffer.clear();
        }
    }
}
public class MyServer5 {
    public static void main(String[] args) throws Exception {
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);
        serverSocketChannel.bind(new InetSocketAddress(8000));

        Selector selector = Selector.open();
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT, null);

        while (true) {
            selector.select();
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()) {
                SelectionKey selectedKey = iterator.next();
                iterator.remove();

                if (selectedKey.isAcceptable()) {
                    ServerSocketChannel server = (ServerSocketChannel) selectedKey.channel();
                    SocketChannel socketChannel = server.accept();
                    socketChannel.configureBlocking(false);
                    SelectionKey scKey = socketChannel.register(selector, SelectionKey.OP_READ);

                    // 准备数据写回
                    StringBuilder sb = new StringBuilder();
                    for (int i = 0; i < 3000000; i++) {
                        sb.append("s");
                    }

                    ByteBuffer buffer = Charset.defaultCharset().encode(sb.toString());
                    if (buffer.hasRemaining()) {
                        // 为当前的SocketChannel新增一个写事件
                        scKey.interestOps(scKey.interestOps() + SelectionKey.OP_WRITE);
                        
                        // 将buffer绑定到当前的key进行传递
                        scKey.attach(buffer);
                    }
                } else if (selectedKey.isWritable()) {
                    SocketChannel socketChannel = (SocketChannel) selectedKey.channel();
                    ByteBuffer buffer = (ByteBuffer) selectedKey.attachment();
                    socketChannel.write(buffer);

                    if (!buffer.hasRemaining()) {
                        selectedKey.attach(null);
                        
                        // 写完后取消写事件(当前这个selectedKey是一个SocketChannel)
                        selectedKey.interestOps(selectedKey.interestOps() - SelectionKey.OP_WRITE);
                    }
                }
            }
        }
    }
}

总结

本文主要探讨了服务端在向客户端发送数据过程中可能遇到的问题,并提出了通过使用Selector监听WRITE事件的解决方案。我们发现,当客户端无法及时处理来自服务端的数据时,服务端会发送空包,这无疑浪费了宝贵的CPU资源。为了解决这个问题,我们引入了Selector,并注册了WRITE事件。只有当有数据需要发送时,我们才会监听这个事件,这样就可以避免在客户端无法接收数据时,浪费资源发送空包。

在数据成功发送后,我们取消了对WRITE事件的监听,这样我们的服务端程序就不会在没有必要的情况下占用CPU资源。这种方法让我们的服务端程序运行得更高效,并且能更好地满足我们的需求。

至此,关于Java NIO中Selector的相关内容就讲述完毕。通过本文,我们了解了如何利用Selector来提高服务端程序的效率,并避免资源的浪费。希望这些内容能对你在实际开发中有所帮助。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

06-解决Spirng中的循环依赖问题

Bean的循环依赖问题 循环依赖: A对象中有B属性 , B对象中有A属性(丈夫类Husband中有Wife的引用, 妻子类Wife中有Husband的引用) toString()方法重写时直接输出wife/husband会出现递归导致的栈内存溢出错误 直接输出wife/husband会调用它们的toString()方法, 在toString()方法…

小黑子—springMVC:第二章

springMVC入门2.0 4、小黑子的springMVC拦截器4.1 Interceptor简介4.2 拦截器快速入门4.3 拦截器执行顺序4.4 拦截器执行原理 5、小黑子的springMVC全注解开发5.1 spring-mvc.xml中组件转化为注解形式5.1.1 消除spring-mvc.xml一二三 5.1.2 消除web.xml 6、小黑子的springMVC组…

新的开始吧

项目答辩终于结束了&#xff1a; 学习规划 下面先对自己的目前的情况来说&#xff1a; 学长学姐让我先把vue和boot学完&#xff0c;所以我打算先把vue3和boot学一下&#xff0c;但是每天还要花一点时间在六级的听力和阅读上面&#xff0c;还有就是算法&#xff1b; 下面进行…

数据结构----链式栈的操作

链式栈的定义其实和链表的定义是一样的&#xff0c;只不过在进行链式栈的操作时要遵循栈的规则----即“先进后出”。 1.链式栈的定义 typedef struct StackNode {SElemType data;struct StackNode *next; }StackNode,*LinkStack; 2.链式栈的初始化 Status InitStack(LinkSta…

Python---字典的增、删、改、查操作

字典的增操作 基本语法&#xff1a; 字典名称[key] value 注&#xff1a;如果key存在则修改这个key对应的值&#xff1b;如果key不存在则新增此键值对。 案例&#xff1a;定义一个空字典&#xff0c;然后添加name、age以及address这样的3个key # 1、定义一个空字典 person {…

RT-DETR算法改进:更换损失函数DIoU损失函数,提升RT-DETR检测精度

💡本篇内容:RT-DETR算法改进:更换损失函数DIoU损失函数 💡本博客 改进源代码改进 适用于 RT-DETR目标检测算法(ultralytics项目版本) 按步骤操作运行改进后的代码即可🚀🚀🚀 💡改进 RT-DETR 目标检测算法专属 文章目录 一、DIoU理论部分 + 最新 RT-DETR算法…

实验一 Anaconda安装和使用(上机Python程序设计实验指导书)

实验一 Anaconda安装和使用 一、实验目的和要求 &#xff08;一&#xff09;掌握Windows下Anaconda的安装和配置。 &#xff08;二&#xff09;掌握Windows下Anaconda的简单使用&#xff0c;包括IDLE、Jupyter Notebook、Spyder工具的使用。 &#xff08;三&#xff09;掌…

基于蚁狮算法优化概率神经网络PNN的分类预测 - 附代码

基于蚁狮算法优化概率神经网络PNN的分类预测 - 附代码 文章目录 基于蚁狮算法优化概率神经网络PNN的分类预测 - 附代码1.PNN网络概述2.变压器故障诊街系统相关背景2.1 模型建立 3.基于蚁狮优化的PNN网络5.测试结果6.参考文献7.Matlab代码 摘要&#xff1a;针对PNN神经网络的光滑…

数据结构 栈(C语言实现)

目录 1.栈的概念及结构2.栈的代码实现 1.栈的概念及结构 栈&#xff1a;一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端 称为栈顶&#xff0c;另一端称为栈底。栈中的数据元素遵守后进先出LIFO&#xff08;Last In F…

【Qt】撤销/恢复的快捷键

使用Qt的时候&#xff0c;有时需要撤销修改的代码&#xff0c;但可能回撤过头了。 下面提供2个快捷键&#xff0c;当撤销过头时&#xff0c;可恢复撤销内容。 撤销的快捷键是 CtrlZ 恢复/向前的快捷键是 CtrlShiftZ 我们可以自定义快捷键。 点击【工具】->【选项】 点击…

并发安全问题之--事物失效问题

并发安全问题之–事物失效问题 事物失效常见的6种原因&#xff1a; 1、事物方法非public修饰 2、非事物方法调用事物方法 3、事物方法抛出的异常被捕获了 4、事物方法抛出的异常类型不对 5、事物传播行为不对&#xff08;事物发生嵌套时有事物传播&#xff09; 6、事物锁属类没…

【Java】定时任务 - Timer/TimerTask 源码原理解析

一、背景及使用 日常实现各种服务端系统时&#xff0c;我们一定会有一些定时任务的需求。比如会议提前半小时自动提醒&#xff0c;异步任务定时/周期执行等。那么如何去实现这样的一个定时任务系统呢&#xff1f; Java JDK提供的Timer类就是一个很好的工具&#xff0c;通过简单…

C++二分查找算法:132 模式

说明 本篇是视频课程的讲义&#xff0c;可以看直接查看视频。也可以下载源码&#xff0c;包括空源码。 题目 给你一个整数数组 nums &#xff0c;数组中共有 n 个整数。132 模式的子序列 由三个整数 nums[i]、nums[j] 和 nums[k] 组成&#xff0c;并同时满足&#xff1a;i &l…

基于springboot实现沁园健身房预约管理系统【项目源码】

基于springboot实现沁园健身房预约管理系统演示 B/S架构 B/S结构是目前使用最多的结构模式&#xff0c;它可以使得系统的开发更加的简单&#xff0c;好操作&#xff0c;而且还可以对其进行维护。使用该结构时只需要在计算机中安装数据库&#xff0c;和一些很常用的浏览器就可以…

【算法】繁忙的都市(Kruskal算法)

题目 城市C是一个非常繁忙的大都市&#xff0c;城市中的道路十分的拥挤&#xff0c;于是市长决定对其中的道路进行改造。 城市C的道路是这样分布的&#xff1a; 城市中有 n 个交叉路口&#xff0c;编号是 1∼n &#xff0c;有些交叉路口之间有道路相连&#xff0c;两个交叉…

配置开启Docker2375远程连接与解决Docker未授权访问漏洞

一、配置开启Docker远程连接 首先需要安装docker,参考我这篇文章&#xff1a;基于CentOS7安装配置docker与docker-compose 配置开启Docker远程连接的步骤&#xff1a; //1-编辑/usr/lib/systemd/system/docker.service 文件 vim /usr/lib/systemd/system/docker.service //2…

合并集合(并查集)

一共有 n个数&#xff0c;编号是 1∼n&#xff0c;最开始每个数各自在一个集合中。 现在要进行 m 个操作&#xff0c;操作共有两种&#xff1a; M a b&#xff0c;将编号为 a 和 b 的两个数所在的集合合并&#xff0c;如果两个数已经在同一个集合中&#xff0c;则忽略这个操作…

解析JSON字符串:属性值为null的时候不被序列化

如果希望属性值为null及不序列化&#xff0c;只序列化不为null的值。 1、测试代码 配置代码&#xff1a; mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); 或者通过注解JsonInclude(JsonInclude.Include.NON_NULL) //常见问题2&#xff1a;属性为null&a…

python异常、模块与包

1.异常 异常&#xff1a;当检测到一个错误时&#xff0c;Python解释器就无法继续执行了&#xff0c;反而出现了一些错误的提示&#xff0c;这就是所谓的“异常”&#xff0c;也就是我们常说的BUG。 1.1捕获异常 基本语法&#xff1a; try:可能发生错误代码 except:如果出现…

数据结构之栈

目录 引言 栈的概念与结构 栈的实现 定义 初始化 销毁 压栈 检测栈是否为空 出栈 获取栈顶元素 检测栈中有效元素个数 元素访问 源代码 stack.h stack.c test.c 引言 数据结构之路经过链表后&#xff0c;就来到了栈&#xff08;Stack&#xff09; 栈的概念…