03 Netty的组件和设计

点击查看 《Netty in Action》笔记目录

本文是对《Netty in Action》第3章内容的笔记和翻译,主要内容包括:

  • Netty 技术和架构方面的介绍
  • ChannelEventLoopChannelFuture
  • ChannelHandlerChannelPipeline
  • 启动( Bootstrapping)

Channel、EventLoop 和 ChannelFuture

下面这些可以被看做 Netty 网络模型的抽象:

  • ChannelSockets
  • EventLoop:流控、多线程、并发
  • ChannelFuture:异步通知

Channel 接口

Java 中基本的网络 I/O 操作依赖 Socket 类,如:bind()connect()read()write()。Netty 中的 Channel 接口提供的 API 与直接操作 Sockets API 相比,大大减少了复杂度。此外,Channel 是很多专有实现的基类,这些实现包括:

  • EmbeddedChannel
  • LocalServerChannel
  • NioDatagramChannel
  • NioSctpChannel
  • NioSocketChannel

EventLoop 接口

EventLoop 定义了 Netty 核心抽象:在一个连接周期中处理发生的事件。图3.1 在一个高层次展示了 ChannelsEventLoopsThreadsEventLoopGroups 的关系。关系如下:

  • 一个 EventLoopGroup 包括了一个或多个 EventLoop
  • 一个 EventLoop 在生命周期中只会绑定到一个单一的线程 Thread
  • 所有被 EventLoop 处理的 I/O 事件都会被它绑定的线程 Thread 处理。
  • 一个 Channel 会在一个单一的 EventLoop 中注册它的生命周期。
  • 一个单一的 EventLoop 可能会被分配多个 Channels
图3.1 Channels、EventLoops 和 EventLoopGroups 的关系

值得注意的是:在这个设计中,一个给定 Channel 的 I/O 的操作都是由同一个线程执行的,也就是这些操作是单线程的,所以无形中消除了同步的问题。

ChannelFuture 接口

Netty 中提供了 ChannelFuture,这个接口具有 addListener()方法,通过这个方法可以注册一个 ChannelFutureListener,这个监听器会在方法结束时被调用(无论执行是否成功)。

ChannelFuture 的更多介绍
可以把 ChannelFuture 看做是存放结果的一个站位空间,这里的结果将会在未来的某个时刻被填满。可以确定的是:对于同一个 Channel 的所有操作都会被执行,并且执行顺序与调用的顺序一致。

ChannelHandler 和 ChannelPipeline

ChannelHandler 接口

对于应用开发者来说,Netty 中最重要的组件是 ChannelHandler。这个组件承载了对输入输出数据的所有应用处理逻辑。

事实上,ChannelHandler 几乎可以满足所有的操作,包括:转换数据的格式、处理过程中抛出的异常等。

ChannelPipeline 接口

ChannelPipeline 提供了链式 ChannelHandlers 的一个承载容器,并且定义了在这个链中传递输入输出事件的 API。当一个 Channel 被创建的时候,它会被自动绑定到它所在的 ChannelPipeline 上。

ChannelHandler 通过以下步骤安装到 ChannelPipeline 上:

  • ServerBootstrap 上注册一个 ChannelInitializer 的实现。
  • ChannelInitializer.initChannel() 函数被调用的时候,ChannelInitializer 将会在 pipeline 中安装一系列的 ChannelHandler
  • 然后 ChannelInitializerChannelPipeline 中把它自身删除。

图3.2 展示了从 ChannelHandler 派生出来的 ChannelInboundHandlerChannelOutboundHandler

图3.2 ChannelHandler 类的继承关系

图3.3 展示了 Netty 应用中输入和输出端数据流的区别。

图3.3 输入和输出端数据流的区别

输入输出处理器的更多介绍
一个事件可以通过 ChannelHandlerContext 传递到链中的下一个处理器,ChannelHandlerContext 在每个方法中都会被作为一个输入参数。因为你有时候会忽略不感兴趣的事件,Netty 提供了抽象基础类 ChannelInboundHandlerAdapterChannelOutboundHandlerAdapter。每个都提供了方法实现,通过调用 ChannelHandlerContext 中的相应方法,可以简化传递到下一个处理者的操作。然后,你可以扩展类并覆盖你感兴趣的方法。

