Netty入门指南之NIO Selector写操作

news2024/11/18 7:26:51

作者简介:☕️大家好,我是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来提高服务端程序的效率,并避免资源的浪费。希望这些内容能对你在实际开发中有所帮助。

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

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

相关文章

transfomer模型——简介,代码实现,重要模块解读,源码,官方

一、什么是transfomer Transformer是一种基于注意力机制&#xff08;attention mechanism&#xff09;的神经网络架构&#xff0c;最初由Vaswani等人在论文《Attention Is All You Need》中提出。它在自然语言处理&#xff08;NLP&#xff09;领域取得了巨大成功&#xff0c;特…

自定义Graph Component:1.1-JiebaTokenizer具体实现

JiebaTokenizer类继承自Tokenizer类&#xff0c;而Tokenizer类又继承自GraphComponent类&#xff0c;GraphComponent类继承自ABC类&#xff08;抽象基类&#xff09;。本文使用《使用ResponseSelector实现校园招聘FAQ机器人》中的例子&#xff0c;主要详解介绍JiebaTokenizer类…

JavaScript_动态表格_添加功能

1、动态表格_添加功能.html <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>动态表格_添加功能</title><style>table{border: 1px solid;margin: auto;width: 100%;}td,th{text-align: ce…

Python:Unittest框架快速入门:用例、断言、夹具、套件、HTML报告、ddt数据驱动

快速看了套Unittest的入门教程 软件测试全套资料赠送_哔哩哔哩_bilibili软件测试全套资料赠送是快速入门unittest测试框架&#xff01;全实战详细教学&#xff0c;仅此一套&#xff01;的第1集视频&#xff0c;该合集共计11集&#xff0c;视频收藏或关注UP主&#xff0c;及时了…

ElasticSearch学习和使用 (使用head软件可视化es数据)

使用步骤 直接使用 Elasticsearch的安装和使用 下载Elasticsearch6.2.2的zip包&#xff0c;并解压到指定目录&#xff0c;下载地址&#xff1a;https://www.elastic.co/cn/downloads/past-releases/elasticsearch-6-2-2运行bin目录下的elasticsearch.bat启动Elasticsearch安…

【Python】python读取,显示,保存图像的几种方法

一、PIL&#xff1a;Python Imaging Library&#xff08;pillow&#xff09; PIL读取图片不直接返回numpy对象&#xff0c;可以用numpy提供的函数np.array()进行转换&#xff0c;亦可用Image.fromarray()再从numpy对象转换为原来的Image对象&#xff0c;读取&#xff0c;显示&…

Deepsort项目详解

一、目标追踪整体代码 代码目录如下图所示&#xff1a; 、 追踪相关代码&#xff1a; 检测相关代码和权重 调用 检测 和 追踪的代码&#xff1a; 首先代码分为三个部分&#xff1a; 目标追踪的相关代码和权重目标检测相关代码和权重&#xff0c;这里用的是yolov5.5目标检…

c语言练习11周(6~10)

输入任意字串&#xff0c;将串中除了首尾字符的其他字符升序排列显示&#xff0c;串中字符个数最多20个。 题干 输入任意字串&#xff0c;将串中除了首尾字符的其他字符升序排列显示&#xff0c;串中字符个数最多20个。输入样例gfedcba输出样例gbcdefa 选择排序 #include<s…

java--JDBC学习

文章目录 今日内容0 复习昨日1 JDBC概述2 JDBC开发步骤2.1 创建java项目2.2 导入mysql驱动包2.2.1 复制粘贴版本2.2.2 idea导入类库版本 2.3 JDBC编程 3 完成增删改3.1 插入3.2 更新3.3 删除 4 查询结果集ResultSet【重要】5 登录案例【重要】6 作业 今日内容 0 复习昨日 1 JDB…

数据结构:树的存储结构(孩子兄弟表示法,树和森林的遍历)

目录 1.树的存储结构1.双亲表示法&#xff08;顺序存储&#xff09;1.优缺点 2.孩子表示法&#xff08;顺序链式存储&#xff09;3.孩子兄弟表示法&#xff08;链式存储&#xff09;4.森林与二叉树的转换 2.树的遍历1.先根遍历2.后根遍历3.层序遍历 3.森林的遍历1.先序遍历2.中…

汉明距离(Java)

两个整数之间的 汉明距离 指的是这两个数字对应二进制位不同的位置的数目。 给你两个整数 x 和 y&#xff0c;计算并返回它们之间的汉明距离。 方法1:使用内置函数 class Solution {public int hammingDistance(int x, int y) {return Integer.bitCount(x ^ y);} }方法2:移位实…

Flutter:改变手机状态栏颜色,与appBar状态颜色抱持一致

前言 最近在搞app的开发&#xff0c;本来没怎么注意appBar与手机状态栏颜色的问题。但是朋友一说才注意到这两种的颜色是不一样的。 我的app 京东 qq音乐 这样一对比发现是有的丑啊&#xff0c;那么如何实现呢&#xff1f; 实现 怎么说呢&#xff0c;真不会。百度到的一些是…

java的类和继承构造

一些小技巧 类和对象 什么是类&#xff0c;对象&#xff0c;方法&#xff1f; 在下面的 Java 代码中&#xff0c;定义了一个名为 Person 的类&#xff0c;并提供了构造方法来初始化对象的属性。类中定义了 eat、sleep 和 work 三个方法&#xff0c;用于表示人的行为。在 main 方…

ValueError: ‘x‘ and ‘y‘ must have the same size

ValueError: ‘x’ and ‘y’ must have the same size 问题描述 出错代码 axes[0].errorbar(dates_of_observation, observed_lai, yerrstd_lai, fmt"o")X是观测的日期&#xff0c;16天&#xff0c;而且数据也是对应的16个&#xff0c;为什么不对应呢&#xff1f;…

python工具CISCO ASA设备任意文件读取

​python漏洞利用 构造payload&#xff1a; /CSCOT/translation-table?typemst&textdomain/%2bCSCOE%2b/portal_inc.lua&default-language&lang../漏洞证明&#xff1a; 文笔生疏&#xff0c;措辞浅薄&#xff0c;望各位大佬不吝赐教&#xff0c;万分感谢。 免…

git的分支及标签使用及情景演示

目录 一. 环境讲述 二.分支 1.1 命令 1.2情景演练 三、标签 3.1 命令 3.2 情景演示 ​编辑 一. 环境讲述 当软件从开发到正式环境部署的过程中&#xff0c;不同环境的作用如下&#xff1a; 开发环境&#xff1a;用于开发人员进行软件开发、测试和调试。在这个环境中…

git push origin masterEverything up-to-date

按住这个看一下很简单的问题&#xff0c;我在网上看了很多就是没找到能用的&#xff0c;最后找到了这个看起来写的很简单的一个文章&#xff0c;但他写的真的有用。 出现的问题 解决步骤

前端开发引入element plus与windi css

背景 前端开发有很多流行框架&#xff0c;像React 、angular、vue等等&#xff0c;本文主要讲vue 给新手用的教程&#xff0c;其实官网已经写的很清楚&#xff0c;这里再啰嗦只是为了给新手提供一个更加简单明了的参考手册。 一、打开element plus官网选则如图所示模块安装命令…

【学习笔记】Understanding LSTM Networks

Understanding LSTM Networks 前言Recurrent Neural NetworksThe Problem of Long-Term DependenciesLSTM Networks The Core Idea Behind LSTMsStep-by-Step LSTM Walk ThroughForget Gate LayerInput Gate LayerOutput Gate Layer Variants on Long Short Term MemoryConclus…

go学习之接口知识

文章目录 接口1.接口案例代码展示2.基本介绍3.基本语法4.应用场景介绍5.注意事项和细节6.接口编程经典案例7.接口与继承之间的比较8.面向对象编程--多态1&#xff09;基本介绍2&#xff09;快速入门3&#xff09;接口体现多态的两种形式 9.类型断言1&#xff09;先看一个需求2&…