Nettty(1) - 异步和事件驱动

1 Java网络编程

早期的Java API只支持由本地系统套接字提供的所谓的阻塞函数,下面将使用这些函数编写服务端的代码。

1.1 服务端

package com.socket.test;

import lombok.extern.slf4j.Slf4j;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;

@Slf4j
public class SocketServer {

    public static void main(String[] args) {
        ServerSocket serverSocket = null;
        BufferedReader input = null;
        PrintWriter out = null;
        Socket socket = null;

        try {
            // 创建一个新的ServerSocket,用以监听指定端口上的链接请求
            serverSocket = new ServerSocket(4700);

            // 对accept()方法的调用将被阻塞,直到一个链接的建立
            socket = serverSocket.accept();

            // 这些流对象都派生于该套接字的流对象,input代表从socket中读取客户端的信息,out代表向客户端发送通信
            input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            out = new PrintWriter(socket.getOutputStream(), true);

            String request;
            String response;

            while ((request = input.readLine()) != null) {
                // 如果客户端发送了OK,则退出循环
                if ("OK".equals(request)) {
                    break;
                }
                log.info("收到客户端请求 {}", request);
                // 这里是服务端的业务处理流程,可以是一个方法
                response = request + "处理后的结果";
                out.println(response);
            }

        } catch (IOException e) {
            log.error("Init server error ", e);
        } finally {
            if (serverSocket != null) {
                try {
                    serverSocket.close();
                } catch (IOException e) {
                    log.error("Close ServerSocket fail ", e);
                }
            }

            if (input != null) {
                try {
                    input.close();
                } catch (IOException e) {
                    log.error("Close input stream fail ", e);
                }
            }

            if (socket != null) {
                try {
                    socket.close();
                } catch (IOException e) {
                    log.error("Close socket fail ", e);
                }
            }

            if (out != null) {
                out.close();
            }
        }
    }


}

如代码所示,该服务端中同一时刻只能处理一个连接,要管理多个并发客户端,需要为每个新的客户端socket创建一个新的Thread。如下图所示:

以上方案的弊端:

  1. 在任何时候都可能有大量的线程处于休眠状态,只是等待输入或者输出数据就绪,这可能算是一种资源浪费。
  2. 需要为每个线程的调用栈都分配内存,其默认值大小区间为64KB~1MB,具体取决于操作系统。
  3. 即使JVM在物理上可以支持大量的线程,但远在达到该极限前,上下文切换所带来的开销就会带来麻烦。

综上,这种并发方案支撑中小数据量的客户端来说还算能接受,但支撑1W~10W或者更多并发连接时所需要的资源使得它很不理想。

 2 Java NIO

本地套接字也提供了非阻塞调用,其为网络资源的利用率提供了很多的控制。

  • 可以使用setsockopt()方法配置套接字,以便读/写调用在没有数据的时候立即返回。
  • 可以使用操作系统的事件通知API注册一组非阻塞套接字,以确定它们中是否有任何的套接字已经有数据的可供读写。

Java对于非阻塞I/O的支持是在2002年引入的,位于JDK 1.4的java.nio包中。

下图展示的非阻塞设计,实际上消除了上一节中所描述的哪些弊端。

Class java.nio.channels.Selector 是Java的非阻塞I/O实现的关键。它使用了事件通知API以确定在一组非阻塞套接字中有哪些已经就绪能够就行I/O相关的操作。因为可以在任何的事件检查任意的读操作或者写操作的完成状态,做到一个单一的线程便可以处理多个并发的链接。 

与阻塞I/O模型相比,这种模型提供了更好的资源管理:

  • 使用较少的线程便可以处理更多的链接,减少了内存管理和上下文切换所带来的开销。
  • 当没有I/O操作需要处理的时候,线程也可以被用于其他任务。

在高负载下可靠和高效地处理和调度I/O操作是一项繁琐而且容易出错的任务,做好使用基于NIO封装得较好的Netty进行编程。

3 Netty

3.1 netty特性

分类Netty特性
设计
  • 统一的API,支持多种传输协议,阻塞的和非阻塞的
  • 简单而强大的线程模型
  • 真正的无连接数据报套接字支持
  • 链接逻辑组件以支持复用
易于使用
  • 详实的Javadoc和大量的示例集
  • Jdk1.6及以上可以支持
性能
  • 比Java的核心API吞吐量高且延迟低
  • 因池化和复用,拥有更低的资源消耗
  • 最少的内存复制
健壮性
  • 不会因为慢速、快速或者超载的连接而导致OOM
  • 消除在高速网络中NIO应用程序常见的不公平读/写比率
安全性
  • 完整的SSL/TLS以及StartTLS的支持
  • 可用于受限环境下,如Applet和OSGI
