Netty实战(三)

目录
  • 一、Channel、EventLoop 和 ChannelFuture
    • 1.1 Channel 接口
    • 1.2 EventLoop 接口
    • 1.3 ChannelFuture 接口
  • 二、ChannelHandler 和 ChannelPipeline
    • 2.1 ChannelHandler 接口
    • 2.2 ChannelPipeline 接口
    • 2.3 编码器和解码器
    • 2.4 抽象类 SimpleChannelInboundHandler
  • 三、引导

一、Channel、EventLoop 和 ChannelFuture

上一篇博文我们在构建服务端和客户端中出现了一些新的类,可能有些同学还有些不了解它们的具体功能。没关系,接下来我们对于 Channel、EventLoop 和 ChannelFuture 类进行的讨论增添更多的细节,这些类合在一起,可以被认为是 Netty 网络抽象的代表:

  • Channel : Socket;
  • EventLoop : 控制流、多线程处理、并发;
  • ChannelFuture : 异步通知。

1.1 Channel 接口

基本的 I/O 操作(bind()、connect()、read()和 write())依赖于底层网络传输所提供的原语。在基于 Java 的网络编程中,其基本的构造是 class Socket。Netty 的 Channel 接口所提供的 API,大大地降低了直接使用 Socket 类的复杂性。此外,Channel 也是拥有许多预定义的、专门化实现的广泛类层次结构的根,下面是一个简短的部分清单:

  • EmbeddedChannel;
  • LocalServerChannel;
  • NioDatagramChannel;
  • NioSctpChannel;
  • NioSocketChannel。

1.2 EventLoop 接口

EventLoop 定义了 Netty 的核心抽象,用于处理连接的生命周期中所发生的事件。如图在高层次上说明了 Channel、EventLoop、Thread 以及 EventLoopGroup 之间的关系。

在这里插入图片描述

这些关系可以表述为:

  • 一个 EventLoopGroup 包含一个或者多个 EventLoop;
  • 一个 EventLoop 在它的生命周期内只和一个 Thread 绑定;
  • 所有由 EventLoop 处理的 I/O 事件都将在它专有的 Thread 上被处理;
  • 一个 Channel 在它的生命周期内只注册于一个 EventLoop;
  • 一个 EventLoop 可能会被分配给一个或多个 Channel。

注意,在这种设计中,一个给定 Channel 的 I/O 操作都是由相同的 Thread 执行的,实际上消除了对于同步的需要

1.3 ChannelFuture 接口

Netty 中所有的 I/O 操作都是异步的。因为一个操作可能不会立即返回,所以我们需要一种用于在之后的某个时间点确定其结果的方法。为此,Netty 提供了ChannelFuture 接口,其 addListener()方法注册了一个ChannelFutureListener,以便在某个操作完成时(无论是否成功)得到通知。

可以将 ChannelFuture 看作是将来要执行的操作的结果的占位符。它究竟什么时候被执行则可能取决于若干的因素,因此不可能准确地预测,但是可以肯定的是它将会被执行。此外,所有属于同一个 Channel 的操作都被保证其将以它们被调用的顺序被执行。

二、ChannelHandler 和 ChannelPipeline

2.1 ChannelHandler 接口

从应用程序开发人员的角度来看,Netty 的主要组件是 ChannelHandler,它充当了所有处理入站和出站数据的应用程序逻辑的容器。这是可行的,因为 ChannelHandler 的方法是由网络事件(其中术语“事件”的使用非常广泛)触发的。事实上,ChannelHandler 可专门用于几乎任何类型的动作,例如将数据从一种格式转换为另外一种格式,或者处理转换过程中所抛出的异常。


举例来说,ChannelInboundHandler 是一个我们会经常实现的子接口。这种类型的ChannelHandler 接收入站事件和数据,这些数据随后将会被你的应用程序的业务逻辑所处理。当我们要给连接的客户端发送响应时,也可以从 ChannelInboundHandler 冲刷数据。我们的应用程序的业务逻辑通常驻留在一个或者多个 ChannelInboundHandler 中。

Netty 以适配器类的形式提供了大量默认的 ChannelHandler 实现,其旨在简化应用程序处理逻辑的开发过程。如ChannelPipeline中的每个ChannelHandler将负责把事件转发到链中的下一个 ChannelHandler。这些适配器类(及它们的子类)将自动执行这个操作,所以我们只重写那些你想要特殊处理的方法和事件。

