先上一张手稿图 :
具体步骤上面写的很清楚,Netty创建了两个连接,一个用于监听请求,只关注Accept 事件也就是连接事件, 一个是正在处理的对象也就是WorkGroup对象, 这两个之间通过selectionKey集合来传递
源码分析:
首先我们看看示例,然后分析相对应的方法,以及作用: 看注释哦
public static void main(String[] args) throws Exception {
/**
*
* 底层就是一个死循环 不停的去侦测底层的事件(也就是输入输出的时间不停的去处理)
* 事件循环组
* NioEventLoopGroup-->异步的时间循环组
* 最终 -->完成变量的赋值
* -->父接口EventLoopGroup循环组extend 多线程事件执行组MultithreadEventExecutorGroup
* EventLoopGroup 是有多个线程,也就是
*
**/
EventLoopGroup bossGroup = new NioEventLoopGroup();
//实际的业务处理 连接
EventLoopGroup workGroup = new NioEventLoopGroup();
try {
//启动服务管道(什么都没有做,只是对启动信息进行封装)
ServerBootstrap bootstrap = new ServerBootstrap();
//定义好组 channel 是通道 NioServerSocketChannel反射的方式创建的
bootstrap.group(bossGroup, workGroup).channel(NioServerSocketChannel.class)
//请求处理器 ,我们自己定义的子处理器
.childHandler(new MyServerInitializer());
ChannelFuture syncfuture = bootstrap.bind(8899).sync();
syncfuture.channel().closeFuture().sync();
} finally {
//关闭链接
bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
}
接下来我们分析bind 方法源码: 一直往下追发现了
AbstractBootstrap.doBind ()方法
private ChannelFuture doBind(final SocketAddress localAddress) {
//返回一个ChannelFuture 关注init的方法 构建ServerBootsorp的时候创建好
//并且赋值到这里面去
final ChannelFuture regFuture = initAndRegister();
final Channel channel = regFuture.channel();
if (regFuture.cause() != null) {
return regFuture;
}
init AndRefister()方法具体实现
final ChannelFuture initAndRegister() {
Channel channel = null;
try {
// -- > ReflectiveChannelFactory.newChannel()方法
// 通过反射的方法调用 --> 也就是NioServerSocketChannel 无参构造方法
channel = channelFactory.newChannel();
init(channel);
} catch (Throwable t) {
.....
}
我们看到他的newChannel方法 也就是说返回一个Channel实例:
@Override
public T newChannel() {
try {
return constructor.newInstance();
} catch (Throwable t) {
.....
}
}
我们看到它只是一个newInstance()方法来返回的,但是我们知道Channel类只是一个接口那么它具体的实现类是什么呢?
很简单我们DEbug看一看,:
而根据我们之前所传的类,发现它生成了一个 NioSeverSocketChannel对象 那我们就去这个类中去看看它是怎么生成的,总所周知,反射方法生成当然是无参构造 :
/**
* Create a new instance
*/
public NioServerSocketChannel() {
this(newSocket(DEFAULT_SELECTOR_PROVIDER));
}
继续追它自己的构造方法 :
public NioServerSocketChannel(ServerSocketChannel channel) {
//只关注的OP_ACCEPT 这个方法
super(null, channel, SelectionKey.OP_ACCEPT);
//去构造channel并且设置相关参数
config = new NioServerSocketChannelConfig(this, javaChannel().socket());
}
重点来了,这个就论证了我上面的说的也就是说BossGroup只关注这个链接事件
我们顺着它的父类一直往下捋 到了
DefaultChannelConfig.class 里面的构造函数 :
public DefaultChannelConfig(Channel channel) {
//可分配的接收字节
this(channel, new AdaptiveRecvByteBufAllocator());
}
我们重点关注一下这个类AdaptiveRecvByteBufAllocator 具体字面意思是
AdaptiveRecvByteBufAllocator主要用于构建一个最优大小的缓冲区来接收数据。比如,在读事件中就会通过该类来获取一个最优大小的的缓冲区来接收对端发送过来的可读取的数据。
分配一个新的接收缓存,该缓存的容量会尽可能的足够大以读入所有的入站数据并且该缓存的容量也尽可能的小以不会浪费它的空间。
其他具体信息请看这里
doc说到 :
/**
* The {@link RecvByteBufAllocator} that automatically increases and
* decreases the predicted buffer size on feed back. 根据反馈自动的增加buffer的大小
* <p>
* 基于前一次读的数量与读取到buffer的大小 根据结果 自动的增加缓存区的大小
/**缓存区的大小初始值是1024 不会比64小 不会超过 65536
* Creates a new predictor with the default parameters. With the default
* parameters, the expected buffer size starts from {@code 1024}, does not
* go down below {@code 64}, and does not go up above {@code 65536}.
*/
public AdaptiveRecvByteBufAllocator() {
//默认的构造参数 那么怎么自动变化大小的呢?
this(DEFAULT_MINIMUM, DEFAULT_INITIAL, DEFAULT_MAXIMUM);
}
我们在这个类中找到了一个静态代码块:
//在构造方法执行之前就执行的
static {
List<Integer> sizeTable = new ArrayList<Integer>();
//16进制规律
for (int i = 16; i < 512; i += 16) {
sizeTable.add(i);
}
//i向左位移1位,也就是乘以 2 一旦溢出 就跳出循环
for (int i = 512; i > 0; i <<= 1) {
sizeTable.add(i);
}
//sizeTable 是做什么事情?
//创建同样大小的SIZE_TABLE 从小到大的顺序 设定可分配缓存区大小
//也就是说根据前一次的缓存区大小分配 SIZE_TABLE 的大小去判断下一个大小并且去创建
SIZE_TABLE = new int[sizeTable.size()];
for (int i = 0; i < SIZE_TABLE.length; i ++) {
SIZE_TABLE[i] = sizeTable.get(i);
}
}
那么问题来了,怎么去申请缓存区呢?
同过上下问的分析找到了 这个类下面的私有的子类
我们在他的父类里面看到了缓存区的生成方法 :
//进行缓存区的分配
@Override
public ByteBuf allocate(ByteBufAllocator alloc) {
return alloc.ioBuffer(guess());
}
//判断是直接缓存还是间接缓存
@Override
public ByteBuf ioBuffer(int initialCapacity) {
if (PlatformDependent.hasUnsafe() || isDirectBufferPooled()) {
//直接缓存 也就是操作系统层面的
return directBuffer(initialCapacity);
}
//堆内缓存 直接返回一个字节数组
return heapBuffer(initialCapacity);
}
关于直接缓存与间接缓存的区别 去这里
我们看到了他是通过hasUnsafe方法来判断的那么看看它的具体实现:
/**
* Return {@code true} if {@code sun.misc.Unsafe} was found on the classpath and can be used for accelerated
* direct memory access.
*/
//判断是否包含 sun.misc.Unsafe这个路径
public static boolean hasUnsafe() {
return UNSAFE_UNAVAILABILITY_CAUSE == null;
}
简单思路的大体流程,如果要看具体的还要自己深入的去慢慢读
Reactor模式的角色构成,一共有5个角色构成 :
1 Handle:句柄或者是描述符,对于win来说是句柄,Linux来说是描述符,本质上是一种资源,该资源表示一个个的事件,比如说文件描述,或者是针对网络编程中的Socket的描述符.事件既可以来自于外部,也可以来自于内部,外部事件比如说客户端向服务器端发起的链接请求,客户端发送过来的事件等等,内部事件指的是操作系统产生的定时器事件等,它本质上就是一个文件描述符.也就是说产生事件的地方.
2 Synchronous Event Demultiplexer(同步事件分离器):本身是一个系统调用,用于等待事件的发生(事件可能是一个,也可能是多个)调用方在调用他的时候会被阻塞,一直阻塞到同步事件分离器上有时间产生为止.对于Linux来说6同步事件分离器指的就是常用的I/O多路复用机制,比如说select poll ,epoll等,在JAVA NIO 中同步事件分离器对于的组件就是Selector方法中的select()方法.
3 Event Hadler 事件处理器:本身由多个回调方法构成,这些回调方法构成了与应用相关的对于某个事件的反馈事件. Neyyt相比于java Nio 来说在事件处理器这个角色上进行了一个升级,为我们开发者提供了大量的回调方法,供我们在特定事件产生的时相对应的回调方法进行业务逻辑的处理.
4Concrete Event Handler 继承与Event Handler 是事件处理器的实现,它本身实现了事件处理器所提供的各个回调方法,从而实现了特定业务的逻辑,本质上就是我们所编写的各个处理器的实现 . (对应于我们自己写的处理器) channelRead0 是由WorkGroup来确定的. (可以看我写的Demo ) 还有文档
5 initation Dispatcher : (初始分发器)实际上就是Reactor角色,它本身定义了一些规范,这些规范用控制事件的调度方式,同时又提供了应用进行时间处理器的注册,删除等设施. 它本身是整个事件处理的核心所在,它会通过同步事件分离器来等待事件的发生,一旦事件发生,Init 首先会分离出每一个事件(遍历SelectionKey 里面的每一个集合 ),然后调用事件处理器,最后调相关的方法来处理这些事件.
Reactor模式的流程
1.服务器启动,当应用向initiation Dispatcher注册具体的事件处理器时,应用会标识出该事件处理器希望Initiation Dispatcher在某个事件发生时向其通知的该事件,该事件与Handle关联。
2.Initiation Dispatcher会要求每个事件处理器向其传递内部的Handle。该Handle向操作系统标识了事件处理器。
3.当所有的事件处理器注册完毕后,应用会调用handle_events方法来启动Initiation Dispatcher的事件循环。这时,Initiation Dispatcher会将每个注册的事件管理器的HandLe合并起来,并使用同步事件分离器等待这些事件的发生。比如说,Tcp协议层会使用select同步事件分离器操作来等待客户端发送的数据到达连接的socketHandle上。
4.当与某个事件源对应的Handle变为ready状态时(比如说,TCPsocket变为等待读状态时),同步事件分离器就会通知工nitiation Dispatcher。
5.Initiation Dispatcher会触发事件处理器的回调方法,从而响应这个处于ready状态的Handle。当事件发生时,Initiation Dispatcher会将被事件源激活的Handle作为keya来寻找并分发恰当的事件处理器回调方法。
6.Initiation Dispatcher会回调事件处理器的handle_events回调方法来执行特定于应用的功能(开发者自己所编写的功能),从而响应这个事件。所发生的事件类型可以作为该方法参数并被该方法内部使用来执行额外的特定于服务的分离与分发。
我们可以定义多个Handle并且增加到Piepline当中 相当于 把我们自己写的Handle来注册到Dispatch() 里面去
public class MyServerInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4));
pipeline.addLast(new LengthFieldPrepender(4));
pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
pipeline.addLast(new MyServerHandler());
}
}
我们在来看看Dogli写的文档,并与这个上图做个对比.
加油!!!!!!