社区驱动
  • 发布快速而且频繁
  • 文档参考较多

3.2 异步和事件驱动

netty实现的关键:异步与事件驱动。

  • 非租塞网络调用使我们可以不必等到一个操作的完成。完全异步的I/O正是基于这个特性构建的,并且更进一步;异步方案会立即返回,并在它完成时,会直接或者在稍后的某个时间点通知用户。
  • 选择器使我们能够通过较少的线程便可监视许多连接上的事件。

3.3 netty的核心组件

netty的主要构建块:

  • Channel
  • 回调
  • Future
  • 事件和ChannelHandler

这些构建块代表了不同类型的构造:资源、逻辑以及通知。你的应用程序将使用它们来访问网络以及流经网络的数据。

3.3.1 Channel

Channel是Java NIO的一个基本构造。

它代表一个到实体(如硬件设备、文件、网络套接字或者能够执行一个或者多个不同的I/O操作的程序组件)的开放连接,如读操作和写操作。

当前,可以把Channel看作是传入(入站)或者传出(出站)数据的载体。因此它可以被打开或被关闭,连接或者断开连接。 

3.3.2 回调

一个回调其实就是一个方法,一个指向已经被提供给另一个方法的方法的引用。这使得后者可以在适当的时候调用前者。回调在广泛的编程场景中都有应用,而且也是在操作完成后通知相关方最常见的方式之一

Netty在内部使用回调来处理事件:当一个回调被触发时,相关的事件可以被一个接口ChannelHandler的实现处理。

如下代码展示了:当一个新的链接已经创建时,ChannelHandler的channelActive()回调方法将被调用,并打印出一条信息。


public class ConnectHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        // 当一个新的链接已经建立时,channelActive(ChannelHandlerContext)将会被调用
        System.out.println("Client " + ctx.channel().remoteAddress() + " connected");
    }
}

3.3.3 Future

Future提供了另一种在操作完成时通知应用程序的方式。这个对象可以看作是一个异步操作的结果的占位符;它将在未来的某个时刻完成,并提供对其结果的访问。

JDK预置了 interface java.util.concurrent.Future,但是其所提供的实现,只允许手动检查对应的操作是否已经完成,或者一直阻塞直到它完成。整个过程非常繁琐,所以Netty只提供了它自己的实现——ChannelFuture,用于在执行异步操作的时候使用。

ChannelFuture 提供了几种额外方法,这些方法使我们能够注册一个或者多个ChannelFutureListener实例。监听器可以判断回调方法 operationComplete(),将会在对应的操作完成时被调用。然后监听器可以判断该操作是否成功。如果失败,可检索产生的Throwable。总之,由ChannelFutureListener提供的通知机制笑出了手动检查对应的操作是否完成的必要。

每个Netty的出站I/O操作都将返回一个ChannelFuture;也就是说,它们都不会阻塞,故Netty完全是异步和事件驱动的。

如下代码展示了一个ChannelFuture作为一个I/O操作的一部分返回的例子。这里的connect()方法将会直接返回,而不会阻塞,该调用将会在后台完成。

  Channel channel = new NioSocketChannel();
  // 异步连接到远程节点,不会被block
  ChannelFuture connect = channel.connect(new InetSocketAddress("127.0.0.1", 25));

如下代码显示如何利用 channelFutureListener。 

        Channel channel = new NioSocketChannel();
        // 异步连接到远程节点,不会被block
        ChannelFuture future = channel.connect(new InetSocketAddress("127.0.0.1", 25));

        // 注册一个ChannelFutureListener,以便在操作完成时获得通知
        future.addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture channelFuture) throws Exception {
                // 检查操作状态,如果操作成功,数据将写到Channel中。
                if (future.isSuccess()) {
                    // 如果操作是陈工的,则创建一个ByteBuf 以持有数据
                    ByteBuf byteBuf = Unpooled.copiedBuffer("你好", Charset.defaultCharset());
                    // 将数据异步地发送到远程节点,返回一个channelFuture
                    ChannelFuture writeAndFlush = future.channel().writeAndFlush(byteBuf);
                } else {
                    // 如果错误,则打印具体原因。
                    Throwable cause = future.cause();
                    log.error("Run future fail ", cause);
                }
            }
        });

回调和Future是相互补充的机制,它们相互结合,构成了Netty关键的构件块。

3.3.4 事件和ChannelHandler

netty使用不同的事件来通知我们状态的改变或者操作的状态。这使得我们能基于已经发生的事件触发适当的操作。这些操作可以是:

  • 记录日志
  • 数据转换
  • 流控制
  • 应用程序逻辑