那么为什么要用适配器的形式提供这些?

那是因为有一些适配器类可以将编写自定义的 ChannelHandler 所需要的努力降到最低限度,因为它们提供了定义在对应接口中的所有方法的默认实现。下面这些是编写自定义 ChannelHandler 时经常会用到的适配器类:

  • ChannelHandlerAdapter
  • ChannelInboundHandlerAdapter
  • ChannelOutboundHandlerAdapter
  • ChannelDuplexHandler

2.2 ChannelPipeline 接口

ChannelPipeline 提供了 ChannelHandler 链的容器,并定义了用于在该链上传播入站和出站事件流的 API。当 Channel 被创建时,它会被自动地分配到它专属的 ChannelPipeline。ChannelHandler 安装到 ChannelPipeline 中的过程如下所示:

  • 一个ChannelInitializer的实现被注册到了ServerBootstrap中或用于客户端的Bootstrap
  • 当 ChannelInitializer.initChannel()方法被调用时,ChannelInitializer将在 ChannelPipeline 中安装一组自定义的 ChannelHandler;
  • ChannelInitializer 将它自己从 ChannelPipeline 中移除。

为了审查发送或者接收数据时将会发生什么,让我们来更加深入地研究 ChannelPipeline和 ChannelHandler 之间的共生关系吧。

ChannelHandler 是专为支持广泛的用途而设计的,可以将它看作是处理往来 ChannelPipeline 事件(包括数据)的任何代码的通用容器。如图,其展示了从 ChannelHandler 派生的 ChannelInboundHandler 和ChannelOutboundHandler 接口。
在这里插入图片描述
使得事件流经 ChannelPipeline 是 ChannelHandler 的工作,它们是在应用程序的初始化或者引导阶段被安装的。这些对象接收事件、执行它们所实现的处理逻辑,并将数据传递给链中的下一个 ChannelHandler(有点类似责任链模式)。它们的执行顺序是由它们被添加的顺序所决定的。实际上,被我们称为 ChannelPipeline 的是这些 ChannelHandler 的编排顺序。

如图,说明了一个 Netty 应用程序中入站和出站数据流之间的区别。从一个客户端应用程序的角度来看,如果事件的运动方向是从客户端到服务器端,那么我们称这些事件为出站的,反之则称为入站的。
在这里插入图片描述
从上图看入站和出站 ChannelHandler 可以被安装到同一个 ChannelPipeline中。如果一个消息或者任何其他的入站事件被读取,那么它会从 ChannelPipeline 的头部开始流动,并被传递给第一个 ChannelInboundHandler。这个 ChannelHandler 不一定会实际地修改数据,具体取决于它的具体功能,在这之后,数据将会被传递给链中的下一个ChannelInboundHandler。最终,数据将会到达 ChannelPipeline 的尾端,届时,所有处理就都结束了。

数据的出站运动(即正在被写的数据)在概念上也是一样的。在这种情况下,数据将从ChannelOutboundHandler 链的尾端开始流动,直到它到达链的头部为止。在这之后,出站数据将会到达网络传输层,这里显示为 Socket。通常情况下,这将触发一个写操作。

ps:通过使用作为参数传递到每个方法的 ChannelHandlerContext事件可以被传递给当前ChannelHandler 链中的下一个ChannelHandler。因为你有时会忽略那些不感兴趣的事件,所以 Netty提供了抽象基类
ChannelInboundHandlerAdapter 和 ChannelOutboundHandlerAdapter。 ChannelHandlerContext 上的对应方法,每个都提供了简单地将事件传递给下一ChannelHandler的方法的实现。随后,我们可以通过重写你所感兴趣的那些方法来扩展这些类。

上图中出站和入站的ChannelHandler都在同一个ChannelPipeline中,那么ChannelPipeline是如何区分和处理这两种不同的类别的呢?

虽然 ChannelInboundHandle 和ChannelOutboundHandle 都扩展自 ChannelHandler,但是 Netty 能区分 ChannelInboundHandler 实现和 ChannelOutboundHandler 实现,并确保数据只会在具有相同定向类型的两个 ChannelHandler 之间传递。

