dotnetty源码解读一些要点
DefaultAttributeMap
它绑定在Channel或者ChannelHandlerContext上的一个附件。 ChannelHandlerContext都是ChannelHandler和ChannelPipeline之间连接的桥梁, 每一个ChannelHandlerContext都有属于自己的上下文, 每一个ChannelHandlerContext上如果有AttributeMap都是绑定上下文的, A的ChannelHandlerContext中的AttributeMap,B的ChannelHandlerContext是无法读取到的。 但是Channel上的AttributeMap是共享的,每一个ChannelHandler都能获取到。
AbstractUnsafe
unsafe 是内部接口, 从设计上说这个接口的所有实现都是netty的内部代码,只能被netty自己使用. netty的使用者时不应该 直接调用这些内部实现的.Unsafe 操作不能被用户代码调用.这些方法仅仅用于实现具体的transport, 而且必须被I/O线程调用.
unsafe 特别关键, 它封装了对底层 Socket 的操作, 因此实际上是沟通 Netty 上层和 底层的重要的桥梁.
- RegisterAsync 会把eventloop注册进来
- 其内部方法.大多是通过pipe管道的head调用的.channel.bind->pipe.bind->tail.bind->head.bind->unsafe.bind
SocketChannelAsyncOperation
继承与SocketAsyncEventArgs,当出发完成事件的时候会执行AbstractSocketChannel.IoCompletedCallback
1. 服务器端执行流程
* bootstrap.BindAsync()->channel.BindAsync->pipeline.BindAsync->tail.BindAsync->head.BindAsync->unsafe.BindAsync
* channel.DoBind() 使用socket绑定并监听.并将channel设置为active
* this.channel.pipeline.FireChannelActive() 出发管道中的信号激活
* channel.read->pipe.read->unsafe.BeginRead
* channel.DoBeginRead->channel.ScheduleSocketRead
* 使用ReadOperation(SocketChannelAsyncOperation
实例)进行receiveasync
* unsafe.FinishRead 实例化TcpSocketChannel保存新建立的socket并触发pipe.FireChannelRead
* bootstrap的时候会添加ServerBootstrapAcceptor.这个时候会触发
* 将bootstrap中的child相关的属性设置给新的channel
* 在新的channel实例化的时候仍然会实例化SocketAsyncEventArgs然后就经过receivea完成事件
* AbstractSocketByteChannel.FinishRead 是完成读取字节的关键
TcpServerSocketChannelUnsafe.FinishRead
```
while (allocHandle.ContinueReading())
{
connectedSocket = ch.Socket.Accept();
message = this.PrepareChannel(connectedSocket);
connectedSocket = null;
ch.ReadPending = false;
pipeline.FireChannelRead(message);
allocHandle.IncMessagesRead(1);
}
```
这一段不知道干什么的..因为socket是非阻塞的.在调用第一句 ch.Socket.Accept();就会抛出异常..(SocketException ex) when (ex.SocketErrorCode == SocketError.WouldBlock)
https://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.accept.aspx
- 客户端执行流程 待做
由上可见,netty中channel的任何调用都会先经过管道.再由管道按照顺序进行执行.而unsafe是socket底层的一些操作.也总是会被默认执行(或者在head中或者直接在pipe中)
DefaultChannelPipeline
- DefaultChannelPipeline 中, 还有两个特殊的字段, 即 head 和 tail, 而这两个字段是一个双向链表的头和尾. 其实在 DefaultChannelPipeline 中, 维护了一个以 AbstractChannelHandlerContext 为节点的双向链表, 这个链表是 Netty 实现 Pipeline 机制的关键.
- 即 header 是一个 outboundHandler, 而 tail 是一个inboundHandler,
AbstractChannelHandlerContext 中有 inbound 和 outbound 两个 boolean 变量, 分别用于标识 Context 所对应的 handler 的类型, 即:
* inbound 为真时, 表示对应的 ChannelHandler 实现了 ChannelInboundHandler 方法.
* outbound 为真时, 表示对应的 ChannelHandler 实现了 ChannelOutboundHandler 方法.
I/O Request
via Channel or
ChannelHandlerContext
|
+---------------------------------------------------+---------------+
| ChannelPipeline | |
| \|/ |
| +---------------------+ +-----------+----------+ |
|head| Inbound Handler N | | Outbound Handler 1 |head|
| +----------+----------+ +-----------+----------+ |
| /|\ | |
| | \|/ |
| +----------+----------+ +-----------+----------+ |
| | Inbound Handler N-1 | | Outbound Handler 2 | |
| +----------+----------+ +-----------+----------+ |
| /|\ . |
| . . |
| ChannelHandlerContext.fireIN_EVT() ChannelHandlerContext.OUT_EVT()|
| [ method call] [method call] |
| . . |
| . \|/ |
| +----------+----------+ +-----------+----------+ |
| | Inbound Handler 2 | | Outbound Handler M-1 | |
| +----------+----------+ +-----------+----------+ |
| /|\ | |
| | \|/ |
| +----------+----------+ +-----------+----------+ |
|tail| Inbound Handler 1 | | Outbound Handler M |tail|
| +----------+----------+ +-----------+----------+ |
| /|\ | |
+---------------+-----------------------------------+---------------+
| \|/
+---------------+-----------------------------------+---------------+
| | | |
| [ Socket.read() ] [ Socket.write() ] |
| |
| Netty Internal I/O Threads (Transport Implementation) |
+-------------------------------------------------------------------+
inbound 事件和 outbound 事件的流向是不一样的, inbound 事件的流行是从下至上, 而 outbound 刚好相反, 是从上到下. 并且 inbound 的传递方式是通过调用相应的 ChannelHandlerContext.fireIN_EVT() 方法, 而 outbound 方法的的传递方式是通过调用 ChannelHandlerContext.OUT_EVT() 方法. 例如 ChannelHandlerContext.fireChannelRegistered() 调用会发送一个 ChannelRegistered 的 inbound 给下一个ChannelHandlerContext,
Outbound 事件的传播方向是 tail -> customContext -> head.
Inbound 的特点是它传播方向是 head -> customContext -> tail.
Inbound 事件传播方法有:
ChannelHandlerContext.fireChannelRegistered()
ChannelHandlerContext.fireChannelActive()
ChannelHandlerContext.fireChannelRead(Object)
ChannelHandlerContext.fireChannelReadComplete()
ChannelHandlerContext.fireExceptionCaught(Throwable)
ChannelHandlerContext.fireUserEventTriggered(Object)
ChannelHandlerContext.fireChannelWritabilityChanged()
ChannelHandlerContext.fireChannelInactive()
ChannelHandlerContext.fireChannelUnregistered()
Oubound 事件传输方法有:
ChannelHandlerContext.bind(SocketAddress, ChannelPromise)
ChannelHandlerContext.connect(SocketAddress, SocketAddress, ChannelPromise)
ChannelHandlerContext.write(Object, ChannelPromise)
ChannelHandlerContext.flush()
ChannelHandlerContext.read()
ChannelHandlerContext.disconnect(ChannelPromise)
ChannelHandlerContext.close(ChannelPromise)
注意, 如果我们捕获了一个事件, 并且想让这个事件继续传递下去, 那么需要调用 Context 相应的传播方法.
例如:
public class MyInboundHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) {
System.out.println("Connected!");
ctx.fireChannelActive();
}
}
public clas MyOutboundHandler extends ChannelOutboundHandlerAdapter {
@Override
public void close(ChannelHandlerContext ctx, ChannelPromise promise) {
System.out.println("Closing ..");
ctx.close(promise);
}
}
上面的例子中, MyInboundHandler 收到了一个 channelActive 事件, 它在处理后, 如果希望将事件继续传播下去, 那么需要接着调用 ctx.fireChannelActive().
对于 Outbound事件:
* Outbound 事件是请求事件(由 Connect 发起一个请求, 并最终由 unsafe 处理这个请求)
* Outbound 事件的发起者是 Channel
* Outbound 事件的处理者是 unsafe
* Outbound 事件在 Pipeline 中的传输方向是 tail -> head.
* 在 ChannelHandler 中处理事件时, 如果这个 Handler 不是最后一个 Hnalder, 则需要调用 ctx.xxx (例如 ctx.connect) 将此事件继续传播下去. 如果不这样做, 那么此事件的传播会提前终止.
* Outbound 事件流: Context.OUT_EVT -> Connect.findContextOutbound -> nextContext.invokeOUT_EVT -> nextHandler.OUT_EVT -> nextContext.OUT_EVT
对于 Inbound 事件:
* Inbound 事件是通知事件, 当某件事情已经就绪后, 通知上层.
* Inbound 事件发起者是 unsafe
* Inbound 事件的处理者是 Channel, 如果用户没有实现自定义的处理方法, 那么Inbound 事件默认的处理者是 TailContext, 并且其处理方法是空实现.
* Inbound 事件在 Pipeline 中传输方向是 head -> tail
* 在 ChannelHandler 中处理事件时, 如果这个 Handler 不是最后一个 Hnalder, 则需要调用 ctx.fireIN_EVT (例如 ctx.fireChannelActive) 将此事件继续传播下去. 如果不这样做, 那么此事件的传播会提前终止.
* Outbound 事件流: Context.fireIN_EVT -> Connect.findContextInbound -> nextContext.invokeIN_EVT -> nextHandler.IN_EVT -> nextContext.fireIN_EVT
* outbound 和 inbound 事件十分的镜像, 并且 Context 与 Handler 直接的调用关系是否容易混淆, 因此读者在阅读这里的源码时, 需要特别的注意.
AbstractChannelHandlerContext
- FindContextInbound 同下
- FindContextOutbound 从 DefaultChannelPipeline 内的双向链表的 tail 开始, 不断向前寻找第一个 outbound 为 true 的 AbstractChannelHandlerContext, 然后调用它的 invokeConnect 方法,在 DefaultChannelPipeline 的构造器中, 会实例化两个对象: head 和 tail, 并形成了双向链表的头和尾. head 是 HeadContext 的实例, 它实现了 ChannelOutboundHandler 接口, 并且它的 outbound 字段为 true. 因此在 findContextOutbound 中, 找到的 AbstractChannelHandlerContext 对象其实就是 head.
- HeadContext 和 TailContext 继承于 AbstractChannelHandlerContext 的同时也实现了 ChannelHandler 接口了, 因此它们有 Context 和 Handler 的双重属性.
ChannelInitializer
此类就是一个向管道注册处理器的适配器,在channel执行Registered的时候,向管道中注入一些处理.然后把该管道处理方法删除
bossGroup 与 workerGroup
- 其实呢, bossGroup 是用于服务端 的 accept 的, 即用于处理客户端的连接请求. 我们可以把 Netty 比作一个饭店, bossGroup 就像一个像一个前台接待, 当客户来到饭店吃时, 接待员就会引导顾客就坐, 为顾客端茶送水等. 而 workerGroup, 其实就是实际上干活的啦, 它们负责客户端连接通道的 IO 操作: 当接待员 招待好顾客后, 就可以稍做休息, 而此时后厨里的厨师们(workerGroup)就开始忙碌地准备饭菜了.
- 服务器端 bossGroup 不断地监听是否有客户端的连接, 当发现有一个新的客户端连接到来时, bossGroup 就会为此连接初始化各项资源, 然后从 workerGroup 中选出一个 EventLoop 绑定到此客户端连接中. 那么接下来的服务器与客户端的交互过程就全部在此分配的 EventLoop 中了
服务器端的 ServerSocketChannel 只绑定到了 bossGroup 中的一个线程, 因此在调用 Java NIO 的 Selector.select 处理客户端的连接请求时, 实际上是在一个线程中的, 所以对只有一个服务的应用来说, bossGroup 设置多个线程是没有什么作用的, 反而还会造成资源浪费.:netty作者说:我们在不同的服务器引导之间共享NioEventLoopGroup,多个boss线程是有用的,
bossgroup中的socket是非阻塞的..因为其只是接受新的socket..如果在处理accept的时候阻塞了就会影响新的socket建立.所以不能是阻塞的.workgroup内的socket是阻塞的因为他要等待一个请求处理完成,要不然多个请求,修改程序某个变量会造成锁的问题.
bootstrap
serverbootstrap在channel初始化的时候会.向channel中写入各种配置包括基本的管道处理
* 服务器端的 handler 与 childHandler 的区别与联系:
* 在服务器 NioServerSocketChannel 的 pipeline 中添加的是 handler 与 ServerBootstrapAcceptor.
* 当有新的客户端连接请求时, ServerBootstrapAcceptor.channelRead 中负责新建此连接的 NioSocketChannel 并添加 childHandler 到 NioSocketChannel 对应的 pipeline 中, 并将此 channel 绑定到 workerGroup 中的某个 eventLoop 中.
* handler 是在 accept 阶段起作用, 它处理客户端的连接请求.
* childHandler 是在客户端连接建立以后起作用, 它负责客户端连接的 IO 交互.
SingleThreadEventLoop
在 AbstractScheduledEventExecutor 中, Netty 实现了 NioEventLoop 的 schedule 功能, 即我们可以通过调用一个 NioEventLoop 实例的 schedule 方法来运行一些定时任务. 而在 SingleThreadEventLoop 中, 又实现了任务队列的功能, 通过它, 我们可以调用一个 NioEventLoop 实例的 execute 方法来向任务队列中添加一个 task, 并由 NioEventLoop 进行调度执行.
通常来说, NioEventLoop 肩负着两种任务, 第一个是作为 IO 线程, 执行与 Channel 相关的 IO 操作, 包括 调用 select 等待就绪的 IO 事件、读写数据与数据的处理等; 而第二个任务是作为任务队列, 执行 taskQueue 中的任务, 例如用户调用 eventLoop.schedule 提交的定时任务也是这个线程执行的.
Netty 的 IO 处理循环
netty 中必然有一个 Selector 线程, 用于不断调用 Java NIO 的 Selector.select 方法, 查询当前是否有就绪的 IO 事件.
dotnetty源码解读一些要点的更多相关文章
- React16源码解读:开篇带你搞懂几个面试考点
引言 如今,主流的前端框架React,Vue和Angular在前端领域已成三足鼎立之势,基于前端技术栈的发展现状,大大小小的公司或多或少也会使用其中某一项或者多项技术栈,那么掌握并熟练使用其中至少一种 ...
- SDWebImage源码解读之SDWebImageDownloaderOperation
第七篇 前言 本篇文章主要讲解下载操作的相关知识,SDWebImageDownloaderOperation的主要任务是把一张图片从服务器下载到内存中.下载数据并不难,如何对下载这一系列的任务进行设计 ...
- SDWebImage源码解读 之 NSData+ImageContentType
第一篇 前言 从今天开始,我将开启一段源码解读的旅途了.在这里先暂时不透露具体解读的源码到底是哪些?因为也可能随着解读的进行会更改计划.但能够肯定的是,这一系列之中肯定会有Swift版本的代码. 说说 ...
- SDWebImage源码解读 之 UIImage+GIF
第二篇 前言 本篇是和GIF相关的一个UIImage的分类.主要提供了三个方法: + (UIImage *)sd_animatedGIFNamed:(NSString *)name ----- 根据名 ...
- SDWebImage源码解读 之 SDWebImageCompat
第三篇 前言 本篇主要解读SDWebImage的配置文件.正如compat的定义,该配置文件主要是兼容Apple的其他设备.也许我们真实的开发平台只有一个,但考虑各个平台的兼容性,对于框架有着很重要的 ...
- SDWebImage源码解读_之SDWebImageDecoder
第四篇 前言 首先,我们要弄明白一个问题? 为什么要对UIImage进行解码呢?难道不能直接使用吗? 其实不解码也是可以使用的,假如说我们通过imageNamed:来加载image,系统默认会在主线程 ...
- SDWebImage源码解读之SDWebImageCache(上)
第五篇 前言 本篇主要讲解图片缓存类的知识,虽然只涉及了图片方面的缓存的设计,但思想同样适用于别的方面的设计.在架构上来说,缓存算是存储设计的一部分.我们把各种不同的存储内容按照功能进行切割后,图片缓 ...
- SDWebImage源码解读之SDWebImageCache(下)
第六篇 前言 我们在SDWebImageCache(上)中了解了这个缓存类大概的功能是什么?那么接下来就要看看这些功能是如何实现的? 再次强调,不管是图片的缓存还是其他各种不同形式的缓存,在原理上都极 ...
- AFNetworking 3.0 源码解读 总结(干货)(下)
承接上一篇AFNetworking 3.0 源码解读 总结(干货)(上) 21.网络服务类型NSURLRequestNetworkServiceType 示例代码: typedef NS_ENUM(N ...
随机推荐
- zookeeper的集群部署
1.上传安装包到集群服务器 2.解压 3.修改配置文件 进入zookeeper的安装目录的conf目录 cp zoo_sample.cfg zoo.cfg vi zoo.cfg # The numbe ...
- 为RAC私有网络配置网卡Bonding
在RAC的安装部署过程中.并不不过简单的安装完毕了事.整个安装过程要考虑可能出现的单点问题,当中比較重要的是私有网络. 私有网络是RAC节点间通信的通道.包含节点间的网络心跳信息.Cache fusi ...
- hdu 3255 Farming(扫描线)
题目链接:hdu 3255 Farming 题目大意:给定N个矩形,M个植物,然后给定每一个植物的权值pi,pi表示种植物i的土地,单位面积能够收获pi,每一个矩形给定左下角和右上角点的坐标,以及s, ...
- Essay
要养成先连续输入一对匹配的字符——比如"("和")",以及"{"和"}"——再在其中填写内容的习惯.如果先填写内容,很容 ...
- 搜索学术论文訪问google的能用的几个IP地址
google搜索引擎打不开时的解决的方法,谷歌(google)的IP是多少? google IP镜像. 这里搜集了几个经过測试可用的IP,用来在不能域名訪问google的时候进行訪问 更新一个最新的. ...
- getifaddrs
getifaddrs 获取本地网络接口的信息.在路由器上可以用这个接口来获取wan/lan等接口当前的ip地址,广播地址等信息. #include <sys/types.h> #inclu ...
- 在VC6.0中多线程编程演示样例(带同步信号量)
直接上代码: #include <windows.h>//必要的头文件,使用Windows API函数 #include <stdio.h> int index = 0; in ...
- mongo11---Access control is not enabled for the database
今天使用MongoDB时遇到了一些问题 建立数据库连接时出现了warnings 出现这个警告的原因是新版本的MongDB为了让我们创建一个安全的数据库 必须要进行验证 后来在外网找到了答案 解决方案如 ...
- YTU 2844: 改错题A-看电影
2844: 改错题A-看电影 时间限制: 1 Sec 内存限制: 128 MB 提交: 69 解决: 47 题目描述 注:本题只需要提交标记为修改部分之间的代码,请按照C++方式提交. 小平家长为 ...
- html5--html实现乘法口诀表
html5--html实现乘法口诀表 总结: 1.多种情况问题:分情况讨论就出来了 2.汉字和数字的对应关系 3.相同的部分统一处理,不同的部分分别对待(这里用中间变量) 问题:九九乘法口诀 两点提示 ...