Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序[官方定义],整体来看其
包含了以下内容:1.提供了丰富的协议编解码支持,2.实现自有的buffer系统,减少复制所带来的消耗,3.整套channel的实现,4.基于事件
的过程流转以及完整的网络事件响应与扩展,5.丰富的example。本文并不对Netty实际使用中可能出现的问题做分析,只是从代码角度分析它的架构
以及实现上的一些关键细节。
首先来看下最如何使用Netty(其自带example很好展示了使用),Netty普通使用一般是通过BootStrap来启
动,BootStrap主要分为两类:1.面向连接(TCP)的BootStrap(ClientBootStrap和
ServerBootstrap),2.非面向连接(UDP) 的(ConnectionlessBootstrap)。
Netty整体架构很清晰的分成2个部分,ChannelFactory
和ChannelPipelineFactory,前者主要生产网络通信相关的Channel实例和ChannelSink实例,Netty提供的
ChannelFactory实现基本能够满足绝大部分用户的需求,当然你也可以定制自己的ChannelFactory,后者主要关注于具体传输数据的
处理,同时也包括其他方面的内容,比如异常处理等等,只要是你希望的,你都可以往里添加相应的handler,一般
ChannelPipelineFactory由用户自己实现,因为传输数据的处理及其他操作和业务关联比较紧密,需要自定义处理的handler。
现在,使用Netty的步骤实际上已经非常明确了,比如面向连接的Netty服务端客户端使用,第一步:实例化一个BootStrap,并且通过构
造方法指定一个ChannelFactory实现,第二步:向bootstrap实例注册一个自己实现的ChannelPipelineFactory,
第三步:如果是服务器端,bootstrap.bind(new
InetSocketAddress(port)),然后等待客户端来连接,如果是客户端,bootstrap.connect(new
InetSocketAddress(host,port))取得一个future,这个时候Netty会去连接远程主机,在连接完成后,会发起类型为
CONNECTED的ChannelStateEvent,并且开始在你自定义的Pipeline里面流转,如果你注册的handler有这个事件的响应
方法的话那么就会调用到这个方法。在此之后就是数据的传输了。下面是一个简单客户端的代码解读。
ClientBootstrap bootstrap =
new
ClientBootstrap(
|
new
NioClientSocketChannelFactory(
|
Executors.newCachedThreadPool(),
|
Executors.newCachedThreadPool()));
|
bootstrap.setPipelineFactory(
new
FactorialClientPipelineFactory(count));
|
ChannelFuture connectFuture =
|
bootstrap.connect(
new
InetSocketAddress(host, port));
|
Channel channel = connectFuture.awaitUninterruptibly().getChannel();
|
FactorialClientHandler handler =
|
(FactorialClientHandler) channel.getPipeline().getLast();
|
"Factorial of %,d is: %,d"
, count, handler.getFactorial());
|
Netty提供了NIO与BIO(OIO)两种模式处理这些逻辑,其中NIO主要通过一个BOSS线程处理等待链接的接入,若干个WORKER线程
(从worker线程池中挑选一个赋给Channel实例,因为Channel实例持有真正的
java网络对象)接过BOSS线程递交过来的CHANNEL进行数据读写并且触发相应事件传递给pipeline进行数据处理,而BIO(OIO)方式
服务器端虽然还是通过一个BOSS线程来处理等待链接的接入,但是客户端是由主线程直接connect,另外写数据C/S两端都是直接主线程写,而数据读
操作是通过一个WORKER 线程BLOCK方式读取(一直等待,直到读到数据,除非channel关闭)。
网络动作归结到最简单就是服务器端bind->accept->read->write,客户端
connect->read->write,一般bind或者connect后会有多次read、write。这种特性导
致,bind,accept与read,write的线程分离,connect与read、write线程分离,这样做的好处就是无论是服务器端还是客户
端吞吐量将有效增大,以便充分利用机器的处理能力,而不是卡在网络连接上,不过一旦机器处理能力充分利用后,这种方式反而可能会因为过于频繁的线程切换导
致性能损失而得不偿失,并且这种处理模型复杂度比较高。
采用什么样的网络事件响应处理机制对于网络吞吐量是非常重要的,Netty采用的是标准的SEDA(Staged Event-Driven
Architecture)架构[http://en.wikipedia.org/wiki/
Staged_event-driven_architecture],其所设计的事件类型,代表了网络交互的各个阶段,并且在每个阶段发生时,触发相应
事件交给初始化时生成的pipeline实例进行处理。事件处理都是通过Channels类的静态方法调用开始的,将事件、channel传递给
channel持有的Pipeline进行处理,Channels类几乎所有方法都为静态,提供一种Proxy的效果(整个工程里无论何时何地都可以调用
其静态方法触发固定的事件流转,但其本身并不关注具体的处理流程)。
Channels部分事件流转静态方法
1.fireChannelOpen 2.fireChannelBound 3.fireChannelConnected
4.fireMessageReceived 5.fireWriteComplete 6.fireChannelInterestChanged
7.fireChannelDisconnected 8.fireChannelUnbound 9.fireChannelClosed 10.fireExceptionCaught 11.fireChildChannelStateChanged
Netty提供了全面而又丰富的网络事件类型,其将java中的网络事件分为了两种类型Upstream和Downstream。一般来
说,Upstream类型的事件主要是由网络底层反馈给Netty的,比如messageReceived,channelConnected等事件,而
Downstream类型的事件是由框架自己发起的,比如bind,write,connect,close等事件。
Netty的Upstream和Downstream网络事件类型特性也使一个Handler分为了3种类型,专门处理Upstream,专门处理
Downstream,同时处理Upstream,Downstream。实现方式是某个具体Handler通过继承
ChannelUpstreamHandler和ChannelDownstreamHandler类来进行区分。PipeLine在
Downstream或者Upstream类型的网络事件发生时,会调用匹配事件类型的Handler响应这种调用。ChannelPipeline维持
有所有handler有序链表,并且由handler自身控制是否继续流转到下一个handler(ctx.sendDownstream(e),这样设
计有个好处就是随时终止流转,业务目的达到无需继续流转到下一个handler)。下面的代码是取得下一个处理Downstream事件的处理器。
DefaultChannelHandlerContext realCtx = ctx;
|
while
(!realCtx.canHandleUpstream()) {
|
如果是一个网络会话最末端的事件,比如messageRecieve,那么可能在某个handler里面就直接结束整个会话,并把数据交给上层应
用,但是如果是网络会话的中途事件,比如connect事件,那么当触发connect事件时,经过pipeline流转,最终会到达挂载
pipeline最底下的ChannelSink实例中,这类实例主要作用就是发送请求和接收请求,以及数据的读写操作。
NIO方式ChannelSink一般会有1个BOSS实例(implements
Runnable),以及若干个worker实例(不设置默认为cpu
cores*2个worker),这在前面已经提起过,BOSS线程在客户端类型的ChannelSink和服务器端类型的ChannelSink触发条
件不一样,客户端类型的BOSS线程是在发生connect事件时启动,主要监听connect是否成功,如果成功,将启动一个worker线程,将
connected的channel交给这个线程继续下面的工作,而服务器端的BOSS线程是发生在bind事件时启动,它的工作也相对比较简单,对于
channel.socket().accept()进来的请求向Nioworker进行工作分配即可。这里需要提到的是,Server端
ChannelSink实现比较特别,无论是NioServerSocketPipelineSink
还是OioServerSocketPipelineSink的eventSunk方法实现都将channel分为
ServerSocketChannel和SocketChannel分开处理。这主要原因是Boss线程accept()一个新的连接生成一个
SocketChannel交给Worker进行数据接收。
ChannelPipeline pipeline, ChannelEvent e)
throws
Exception {
|
Channel channel = e.getChannel();
|
if
(channel
instanceof
NioServerSocketChannel) {
|
}
else
if
(channel
instanceof
NioSocketChannel) {
|
NioWorker worker = nextWorker();
|
worker.register(
new
NioAcceptedSocketChannel(
|
channel.getFactory(), pipeline, channel,
|
NioServerSocketPipelineSink.
this
, acceptedSocket,
|
worker, currentThread),
null
);
|
另外两者实例化时都会走一遍如下流程:
fireChannelBound(
this
, getLocalAddress());
|
fireChannelConnected(
this
, getRemoteAddress());
|
而对应的ChannelSink里面的处理代码就不同于ServerSocketChannel了,因为走的是
handleAcceptedSocket(e)这一块代码,从默认实现代码来说,实例化调用
fireChannelOpen(this);fireChannelBound(this,getLocalAddress());fireChannelConnected(this,getRemoteAddress())
没有什么意义,但是对于自己实现的ChannelSink有着特殊意义。具体的用途我没去了解,但是可以让用户插手Server
accept连接到准备读写数据这一个过程的处理。
if
(Boolean.FALSE.equals(value)) {
|
channel.worker.close(channel, future);
|
channel.worker.close(channel, future);
|
channel.worker.setInterestOps(channel, future, ((Integer) value).intValue());
|
Netty提供了大量的handler来处理网络数据,但是大部分是CODEC相关的,以便支持多种协议,下面一个图绘制了现阶段Netty提供的Handlers(红色部分不完全)
Netty实现封装实现了自己的一套ByteBuffer系统,这个ByteBuffer系统对外统一的接口就是ChannelBuffer,这个
接口从整体上来说定义了两类方法,一种是类似getXXX(int index…),setXXX(int
index…)需要指定开始操作buffer的起始位置,简单点来说就是直接操作底层buffer,并不用到Netty特有的高可重用性buffer特
性,所以Netty内部对于这类方法调用非常少,另外一种是类似readXXX(),writeXXX()不需要指定位置的buffer操作,这类方法实
现放在了AbstractChannelBuffer,其主要的特性就是维持buffer的位置信息,包括
readerIndex,writerIndex,以及回溯作用的markedReaderIndex和markedWriterIndex,当用户调用
readXXX()或者writeXXX()方法时,AbstractChannelBuffer会根据维护的
readerIndex,writerIndex计算出读取位置,然后调用继承自己的ChannelBuffer的getXXX(int
index…)或者setXXX(int
index…)方法返回结果,这类方法在Netty内部被大量调用,因为这个特性最大的好处就是很方便地重用buffer而不必去费心费力维护index
或者新建大量的ByteBuffer。
另外WrappedChannelBuffer接口提供的是对ChannelBuffer的代理,他的用途说白了就是重用底层buffer,但是会
转换一些buffer的角色,比如原本是读写皆可
,wrap成ReadOnlyChannelBuffer,那么整个buffer只能使用readXXX()或者getXXX()方法,也就是只读,然后
底层的buffer还是原来那个,再如一个已经进行过读写的ChannelBuffer被wrap成TruncatedChannelBuffer,那么
新的buffer将会忽略掉被wrap的buffer内数据,并且可以指定新的writeIndex,相当于slice功能。
Netty实现了自己的一套完整Channel系统,这个channel说实在也是对java
网络做了一层封装,加上了SEDA特性(基于事件响应,异步,多线程等)。其最终的网络通信还是依靠底下的java网络api。提到异步,不得不提到
Netty的Future系统,从channel的定义来说,write,bind,connect,disconnect,unbind,close,
甚至包括setInterestOps等方法都会返回一个channelFuture,这这些方法调用都会触发相关网络事件,并且在pipeline中流
转。Channel很多方法调用基本上不会马上就执行到最底层,而是触发事件,在pipeline中走一圈,最后才在channelsink中执行相关操
作,如果涉及网络操作,那么最终调用会回到Channel中,也就是
serversocketchannel,socketchannel,serversocket,socket等java原生网络api的调用,而这些
实例就是jboss实现的channel所持有的(部分channel)。
Netty新版本出现了一个特性zero-copy,这个机制可以使文件内容直接传输到相应channel上而不需要通过cpu参与,也就少了一次
内存复制。Netty内部ChunkedFile 和 FileRegion 构成了non zero-copy
和zero-copy两种形式的文件内容传输机制,前者需要CPU参与,后者根据操作系统是否支持zero-copy将文件数据传输到特定
channel,如果操作系统支持,不需要cpu参与,从而少了一次内存复制。ChunkedFile主要使用file的read,readFully等
API,而FileRegion使用FileChannel的transferTo
API,2者实现并不复杂。Zero-copy的特性还是得看操作系统的,本身代码没有很大的特别之处。
最后总结下,Netty的架构思想和细节可以说让人眼前一亮,对于java网络IO的各个注意点,可以说Netty已经解决得比较完全了,同时
Netty
的作者也是另外一个NIO框架MINA的作者,在实际使用中积累了丰富的经验,但是本文也只是一个新手对于Netty的初步理解,还没有足够的能力指出某
一细节的所发挥的作用。
相关推荐
通过分析和运行这些示例,你可以了解 Netty 的基本用法,如创建服务器、连接服务器、处理 I/O 事件、自定义编码解码器等。同时,也可以尝试扩展这些示例,例如添加新的处理器以处理特定的业务逻辑,或者实现更复杂的...
Netty是Java领域的一款高性能、异步事件驱动的网络应用框架,主要用于快速开发可...通过阅读"Netty代码分析 淘宝网综合业务平台团队博客.htm"这篇博客文章,可以获取更多关于Netty在实际项目中的具体应用和优化策略。
示例以:TCP/IP自定义报文协议进行解析分析,基于帧头HEAD_DATA=0x76解析过程的示例代码,并对数据进行粘包分离的处理。粘包处理方式有两种:1.自定义报文协议 然后进行粘包分离【此示例为该解决方案】 2.用netty...
通过本课程的学习,开发者不仅能够掌握如何使用Netty开发高性能的应用程序,还能够学会如何阅读和理解其源代码,从而更好地进行定制化开发和故障排查。 ### Netty核心组件解析 1. **Channel**:Netty中的Channel是...
Netty 是一个高性能、异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能...同时,通过阅读和分析源代码,我们可以更深入地理解 Netty 的事件驱动架构和管道模型,以及如何利用这些特性来优化网络应用性能。
这个“netty3.2源代码”包含了 Netty 框架的3.2版本的源码,让我们有机会深入理解其内部机制。 Netty 的核心特性在于它的异步事件驱动模型。这种模型允许程序处理多个连接同时进行,而不是像传统的同步模型那样,每...
以上仅是Netty源码分析的一些关键点,实际的学习中还需要结合具体代码和实际案例来深入理解。在分析源码的过程中,我们通常会关注类的设计模式、线程模型、内存管理以及性能优化等方面,这对于提升网络编程和系统...
### Netty源码分析之Buffer #### Java Buffer 的相关基础知识 **1. Java 基本数据类型** Java 提供了八种基本数据类型:`byte`, `char`, `short`, `int`, `long`, `float`, `double`, `boolean`。 - **`byte`**:...
总的来说,Netty 源码分析涉及了网络编程、并发处理、事件驱动、协议编解码等多个领域,对理解 Java 高性能网络应用开发有着重要的指导意义。通过阅读源码,我们可以更深入地了解 Netty 如何实现高效的网络通信,并...
本书的源代码位于`book-netty-master`目录下,包含了书中的示例代码,读者可以通过阅读和运行这些代码,加深对Netty的理解和应用。通过学习这本书,你不仅可以学会Netty的基本使用,还能深入了解其设计思想和优化...
**四、源代码分析** 由于没有提供具体的源代码,这里只能进行一般性的分析。在源代码中,可能会有以下关键类: - `ServerBootstrapConfig`:配置Netty服务器的启动参数。 - `NettyServer`:使用`ServerBootstrap`...
Netty 是一个高性能、异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。这个“demo.rar”压缩包包含了基于 ...通过分析和运行这些代码,我们可以深入理解 Netty 的工作原理和实际应用。
Netty的源代码分析是学习过程中的重要环节。"nettyBookSourceV2.zip"包含的源代码,可以帮助读者深入理解Netty框架的内部机制,如Channel、EventLoop、Pipeline、Handler等核心组件的工作原理。通过对这些源代码的...
Netty 是一个高性能、异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议...通过 TimeServer 和 TimeClient 示例,我们可以逐步掌握 Netty 的基本用法,并为进一步探索其高级特性和源码分析打下基础。
赠送源代码:reactor-netty-core-1.0.15-sources.jar; 赠送Maven依赖信息文件:reactor-netty-core-1.0.15.pom; 包含翻译后的API文档:reactor-netty-core-1.0.15-javadoc-API文档-中文(简体)版.zip; Maven坐标:...
分析这个代码,我们可以看到Netty如何创建服务器、设置管道、以及如何定义和使用自定义的解码器和编码器来处理16进制数据。 通过上述步骤,Netty服务器可以轻松地解析16进制数据,从而支持各种网络协议,无论它们是...
在这个"Netty框架,服务端、客户端代码示例"中,我们将深入探讨如何使用Netty构建服务端和客户端的通信。 首先,让我们了解Netty的基本架构。Netty的核心是它的“线程模型”和“通道”概念。线程模型采用“事件循环...
Netty 是一个高性能、异步事件驱动的...通过阅读和分析源码,可以掌握如何设计高效、可扩展的网络应用,并为自己的项目选择合适的Netty组件和策略。对于想要深入Java NIO和网络编程的开发者来说,这是一个宝贵的资源。
通过分析Netty的源代码,你可以深入理解其内部机制,比如: - 如何实现高效的内存管理,减少不必要的对象创建和垃圾收集。 - 异步I/O的实现方式,如何利用Java NIO进行非阻塞通信。 - 事件驱动模型是如何工作的,...
在理论方面,讲解了Netty的逻辑架构模型和核心类库的设计原理,而且对Netty在大数据、互联网、游戏行业的应用进行了深入分析;实战方面,从第一个Netty入门程序到私有协议栈的设计和开发,通过实际例程,由浅入深地...