当ChannelHandler 被添加到ChannelPipeline 时,它将会被分配一个ChannelHandlerContext,其代表了 ChannelHandler 和 ChannelPipeline 之间的绑定。虽然这个对象可以被用于获取底层的 Channel,但是它主要还是被用于写出站数据。

在 Netty 中,有两种发送消息的方式。我们可以直接写到 Channel 中,也可以 写到和 ChannelHandler相关联的ChannelHandlerContext对象中。前一种方式将会导致消息从ChannelPipeline 的尾端开始流动,而后者将导致消息从 ChannelPipeline 中的下一个 ChannelHandler 开始流动。

总结一下:

  • 将消息写入Channel 它将从尾端开始流动。
  • 将消息写入ChannelHandler中,它将会从下一个ChannelHandler开始流动。

2.3 编码器和解码器

当我们通过 Netty 发送或者接收一个消息的时候,就将会发生一次数据转换。入站消息会被解码;也就是说,从字节转换为另一种格式,通常是一个 Java 对象。如果是出站消息,则会发生相反方向的转换:它将从它的当前格式被编码为字节。这两种方向的转换的原因很简单:网络数据总是一系列的字节。(编解码)

对应于特定的需要,Netty 为编码器和解码器提供了不同类型的抽象类。例如,我们的应用程序可能使用了一种中间格式,而不需要立即将消息转换成字节。我们仍然需要一个编码器,但是它将派生自一个不同的超类。为了确定合适的编码器类型,我们可以应用一个简单的命名约定。通常来说,这些基类的名称将类似于 ByteToMessageDecoder 或 MessageToByteEncoder。对于特殊的类型,我们会发现类似于 ProtobufEncoder 和 ProtobufDecoder这样的名称——预置的用来支持 Google 的 Protocol Buffers。

严格地说,其他的处理器也可以完成编码器和解码器的功能。但是,正如有用来简化ChannelHandler 的创建的适配器类一样,所有由 Netty 提供的编码器/解码器适配器类都实现了 ChannelOutboundHandler 或者 ChannelInboundHandler 接口。

我们会发现对于入站数据来说,channelRead 方法/事件已经被重写了。对于每个从入站Channel 读取的消息,这个方法都将会被调用。随后,它将调用由预置解码器所提供的 decode()方法,并将已解码的字节转发给 ChannelPipeline 中的下一个 ChannelInboundHandler。
出站消息的模式是相反方向的:编码器将消息转换为字节,并将它们转发给下一个ChannelOutboundHandler。

2.4 抽象类 SimpleChannelInboundHandler

最常见的情况是,我们的应用程序会利用一个 ChannelHandler 来接收解码消息,并对该数据应用业务逻辑。要创建一个这样的 ChannelHandler,我们只需要扩展基类 SimpleChannelInboundHandler,其中 T 是我们要处理的消息的 Java 类型 。在这个 ChannelHandler 中,我们需要重写基类的一个或者多个方法,并且获取一个到 ChannelHandlerContext 的引用,这个引用将作为输入参数传递给 ChannelHandler 的所有方法。

在这种类型的 ChannelHandler 中,最重要的方法是 channelRead0(ChannelHandlerContext,T)。除了要求不要阻塞当前的 I/O 线程之外,其具体实现完全取决于我们。

三、引导

Netty 的引导类为应用程序的网络层配置提供了容器,这涉及将一个进程绑定到某个指定的端口(服务端),或者将一个进程连接到另一个运行在某个指定主机的指定端口上的进程(客户端)。

严格来说,“连接”这个术语仅适用于面向连接的协议,如 TCP,其保证了两个连接端点之间消息的有序传递

因此,有两种类型的引导:一种用于客户端(简单地称为 Bootstrap),而另一种(ServerBootstrap)用于服务器。无论我们的应用程序使用哪种协议或者处理哪种类型的数据,唯一决定它使用哪种引导类的是它是作为一个客户端还是作为一个服务器(后面我们单独提出来说明引导)。

类别 Bootstrap ServerBootstrap
网络编程中的作用 连接到远程主机和端口 绑定到一个本地端口
EventLoopGroup 的数目 1 2

