Netty网络编程(一):初步了解

2023-11-15

简介

我们常用浏览器来访问web页面得到相关的信息,通常来说使用的都是HTTP或者HTTPS协议,这些协议的本质上都是IO,客户端的请求就是In,服务器的返回就是Out。但是在目前的协议框架中,并不能完全满足我们所有的需求。比如使用HTTP下载大文件,可能需要长连接等待。
我们也知道IO方式有多种多样的,包括同步IO,异步IO,阻塞IO和非阻塞IO等。不同的IO方式其性能也是不同的,而netty就是一个基于异步事件驱动的NIO框架。

本系列文章将会探讨netty的详细使用,通过原理+例子的具体结合,让大家了解和认识netty的魅力。

netty介绍

netty是一个优秀的NIO框架,大家对IO的第一印像应该是比较复杂,尤其是跟各种HTTP、TCP、UDP协议打交道,使用起来非常复杂。但是netty提供了对这些协议的友好封装,通过netty可以快速而且简洁的进行IO编程。netty易于开发、性能优秀,同时兼具稳定性和灵活性。如果你希望开发高性能的服务,那么使用netty总是没错的。

netty的最新版本是4.1.66.Final,事实上这个版本是官方推荐的最稳定的版本,netty还有5.x的版本,但是官方并不推荐。

如果要在maven项目中使用,可以引入下面的依赖坐标:

    <dependency>
        <groupId>io.netty</groupId>
        <artifactId>netty-all</artifactId>
        <version>4.1.66.Final</version>
    </dependency>

下面我们将会从一个最简单的例子,体验netty的魅力。

netty的第一个服务器

什么叫做服务器?能够对外提供服务的程序就可以被称作是服务器。建立服务器是所有对外服务的第一步,怎么使用netty建立一个服务器呢?服务器主要负责处理各种服务端的请求,netty提供了一个ChannelInboundHandlerAdapter的类来处理这类请求,我们只需要继承这个类即可。

在NIO中每个channel都是客户端和服务器端沟通的通道。ChannelInboundHandlerAdapter定义了在这个channel上可能出现一些事件和情况,如下图所示:

在这里插入图片描述

如上图所示,channel上可以出现很多事件,比如建立连接,关闭连接,读取数据,读取完成,注册,取消注册等。这些方法都是可以被重写的,我们只需要新建一个类,继承ChannelInboundHandlerAdapter即可。

这里我们新建一个FirstServerHandler类,并重写channelRead和exceptionCaught两个方法,第一个方法是从channel中读取消息,第二个方法是对异常进行处理。

public class FirstServerHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        // 对消息进行处理
        ByteBuf in = (ByteBuf) msg;
        try {
            log.info("收到消息:{}",in.toString(io.netty.util.CharsetUtil.US_ASCII));
        }finally {
            ReferenceCountUtil.release(msg);
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        // 异常处理
        log.error("出现异常",cause);
        ctx.close();
    }
}

上面例子中,我们收到消息后调用release()方法将其释放,并不进行实际的处理。调用release方法是在消息使用完成之后常用的做法。上面代码将msg进行了ByteBuf的强制转换,如果并不想进行转换的话,可以直接这样使用:

   try {
        // 消息处理
    } finally {
        ReferenceCountUtil.release(msg);
    }

在异常处理方法中,我们打印出异常信息,并关闭异常的上下文。

有了Handler,我们需要新建一个Server类用来使用Handler创建channel和接收消息。接下来我们看一下netty的消息处理流程。

在netty中,对IO进行处理是使用多线程的event loop来实现的。netty中的EventLoopGroup就是这些event loop的抽象类。

我们来观察一下EventLoopGroup的类结构。

在这里插入图片描述

可以看出EventLoopGroup继承自EventExecutorGroup,而EventExecutorGroup继承自JDK自带的ScheduledExecutorService。

所以EventLoopGroup本质是是一个线程池服务,之所以叫做Group,是因为它里面包含了很多个EventLoop,可以通过调用next方法对EventLoop进行遍历。

EventLoop是用来处理注册到该EventLoop的channel中的IO信息,一个EventLoop就是一个Executor,通过不断的提交任务进行执行。当然,一个EventLoop可以注册多个channel,不过一般情况下并不这样处理。

EventLoopGroup将多个EventLoop组成了一个Group,通过其中的next方法,可以对Group中的EventLoop进行遍历。另外EventLoopGroup提供了一些register方法,将Channel注册到当前的EventLoop中。