Netty是一个网络编程框架,所以事件是按照它们与入站或者出站数据流的相关性进行分类的。可能由入站数据或者相应的状态更改而触发的事件包括:

  • 连接已被激活或者连接失活
  • 数据读取
  • 用户事件
  • 错误事件

出站时间是未来将会被处罚的每个动作的操作结果,这些动作包括:

  • 打开或者关闭到远程节点的链接
  • 将数据写到或者冲刷到套接字

每个时间都可以被分发给ChanelHandler类中的某个用户实现的方法。这是一个很好将事件驱动范式直接转换为应用程序构建块的例子。如下所示:

 Netty的ChannelHandler为处理器提供了基本的抽象,如上图所示,每个ChannelHandler的实例都类似于一种为了响应特定事件而被执行的回调。

Netty提供了大量预定义的开箱即用的ChannelHandler实现,包括各种协议入HTTP和SSL/TLS的handler实现。

3.4 总结

 3.4.1 Future、回调和ChannelHandler

Netty的异步编程模型是建立在Future和回调的概念上的,而将事件派发到ChannelHandler的方法则发生在更深的层次。结合在一起,这些元素就提供了一个处理环境,使你的应用程序逻辑可以独立于任何网络操作相关的顾虑而独立演变。这也是Netty设计的关键目标之一。

拦截操作以及高速地转换入站数据和出站数据,都只需要你提供回调或者利用操作所返回的Future。这使得链接操作变得即简单又高效,并且促进了可重用的通用代码编写。

3.4.2 选择器、事件和EventLoop

Netty通过触发事件将Selector从应用程序中抽象出来,消除了所有本来将需要手动编写的派发代码。在内部,会为每个Channel分配一个EventLoop用作处理所有事件,包括:

  • 注册感兴趣的事件
  • 将事件派发给ChannelHandler
  • 安排进一步的动作

EventLoop本身只由一个线程驱动,其处理了一个Channel的所有I/O事件,并且在该EventLoop的整个生命周期内都不会改变。这个简单而强大的设计消除了所有在ChannelHandler需要进行同步的任何顾虑。因此,程序员可以专注于正确的逻辑,用来在有感兴趣的数据要处理的时候执行。

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

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

相关文章

C#,图论与图算法,图最短路径的迪杰斯特拉(Dijkstra)算法与源代码

1 图的最短路径 给定一个图和图中的源顶点,查找从源到给定图中所有顶点的最短路径。 Dijkstra的算法与Prim的最小生成树算法非常相似。像Prim的MST一样,我们生成一个以给定源为根的SPT(最短路径树)。我们维护两个集合,一个集合包含最短路径树中包含的顶点,另一个集合包…

实验2 E-R图

实 验 名 称 实验2 E-R图 实验目的 (1)通过绘制系统E-R图,熟练掌握系统E-R图的绘制,以及写出E-R图的关系模式。 实验内容 绘用Microsoft Visio绘制出某防疫管理系统的E-R图并写出关系模式。 在抗击新冠肺炎疫情中&#xff…

海外媒体发稿:如何通过海外媒体推广发稿平台提升知名度-华媒舍

在数字时代,传统媒体受到了前所未有的冲击。海外媒体推广发稿平台成为了一种有效的方式,可以帮助个人、公司或组织提升在全球范围内的知名度。本文将介绍如何通过海外媒体推广发稿平台来实现这一目标,并给出一些建议和注意事项。 一、海外媒体…

使用 GTSAM 进行曲线拟合的示例

GTSAM介绍 GTSAM(通用因子图优化库)是一种用于状态估计和传感器数据融合的开源C++库。它提供了强大的工具,用于在机器人和自主系统领域进行感知、决策和控制。 功能和特点 状态估计与优化: GTSAM 提供了灵活且高效的状态估计框架,能够处理从传感器获取的数据,并…

支持普通表单+收费在线支付活动 自定义表单系统源码 带完整的安装代码包以及搭建教程

在当前信息化社会中,表单作为数据收集和信息交互的重要工具,被广泛应用于各类网站和平台。然而,市面上的表单系统大多功能单一,无法满足用户多样化的需求。特别是对于一些需要实现在线支付功能的收费活动,更是缺乏一套…

固定资产管理系统|基于JSP技术+ Mysql+Java+ B/S结构的固定资产管理系统设计与实现(可运行源码+数据库+设计文档)