ps:实际上,ServerBootstrap 类也可以只使用一个 EventLoopGroup,此时其将在两个场景下共用同一个 EventLoopGroup

细心的同学应该发现了,ServerBootstrap使用了2个EventLoopGroup,这是因为服务器需要两组不同的 Channel。

  • 第一组将只包含一个 ServerChannel,代表服务器自身的已绑定到某个本地端口的正在监听的套接字。(专门用来创建Channel )
  • 而第二组将包含所有已创建的用来处理传入客户端连接(对于每个服务器已经接受的连接都有一个)的 Channel。(专门为Channel分配EventLoop)

它们的关系如图:
在这里插入图片描述
ServerChannel 相关联的 EventLoopGroup 将分配一个负责为传入连接请求创建Channel 的 EventLoop。一旦连接被接受,第二个 EventLoopGroup 就会给它的 Channel分配一个 EventLoop。

本文转载于网络 如有侵权请联系删除

相关文章

  • 一文搞懂:离线数据、实时数据究竟该如何选择

    做数据和用数据的人绕不开的问题是数据的时效性,离线数据、实时数据分别指的是什么,业务应用时,究竟该以什么标准选择呢?很多业务产品或运营搞不懂两者的区别。提数据分析需求,想着肯定越实时越好,数据团队怎样拒绝?一、什么是离线数据、实时数据?数据从业务端产生,到分析或者反哺业务使用,需要经过一系列的清洗、处理过程,而这一过程带来时间窗口大小,就是数据的时效性。按照数据延迟的大小,可以将数据分为离线数据和以及实时数据(准实时)。1.离线数据离线数据一般是指T-1的日期,例如今天的日期T=2021-11-12,那么数据结果中,能够体现的业务数据只包括前一天的(昨日数据)。有人也称之为T+1的数据,把数据日期当作T,叫法不同,但本质都是指的今天处理的数据最新日期是截止昨天。2.实时数据实时数据主要是指的数据延迟小,例如毫秒、秒、分钟级的延迟,小时级的延迟称之为“准实时数据“更为准确了。例如,你熬夜赶在双十一晚上的最后1分钟,成功付了尾款,在双十一实时统计大屏中,GMV的值又滚动了一下。二、处理技术有何差异1.离线数据处理 离线数据处理也称之为“批处理”,数据产生之后,不会立即进行清洗,而是在固定的

  • 奇思妙想 | 重参数化,解耦网络结构的训练和推理

    来源丨https://zhuanlan.zhihu.com/p/361090497清华丁霄汉:本届CVPR收成不错,在旷视实习期间的工作RepVGG和DiverseBranchBlock(也可以称之为ACNetv2)都中了。前者是VGG类极简架构,3x3卷积一卷到底,连分支结构都没有,ImageNet上可达80.5%正确率,跟SOTA架构如RegNet比都有可见的性能提升。代码和模型全都放出了,Git上已经1400+star了。之前写了个稿子在这里:丁霄汉:RepVGG:极简架构,SOTA性能,让VGG式模型再次伟大(CVPR-2021)https://zhuanlan.zhihu.com/p/344324470后者是一个通用buildingblock,可以用一个很像Inception的block(其中包括averagepooling,1x1-BN-3x3-BN连续卷积)来替换通常的卷积,极大地丰富模型的微观结构,提升多种架构的性能。有意思的点在于,这样一个复杂的block可以在训练结束后等价转换为一个卷积,因此模型的最终大小和速度(相对于使用普通卷积的模型)完全不变!这两个工作的共同

  • 大型项目技术栈第五讲 富文本编辑器

    富文本编辑器一、vue与UEditor集成UEditor是由百度「FEX前端研发团队」开发的所见即所得富文本web编辑器,具有轻量,可定制,注重用户体验等特点1.前端实现1.1.下载最新编译的UEditor并放入项目对应目录将下载的文件夹命名成ueditor后复制到项目的webapp/static目录下1.2.页面引入VueUeditorWrap组件,该组件是一个Vue+UEditor+v-model双向绑定的vue组件<scriptsrc="https://cdn.jsdelivr.net/npm/vue-ueditor-wrap@latest/lib/vue-ueditor-wrap.min.js"></script>复制引入其他UEditor的js<scriptsrc="static/ueditor/ueditor.config.js"></script> <scriptsrc="static/ueditor/ueditor.all.js"></scr

  • c语言之遍历数组的几种方式

    假设现在我们有这么一个数组:inta[5]={1,2,3,4,5};复制第一种方式:直接通过下标遍历。for(inti=0;i<5;i++) { printf("%d\n",a[i]); }复制第二种方式:数组名就是首元素的地址,因此通过数组名,使用*获取其中的值的方式来遍历。for(inti=0;i<5;i++) { printf("%d\n",*(a+i)); }复制第三种方式:使用指针来遍历。int*p=a; for(inti=0;i<5;i++) { printf("%d\n",*(p+i)); }复制指针指向的是数组a的首元素的地址,然后通过(*指针)来解引用获取其中的值,最后通过(*指针+1)获取下一个元素的值。

  • LeetCode 205. 同构字符串(哈希map)

    1.题目给定两个字符串s和t,判断它们是否是同构的。如果s中的字符可以被替换得到t,那么这两个字符串是同构的。所有出现的字符都必须用另一个字符替换,同时保留字符的顺序。 两个字符不能映射到同一个字符上,但字符可以映射自己本身。示例1: 输入:s="egg",t="add" 输出:true 示例2: 输入:s="foo",t="bar" 输出:false 示例3: 输入:s="paper",t="title" 输出:true 说明: 你可以假设s和t具有相同的长度。复制来源:力扣(LeetCode)链接:https://leetcode-cn.com/problems/isomorphic-strings 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 2.解题两个哈希map来回查找对方classSolution{ public: boolisIsomorphic(strings,stringt){ unordered_map<char,

  • 文献解读|使用hi-C数据辅助埃及伊蚊基因组的组装

    早在2013年的时候,就已经有科学家提出了利用Hi-C数据来辅助基因组组装的思路,可以将scaffold进一步提升到染色体级别的长度,并提供了配套的分析软件LACHESIS。该软件默认输入的基因组组装结果完全正确,后续的操作都是建立在这个前提下。然而实际情况中,受到组装算法的限制,基因组草图中会存在拼接错误的情况。在2017年的时候,又有科学家提出了利用hi-c辅助基因组组装的新思路,并利用该思路进行了埃及伊蚊的基因组组装,对应的文章发表在science上,链接如下https://science.sciencemag.org/content/356/6333/92/tab-pdf在该文章提供的分析思路中,首先会根据hi-c数据的结果,对输入的基因组组装结果中的拼接错误进行校正,然后在进行后续分析,整个pipeline如下图所示可以分成以下4个大的步骤preliminaryfiltrationmisjoincorrectionorderingandorientationoverlapmerging 第一步首先对基因组组装的结果进行预处理,去除长度太短的scafflod。长度过短的sca

  • Moore-Penrose伪逆

    对于非方阵矩阵而言,其逆矩阵没有定义。假设在下面的问题中。我们希望通过矩阵A的左逆B来求解线性方程:Ax=y等式两边左乘左逆B后,我们得到x=By取决于问题的形式,我们可能无法设计一个唯一的映射A映射到B。如果矩阵A的行数大于列数,那么上述方程可能没有解。如果矩阵A的行数小于列数,那么上述矩阵可能有多个解。Moore-Penrose伪逆(Moore-Penrosepseudoinverse)使我们在这类问题上取得了一定进展。矩阵A的伪逆定义为:A^{+}=\lim_{\alpha\rightarrow0}\left(A^{T}A+\alphaI\right)^{-1}A^{T}计算伪逆的实际算法没有基于这个定义,而是使用下面的公式:A^{+}=VD^{+}U^{T}其中,矩阵U、D和V是矩阵A奇异值分解后得到的矩阵。对角矩阵D的伪逆D^+是其非零元素取倒数之后再转置得到的。当矩阵A的列数多于行数时,使用伪逆求解线性方程组是众多可能解法中的一种。特别地,x=A^{+}y是方程所有可行解中欧几里得范数\|x\|_{2}最小的一个。当矩阵A的函数多于列数时,可能没有解。在这种情况下,通过伪逆

  • 敏捷开发之Scrum扫盲篇

    现在敏捷开发是越来越火了,人人都在谈敏捷,人人都在学习Scrum和XP...什么是敏捷开发?敏捷开发(AgileDevelopment)是一种以人为核心、迭代、循序渐进的开发方法。怎么理解呢?首先,我们要理解它不是一门技术,它是一种开发方法,也就是一种软件开发的流程,它会指导我们用规定的环节去一步一步完成项目的开发;而这种开发方式的主要驱动核心是人;它采用的是迭代式开发;为什么说是以人为核心?我们大部分人都学过瀑布开发模型,它是以文档为驱动的,为什么呢?因为在瀑布的整个开发过程中,要写大量的文档,把需求文档写出来后,开发人员都是根据文档进行开发的,一切以文档为依据;而敏捷开发它只写有必要的文档,或尽量少写文档,敏捷开发注重的是人与人之间,面对面的交流,所以它强调以人为核心。什么是迭代?迭代是指把一个复杂且开发周期很长的开发任务,分解为很多小周期可完成的任务,这样的一个周期就是一次迭代的过程;同时每一次迭代都可以生产或开发出一个可以交付的软件产品。关于Scrum和XP前面说了敏捷它是一种指导思想或开发方式,但是它没有明确告诉我们到底采用什么样的流程进行开发,而Scrum和XP就是敏捷开发

  • Optional

    目前的理解:1.Optional中包含一个可以为空的对象,应该在所有可能为空的地方都加上Optional作为返回值。强迫调用方自行判断是否为空。自己因为老是忘记判断是否为null而导致空指针。2.Optional之前自己最习惯的用法是先判断是否是ifPresent,然后去进行下一步操作。看了代码之后,发现也可以通过orElse来设定默认值,或者是通过orElseThrow来抛出为空时的异常。3.看了Effectivejava,容器类的集合不应该返回Optional,直接返回一个空集合更加合适。以下为查看源代码学习:Optional是一个包含了可能为空的对象的容器对象,如果值存在(isPresent()),可以利用get()获取到-----------------------第一个对象:privatestaticfinalOptional<?>EMPTY=newOptional<>();复制一个空对象-------------------------privatefinalTvalue;复制非空,get()获取到的就是这个对象-------------------

  • 一个简单的sql审核案例 (r8笔记第90天)

    今天开发的同学发来一封邮件,希望我帮忙对一个sql语句做一个评估。他们也着急要用,但是为了稳妥起见,还是希望我来审核一下,这是一个好的习惯。 打开邮件,看到的语句是下面这样的形式。 selecta.cout1+b.cout2from(selectcount(*)ascout1fromTEST_ONLINEwhereCN=''andto_char(LOGIN_TIME,'yyyymmdd')=to_char(sysdate,'yyyymmdd')andrownum=1)a,(selectcount(*)ascout2fromTEST_USER_CENTERwhereCN=''andto_char(LAST_LOGOUT,'yyyymmdd')=to_char(sysdate,'yyyymmdd')andrownum=1)b; 看到这个语句,确实需要审核。 首先从sql语句结构上来说,实在不够好。 如果两个子查询的结果集条数大于1,很可能走笛卡尔积,貌似开发的同学也注意到

  • Future详解

    Future模式   【1】Future模式是多线程开发中常见的设计模式,它的核心思想是异步调用。对于Future模式来说,它无法立即返回你需要的数据,但是它会返回一个契约,将来你可以凭借这个契约去获取你需要的信息。   【2】通俗一点就是生产者-消费者模型的扩展。经典“生产者-消费者”模型中消息的生产者不关心消费者何时处理完该条消息,也不关心处理结果。Future模式则可以让消息的生产者等待直到消息处理结束,如果需要的话还可以取得处理结果。   java中是如何实现Future模式   【1】直接继承Thread或者实现Runnable接口都可以创建线程,但是这两种方法都有一个问题就是:没有返回值,也就是不能获取执行完的结果。   【2】因此java1.5就提供了Callable接口来实现这一场景,而Future和FutureTask就可以和Callable接口配合起来使用。【从而达到Future模式的效果】   Callable和Runnable的区别   【1】源码展示 @FunctionalInterface publicinterfaceCallable

  • 《闲聊瞎扯系列:科目二考试日记》

    前言:   本分类主要是记录本人自己的一些事情,和技术无关,主要用自己的语言风格描述一下生活中的事情。   今天我把科目二过了,实在太开心了,要是很顺利的通过可能还没这么开心,容我慢慢道来啊。   文章正文   早上是我要考科目二的日子,一大早发现下了大雨,内心骚动不安,隐隐觉得不妙,同事和朋友也相继发来贺电,恭贺下雨。果不其然,雨是越下越大。在候考厅的时间很难熬,我是最后一个,看着同行的几个人相继GG,看着教练惆怅的面部神经,我内心却安静得像一湖清水。什么,难道我一点不紧张吗?非也,我已经在计算补考的时间和因此要损失的成本。   终于轮到我了,教练赶紧撵着我,让我赶紧过去,到了考场之后,引车员竟然没给我好脸色,一直催我(卧槽,老子不是给了你红包了吗),这辆车也略破,后视镜竟然裂开,而且调不下去,离合也要抬很高才能响应。没办法,他强由他强,做好自己的。第一关,倒车入库,我的拿手啊,完美的弧线之后,我顺利入库,修都不用修。对于这种表现,系统回应给我的竟然是一段语音,“考试结束,请准备补考。。。”,啧啧。why?tellmewhy?哦哦,原来雨天导致我看错了线,没入库进去。好吧,

  • 【运维--监控】zabbix自定义发现策略

    目录: 前言 调整服务器 调整web模板 测试 前言   想想看系统监控工程师以前是一个多么高大上的职业,现在也渐渐消失了。因为自动化的普及,成功的实现了一个会开发,懂架构的人,可以干多少个技术岗位的工作。阿里出圈后,有多少公司,一群一群的高级人才想围住低级人才建立技术壁垒,把普通人打入到靠想法,靠体力活着。想法好,抽你9成水,体力抽你9.9成。  技术平台(地主)-->数据接口(卖铲子的人)-->使用者(码农)-->真正的业务(产出)-->干掉一名监控运维  感叹一下,回到正题。zabbix的自定义发现策略,真正的产出结果。本例以开源项目zabbix开发出自动添加为图片显示IP为例。展示如何干掉一名值班运维的。 调整服务器 1、脚本格式 bash版   #!/bin/bash ip_dev=($(ipa|awk-F"|:"'/stateUP/{printf"%s",$3}')) functionzabbix_json(){ printf"{\n" printf'\t'"\"data\":[

  • ASP.NET Core Autofac生命周期

    1.瞬时生命周期:每一期获取对象都是一个新的实例(默认的生命周期)  2.单例生命周期:在整个进程中,对象永远都是同一个实例(关键字:SingleInstance)  3.每个生命周期范围一个实例:同一个生命周期范围内是同一个实例。不同的生命周期范围,实例不同。(关键字:InstancePerLifetimeScope)  4.每个匹配生命周期范围一个实例(关键字:InstancePerMatchingLifetimeScope(名称))    5.每一个请求一个实例(关键字:InstancePerRequest)-主要用于MVC页面请求 作者:阿笨       【官方QQ一群:跟着阿笨一起玩NET(已满)】:422315558       【官方QQ二群:跟着阿笨一起玩C#(已满)】:574187616       【官方QQ

  • Autojs - 用 JavaScript 实现自己的安卓手机自动化工具脚本

    原文链接 一个神奇的APP 这个软件叫做Auto.js,只支持安卓,是一个不需要Root权限的JavaScript自动化软件。什么意思呢,就是在你的安卓手机上安装这个APP,然后通过编写JS脚本的方式实现自动化操作。类似的也有其他的一些软件,比如苹果自带的快捷操作,可以通过自定义配置完成一系列的流程,但是由于其目标是所有人都可以使用,所以定制的时候就没有那么灵活。 而Auto.js通过写代码的方式定制,那不用多说,灵活性肯定是没的说,关键的是,竟然写JS就可以,不用懂Java,也不用懂kotlin,是不是有点儿优秀呢。 有人说了,这有什么用呢,一般人看到还真觉得用处不大,但是,我们先来看看通过AutoJS能实现什么功能吧,只有你想不到的,没有做不到的,随便举几个例子: 基本操作,比如安装、卸载、启动、停止APP,切换网络模式、自动打开网址等; 微信轰炸机,也就是自动不断的给某个人发消息; 自动收取蚂蚁森林能量; 百度贴吧签到; 王者荣耀刷金币; 抖音、快手自动养号; 是不是操作逐渐具有迷惑性、邪恶感,有一些人可以用它来赚钱,那就是搞手机群控的,当然中间少不了黑灰产,以至于作者都已经在

  • Linq to Sql:更新之属性遍历法

    最近在学习LinqtoSql,于是自己做了一个例子。但是,当用到LinqtoSql来更新数据的时候,我傻眼了。 wwsDataContextdb=newwwsDataContext(); Tb_UserInforuserinfor=db.Tb_UserInfor.First(u=>u.ID==1); userinfor.UserName="ZhangLi"; db.submitchanges();复制 网上一搜索,全是这样的。那我就想了,要是一个表有几十个字段,那不是要写死人???于是乎我就开始想这个各种办法偷懒: wwsDataContextdb=newwwsDataContext(); db.Tb_UserInfor.attach(model); db.submitchanges();复制 结果报错,不能用。然后看到说attach(entity)方法默认调用attach(entity,false),要使用attach(entity,true),于是改了,但可惜,还是报错。然后我就想,不就是先查询出相应的实体类,然后给各个属性赋值嘛。那我能不能遍历属性呢? 百度一下,在

  • 汇总

    见:http://pgs98.com/  

  • 网络分析

    <mx:Script> <![CDATA[ [Bindable]privatevarstops:FeatureSet=newFeatureSet([]); [Bindable]privatevarbarriers:FeatureSet=newFeatureSet([]); [Bindable]privatevarlastRoute:Graphic; privatefunctionmapClickHandler(event:MapMouseEvent):void { if(selectedBtn==addStopsBtn) { varstop:Graphic=newGraphic(event.mapPoint,stopSymbol); inputsLayer.add(stop); stops.features.push(stop); } else { varbarrier:Graphic=newGraphic(event.mapPoint,barrierSymbol); inputsLayer.add(barrier); barriers.features.push(b

  • 修改const保护的值

    先看代码: #include<stdio.h> voidmain() { const int num = 10; int *a = (int *)(void *)&num; //把地址给a *a = 20; //对地址赋值为20 printf("*a=%d\tnum=%d\n",*a,num); } 这里通过强制类型转换 (int *)(void *)&num确实可以改变内存中的值,但是对于num,编译器已经将其处理为一个常量了,可以看下汇编就知道了(大神提供的回复)。 通过看printf的汇编: printf("*a=%d\tnum=%d\n",*a,num);009717BE push       0Ah 009717C0 mov      

  • 【转】 Pro Android学习笔记(九一):了解Handler(5):组件生命

       文章转载只能用于非商业性质,且不能带有虚拟货币、积分、注册等附加条件。转载须注明出处:http://blog.csdn.net/flowingflying/ 对于activity,消息是在OnCreate到OnDestory阶段都是有效的,即整个activity生命周期是有效的,worker线程也是,即使Activity不可视,worker线程和handler也有效。另一方面,在有worker线程的情况下,我们应该在onDestory中进行相应处理,所谓的gracefully关闭worker线程。 在低内存的情况下,当Activity不可视,会被回收,Android会删除进程。如果一个Activty被终结,只有在配置改变的情况下(例如横屏变竖屏)会自动重启。相比而言,Service的优先级别更高,Android会尽量保持它,即使因低内存而被回收,如果还有message未处理,则会被重启,但出现这种情况时,系统也不能确保运行完整。 Receiver将在后面学习。是call-and-be-gone模式,即被call,运行,然后结束。broadcastreceiver在主线程中执行,运

  • python + pytestTestreport生成测试报告_报告没有生成图标和报告样式错乱

    pytestreport生成测试报告的问题 1、生成报告html页面的样式错乱 2、生成报告html页面的图标没有展示 3、生成报告html页面的查询详情按钮点击没有相应     问题排除: 浏览器开发者工具,查看报告请求的资源,发现其中的dist/css/bootstrap.min.css、dist/jquery.slim.min.js、/dist/echarts.min.js加载失败 所以导致了报告展示出现问题   问题解决: 查看当前的网络是否正常,网络正常的情况下,再次访问,这三个资源正确加载,报告展示正常  

相关推荐

推荐阅读