从上图可以看到,register的返回结果是一个ChannelFuture,Future大家都很清楚,可以用来获得异步任务的执行结果,同样的ChannelFuture也是一个异步的结果承载器,可以通过调用sync方法来阻塞Future直到获得执行结果。

可以看到,register方法还可以传入一个ChannelPromise对象,ChannelPromise它同时是ChannelFuture和Promise的子类,Promise又是Future的子类,它是一个特殊的可以控制Future状态的Future。

EventLoopGroup有很多子类的实现,这里我们使用NioEventLoopGroup,Nio使用Selector对channel进行选择。还有一个特性是NioEventLoopGroup可以添加子EventLoopGroup。

对于NIO服务器程序来说,我们需要两个Group,一个group叫做bossGroup,主要用来监控连接,一个group叫做worker group,用来处理被boss accept的连接,这些连接需要被注册到worker group中才能进行处理。

将这两个group传给ServerBootstrap,就可以从ServerBootstrap启动服务了,相应的代码如下:

//建立两个EventloopGroup用来处理连接和消息
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new FirstServerHandler());
                        }
                    })
                    .option(ChannelOption.SO_BACKLOG, 128)
                    .childOption(ChannelOption.SO_KEEPALIVE, true);

            // 绑定端口并开始接收连接
            ChannelFuture f = b.bind(port).sync();

我们最开始创建的FirstServerHandler最作为childHandler的处理器在初始化Channel的时候就被添加进去了。

这样,当有新建立的channel时,FirstServerHandler就会被用来处理该channel的数据。

上例中,我们还指定了一些ChannelOption,用于对channel的一些属性进行设定。

最后,我们绑定了对应的端口,并启动服务器。

netty的第一个客户端

上面我们已经写好了服务器,并将其启动,现在还需要一个客户端和其进行交互。

如果不想写代码的话,可以直接telnet localhost 8000和server端进行交互即可,但是这里我们希望使用netty的API来构建一个client和Server进行交互。

构建netty客户端的流程和构建netty server端的流程基本一致。首先也需要创建一个Handler用来处理具体的消息,同样,这里我们也继承ChannelInboundHandlerAdapter。

上一节讲到了ChannelInboundHandlerAdapter里面有很多方法,可以根据自己业务的需要进行重写,这里我们希望当Channel active的时候向server发送一个消息。那么就需要重写channelActive方法,同时也希望对异常进行一些处理,所以还需要重写exceptionCaught方法。如果你想在channel读取消息的时候进行处理,那么可以重写channelRead方法。

创建的FirstClientHandler代码如下:

@Slf4j
public class FirstClientHandler extends ChannelInboundHandlerAdapter {

    private ByteBuf content;
    private ChannelHandlerContext ctx;

    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        this.ctx = ctx;
        content = ctx.alloc().directBuffer(256).writeBytes("Hello World".getBytes(StandardCharsets.UTF_8));
        // 发送消息
        sayHello();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        // 异常处理
        log.error("出现异常",cause);
        ctx.close();
    }

    private void sayHello() {
        // 向服务器输出消息
        ctx.writeAndFlush(content.retain());
    }
}

上面的代码中,我们首先从ChannelHandlerContext申请了一个ByteBuff,然后调用它的writeBytes方法,写入要传输的数据。最后调用ctx的writeAndFlush方法,向服务器输出消息。

接下来就是启动客户端服务了,在服务端我们建了两个NioEventLoopGroup,是兼顾了channel的选择和channel中消息的读取两部分。对于客户端来说,并不存在这个问题,这里只需要一个NioEventLoopGroup即可。

服务器端使用ServerBootstrap来启动服务,客户端使用的是Bootstrap,其启动的业务逻辑基本和服务器启动一致:

