Netty源码分析--创建Channel(三)
先看一下我Netty的启动类
private void start() throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 128)
.option(ChannelOption.SO_KEEPALIVE, true)
.handler(new LoggingHandler(LogLevel.INFO))
.localAddress(new InetSocketAddress(port))
.childHandler(new ChannelInitializer<SocketChannel>() {
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new IdleStateHandler(5, 0, 0, TimeUnit.MINUTES));
ch.pipeline().addLast(new ProtobufVarint32FrameDecoder());
ch.pipeline().addLast(new ProtobufDecoder(ChannelRequestProto.ChannelRequest.getDefaultInstance()));
ch.pipeline().addLast(new ProtobufVarint32LengthFieldPrepender());
ch.pipeline().addLast(new ProtobufEncoder());
ch.pipeline().addLast(new HeartBeatServerHandler());
ch.pipeline().addLast(new XtsCoreServerHandler());
}
});
ChannelFuture future = bootstrap.bind().sync();
future.channel().closeFuture().sync();
} catch (Exception e) {
bossGroup.shutdownGracefully().sync();
workerGroup.shutdownGracefully().sync();
}
}
Netty先创建了两个事件循环组 EventLoopGroup ,这个就对应了上文提到的模型, 第一个事件循环组的职责是 负责接收新的客户端连接 并 把客户端Channel注册到多路复用器上面。 第二个事件循环组的职责是 处理客户端的读写等事件。
Netty使用 ServerBootstrap 这个链式编程的方式把启动服务端的代码给串联起来,使用非常方便和优雅。所以我们在看源码的同时也要去好好品读Netty源码书写的巧妙,理解设计思想和方式,巧妙地运用到我们自己的代码中。接下来我会把源码贴出来,并把重要的部分进行解释和着重指出
看下 group 方法
public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) {
super.group(parentGroup); // 第一个事件循环组,我们叫父循环组吧,它是继续调用了父类的group方法,也就是 AbstractBootstrap 并且赋给 group 成员变量
if (childGroup == null) {
throw new NullPointerException("childGroup");
}
if (this.childGroup != null) {
throw new IllegalStateException("childGroup set already");
}
this.childGroup = childGroup; // 第二个事件循环组, 我们叫子循环组吧, 直接赋值给 childGroup 这个成员变量
return this; // 返回自己,这就是链式编程的原因
}
接下来是 channel 方法, 设置了 channal 的类型是 NioServerSocketChannel (当然客户端是 NioSocketChannel),这里在创建的时候用了反射的方法来创建实例,后面涉及到再具体说。
public B channel(Class<? extends C> channelClass) {
if (channelClass == null) {
throw new NullPointerException("channelClass");
}
return channelFactory(new ReflectiveChannelFactory<C>(channelClass)); // NioServerSocketChannel 使用了 ReflectiveChannelFactory 工厂封装,并且设置到了 AbstractBootstrap
// 的 channelFactory 的这个成员变量中, 注意 这里的channal 是 父级 的 channal
}
.option 方法 是在设置 父级 的一些参数信息,这些就不说了, 当然这里 .handler(new LoggingHandler(LogLevel.INFO)) 方法 也是为父级的事件循环器设置的 handler 。
.childHandler(new ChannelInitializer<SocketChannel>() { // 当然这里是设置 子事件循环组器的 handlers ,但是这里的 initChannel 方法不是在这里调用的,这个后面会具体提到。
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new IdleStateHandler(5, 0, 0, TimeUnit.MINUTES));
ch.pipeline().addLast(new ProtobufVarint32FrameDecoder());
ch.pipeline().addLast(new ProtobufDecoder(ChannelRequestProto.ChannelRequest.getDefaultInstance()));
ch.pipeline().addLast(new ProtobufVarint32LengthFieldPrepender());
ch.pipeline().addLast(new ProtobufEncoder());
ch.pipeline().addLast(new HeartBeatServerHandler());
ch.pipeline().addLast(new XtsCoreServerHandler());
}
});
好了,接下来进入正式部分。
ChannelFuture future = bootstrap.bind().sync(); // 就是从这里的bind()进入
进入doBind方法
private ChannelFuture doBind(final SocketAddress localAddress) {
final ChannelFuture regFuture = initAndRegister();
final Channel channel = regFuture.channel();
if (regFuture.cause() != null) {
return regFuture;
}
... 省略一大波代码
}
进入 initAndRegister
final ChannelFuture initAndRegister() {
Channel channel = null;
try {
channel = channelFactory.newChannel(); // 这里就是创建一个父级的channel
init(channel);
} catch (Throwable t) {
if (channel != null) {
// channel can be null if newChannel crashed (eg SocketException("too many open files"))
channel.unsafe().closeForcibly();
// as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor
return new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t);
}
// as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor
return new DefaultChannelPromise(new FailedChannel(), GlobalEventExecutor.INSTANCE).setFailure(t);
}
...省略一大波代码
}
这就是我前面为什么着重指出 NioServerSocketChannel 使用了 ReflectiveChannelFactory 工厂封装 , 就是上图这里了。然后进入 ReflectiveChannelFactory
使用了 NioServerSocketChannel 的无参构造方法实例化这个类。ok,那我们来看下NioServerSocketChannel 的 无参构造方法。
传入一个默认的多路复用器创建器
使用它来调用 openServerSocketChannel() 方法来创建一个ServerSocketChannel, 具体这个方法就是NIO里面的内容了,有兴趣的自己去看一下。
看到这里,我们知道channel创建完成(为父级使用),返回到这里
大家千万不要忽视一点,这里有个this,我当时就没注意到这里,粗心了,导致其中有一步始终想不通,后来重新仔细看的时候,打自己的心都有了。
这里继续调用了另外一个有参的构造方法。
不断调用父类构造方法,就进入到
这里设置了父级的成员变量channel,并且把感兴趣的key设置为16(接收新的客户端),并且设置非阻塞。这里在第一篇启动NIO服务端的时候,也有这句,大家应该也记得。
我们继续看调用的父类构造方法。
我们知道了为Channel设置了一个ID,并且创建了Pipleline.并且初始化了两个上下文分别为头和尾,通过链表链接。
我们说到这里,简单回顾一下Pipeline. 我们开看下ChannelPipeline官方说明
讲到了, Pipeline 是 Channel中出站和入站操作的处理器或拦截器的一个列表。同时官方给出了一个表单我也贴出来
下图说明了I/O读写事件是怎么在PipeLine中的Handlers中传递的。需要通过 ChannelHandlerContext, 例如 ChannelHandlerContext#fireChannelRead(Object) 和 ChannelHandlerContext#write(Object)
这点了解Netty的一下子应该就看得明白,后面设计到PipeLine的地方我们再展开讲解,继续回到NioServerSocketChannel 的有参构造方法,继续往下看。
这里为刚刚创建的channel创建了一个配置类,并且是一个内部类。传入了channel和套接字。
不断往下跟,看到这里
这里传入了一个小内存分配器,也就是说为这个channel初始化了一个分配器。
ok,我们来简单说下这个分配器,后面在Netty的内存模型部分,我们再细说。
构造方法,传入了三个默认值,并且说明了 默认分配缓冲区的大小为1024 ,最小是64,最大是65536
通篇看一下,看到了一个非常重要的静态代码块
依次往sizeTable添加元素:[16 , (512-16)]之间16的倍数。即,16、32、48...496
然后再往sizeTable中添加元素:[512 , 512 * (2^N)),N > 1; 直到数值超过Integer的限制(2^31 - 1);
根据sizeTable长度构建一个静态成员常量数组SIZE_TABLE,并将sizeTable中的元素赋值给SIZE_TABLE数组。注意List是有序的,所以是根据插入元素的顺序依次的赋值给SIZE_TABLE,SIZE_TABLE从下标0开始。SIZE_TABLE为预定义好的以从小到大的顺序设定的可分配缓冲区的大小值的数组。因为AdaptiveRecvByteBufAllocator作用是可自动适配每次读事件使用的buffer的大小。这样当需要对buffer大小做调整时,只要根据一定逻辑从SIZE_TABLE中取出值,然后根据该值创建新buffer即可。
先了解这些,具体更加详细的内容,我们后面再介绍
Netty源码分析--创建Channel(三)的更多相关文章
- Netty源码分析之NioEventLoop(三)—NioEventLoop的执行
前面两篇文章Netty源码分析之NioEventLoop(一)—NioEventLoop的创建与Netty源码分析之NioEventLoop(二)—NioEventLoop的启动中我们对NioEven ...
- [编织消息框架][netty源码分析]8 Channel 实现类NioSocketChannel职责与实现
Unsafe是托委访问socket,那么Channel是直接提供给开发者使用的 Channel 主要有两个实现 NioServerSocketChannel同NioSocketChannel 致于其它 ...
- Netty源码分析-- 处理客户端接入请求(八)
这一节我们来一起看下,一个客户端接入进来是什么情况.首先我们根据之前的分析,先启动服务端,然后打一个断点. 这个断点打在哪里呢?就是NioEventLoop上的select方法上. 然后我们启动一个客 ...
- Netty源码分析--内存模型(上)(十一)
前两节我们分别看了FastThreadLocal和ThreadLocal的源码分析,并且在第八节的时候讲到了处理一个客户端的接入请求,一个客户端是接入进来的,是怎么注册到多路复用器上的.那么这一节我们 ...
- netty源码分析系列文章
netty源码分析系列文章 nettynetty源码阅读netty源码分析 想在年终之际将对netty研究的笔记记录下来,先看netty3,然后有时间了再写netty4的,希望对大家有所帮助,这个是 ...
- Netty源码分析第2章(NioEventLoop)---->第1节: NioEventLoopGroup之创建线程执行器
Netty源码分析第二章: NioEventLoop 概述: 通过上一章的学习, 我们了解了Server启动的大致流程, 有很多组件与模块并没有细讲, 从这个章开始, 我们开始详细剖析netty的各个 ...
- Netty源码分析第3章(客户端接入流程)---->第2节: 处理接入事件之handle的创建
Netty源码分析第三章: 客户端接入流程 第二节: 处理接入事件之handle的创建 上一小节我们剖析完成了与channel绑定的ChannelConfig初始化相关的流程, 这一小节继续剖析客户端 ...
- Netty源码分析第3章(客户端接入流程)---->第3节: NioSocketChannel的创建
Netty源码分析第三章: 客户端接入流程 第三节: NioSocketChannel的创建 回到上一小节的read()方法: public void read() { //必须是NioEventLo ...
- Netty源码分析第1章(Netty启动流程)---->第2节: NioServerSocketChannel的创建
Netty源码分析第一章: Server启动流程 第二节:NioServerSocketChannel的创建 我们如果熟悉Nio, 则对channel的概念则不会陌生, channel在相当于一个通 ...
随机推荐
- 一篇简单易懂的原理文章,让你把JVM玩弄与手掌之中
jvm原理 Java虚拟机是整个java平台的基石,是java技术实现硬件无关和操作系统无关的关键环节,是java语言生成极小体积的编译代码的运行平台,是保护用户机器免受恶意代码侵袭的保护屏障.JVM ...
- 深入Qt 学习 -- 反射机制(比较简单清楚)
相对于Java天生的这一特性, C++并不具备;但进入到Qt领域,这一切都变得简单自如了. 从Qt的元对象系统可知,除了提供信号/槽机制的特性之外,它还提供了以下特性: ■ QObject::meta ...
- JS简单验证password强度
<input type="password" id="password" value=""/><button id=&qu ...
- Leetcode 100 Same Tree 二叉树
就是判断两棵树的值和结构是否相同 注意:要判断是否所有的树节点是否为NULL /** * Definition for a binary tree node. * struct TreeNode { ...
- OpenGL(二十) glutSpecialFunc响应键盘方向控制键
OpenGL的glut中使用glutMouseFunc函数注册鼠标响应事件,使用glutKeyboardFunc函数注册键盘响应事件,对键盘上特殊的4个方向按键的响应函数是glutSpecialFun ...
- Qt on Android 资源文件系统qrc与assets
使用 Qt 为 Android 开发应用时,有时我们的应用会携带一些资源文件,如 png . jpg 等,也可能有一些配置文件,如 xml 等,这些文件放在哪里呢?有两种方式:qrc和assets,咱 ...
- 大约Android远程监控APP源代码
这篇文章的目的,关心询问名人,要打开源代码.这里说明,远程监控摄像头场外,相反,用手机摄像头摄像头server上,要理解这一点.关于非常网上的文章达到server道路.它能够准确,念就乱发博文,当然假 ...
- 使用 NodeJS + Express从GET/POST Request 取值
过去无论哪一种网站应用程式的开发语言,初学者教学中第一次会提到的起手式,八九不离十就是GET/POST Request 的取值.但是,在Node.js + Express 的世界中,仿佛人人是高手,天 ...
- linux 修改 ssh 的端口号,启动hg服务
修改 ssh 的端口号 # vim /etc/ssh/sshd_config 将其中的 Port 改为 想要修改的端口号. # service sshd restart 启动hg服务 # hg ...
- 动态加载并执行Win32可执行程序
本文所贴出的PoC代码将告诉你如何通过CreateProcess创建一个傀儡进程(称之为可执行程序A),并把dwCreationFlags设置为CREATE_SUSPENDED,然后把另一个可执行程序 ...