背景
最忌工作中接触到Netty相关应用场景,之前看过mima的部分源码,所以最近看了Netty的部分源码和学习其设计思想,做个简单的分享(学习代码为:Netty:3.6.3.FINALE)。
Netty概述
官方:Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients.
特点:和Mina,Grizzly一样都是基于nio的通信框架,事件驱动,异步非阻塞,高性能,高可靠性。
Mina和Neety的主导作者都是Trustin Lee. Mina是apache社区提供的一个基于NIO的高性能网络应用程序框架,Trustin Lee离开apache后加入redhat开启了Netty,所以两者的设计思路基本一致,目前Netty的更新更加频繁,文档也比较全。下图为netty官网上的一张体系结构图。
源码阅读:
先从Netty的ServerBootstrap初始化到bind()过程梳理
bootstrap = new ServerBootstrap(new NioServerSocketChannelFactory(Executors.newCachedThreadPool(serverBossTF), Executors.newCachedThreadPool(serverWorkerTF))); bootstrap.setPipelineFactory(new ChannelPipelineFactory() { public ChannelPipeline getPipeline() throws Exception { ChannelPipeline pipeline = new DefaultChannelPipeline(); pipeline.addFirst("firewall", firewall); pipeline.addLast("decoder", new NettyProtocolDecoder()); pipeline.addLast("encoder", new NettyProtocolEncoder()); pipeline.addLast("handler", serverHandler); return pipeline; } }); bootstrap.bind(new InetSocketAddress(bindHost, listenPort));
以上三步为:
1. 初始化ServerBootstrap,指定其ChannelFactory为NioServerSocketChannelFactory(用来创建NioServerSocketChannel)
2. 为bootstrap指定了PipelineFactory,所有Accepted Channel在创建时会使用该工厂创建一个pipeline处理事件。最后的serverHandler为自定义实现的Handler,最终负责处理请求相关业务逻辑等。
3. 绑定一个端口监听请求
从上面三步可以看到Netty的使用还是比较简单,可以自定义的扩展pipeline和实现自己的Handler。
整个过程屏蔽复杂的nio和多线程并发处理的细节,所以最近通过阅读源码了解Netty的部分实现。
NioServerSocketChannelFactory关键构造方法:
public NioServerSocketChannelFactory(BossPool<NioServerBoss> bossPool, WorkerPool<NioWorker> workerPool) { if (bossPool == null) { throw new NullPointerException("bossExecutor"); } if (workerPool == null) { throw new NullPointerException("workerPool"); } this.bossPool = bossPool; this.workerPool = workerPool; sink = new NioServerSocketPipelineSink(); }
bossPool 通过new NioServerBossPool(bossExecutor, bossCount, null)创建,这里bossCount默认为1,这里的boss线程通过new NioServerBoss(executor, determiner)创建,在这里完成Selector.open();主要会处理accept事件。
workerPool 通过new NioWorkerPool(workerExecutor, workerCount)创建,workerCount为当前可以cpu数×2(Runtime.getRuntime().availableProcessors() * 2 ),通过new NioWorker(executor, determiner)创建,每个worker持有一个Selector;主要处理数据读写操作。
sink NioServerSocketPipelineSink 接收并处理下行(downstream)末端的ChannelEvent,主要负责执行最终ServerSocketChannel绑定以及注册OP_ACCEPT到Selector上。
接下来看bootstrap.bind()
// ServerBootstrap.java --> bindAsync() // 初始一个Binder,Binder继承自SimpleChannelUpstreamHandler,接收并处理上行的ChannelEvent Binder binder = new Binder(localAddress); // 初始化一个bossPipeline,之前初始化那个pipeline是工作线程中用的 ChannelPipeline bossPipeline = pipeline(); // 将binder放入pipline中 bossPipeline.addLast("binder", binder); ... ... // 获取ServerBootstrap初始化时候初始的NioServerSocketChannelFactory来完成服务端channel创建,创建出的是一个NioServerSocketChannel Channel channel = getFactory().newChannel(bossPipeline);
NioServerSocketChannel
// 这是真正java nio的Channel,Netty包装了一层channel的概念 socket = ServerSocketChannel.open(); // 设置为非阻塞模式 socket.configureBlocking(false); // 将open事件作为一个上行事件丢到pipeline中(由对应的handler来handle事件) // 这里的handler从前面的初始化来看就是交给binder来处理,binder继承自SimpleChannelUpstreamHandler,可以处理上行事件 fireChannelOpen(this);
Binder --> channelOpen()
// 执行Channel的bind, 在pipeline中发送一个BOUND的下行事件 evt.getChannel().bind(localAddress)
DefaultChannelPipeline
// 这里的sink是初始化时上面提到的NioServerSocketPipelineSink getSink().eventSunk(this, e);
NioServerSocketPipelineSink --> eventSunk() --> handleServerSocket()
// 这里的boss是初始化时设置的 NioServerBoss ((NioServerBoss) channel.boss).bind(channel, future, (SocketAddress) value);
在boss的bind方法中new了一个内部的RegisterTask来完成真正的ServerSocket.bind()及注册到selector上,并唤醒Selector
NioServerBoss --> RegisterTask
//java NIO ServerSocketChannel.bind()操作 channel.socket.socket().bind(localAddress, channel.getConfig().getBacklog()); bound = true; // 更新future future.setSuccess(); // 出发一个绑定成功的事件 fireChannelBound(channel, channel.getLocalAddress()); // 向selector中注册OP_ACCEPT事件 channel.socket.register(selector, SelectionKey.OP_ACCEPT, channel);
接下来boss线程和worker线程就要开始干活接客了
NioServerBoss 继承自 AbstractNioSelector,其run方法:
public void run() { ... for (;;) { ... // 执行 selector.select(SELECT_TIMEOUT) // 阻塞等待客户端连接 int selected = select(selector); .... // 当请求到达唤醒select,处理具体的事件 // 该抽象方法由NioServerBoss实现 process(selector); } ... } protected void process(Selector selector) { // 获取select出来的SelectionKey,逐个accept Set<SelectionKey> selectedKeys = selector.selectedKeys(); for (Iterator<SelectionKey> i = selectedKeys.iterator(); i.hasNext();) { NioServerSocketChannel channel = (NioServerSocketChannel) k.attachment(); // accept connections in a for loop until no new connection is ready for (;;) { // NIO 的accept,建立和客户端之间的socket连接 SocketChannel acceptedSocket = channel.socket.accept(); .... registerAcceptedChannel(channel, acceptedSocket, thread); } .... } } //将accept成功的SocketChannel交给工作线程处理读写操作 private static void registerAcceptedChannel(NioServerSocketChannel parent, SocketChannel acceptedSocket, Thread currentThread) { .... ChannelSink sink = parent.getPipeline().getSink(); ChannelPipeline pipeline = parent.getConfig().getPipelineFactory().getPipeline(); NioWorker worker = parent.workerPool.nextWorker(); worker.register(new NioAcceptedSocketChannel( parent.getFactory(), pipeline, parent, sink , acceptedSocket, worker, currentThread), null); .... }
总结
以上过程梳理了Netty初始化的主流程,以及NIO服务端的初始化过程(以上红色部分);顺序梳理完一遍源码,反过来总结下Netty的设计和结构。
代码梳理过程中涉及的核心接口及作用
Bootstrap:Netty框架启动的工具类,分client,server和udp3种,其核心功能是初始化主channel和pipeline。
ChannelFactory:创建Channel的工厂。针对不同场景,netty提供了各种ChannelFactory,比如ServerChannelFactory用来创建server端的Channel。
Channel:封装了java.nio.channels包中Channel的相关实现,封装了各种IO操作。
ChannelEvent:Netty中几乎所有的操作,都是以事件(ChannelEvent)驱动。事件处理都是通过Channels类的静态方法调用开始的,将事件、channel传递给 channel持有的Pipeline进行处理。ChannelEvent分为两种,upstream events和downstream events。Upstream events指由底层传到高层的事件,一般指socket层发生某些事件通知到应用程序,比如当socket接受到数据后会发送UpstreamMessageEvent事件,交给ChannelHandler来处理。Downstream events指由高层传到底层的事件,一般指用户主动发起的操作,比如用户调用channel.write(buffer),则会生成DownstreamMessageEvent事件,经过ChannelHandler处理后,交给socket层发起通信。
ChannelHandler:真正的处理类,一般由应用程序自己实现。主要分2个子接口ChannelUpstreamHandler和ChannelDownstreamHandler,分别处理UpstreamEvent和DownstreamEvent。
ChannelPipeline:就是由ChannelHandler(ChannelHanlderContext)组成的双向链表。如果是upstream events,则经过ChannelPipeline中由前往后的所有ChannelUpstreamHandler依次处理,最后事件被丢弃。如果是downstream events,则经过ChannelPipeline中由后往前的所有ChannelDownstreamHandler依次处理,最后事件被转交给ChannelSink。
ChannelSink:所有的downstream events经过ChannelPipeline处理后,最后由ChannelSink的eventSunk方法处理。它是管理IO线程和事件处理的桥梁。
ChannelPipeline的处理流程(源码注释中的流程图)
服务端初始化和接收请求抽象流程
Netty中的Reactor模式
使用Doug Lea大师在Scalable IO in Java中的一张图来说明:
这里的mainReactor对应了Netty中的NioServerBoss(AbstractNioSelector.run()),被分配到bossPool中执行 ,selector.select(SELECT_TIMEOUT)阻塞。
有客户端连接请求被唤醒后执行accept,然后通过worker.register() 交给subReactor的ThreadPool执行(注册OP_READ到NioWorker的Selector中);在netty里为workerPool,其中Worker的线程数由WorkerPoo大小决定,从上面初始化代码中看到默认是可用cpu个数*2。
当客户端有数据写入时,NioWorker持有的Selector被唤醒执行read事件,后会触发messageReceived事件,交给pipeline中的handler执行。
这里事件驱动的关键Selector在不同的操作系统中有不同的实现,系统级的select和pool原理都是通过轮询内核中等待中的FD,轮询到就绪的FD后写用户空间,然后唤醒阻塞的Selector;java1.5版本开始支持epoll,epoll则是在等待的FD上注册回调函数(真正的事件驱动),根本的提升了NIO的性能。
参考资源
Netty官网:http://netty.io/
Mina官网:http://mina.apache.org/
Trustin Lee :LinkedIn: http://www.linkedin.com/in/trustin
Doug Lea: Scalable IO in Java: http://gee.cs.oswego.edu/dl/cpjslides/nio.pdf
Netty高性能: http://www.infoq.com/cn/articles/netty-high-performance
Netty线程模型:http://www.infoq.com/cn/articles/netty-threading-model
Netty-Mina:http://ifeve.com/netty-mina-in-depth-1/
Select,poll,epoll: http://www.cnblogs.com/xuxm2007/archive/2011/08/15/2139809.html
相关推荐
一个netty的入门教程以及源码分析视频,适合刚学习的人
共分两大章:第1 章:深入浅出Netty源码剖析,第2 章:NIO+Netty5各种RPC架构实战演练,以及课程资料,希望对象学习netty的童靴有用。
Netty-4.1.97.Final源码 适用于想深入学习Netty源码的java程序员 可用于对Netty源码进行阅读以及Netty本地化修改的场景
netty开源 源码,用于阅读学习
netty源码包,可以本地搭建netty源码环境,学习nio模式
学习netty源码,为后续rocketmq等学习打下基础
netty-learning学习Java源代码
Netty全套学习资源(包括源码、笔记、学习文档等)
极客时间《Netty源码剖析与实战》学习记录-boy-learning-netty
Netty in action 第二版 源码 希望对学习netty的 小伙伴 有帮助 Netty in action 第二版 源码 希望对学习netty的 小伙伴 有帮助
Netty是什么?使用Netty能够做什么?为什么要从传统的Socket...如何通过对Netty源码的学习获得更深入地知识?掌握了Netty后,如何将其应用到实际架构中?Netty工程师的就业前景和可涉足的行业是怎样的?本书都会一一解答。
构,底层都需要一个...由于 Netty 源码的复杂性和 NIO 编程本身的技术门槛限制,对于大多数初学者而言,通过自己阅读和分析源码来深入掌握 Netty 的设计原理和实现细节是件非常困难的事情。感兴趣的朋友可以过来看看
Netty5.0架构剖析和源码解读,学习NIO,学习netty,值得学习
1、基于netty+websocket+springboot的实时聊天系统项目源码.zip 2、该资源包括项目的全部源码,下载可以直接使用! 3、本项目适合作为计算机、数学、电子信息等专业的课程设计、期末大作业和毕设项目,作为参考资料...
netty深入学习资料(java源代码实例).zip
Java-NIO-Netty框架学习资源目录:【】Netty5.0架构剖析和源码解读【】Netty5用户指南【】Netty_in_Action(第五版-目录修正版)【】Netty_in_Action_v08_MEAP【】Netty_in_Action_v10_MEAP【】Netty_代码分析【】...
本书适合架构师、设计师、软件开发工程师、测试人员和其他对Java NIO框架、Java通信感兴趣的相关人士阅读,相信通过学习本书,能够熟悉和掌握Netty这一优秀的异步通信框架,实现高可用分布式系统的构建。
《Netty 实战》源码,用于Netty的学习和参考!
Netty4&Netty5源码导入Eclipse会报错,原因为缺少Jar包,提提供给爱学习的你!有分给点,没分单密我,白送~