EventLoopGroup group = new NioEventLoopGroup();
try {
    Bootstrap b = new Bootstrap();
    b.group(group)
     .channel(NioSocketChannel.class)
     .handler(new ChannelInitializer<SocketChannel>() {
         @Override
         protected void initChannel(SocketChannel ch) throws Exception {
             ChannelPipeline p = ch.pipeline();
             p.addLast(new FirstClientHandler());
         }
     });

    // 连接服务器
    ChannelFuture f = b.connect(HOST, PORT).sync();

运行服务器和客户端

有了上述的准备工作,我们就可以运行了。首先运行服务器,再运行客户端。

如果没有问题的话,应该会输出下面的内容:

[nioEventLoopGroup-3-1] INFO com.yyb.FirstServerHandler - 收到消息:Hello World

总结

一个完整的服务器、客户端的例子就完成了。我们总结一下netty的工作流程,对于服务器端,首先建立handler用于对消息的实际处理,然后使用ServerBootstrap对EventLoop进行分组,并绑定端口启动。对于客户端来说,同样需要建立handler对消息进行处理,然后调用Bootstrap对EventLoop进行分组,并绑定端口启动。

有了上面的讨论就可以开发属于自己的NIO服务了。是不是很简单? 后续文章将会对netty的架构和背后的原理进行深入讨论,敬请期待。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Netty网络编程(一):初步了解 的相关文章

  • Mockito 在调用参数数量可变的方法时使用参数匹配器

    我试图在对具有可变数量参数的方法的调用中使用参数匹配器 Java 中的东西 没有成功 我的代码如下 我还将列出我尝试用来完成此工作的所有行 import static org mockito Mockito public class Met
  • 在 mvn 命令中指定 pom.xml 并混合其他项目的目标

    我有多个问题 我可以在 mvn 命令中指定 pom xml 吗 在当前项目上执行 mvn 命令时 我可以混合另一个项目的目标吗 例如 mvn clean otherproject comple otherproject install ot
  • 从 java sdk 向对等方发送提案时出现访问被拒绝错误

    我正在尝试使用以下代码查询区块链并收到访问被拒绝错误 我也遇到同样的错误sendTransactionProposal方法也是如此 UserContext adminUserContext RegisterEnrollUser regist
  • Spring Security 自定义过滤器

    我想自定义 Spring security 3 0 5 并将登录 URL 更改为 login 而不是 j spring security check 我需要做的是允许登录 目录并保护 admin report html 页面 首先 我使用教
  • 使用 Ant 将非代码资源添加到 jar 文件

    我正在将 java 应用程序打包成 jar 文件 我正在使用 ant 和 eclipse 我实际上需要在 jar 中直接在根文件夹下包含几个单独的非代码文件 xml 和 txt 文件 而不是与代码位于同一位置 我正在尝试使用includes
  • Java:在 eclipse 中导出到 .jar 文件

    我正在尝试将 Eclipse 中的程序导出到 jar 文件 在我的项目中 我添加了一些图片和 PDF s 当我导出到 jar 文件时 似乎只有main已编译并导出 我的意愿是如果可能的话将所有内容导出到 jar 文件 因为这样我想将其转换为
  • Spring RestTemplate 使用 cookie 遵循重定向

    最近我遇到了一个问题 我需要做一个GET请求远程服务 我假设使用一个简单的 servlet 并且 RestTemplate 返回Too many redirects 经过一番调查 似乎对指定远程服务发出的第一个请求实际上只是一个 302 重
  • Spring Data JPA 选择不同

    我有一个情况 我需要建立一个select distinct a address from Person a 其中地址是 Person 内的地址实体 类型的查询 我正在使用规范动态构建我的 where 子句并使用findAll Specifi
  • Spring Boot自动装配存储库始终为空[重复]

    这个问题在这里已经有答案了 每次我进入我的服务类时 存储库似乎都没有自动连接 因为它不断抛出 NullPointerException 谁能帮我检查一下我缺少什么吗 这是我的代码 演示应用程序 java package com exampl
  • 是否可以使用 Flying Saucer (XHTML-Renderer) 将 css 解析为类路径资源?

    我正在尝试将资源打包到 jar 中 但我无法让 Flying Saucer 在类路径上找到 css 我无法轻松构建 URL 来无缝解决此问题 https stackoverflow com questions 861500 url to l
  • 如何根据运行的 jar 的结果让我的 ant 任务通过或失败?

    我正在运行 CrossCheck 无浏览器 js 单元测试 作为 ant 脚本的一部分 如果 CrossCheck 测试失败 我希望 ant 报告失败 这是 build xml 中的相关部分
  • 来自十六进制代码的 Apache POI XSSFColor

    我想将单元格的前景色设置为十六进制代码中的给定颜色 例如 当我尝试将其设置为红色时 style setFillForegroundColor new XSSFColor Color decode FF0000 getIndexed 无论我在
  • 什么时候可以在 Java 中使用 Thead.stop() ?

    Thread stop 的 Java 文档听起来好像如果您调用 Thread stop 世界就会终结 已弃用 这种方法本质上是不安全的 停止线程 Thread stop 导致它解锁所有已锁定的监视器 作为未经检查的 ThreadDeath
  • 使用 JUnit 时,有没有办法验证测试方法中是否调用了 try/catch 指令的 Catch 部分?

    例如 如果我想测试以下课程 public class SomeClass public void someMethod try Some code where comething could go wrong catch Exception
  • 在另一个模块中使用自定义 gradle 插件模块

    我正在开发一个自定义插件 我希望能够在稍后阶段将其部署到存储库 因此我为其创建了一个独立的模块 在对其进行任何正式的 TDD 之前 我想手动进行某些探索性测试 因此 我创建了一个使用给定插件的演示模块 到目前为止 我发现执行此操作的唯一方法
  • 返回 Java 8 中的通用函数接口

    我想写一种函数工厂 它应该是一个函数 以不同的策略作为参数调用一次 它应该返回一个函数 该函数根据参数选择其中一种策略 该参数将由谓词实现 嗯 最好看看condition3为了更好的理解 问题是 它没有编译 我认为因为编译器无法弄清楚函数式
  • “无法实例化活动”错误

    我的一个 Android 应用程序拥有大约 100 000 个用户 每周大约 10 次 我会通过 Google 的市场工具向我报告以下异常情况 java lang RuntimeException Unable to instantiate
  • 如何使用play框架上传多个文件?

    我在用play framework 2 1 2 使用java我正在创建视图来上传多个文件 我的代码在这里 form action routes upload up enctype gt multipart form data
  • 将 Apache Camel 执行器指标发送到 Prometheus

    我正在尝试转发 添加 Actuator Camel 指标 actuator camelroutes 将交换 交易数量等指标 发送到 Prometheus Actuator 端点 有没有办法让我配置 Camel 将这些指标添加到 Promet
  • 在浏览器刷新中刷新检票面板

    我正在开发一个付费角色系统 一旦用户刷新浏览器 我就需要刷新该页面中可用的统计信息 统计信息应该从数据库中获取并显示 但现在它不能正常工作 因为在页面刷新中 java代码不会被调用 而是使用以前的数据加载缓存的页面 我尝试添加以下代码来修复

随机推荐

  • mybatis中的options注解

    mybatis的 Options注解能够设置缓存时间 能够为对象生成自增的key 场景 一个表id 主键 设置为自增 而当我们需要在dao层插入数据的时候立刻获取到该自动生成的id 实现 如下 Insert insert into inst
  • 内存检测工具Dr.Memory在Windows上的使用

    之前在https blog csdn net fengbingchun article details 51626705 中介绍过Dr Memory 那时在Windows上还不支持x64 最新的版本对x64已有了支持 这里再总结下 Dr M
  • 抽象工厂方法

    在工厂方法中 我们可以很方便的创建一个产品继承结构下的多个产品 那么考虑这么一种情况 我们需要在工厂中创建多个不同继承结构的产品 例如皮肤库 包含按钮 文本框 选择框等多个不同的元素 Spring皮肤库包含了一组相似的按钮 文本框 选择框
  • Window10手把手带你YOLOV5的火焰烟雾检测+tensorrt量化加速+C++动态库打包

    目录 0 引言 1 yolov5模型训练 1 2 模型训练 1 3 模型测试 2 模型转换 2 1 pt wts engine 2 1 1 pt转wts 2 1 2 wts转engine 3 动态库打包 0 引言 本人配置 win10 py
  • 简单实现点击el-tab-pane中的子组件按钮切换el-tabs

    简单实现点击el tab pane中的子组件按钮切换el tabs 实现效果 点击单条查看详细信息 实现跳转到详细信息面板 实现步骤 1 在父组件中声明函数 二 在要实现点击跳转的子组件中设置点击事件 主要的原理就是 tabs组件的面板激活
  • python笔记5-循环for和while

    1 for 获取列表中的项 for name in Christopher Susan print name name为变量 in后面的是循环列表 for会自动遍历列表 输出结果 Christopher Susan 指定循环次数 for i
  • WEB架构师成长之路之3:要懂哪些知识

    Web架构师究竟都要学些什么 具备哪些能力呢 先网上查查架构师的大概的定义 参见架构师修炼之道这篇文章 写的还不错 再查查公司招聘Web架构师的要求 总结起来大概有下面几点技能要求 一 架构师有优秀的编码能力 解决开发人员无法解决的难题 二
  • 伽罗华有限域的FEC

    FEC算法 cloudfly cn的博客 CSDN博客 fec算法 I 基于IP的语音和视频通话业务为了实时性 一般都是采用UDP进行传输 基站无线一般配置UM模式的RLC承载 因此丢包是不可避免的 在小区信号的边沿则丢包率会更高 为了通话
  • CloudCompare学习笔记(一)-- 界面初识

    本人也是cc纯小白 博客只用来记录学习内容和一些不懂的地方 如有错误还望指正 一 主界面 CC的界面大致可以分为以下几块 中间部分 1 DB Tree 打开的文件或者创建的实体都会存放在这里 2 Properties 选择的文件的属性 在打
  • c++函数为什么带imp_算法

    算法数学之美 日期 2018年1月13日 正文共 2779字3图 预计阅读时间 7分钟 来源 周平 最近网易云课堂开放了一节叫Linux内核分析的课程 一直对操作系统和计算机本质很感兴趣 于是进去看了下 才第一堂课 老师就要求学生写一篇关于
  • 【C++】模板进阶--类模板特化

    这篇博客可参照我的上篇博客模板初阶 中讲的函数模板和类模板的内容 一起学习 本节内容 非类型模板参数 类模板的特化 1 非类型模板参数 模板参数分 类型形参与非类型形参 类型形参 出现在模板参数列表中 跟在class或typename之类的
  • 记录QString的StartsWith()错误使用引发的问题

    一 问题现象 在QT开发的即时通信软件中发送图片看不到缩略图和原图 二 解决方法 在下载图片时会去检擦图片URL开头的服务器地址是否正确 使用了startsWith 去判断 但是这个函数在fileServerUrl为空的情况下也会返回tru
  • git push origin与git push -u origin master的区别

    git push origin 上面命令表示 将当前分支推送到origin主机的对应分支 如果当前分支只有一个追踪分支 那么主机名都可以省略 git push 如果当前分支与多个主机存在追踪关系 那么这个时候 u选项会指定一个默认主机 这样
  • 什么是区块链?

    原文出处 https mp weixin qq com s uzPoQBVkEy WVVS 1DFDoA 在今天 无论商业圈 科技圈还是金融圈 最热的词汇无非只有一个 那就是 区块链 我是黑马程序员的 无崖子 老师 下面我来介绍一下区块链的
  • Sentinel做服务熔断与限流,服务能被监控,但是监控列表为空的问题思考

    首先我觉得 服务和Sentinel不在同一台机器上面 本身是能够正常监控的 只要保证两台机器能够在一个内网中 能够互相连通即可 我在学习Sentinel的时候 我盲目使用云服务器的docker拉取Sentinel镜像 但是我开启了服务后 服
  • CV学习笔记 — 数据集预处理常用脚本总结

    笔者在学习计算机视觉时 需要经常借助脚本对数据集进行预处理 现将常用的脚本总结如下 1 批量修改文件后缀名 批量修改 import os import sys 需要修改后缀的文件目录 os chdir r H 葡萄 datasets JPE
  • 17.subplot():绘制网格区域中的几何形状相同的子区布局

    文章目录 1 函数subplot 的使用方法 2 在极坐标轴上绘制折线图 3 在极坐标轴上绘制散点图 4 在非等分画布的绘图区域上实现图形展示 subplot C R P 表示在C行R列的网格布局上 子区subplot 会被放置到第p个位置
  • 如何用Python提升未来竞争力?十年工作经验老程序员来告诉你。一般人我都不告诉他

    今年很多人在问一个问题 到底什么才是抗风险能力 稳定的工作 存款 理财 有人预测 到2030年 今天一半的工作岗位都将消失 关于哪些工作最先消失 李开复提出过 五秒钟准则 一项工作如果可以在5秒钟内作出相应决定 那就非常可能被人工智能取代
  • 【iOS】【最新】2023苹果开发者账号注册流程(公司类型)

    一 Apple Developer 申请开发者账号 Apple Developer 点击 Account 创建 Apple ID 最好新注册一个新的 专门用做开发 需要注意的是 开发者的名字和 ID 想好在填写 注册成功后 不能自己修改 需
  • Netty网络编程(一):初步了解

    文章目录 简介 netty介绍 netty的第一个服务器 netty的第一个客户端 运行服务器和客户端 总结 简介 我们常用浏览器来访问web页面得到相关的信息 通常来说使用的都是HTTP或者HTTPS协议 这些协议的本质上都是IO 客户端