推荐阅读100套最新项目 最新ssmjava项目文档视频演示可运行源码分享 最新jspjava项目文档视频演示可运行源码分享 最新Spring Boot项目文档视频演示可运行源码分享 2024年56套包含java,ssm,springboot的平台设计与实现项目系统开发资源(可…

flask+ flask_socketio HTTP/1.1“ 400 公网IP 问题解决方案

很经典的一个跨域问题 在服务端改成socketio SocketIO(app, cors_allowed_origins"*")就可以了

文心一言 VS 讯飞星火 VS chatgpt (219)-- 算法导论16.3 1题

一、请解释,在引理 16.2 的证明中,为什么若x.fregb.freg,则有a.fregb.fregx.freqy.freq。如果要写代码,请用go语言。 文心一言: 首先,看起来引理 16.2 的描述中有些混淆,因为 x.freg 和 x.fre…

分巧克力---第八届蓝桥杯省赛c++A,B组

题目描述如下 对于满足某个条件的单调最值问题,我们应该下意识考虑二分,我们分析本题的条件,要找一个边长最大值使得我们所有的巧克力切出该边长的正方形的数量大于等于人数,由于我们的边长一定在1到1e5之间,我们要在这…

jmx_prometheus_javaagent-0.19.0.jar+Prometheus+Grafana 监控Tongweb嵌入式(by lqw)

文章目录 1.思路2.部署准备3.应用jar包修改配置和导入tw嵌入式的依赖(参考)4.Prometheus部署5.Prometheus配置6.安装和配置Grafana 1.思路 Tongweb嵌入式最终是把依赖打入到java应用(也就是jar包里),然后启动jar包进行…

【工具使用】VScode配置gcc开发环境

一,简介 本文主要介绍如何在VScode中配置gcc环境,方便开发调试。 二,配置步骤 2.1 gcc环境配置 2.1.1 安装gcc环境 这里我使用的是msys2,具体安装步骤可以参考我另外一篇文章《史上最全msys2下载配置操作步骤》,这…

武汉星起航电子商务有限公司:引领中国跨境电商迈向全球舞台

在数字技术的浪潮下,跨境电商已成为推动经济持续增长和稳定外贸的关键力量。作为这一领域的领军者,武汉星起航电子商务有限公司正以其卓越的能力和经验,积极引领中国跨境电商走向世界舞台。 武汉星起航电子商务有限公司的崛起,不…

《由浅入深学习SAP财务》:第2章 总账模块 - 2.4 会计凭证处理

2.4.1 会计凭证处理的基本概念 会计凭证是企业经济业务在会计上的反映,它是用会计语言表达的一种单据。 典型生产企业的财务凭证创建方式: 企业在实施SAP的过程中,大部分凭证都是自动生成的。要保证这些凭证能准确地生成,必须要满…

Docker启动失败,报错Is the docker daemon running? Is the docker daemon running?

问题: docker没有正常启动 解决方法: systemctl daemon-reload systemctl restart docker.service

【嵌入式——QT】QThread创建多线程

【嵌入式——QT】QThread创建多线程 概述主要函数图示代码示例 概述 QThread类提供不依赖于平台的管理线程的方法,一个QThread类的对象管理一个线程,一般从QThread继承一个自定义类,并重定义虚函数run(),在run()函数里实现线程需…

java多线程使用与踩坑

SpringBoot使用多线程简单方法:地址 线程安全查阅资料参考:地址 背景: 经过上述资料查看,我想写个方法(依靠notify()唤醒,依靠wait()等待)实现两个线程轮流打印。 实现: 1.线程池配…

语言教育App头牌Duolingo如何重新点燃用户增长350%?

Duolingo是全球最大的语言教育APP,拥有数亿用户,然而用户增长正在放缓,本案例以Duolingo增长 通过数据建模洞察关键指标,并围绕指标用增长实验驱动,设计植根于创新的增长模式,包括启动排行榜,重…

一、初识 Web3

瑾以此系列文章,献给那些出于好奇并且想要学习这方面知识的开发者们 在多数时间里,我们对 web3 的理解是非常模糊的 就好比提及什么是 web1 以及 web2,相关概念的解释是: 1. 从 Web3 的开始 Web3,也被称为Web3.0&…

对接阿里支付宝支付

1. 账号注册 注册地址: 支付宝 文档地址: 小程序文档 - 支付宝文档中心 2.登录商家平台 登录地址: https://b.alipay.com/page/portal/home 1. 产品中心 - 当面付 - 申请开通 3. 登录开放平台 访问地址: 支付宝开放平台 1. 控制台 - 网页移动应用 2. 进入应用详情 - 开发…

nRF Sniffer 在Wireshark中的使用

一、简介 使用nRF Sniffer在wireshark中抓包是经常使用的。但是每次抓包会获取到空气中所有的数据包,数据量非常大。而对于开发人员而言,只需要其中特定的信息。此时就需要掌握数据的过滤语句。 二、过滤 1.根据MAC地址进行过滤 btle.advertising_add…