前一篇blog,讲解了如何快速启动netty服务,并通过telnet命令来访问的简单过程。其中用到了netty中常用的几个类和方法,本文将做一一介绍(其中翻译了netty的api文档,同时结合自己的理解)。
首先,看类:ServerBootstrap,Server的启动过程就是从这里开始的。通过简单的构造方法注入ChannelFactory后设置ChannelPiplineFactory,再调用bind方法,服务器便启动起来了。这里重点关注一下两个工厂类,从类名可以看出是用来产出Channel和ChannelPipline的。Channel和ChannelPipline都是netty的核心概念,贯穿了服务的整个过程。
NIO中的通道
那么Channel在netty中扮演了一个怎么样的角色呢?顾名思义,Channel即是通道的意思。提到Channel,首先会想到NIO。在Nio中,废弃了面向Socket和ServerSocket编程的方式,引入了通道和字节缓冲区(ByteBuffer)的概念。通道关联着某个文件描述符(FD)和字节缓冲区,来将缓冲区的数据写入FD关联的文件或者套接字,或将文件或套接字的内容读入ByteBuffer。所以通道分为读、写通道,分别实现了ReadableByteChannel、WritableByteChannel,当然同时实现了两个接口后便是双向的通道了。不过NIO中已经为我们定义好了很多好用的通道,能够解决我们遇到的大多数问题,不用自己去重新实现。那么在众多的实现中,与网络通信相关联的通道有哪些呢,来看看类图。
从下往上看,ServerSocketChannel、SocketChannel、DatagramChannel是我们需要打交道的面向连接和无连接的通道。它们的继承关系是这样的:AbstractSelectableChannel
—-> … --> InterruptibleChannel –-> Channel。
这里又需要引入一个概念:selector,它是Channel的最佳搭档。我们知道NIO与OIO相比,很大优势在于NIO可以选择非阻塞模式处理I/O事件,从而避免了线程阻塞的情况。尤其是在高并发的情况下,使用传统Socket,往往需要为每一个连接创建一个线程,如果不这样,当工作线程都阻塞了,来了新的请求就没人干活了。然而,创建大量的线程带来的消耗是巨大的,例如:上下文切换等。而在NIO中,可以启用非阻塞的模式来进行,例如,在某个连接(Connect1)中读取消息时,如果此时没有消息到达,读取线程可以立即返回,无需等待读取成功,这样就不怕工作线程都阻塞而导致没有工作人员的情况了。但是仅仅靠非阻塞来处理高并发是不够的,当工作线程去处理Connect2时,将Connect1放在哪里呢、当Connect1中有了新的消息时怎么通知到工作线程呢?selector的加入,完美的解决了这个问题。Selector融合了linux中的select、poll或者epoll模型,通过reactor模式来达到I/O多路复用的目的(在下一篇文章中将会做详细的介绍)。在初始化时,告知selector管理器,当前的通道是哪一个(即关联的socket)、当该通道上面发生了xx事件时需要被记录下来。这个时候,与该通道关联的线程可以去做其他事情(例如:处理其他通道的消息),在必要的时候,该线程去询问selector管理器,自己感兴趣的事件中哪些已经发生了,如果发生了就加入到自己的处理队列中,做好处理的就绪工作。当然,你也可以选择netty中OIO的实现,不过对高并发的处理上,性能相对会低很多。
通道接口InterruptibleChannel表示该通道是可以被中断的,当工作线程在某通道上被阻塞的时候,该线程被中断了,那么通道将会关闭,此线程也会产生一个ClosedByInterruptException异常。假设一个线程的中断状态被设置后,再去访问某通道,此时通道也会被关闭,同时抛出ClosedByInterruptException异常。如果一个通道被关闭,休眠在该通道上面的所有线程都会被唤醒,同时收到一个AsynchronousCloseException异常。从上图可以看出,我们用到的几个socket相关的通道都是可中断的通道。
在类的继承中,ServerSocketChannel与SocketChannel、DatagramChannel是有所不同的。SocketChannel、DatagramChannel同时继承了ReadableByteChannel、WritableByteChannel,可用于读和写。这是由于ServerSocketChannel本身并不会读写,专门用于接收connect,收到connect后,由SocketChannel来处理消息。
Netty中的通道
同样netty中也引入了通道的概念,netty框架在其nio的实现过程中,实际上是对nio的通道进行了上层的封装,它是关联网络socket或者能够用于I/O操作(比如:读、写、连接和绑定)的组件。先看一下NioServerSocketChannel的继承关系:
1.
ServerChannel:用于接收连接请求的通道,它通过accept()方法来创建子通道,例如其子类ServerSocketChannel。
2.
SocketChannel:TCP/IP socket 通道,它通常被serverSocketChannel的accept()方法或者ClientSocketChannelFactory类创建。
3.
AbstractChannel:Channel的抽象实现。
4.
DatagramChannel:UDP/IP通道,通过DatagramChannelFactory创建。
5. LocalChannel:用于本地传输的通道。
Channel给我们提供了:
1.
通道目前的状态(如:是否打开?是否已连接?)
2.
通道的配置参数(如:用于接收消息的buffer的大小)
3.
通道提供的I/O操作(如:写、连接、绑定等)
4.
还提供了用于处理与通道相关联的I/O事件和I/O请求的ChannelPipline
通道中所有的I/O操作都是异步进行的
这意味着所有的I/O调用在结束的时候都不能保证该I/O操作已经完成了。相反的,这个时候用户需要返回一个ChannelFuture实例,当这个请求成功、失败或者取消的时候,Futrue就会通知你。
通道是分层级的
一个通道是否有父通道取决于它的创建方式。例如:通过ServerSocketChannel收到连接时(ServerSocketChannel.accepted()方法)创建的SocketChannel,在channel的getParent()方法中就会返回他的父通道ServerSocketChannel。
分层结构的意义在于你需要的通道是属于哪种传输方式。例如:你可以写一个新通道的实现方式,这个通道和它的子通道共享一个socket连接,例如BEEP协议和SSH协议的实现。
向下转换解决特殊的传输方式
一些网络传输需要附加一些特殊的操作。这时可以通过继承的方式,在子类中去实现这些操作。例如:用OIO的方式处理报文传输,在DatagramChannel中就实现了广播join和leave的操作。
感兴趣事件(InterestOps)
通道有一个被称作InterestOps的属性,这和NIO中的SelectionKey相似。它是由两个标志组成的bit
field来表示的。
1. OP_READ:如果设置了这个标志,那么从远端发送来的消息将会被立即读到。相反,如果没有设置,就有等到被设置过后才能读取远端的消息了。
2. OP_WRITE:如果设置了这个标志,写请求就不会发送到远端,而是停留在队列中,直到清除了这个标志为止。如果没有设置,写请求就会被尽快的进行出队列的操作。
3. OP_READ_WRITE:这个标志关联了OP_READ和OP_WRITE,含义是只有写请求才会被挂起。
4. OP_NONE:这个标志关联了非OP_READ和非OP_WRITE,含义是只有读请求才会被挂起。
用户可以通过setReadable(boolean)函数来设置或者清除OP_READ来挂起和恢复读操作。
需要注意的是,不能像设置或者清除OP_READ一样来处理OP_WRITE,它是只读的,用于告诉应用挂起的写请求是否达到了临界值,避免放入过多挂起的写请求导致内存溢出。比如:在用NIO传输的NioSocketChannelConfig中使用writeBufferLowWaterMark和writeBufferHighWaterMark属性来决定何时可以放入或者清除OP_WRITE标志。
事件
通道封装了NIO的Channel,用于接收连接或者读取消息,收到连接或者消息就代表一个事件发生了,在netty中同样做了相应的映射,抽象出ChannelEvent的概念,表示:和某通道关联的I/O事件或者I/O请求。来看看事件的类图结构:
事件分为UpStream事件和downStream事件,一个事件的处理流向如果是从ChannelPipline中的第一个(head)Handler(后文讲解)开始到最后一个(tail)Handler,那么就称这个事件为UpStream事件,相反,如果一个事件的处理流向是从ChannelPipline中的最后一个Handler开始到第一个Handler,就称这个事件为downStream事件。
当服务器端收到来自客户端的消息时,携带消息的事件是一个Upstream事件。当服务器端向客户端发送消息或者回应客户端的时候,这个事件就为downStream事件。当然,站在客户端的角度看也是一样。Upstream事件往往是由外向内获取资源等操作后触发的,例如:InputStream.read(byte[])等事件发生后通知handler去处理读到的消息,downStream事件往往是由内向外发送请求时所触发的,例如:OutputStream.write(byte[]),Socket.connect
(SocketAddress), and Socket.close()等请求会触发handler进行写、连接、关闭socket等操作。
个人理解:upStream事件是事件发生之后,用于通知handler做相应的处理,这时事件已经发生;downStream事件是通知handler去做相应的请求操作,是为了处理该事件所发起的请求。
UpStream事件包括:
事件名称
|
事件类型与发生条件
|
含义
|
备注
|
messageReceived
|
MessageEvent
|
表示从远端接收到了消息(eg:ChannelBuffer)
|
|
exceptionCaught
|
ExceptionEvent
|
表示在某handler或者I/O线程中发生了异常
|
|
channelOpen
|
ChannelStateEvent
(state=OPEN,value=true)
|
表示某通道打开了,但是还没有绑定或者链接成功
|
注意:这个事件是由Boss 线程内部触发的,所以不要对它做一些重量级的操作,否则会阻塞其他worker线程的调度
|
channelClosed
|
ChannelStateEvent
(state=OPEN,value=false)
|
表示关联的通道已经关闭和相关资源已经释放
|
|
channelBound
|
ChannelStateEvent
(state=BOUND,value=socketAddress)
|
表示通道已经绑定到本地地址,但还没有连接
|
注意:同channelOpen
|
channelUnbound
|
ChannelStateEvent
(state=BOUND,value=null)
|
表示已从当前地址解除绑定
|
|
channelConnected
|
ChannelStateEvent
(state=CONNECTED,value=socketAddress)
|
表示当前通道已经打开、绑定了本地地址、并与远程地址连接成功
|
注意:同channelOpen
|
writeComplete
|
WriteCompletionEvent
|
表示有消息被写到了远端
|
|
channelDisconnected
|
ChannelStateEvent
(state=CONNECTED,value=socketAddress)
|
表示通道与远端的连接断开
|
|
channelInterestChanged
|
ChannelStateEvent
(state= INTEREST_OPS)
|
表示修改了通道感兴趣的事件
|
|
有两种事件只被用于有子通道的通道,比如:ServerSocketChannel
事件名称
|
事件类型与发生条件
|
含义
|
备注
|
childChannelOpen
|
ChildChannelStateEvent
(childChannel.isOpen() = true)
|
当子通道发生OPEN事件的时候,例如:当serverChannel接到连接时
|
|
childChannelClosed
|
ChildChannelStateEvent
(childChannel.isOpen() = false)
|
当子通道发生CLOSE事件的时候,例如:接收到的连接关闭
|
|
downStream事件包括:
事件名称
|
事件类型与发生条件
|
含义
|
备注
|
write
|
MessageEvent
|
向通道发送消息
|
|
bind
|
ChannelStateEvent
(state=BOUND,value=socketAddress)
|
将通道绑定到value所指向的地址
|
|
unbind
|
ChannelStateEvent
(state=BOUND,value=null)
|
请求解除与关联地址的绑定关系
|
|
connect
|
ChannelStateEvent
(state=CONNECTED,value=socketAddress)
|
请求连接到value所指定的地址
|
|
unconnect
|
ChannelStateEvent
(state=CONNECTED,value=null)
|
请求与当前地址解除连接关系
|
|
close
|
ChannelStateEvent
(state=OPEN,value=false)
|
关闭通道
|
|
需要注意的是在downStream事件中没有提到open事件,这是因为ChannelFactory在创建通道的时候它就处于open状态了。
Handler
当接收到一个ChannelEvent时,我们应该做怎么样的处理,比如:在消息被Channel读入的时候我们应该怎么处理,在回复客户端之前应该干点什么,这些都是应该由我们的应用程序来控制的业务逻辑。可以看到,在上一篇文章中,Server中包含了一个内部类MyChannelHandler,在接收到连接时输出当前Channel的信息、接收到消息时回复客户端等操作就是我们的业务逻辑。在netty中,为我们封装了ChannelHandler接口,用于处理或拦截ChannelEvent,并且传递这个事件给所在ChannelPipline中的下一个handler。
子类
ChannelHandler接口没有实现任何方法。用于处理事件的Handler需要去继承它的子接口。以下的两个子接口用于处理接收到的事件,一个是处理upStream事件的,另一个是用来处理downStream事件的。
1.
ChannelUpstreamHandler:用于处理upStream事件。
通常被用于工作者线程拦截到I/O请求中转换(编码等处理)消息或者其它相关的业务逻辑。
SimpleChannelUpstreamHandler是实现中最常用的一个类,因为它已经实现了关于各个事件最基础的方法。当然,遇到特殊的需求,也可以直接实现这个接口来做处理。
2.
ChannelDownstreamHandler:用于处理downStream事件。
ChannelPipline
前面介绍了handler,通过在Handler中注入业务逻辑。但是我们对业务逻辑的处理往往不像前一篇文章中讲到的那么简单,例如:在接收到消息时,先进行解码,得到我们需要的数据结构,再对该数据结构进行真正的逻辑处理等。这时,我们就可以将这两个逻辑放到两个handler中,一个用于解码,另一个用于处理业务,并且规定handler的执行顺序,先解码后处理业务。这样我们就可以把工作拆分开来,代码看起来干净、简洁。尤其是在我们需要做的事情很多时,将任务拆解是一种很好的方式。这就是即将隆重推出的ChannelPipline。在ChannelPipline中注入我们实现好的handler,netty就会在谋事件发生的时候依次执行handler。
其中head和tail对应的类:DefaultChannelHandlerContext,是整个处理流程的上下文。以下为类图:
Context中定义了当前的handler实例,并且根据ChannelHandler的类型记录是用于处理upstream事件还是downstream事件的,分别以两个boolean变量表示。再看看next、prev成员变量,很明显这是一个双向链表的结构,通过next找到下一个handler,通过prev找到上一个handler。
Context是ChannelPipline中的重要角色,被定义为两个变量:head、tail。也就是说可以从ChannelPipline中找到头部和尾部的context即可找到对应的handler。而通过该context的sendUpstream(ChannelEvent)和sendDownstream(ChannelEvent)方法又可以将事件传递给其上下的handler处理,从而串起了upstream事件和downstream事件的整个流程。
- 大小: 63.7 KB
- 大小: 52.3 KB
- 大小: 61.8 KB
- 大小: 28.5 KB
- 大小: 26.9 KB
分享到:
相关推荐
根据提供的文件信息“netty入门到精通”,我们可以深入探讨Netty框架的相关知识点,包括其基本概念、核心组件、应用场景以及如何逐步掌握这项技术。 ### Netty框架简介 Netty是一款高性能、异步事件驱动的网络应用...
这个“Netty 从入门到精通所有代码”压缩包包含了一系列的示例代码,帮助开发者逐步理解并掌握 Netty 的核心概念和实际应用。 1. **Netty 基本概念** - **NIO (Non-blocking I/O)**:Netty 是基于 Java NIO 构建的...
Netty入门教程文档 Netty是Java的网络编程框架,广泛应用于数据采集服务中,本文将对Netty的基本概念和应用进行详细介绍,并将其与ETL技术结合,讲解如何使用Netty进行数据流转和处理。 1. ETL概述 ETL(Extract...
在本篇关于“Netty框架学习——第一个Netty应用”的文章中,我们将深入理解如何使用Netty构建一个简单的Echo服务器和客户端。Netty是一个高性能、异步事件驱动的网络应用程序框架,广泛应用于Java领域的服务器开发。...
通过阅读源码,我们可以了解到 Netty 如何实现高效的网络通信,例如它的非阻塞 I/O 模型、事件驱动架构、内存池管理以及编码解码器等核心组件。其中,Channel、EventLoop、ByteBuf 等关键类是理解 Netty 的基础,而 ...
读书笔记:《Netty实战》源代码——Scala版
本书旨在帮助Java开发者理解Netty的核心概念,掌握其实现网络应用的高效方式。 1. **Netty简介**:Netty是由JBoss公司开源的一个网络通信框架,基于NIO(非阻塞I/O)设计,支持多种传输协议,如TCP、UDP等,适用于...
Netty快速入门系列源码, 参考 https://blog.csdn.net/netcobol Netty是一个java开源框架。Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。 Netty是...
netty案例,netty4.1基础入门篇六《NettyServer群发消息》源码 https://mp.weixin.qq.com/s?__biz=MzIxMDAwMDAxMw==&mid=2650724778&idx=1&sn=72e4b1ea5323475b16e99c6720c7069d&scene=19#wechat_redirect
本资源《高性能网络通信框架Netty从入门到核心源码剖析》将带领读者逐步了解并深入掌握Netty的使用和设计原理。 一、Netty入门 1. 安装与环境配置:介绍如何在Java项目中引入Netty依赖,以及相关的构建工具如Maven...
netty权威指南 PDF电子书下载 带目录书签 完整版,下载
高性能网络通信框架_Netty从入门到核心源码剖析
《Netty+入门与实战:仿写微信+IM+即时通讯系统》是一本专注于使用Netty框架构建即时通讯系统的教程。Netty是一个高性能、异步事件驱动的网络应用框架,适用于开发服务器和客户端的Java应用。它极大地简化了网络编程...
Netty是由JBOSS提供的一个java开源框架。Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。 也就是说,Netty 是一个基于NIO的客户,服务器端编程框架,...
这本书包含了从基础到高级的所有关键知识点,旨在帮助开发者提升网络编程的效率和质量。 在书中,你可以了解到: 1. **Netty简介**:了解Netty的基本概念,包括它的设计理念、核心组件以及与传统Java网络API的区别...
总的来说,这12集的Netty快速入门教程将带你从基础到进阶,全面理解Netty框架的核心组件和用法。通过学习客户端的构建和Netty线程模型,你将具备使用Netty开发高效、可靠网络应用的能力。不论是构建TCP、UDP服务,...
《Netty权威指南》是一本深入探讨Netty框架的详细教程,旨在帮助读者全面理解并熟练运用Netty进行高性能网络应用开发。Netty是Java领域的一款开源、异步事件驱动的网络应用程序框架,广泛应用于高性能服务器和客户端...
Netty从入门到实战,深入Netty底层原理,结合实战,打造百万级连接的IM通讯软件。-netty_study
读书笔记:Netty从入门到实战深入Netty底层原理结合实战打造百万级连接的IM通讯软件。
这个教程将引导我们入门 Netty 编码,让我们深入理解其核心概念和实际应用。 首先,Netty 的核心是其设计模式,即 Reactor 模式,也称为事件驱动模型。Reactor 模式允许 Netty 高效地处理大量并发连接,通过非阻塞 ...