netty高性能网络框架原理&源码解析
2021-06-28 13:43:03 0 举报
netty源码解析,启动流程解析,netty架构,netty原理
作者其他创作
大纲/内容
Channel自己的一些属性
NIO里面设置非阻塞
实例
client
BIO阻塞IO排队打饭
HeadContext
得到
netty简易原理
.childHandler给worker循环组使用的Handler
channelFuture.channel().closeFuture().sync();关闭也是会在完全关闭才会返回
pipeline.fireChannelRegistered();
代理服务器:两个Channel共用同一个eventLoop
新建Selector
自己实现的处理器
涉及数据的复制
主要作用:channelRead实现将Channel转移到workerGroup
io.netty.util.concurrent.GlobalEventExecutor#execute
netty提供的接口netty没有这个,是直接调用具体处理器的
bossGroup
int selectedKeys = selector.select(timeoutMillis);
ChannelInboundHandlerAdapter不是泛型类,需要强制类型转换
JDK提供的Future只能手动检查执行结果,而且是阻塞的。(get或者isDone)netty则对Future进行了增强,使用了观察者模式,一个Future上注册了很多感兴趣的Listener,会调用每一个每一个Listener的Complete方法,会传递自己,自己可以获取到Channel,拿到Channel就可以任意操作promise继承Future,只能写一次
GenericEventExecutorChooser
pipeline
如果设置的时候使用了handler(),也就是给bossGroup设置了Handler,就加到管道里面。ChannelInitializer的initChannel在Channel注册到eventGroupLoop上面的时候会被调用,而且当前ChannelInitializer(并不是Handler,但是可以重写他的initChannel这个方法添加多个Handler,这就是他的使命,一次性添加多个Handler。实际也是这样做的)会被删除,因为没用了最后往管道添加一个接收器ServerBootstrapAcceptor,可以看到事件循环组其实就是一个线程池,execute方法里面是添加ServerBootstrapAcceptor接收器,对应的就是reactor里面的Acceptor
写数据请求解除阻塞SelectionKey.OP_READ
SelectionKey.OP_READ | SelectionKey.OP_ACCEPT
非阻塞
等待结束,一定是执行结束了,Future有结果了
异步执行
ServerBootstrap
reactor模式优点:
自定义编码器
Channel的pipeline
doRegister();
设置Handler
策略模式
startThread();
message
不管是serverSocketChannel还是SocketChannel,一开始都是简单的初始化器。注册结束之后会被替换为真正的Hhandler
EventExecutorChooserFactory.EventExecutorChooser chooser;轮询还是随机next
AIO(1.7)异步IO包厢模式
1 连接请求
for (;;) {
taskQueue.offer(task);
一般是一对一如果添加到pipeline多次,就是多对一
每一个Handler的channelRegistered,注册Handler
NIO(1.4)非阻塞点单被叫
AbstractConstant
消息不会被释放
reactor模式缺点:
一致接口
通用模式的 NIO 实现多路复用器是怎么跨平台的
Channel
TailContext
ch.configureBlocking(false);
4 读取Channel数据交给pipeline
attr()方法作用域(4.1改进):AttributeMap是共用的同一个Channel上的所有的HandlerContext是共享attr的源码的Context最后还是调用了Channel的attr,Channel的又是调用自己的父类Map的attr方法
异步
constant配置信息以及业务信息
删除的逻辑就是会先调用自己写的全部注册进去之后,再调用原本的内部的一个方法将自己从pipeline里面删除handlerAdded--》initChannel--》remove(ctx);
实现
serverSocketChannelsocketChannel最后都是调用这个下面的流程是两种Channel共享的
ReplayingDecoder 、ByteToMessageDecoder 主要区别:decode() and decodeLast() 可以被实现就像所有的字节已经接收到了,而不用检测到底够不够填充当前的类型。自定义协议常用的(头部标记长度字段) public class IntegerHeaderFrameDecoder extends font color=\"#ff0000\
DefaultChannelOption
selectCnt ++;空轮询bug解决
io.netty.bootstrap.ServerBootstrap#init(channel)
菜好了谁来端的问题
事件循环组(线程池)
1
io.netty.bootstrap.ServerBootstrap.ServerBootstrapAcceptor#channelRead在服务器初始化之后注册的Handler,也就是reactor中的Acceptor
输入源
从buffer里面获取需要处理的Channel
重点:去注册了,和服务端共用一套逻辑
select(wakenUp.getAndSet(false));
自己定义的Handler
规避多线程并发问题是当前线程就直接执行,不是当前线程就会作为一个任务加到当前事件循环,等待被当前事件循环的IO线程执行,所以就是把多线程串行 了,避免了并发的问题netty的任务队列保证先进来的队列先执行,综上,netty中Channel的实现是线程安全的。所以可以保存一个Channel引用,想要向远程断点发数据时候,可以用这个引用调用Channel的方法,很多线程再使用也不会有多线程问题而且一定会按照顺序发出去。
value
accept
io.netty.util.concurrent.SingleThreadEventExecutor#doStartThread
pipeline流转需要的AttributeKey以及value注意作用域,下面有写
Channel注册到一个线程(事件循环组里面)
连接建立
自定义协议实现粘包拆包解决
消息短多个拼接在一起,消息长拆分为多个,自己实现比较麻烦,netty会提供编解码器的方式,(基于分隔符或者头部标记长度或者定长)。
netty的NioSocketChannel
read2个实现
处理连接请求SelectionKey.OP_ACCEPT
消息计数-1,引用数少一个,表示消息被消费。会自动释放消息资源使用了模板方法设计模式,推迟到子类执行channelRead0
不用BIO:连接多的时候会阻塞,耗资源效率低
切换的时候没有管socketChannel而是只切换了ServerSocketChannel是因为SocketChannel是ServerSocketChannel在内部使用SocketUtils.accept(javaChannel());创建出来的
添加
为什么不使用AtomicInteger?而是使用了updater的方式?buf用的很多,static的全局变量,一个更新器谁都可以使用。AtomicIntegerFieldUpdater,只有一份,每一个都用一个AtomicInteger就会有性能损耗
客户端服务器启动类
监听端口
Executor executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());每一个任务的线程执行器
ChannelOptionTCP 相关的系统设置项
super(parent);
ChannelFutureListener会被事件循环的IO线程调用,如果是耗时的处理方法是一样的
this(newSocket(DEFAULT_SELECTOR_PROVIDER)new...会创建返回一个serverSocketChannel
select
Selector
处理数据
一些属性的设置
DEFAULT_SELECTOR_PROVIDER = SelectorProvider.provider();这是NIO获取Selector的方式,spi的范式创建里面是同步的,可能会大量连接的时候阻塞,所以直接把这个provider存起来
2
随机读写 ByteBuf buffer = ...; for (int i = 0; i < buffer.capacity(); i ++) { byte b = buffer.getByte(i); System.out.println((char) b); }
next得到一个事件循环(一个线程):(EventLoop) EventExecutor
callHandlerAdded0(newCtx);见名知意
通用的NIO在Linux下也是epoll啊,为什么另外写了?暴露更多的参数,边缘触发和水平触发可以切换,netty实现更少的垃圾回收,性能更好
客户端服务端初始化器
事件循环(线程)死循环监听处理事件
io.netty.channel.socket.nio public class NioServerSocketChannelextends AbstractNioMessageChannelimplements io.netty.channel.socket.ServerSocketChannel启动器里面指定的是什么channel,创建的时候就会使用反射的工厂去找到指定的class创建什么。而且创建的会创建pipeline。init 的时候会设置 Channel pipeline
启动结束
Acceptor
AttributeMap一个Channel多个Handler数据的共享也是在初始化ServerBootStrap的时候可以设置,key以及对应的 T value
Reactor 角色构成与优劣势
io.netty.channel public interface Channel所有的IO操作都是异步的Channel pipeline 是其一个属性,包含了Handler的链条。支持父子关系,SocketChannel是ServerSocketChannel产生的,就有一个parent Channel。他们可以共享一个Socket连接,使用Channel之后要释放句柄。
pipeline.invokeHandlerAddedIfNeeded();调用已经加进来的Handler,就是最开始的ChannelInitializer
socketChannel完成注册的时候会替换为真正的Handler
io.netty.channel.nio.AbstractNioMessageChannel.NioMessageUnsafe#read
为什么说 Netty 的 main reactor 大多并不能用到一个线程组,只能线程组里面的一个?只是bind的时候用一下
io.netty.channel.SingleThreadEventLoop#register(io.netty.channel.Channel)
read
MultithreadEventLoopGroup
要不要死等
IO切换的原理:不同的IO之间的切换,就是直接用到NIO的地方换Oio.channel() 使用反射的方式构建,泛型加反射加工厂实现IO模式的切换工厂模式+泛型+反射实现
场景:代码简单连接有限并发度低不会扩展
TCP 粘包拆包常见解决思路:不同的编解码器实现
ChannelConfig:Channel持有的自己的属性里面可以设置ChannelOption为key的值是什么
register0(promise)
等待超时和连接超时 * // BAD - NEVER DO THIS * {@link Bootstrap} b = ...; * {@link ChannelFuture} f = b.connect(...);font color=\"#ff0000\
调用所有的Handler处理客户端的数据
进入else可能Future还没有完成
反射的方式创建Channel,class在前面已经设置到里面了,不带参数的,直接无参构造器的newInstance
连接超时不应该使用等待超时来配置直接使用连接超时实现
final ChannelFuture regFuture = initAndRegister();异步的初始化和注册
新建ServerChannel
2注册读事件
5个主要角色:1. Handle 句柄或者是描述符,本质是一种资源,用于表示一个个事件的集合,是操作系统提供的,事件既可以来自外部(客户端连接请求以及数据请求)也可以来自内部(定时器),本质是一个文件描述符,是事件产生的发源地2. Synchronous Event Demultiplexer 同步事件分离器:本身是一个OS系统调用,用于等待事件的发生,调用方调用这个的时候会阻塞,直到同步分离器上有事件产生位置返回,对于Linux而言就是常用的IO多路复用(select Poll epoll),对于NIO就是Selector,选择器底层还是会调用OS的多路复用,阻塞的方法就是select()3. 事件处理器:本身是有多个回调方法构成的,这些回调方法构成了事件的反馈机制,NIO没有实现要自己实现,netty有事件对应的回调,我们可以改写。4. 事件处理器的实现:实现了事件处理器提供的各个回调方法,实现了特定于业务的逻辑,本质就是一个个处理器的实现5. Initiation Dispatcher初始分发器:就是reactor(事件循环,线程池),事件处理器的注册删除,控制事件的调度方式,事件发生之后会分离出每一个事件,然后调用事件处理器,最后调用相关的回调方法处理相关的操作
protobuf (编解码) netty(传输)----------- thrift(编解码 + 基于netty的rpc框架) grpc(基于netty的rpc框架)
0 构造器创建
socketChannel绑到childGroup
3 读写请求
Channel注册到事件循环组
将Channel和端口绑定,注册关闭事件
数据容器:ByteBuf io.netty.buffer; 与 jdk 的 ByteBuffer
初始化Channel,添加了ChannelInitializer,后续Channel注册时候使用
io.netty.channel.socket.nio.NioServerSocketChannel#doReadMessages(readBuf)
io.netty.channel.nio.NioEventLoop#run
注意socketChannel注册到worker循环组和serveSocketChannel注册到boss的逻辑是一样的,最后也会调用invokeHandlerAddedIfNeeded将ChannelInitializer替换为我们自己编写的一个个Handler
eventLoop.execute其实就是Executor的execute方法
interface ArchiveSearcher { String search(String target); } * class App { * ExecutorService executor = ... * ArchiveSearcher searcher = ... * void showSearch(final String target) * throws InterruptedException { * Future<String> future * = executor.submit(new Callable<String>() { * public String call() { * return searcher.search(target); * }}); * displayOtherThings(); // do other things while searching * try { * displayText(future.get()); // use future * } catch (ExecutionException ex) { cleanup(); return; } * } * }
init的时候会调用代码写的设置,没有设置的话就是空的
获取这里初始化的只有首尾的pipeline
基于头部长度标记的编解码器LengthFieldBasedFrameDecoder经常在自定义协议里面使用
前面的都是在设置属性值,最后的bind才是真正的开始
pipeline.fireChannelRead(byteBuf);
socket Channel 的 pipeline
连接请求解除阻塞SelectionKey.OP_ACCEPT
io.netty.channel.AbstractChannel.AbstractUnsafe#register
EventLoop
Reactor
看注释补齐
反射创建的时候就会使用
同步
比较;netty 的是两个指针实现的,不需要flipjdk只是使用了一个指针,最后读取的时候还需要flip反转
Channel注册了之后才会被调用
为什么我们写的Handler不需要并发控制?因为netty实现保证了一个事件循环在整个生命周期只会和一个Thread绑定为什么不能在channelRead0写耗时操作?左边第五点,因为会有成百上千Channel属于同一个事件循环,又是单线程,这不是堵死了吗,性能直线下降。这个时候就要用业务线程池EventExecutor,两种实现:实现一:在channelHandler回调方法使用自己定义的JDK线程池:ExecutorService executorService = Executors.newCachedThreadPool(); executorService.submit(() -> { });font color=\"#ff0000\
Channel 注入Selector
客户端
反射创建
MultithreadEventExecutorGroup
按照协议长度,读取数据
基于分隔符的编解码器DelimiterBasedFrameDecoder
p.addLast(new ChannelInitializer<Channel>() {ChannelInitializer 本质是一个InBoundHandler
ChannelInitializer有什么用?不是Handler,但是他的方法可以一次性添加多个Handler,添加完了没用了就可以删除了
io.netty.channel public interface ChannelPipelineChannel创建的时候这个也会创建是一个ChannelHandler的列表(容器),就是event Handler,里面的一个个事件处理器就是Concurrent event Handler,具体的事件是容器元素处理的,不是容器处理的传统的拦截器入和出都要经过,netty是细化的特殊的拦截器,不是双向的,入站的不会管出去的入站处理器会处理IO线程生成的入站数据,从远端读取的,如果没有处理器就走到结尾丢掉出站事件到达最后就会被IO线程写到远端。 ChannelPipeline p = ...; p.addLast(\"1\
pipeline = newChannelPipeline();
AttributeKey维护业务数据流程中后续可以获取
给新连接建立NioSocketChannel之后加到buffer里面
ServerBootstrapAcceptor
主要流程:1. 应用向 Initiation Dispatcher 注册具体的事件处理器,应用会指定自己关注什么事件,事件和Handle相关联。(事件是Handle的子集,事件是读写,Handle是socket的文件描述符)2. Initiation Dispatcher 要求每个事件处理器传递内部的Handle,Handle向操作系统标识了事件处理器3. 所有事件处理器注册之后,启动事件循环,使用同步事件分离器等待这些事件的发生。4. 当与某个事件源对应的Handle变为读状态,同步事件分离器会通知 Initiation Dispatcher5. Initiation Dispatcher 会触发事件处理器的回调方法(自己写的),响应处于ready状态的handle,事件发生时, Initiation Dispatcher 会将事件源激活的Handle作为key寻找分发恰当的事件处理器回调方法6. Initiation Dispatcher 会回调事件处理器的handle_event(一个Handle是可以产生多个事件的,socket的读写连接),处理特定的类型
socketChannel注册到事件循环组的Selector
addLast0(newCtx);完成链表的改变,也就是链表中间插入元素
AbstractChannelHandlerContext.handler().handlerAdded(ctx);这个方法是我们自己可以重写的,标记当前的Handler的包装Context已经加入到pipeline里面了
三种IO
Reactor 单线程
ByteBuf
应用:spark Hadoop、rocketMQ、检索ES、grpc/dubbo/spring5 (和MVC相对的webflux使用,web响应式编程)、zk、async-http-client
SimpleChannelInboundHandler泛型指定只处理特定类型的消息
ChannelHandlerContext连接ChannelHandler以及pipeline的纽带通知pipeline里面下一个Handler,ChannelHandler handler();可以获取对应的Handler,Channel channel(); 获取Channel对象的引用ChannelPipeline pipeline();获取pipeline,也可以修改pipeLine所属的对象
bind(9000).sync();非阻塞方法调用
runAllTasks();
什么是reactor?注册感兴趣的事件 -> 扫描是否有感兴趣的事件发生 -> 事件发生后做出相应的处理。
初始化NioServerSocketChannel
netty 4.0 之前是JBoss的4.0之后是Netty自己独立出来的5.0 是复杂,增加了ForkJoinPool没有显著性能优势
Channel感兴趣的事件accept
新建事件循环
连接建立的socketChannel注册到workGroup上的一个线程的Selector上
AbstractBootstrap
ChannelInitializer
private volatile int refCnt = 1;-1操作;为0 的时候就不能使用了,会调用 deallocate(),堆buf的话数组置为null就好,垃圾回收机制会负责接下来的回收,而直接内存的buf的话io.netty.buffer.AbstractReferenceCountedByteBuf#release0 private boolean release0(int decrement) {font color=\"#ff0000\
netty处理器分为2类:入站处理器(顶层是ChannelInBoundHandler),出站处理器(顶层是ChannelOutBoundHandler)。有回调方法,时间产生的时候就会调度编解码器本质就是处理器,最后在网络里面流动时候都是字节流的形式,编解码统一叫做 codec出站操作--编码--从程序流入网络--ChannelOutBoundHandler入站操作--解码--从网络流入程序--ChannelInBoundHandlernetty里面通常是XXXEncoder命名,解码器通常是XXXDecoder
和Channel互相引用
processSelectedKeys()
addLast方法的详解:往pipeline的链表最后加一个HandlerContext。源码启动流程中每一个Channel一开始都会使用此方法添加ChannelInitializer,我们自己写的addLast也是这个方法
抽象类实现类分类:堆上的直接内存的池化的和非池化的所以组合一共4种:非池化的堆上和直接内存的ByteBuf,释放的时候堆上直接使用null等待GC,直接内存则是使用Cleaner。池化的:引用计数的领域和jdkGC访问可达分析算法不一样。引用计数值release()到达0的时候就会放回池子里面,访问引用计数值为0的对象就会触发异常。增加引用计数可以使用retain()方法。引用计数:https://netty.io/wiki/reference-counted-objects.html衍生buffer和原本的buffer是使用同一个的。引用计数不会变化,所以产生衍生buffer的时候要retain,加1 ,Mark and reset作用是一致的
childGroup.register(child)
按照协议编码其实直接协议对象写入
socketChannel
SingleThreadEventExecutor.this.run();
需要Channel的时候直接调用openServerSocketChannel而不是SocketChannel的open方法避免性能问题issues/2308ServerSocketChannel newSocket(SelectorProvider provider) { return provider.openServerSocketChannel();
ByteBuf的引用计数 : 原子更新 --- CAS + 自旋
io.netty.channel.nio.AbstractNioChannel#doRegister
EventLoopGroupworkerGroup
易混5点:1. 一个事件循环组会有多个事件循环2. 一个事件循环在整个生命周期只会有唯一一个Thread与之绑定3. 所有事件循环所处理的IO事件都将在所关联的Thread上处理4. 一个Channel在他的生命周期只会注册到一个事件循环上5. 一个事件循环在运行过程中,会被分配给一个或多个Channel(也就是会有多个Channel注册上来)
ChannelPromise可写入的ChannelFuture,父子关系
触发执行task.run
内部有自己定义的ByteBufReplayingDecoder传递一个特殊的ByteBuf实现,当缓冲区中没有足够的数据时抛出特定类型的错误。在上面的IntegerHeaderFrameDecoder中,当调用buf.readInt()时,您只是假设缓冲区中有4个或更多字节。如果缓冲区中真的有4个字节,它将像您期望的那样返回整数头。否则,将引发Error,控件将返回到ReplayingDecoder。如果ReplayingDecoder捕获了Error,那么它将把缓冲区的readerIndex倒带回到“初始”位置(即缓冲区的开始),并在缓冲区接收到更多数据时再次调用decode(..)方法。数据不够,解码器可能会重复解码相同的部分。而不像上面的不够就返回,为了解码一个单独的消息会解码好多次。泛型是状态类型,不使用直接Void,使用是用在检查点的时候,用来提高性能,区分不同类型的数据段,自己读取到哪里了
Netty 对 Reactor 模式支持
接口 netty Future为什么不直接使用jdk??对listener的操作,观察者模式。特定操作结束就会调用,不需要手动调用,jdk的需要手动调用监听者GenericFutureListener实现的接口的方法operationComplete会被自动调用
删除已经做好的AIO?window实现成熟,但是不是服务器。LinuxAIO不成熟,对于NIO的性能提升不明显
0 注册accept事件
ChannelInitializer入站处理器
注册Channel到SelectorNIO调用
pipeline.fireChannelRead(readBuf.get(i));
ConstantPool创建并且管理一些常量ConcurrentMap
设置EventLoopGroupworkerGroup(定义在ServerBootStrap)
io.netty.channel.nio.AbstractNioByteChannel.NioByteUnsafe#read
io.netty.channel.nio.NioEventLoop#processSelectedKeysOptimized
java.util.concurrent.Future代表着异步计算的结果,提供了计算是否完成的 check,等待完成,取回等多种方法。isDone主动查询,阻塞;get,会一直阻塞到结果计算完成。cancel,一旦计算完成,就无法被取消了
OioServerSocketChannel基于流的
sync确保初始化注册都结束了,操作完毕 ,返回的channelFuture异步操作结束虽然是异步的,但是也要有同步操作
使用等待超时实现连接超时
本质:网络应用程序框架实现:异步、基于事件驱动用途:开发服务器和客户端特性:高性能可维护和快速开发为什么不直接使用NIO?做的更多:netty支持常用应用层协议,解决传输问题粘包和半包线性;支持流量控制;完善的断连和空闲异常处理做的更好: JDKNIO空轮询epoll bug(选择器无限唤醒,Linux2.4),检测问题的发生,(也就是select的次数大于阈值),之后重新建立一个Selector IP包使用时候抛出异常,IP_TOS 参数,检测到NIOChannelOption是IP_TOS 的时候,直接不设置(work around,搞不定就不支持); JDK的NIO的API不友好,功能薄弱,例如ByteBuffer(flip不能扩容)和ByteBuf(可以自动扩容)之间。以及功能的增强例如ThreadLocal和FastThreadLocal(高性能)自己实现的难度:JDK的bug,自己实现的bug
MessageToMessageEncoder是一个编解码器
服务端
没有数据的复制,推荐使用
基于行的编解码器LineBasedFrameDecoder
处理连接的建立
返回值 接口ChannelFuture针对Channel的Future接口Channel IO操作的结果,netty里面所有的IO都是异步的,因此会立即返回,得到一个ChannelFuture的实例,可以通过其获取IO操作的状态。可以 addListener() ,对Channel监控,完成时候会通知 ChannelFutureListener 不会阻塞,而不是使用await方法,因为会阻塞直到结果返回会有死锁发生。
NioEventLoopGroup
NioEventLoop事件循环组其实就是一个死循环
ChannelFuture regFuture = config().group().register(channel);config返回服务器启动类的config对象,group获取到事件循环组
serversocketChannel注册到事件循环组的Selector
MINA:(虽然是后来者)netty是对mina的重构,更加简单,Apache管理SUN的Grizzly:用的少,文档少,社区不活跃Tomcat、Jetty:实现没有独立出来是为了支持servletTomcat不用是因为创建的时候netty还没有
channel.eventLoop().execute(new Runnable() {
两个指针分别读写,不需要flip()
ChannelOptionTCP 相关的设置项,只维护key的信息,值在其余地方可以用来配置ChannelConfig初始化ServerBootStrap的时候可以设置,key以及对应的 T value
MessageToMessageDecoder#decode
而且isSuccess是isDone的细化,后者可能是失败null结束的
服务端真正的业务逻辑Handler注册我们自己写的,在客户端建立连接之后得到SocketChannel之后才会注册
传统编程模式问题:1. 一个socket一个线程2. 上下文切换开销大3. 没有数据传递,线程保持,浪费资源
适配器模式+模板方法:入出站处理器
Reactor 多线程模式
读取数据 ByteBuf buffer = ...; while (buffer.isReadable()) { System.out.println(buffer.readByte()); }
Bootstrap
ChannelPipeline p = channel.pipeline();一个个的Handler在这里
next().register(channel);
AbstractChannelHandlerContext
Nio....
io.netty.channel.DefaultChannelPipeline#font color=\"#ff0000\
ReplayingDecoder#decode
hashmap的方式
实现了任务和线程创建解耦
非阻塞监听IO事件
workerGroup
acceptor作用是将Channel注册到workerGroup,他是位于bossGroup的pipeline里面的最后一个处理器acceptor是netty绑定端口的时候自动创建的,加到了pipeline,也就是一个处理器,这个处理器会将生成出来的socketChannel绑定到workerGroup(IO线程组,里面有一个线程池)
next.invokeChannelRead(m);
初始化pipeline
FutureTask<String> future = new FutureTask<String>(new Callable<String>() { public String call() { return searcher.search(target); } });executor.execute(future);
自定义协议长度内容
ChannelHandler包含了入站和出站两种,处理不同的数据
SingleThreadEventLoop
基于固定长度的编解码器FixedLengthFrameDecoder
channel = channelFactory.newChannel();实际是ReflectiveChannelFactory反射新建Channel
客户端服务端处理器
ServerBootStrap
观察者放到一个集合,来的时候遍历集合
主从 Reactor 多线程模式
启动流程解析
netty对三种IO的支持
写数据 ByteBuf buffer = ...; // 整数大于4字节,可以放下 while (buffer.maxWritableBytes() >= 4) { buffer.writeInt(random.nextInt()); }
自定义解码器
EventLoopGroupbossGroup
处理连接请求SelectionKey.OP_READ
NioServerSocketChannel
https://www.jianshu.com/p/36b58ef2ff23
.channel反射的方式创建,但是此时不创建,只是NioServerSocketChannel.class给了
感兴趣的事件
设置EventLoopGroupbossGroup用于接收客户端连接(定义在AbstractBootStrap)
SocketChannel ch = SocketUtils.accept(javaChannel());使用java原生的方式获取Channel
Netty 如何支持主从 Reactor 模式的?serverSocketChannel注册到bossGroup(parentGroup)ChannelFuture regFuture = config().group().register(channel);font color=\"#ff0000\
addTask(task);
开始真正启动
for循环处理selectedKeys所有的key
ByteToMessageDecoder#decode
doBind(localAddress);
抽象类ChannelHandlerAdapter
Channel.pipeline().addLast(childHandler);
阻塞
register0(promise) 任务放到队列异步执行
互相引用
serverbootstrap
ChannelHandler
中转
Netty 给 socketChannel 分配 NIO event loop 的规则是什么?线程池里面选一个线程(事件循环)来注册socketChannel,选择的index计算有两种方式,根据初始的线程的个数是否是2的幂决定使用哪一个chooser,决定了index的计算方式不一样childGroup.register(child)--》next().register(channel); --》chooser.next();
readBuf里面填充需要处理的socketChannel
netty主要的基本组件 Channel ChannelHandler ChannelHandlerContext ChannelPipeline
处理器
管道实际存放的是 new DefaultChannelHandlerContext
0 条评论
回复 删除
下一页