当两种类别处理器在同一个 ChannelPipeline 中混合时会发生什么?尽管输入输出 handler 都继承于 ChannelHandler,但 Netty 还是会区分 ChannelInboundHandlerChannelOutboundHandler 的不同实现,并且 确保数据只在方向相同类型的 handler 之间传递

ChannelHandler 被加入到 ChannelPipeline 中时,它会被分配一个 ChannelHandlerContext,这个 ChannelHandlerContext 代表了 ChannelHandlerChannelPipeline 之间的绑定关系。

Netty 中发送数据的方法有两种:

  • 直接写入到 Channel,消息会从 ChannelPipeline 的尾部开始传递。
  • 写入到 ChannelHandler 关联的 ChannelHandlerContext,消息会从 ChannelPipeline 中的__下一个__handler 开始传递。

进一步了解 ChannelHandler

Netty 中有一些适配类,从而减少了常用 ChannelHandler 类的工作量。这些适配类提供了相应接口方法的默认实现。

你在写 handle 时经常会用到的适配类包括:

  • ChannelHandlerAdapter
  • ChannelInboundHandlerAdapter
  • ChannelOutboundHandlerAdapter
  • ChannelDuplexHandlerAdapter

编码和解码

当你使用 Netty 发送和接收数据时,需要进行数据格式的转换。输入的消息将会被解码,也就是将输入的字节转换为另一种格式,典型的是转化为一个 Java 对象。如果数据是输出的,相反的操作将会发生:会从现在的格式编码成自己。需要这样做的原因很简单,网络传输的数据是一系列的字节。

所有 Netty 提供的编码解码(encoder/decoder)的适配类都实现于 ChannelInboundHandler 或者 ChannelOutboundHandler

你将发现:对于输入数据,channelRead 方法或事件会被覆写。对于输入 Channel 的每条数据,这个方法都会被调用。然后会调用解码器的 decode() 方法,并将解码后的字节传输到 pipeline 中的下一个 ChannelInboundHandler

而输出数据的模式正好相反:编码器将消息转换为字节,并将这些字节传递到下一个 ChannelOutboundHandler

抽象类 SimpleChannelInboundHandler

很常见的是,你的应用需要实现一个 handler 来接受解码的数据并且在数据上实现应用逻辑。创建这样的一个 ChannelHandler,你只需要扩展基类 SimpleChannelInboundHandler<T>,其中 T 是你需要处理的数据对应的 Java 类型。

这个 handler 中最重要的一个方法是 channelRead0(ChannelHandlerContext,T)

Bootstrapping

Netty 的启动类可以为应用的网络层提供配置容器。

面向连接的协议
请记住:严格意义上的连接(“connection”)只适用于面向连接的协议,如 TCP。在这样的协议中,可以保证连接的端之间可以按序接收到数据。

相应的,有两种类型的启动类:一个是客户端(Bootstrap),另一个是服务端(ServerBootstrap)。

下表对这两种类型的启动进行了比较。

类别 Bootstrap ServerBootstrap
网络功能 连接远程主机和端口 绑定本地端口
EventLoopGroup 的数目 1 2

启动一个客户端只需要一个 EventLoopGroup,但是服务端的 ServerBootstrap 需要两个(可以是同一个实例),这是为什么?

服务端需要两个区分的 Channel 集合。第一个集合包括一个 ServerChannel,它代表了服务端自己用于监听(listening)的 socket,绑定到本地端口;第二个集合包括了所有创建的 Channels,用于处理输入服务端接收到的各个客户端连接。图3.4 展示了这个模型,并说明了为什么需要2个不同的 EventLoopGroups

图3.4 服务端的2个 EventLoopGroup

ServerChannel 相关的 EventLoopGroupServerChannel分配了一个 EventLoop,这个 EventLoop 负责为即将到来的连接请求创建 Channel。一旦接收到连接,第二个 EventLoopGroup 会为创建出来的 Channel 分配对应的 EventLoop

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,602评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,442评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,878评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,306评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,330评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,071评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,382评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,006评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,512评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,965评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,094评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,732评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,283评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,286评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,512评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,536评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,828评论 2 345

推荐阅读更多精彩内容