上次分析了服务端bind流程,今天继续看服务端读写流程。
术语:worker---NioWorker对象,BT---boss线程,IOT---worker线程,UT---用户线程
先说一下前提条件:所有与具体连接相关的IO操作都是由IOT负责完成的,并且handler也是在IOT执行的,所以才说耗时的操作要自己起线程,不要交给IOT,IOT不是拿给你独占的。
一. 服务端读
① bind并注册OP_ACCEPT到selector后,BT一直不停轮询selection key,当有连接上来时accept,new一个NioAcceptedSocketChannel对象channel,把它传给NioWorker.RegisterTask对象,投递给IOT,让IOT去注册OP_READ到它对应的worker的selector上(注册时把channel当做attachment同时注册上去):
public void run() {
SocketAddress localAddress = channel.getLocalAddress();
SocketAddress remoteAddress = channel.getRemoteAddress();
if (localAddress == null || remoteAddress == null) {
if (future != null) {
future.setFailure(new ClosedChannelException());
}
close(channel, succeededFuture(channel));
return;
}
try {
if (server) {
channel.channel.configureBlocking(false);
}
channel.channel.register(
selector, channel.getRawInterestOps(), channel);
……
NioWorker内部类RegisterTask.run
然后IOT去轮询上面的selector的selection key。
② 当收到数据时,selector返回selection key,从中可以获取注册时放进去的channel(个人认为attachment可以作为一种保存物理连接和逻辑连接映射关系的手段),然后从channel中读取数据。
③ NioWorker.read方法负责具体读数据,主要逻辑是调channel.read读数据,前面是根据预设的size从recvBufferPool缓存池中获得一个ByteBuffer,以及设置字节顺序等操作。成功读完就清理ByteBuffer,把channel和buffer传给handler,然后fireMessageReceived。通常我们处理request的业务逻辑就放在这里,所以从这儿也可以看出确实是IOT在执行messageReceived,IOT耗不起啊。。。如果读失败则fireExceptionCaught。
二. 服务端写
写是IOT负责的,发起写数据请求的可能是IOT(messageReceived发起的写就是IOT本身)或者其它线程(如UT)
① 某线程在Netty层发起写操作,经过之前讲的down stream层层处理、转发后,最终到NioServerSocketPipelineSink.eventSunkàhandleAcceptedSocket,从MessageEvent中获取channel和消息,将消息放入channel对应的writeBufferQueue中,这一步实现了数据的入队操作。
② 然后继续调worker.writeFromUserCode,其中对当前线程是否为IOT作了判断:
1) 如果不是则投递一个writeTask到worker.taskQueue上,等IOT下次processTaskQueue时poll出来执行,这一步实现了writeTask的入队操作。
2) 如果是则直接调write0写数据。
这样设计的优势:可以避免当前线程是IOT时,投递task带来的线程切换开销,因为当前线程是IOT时,如果直接投递,则只能等下一次IOT获取到CPU进行循环时才能从taskQueue poll出writeTask了,这样的话既有线程切换开销,还会带来延迟,所以判断一次可以优化写的效率。
③ 如果是UT投递的任务,会调writeFromTaskLoop,另外还有个writeFromSelectorLoop,是当select到OP_WRITE时IOT发起的,用户发起的写操作都是调用writeFromUserCode,这三个write方法最终都调用write0。
④ write0内部流程:
1) 对channel.writeLock加锁,锁住写操作,目的是防止其它线程调cleanUpWriteBuffer,从writeBufferQueue中poll,导致write0 poll不到任务(例如其它线程调channel.close就会去操作writeBufferQueue)
2) 检查channel.currentWriteEvent,若未被清空则说明之前的写操作还未完成,则继续从currentWriteBuffer中获取之前的byteBuf;若已清空则说明上次写操作成功完成,此时则从writeBufferQueue中poll出byteBuf,然后将byteBuf包装为sendBuffer。
3) 根据预先配置的writeSpinCount,尝试多次写入数据,类似于自旋,这里作了写优化:当select到OP_WRITE,而在写入时返回0,不一定代表连接被关闭。在该情况下,一般可通过再次注册OP_WRITE等待下次select,但缺点是select是OS发起的系统调用,涉及到用户态和内核态的切换,开销大。所以这里的优化方式是通过自旋多尝试几次,尽量延迟注册。
4) 发送完后清空currentWriteBuffer等,若未写完(可能kernel buffer满了)则设addOpWrite和channel.writeSuspended标记,前者用于再次注册OP_WRITE,等kernel buffer可用时由发起writeFromSelectorLoop,并根据channel是否open确定是addOpWrite or removeOpWrite;后者控制的是用户发起的和taskQueue /selector发起的写操作,同时只能有一个。
5) 最后根据当前线程是否为IOT,确定fireWriteComplete/fireWriteCompleteLater。
注:write时判断若为IOT则直接写的缺点是当前线程若被中断会引起channel关闭,这个还不理解。。。
总的来说读写都不复杂,读比写简单,最近时间稍微多一点,简单分析了一下服务端的线程模型,有些自己的理解,也不晓得对不对,改天贴上来大家讨论。
本人辛苦分析、码字,请尊重他人劳动成果,转载不注明出处的诅咒你当一辈子一线搬砖工,嘿嘿~
欢迎讨论、指正~~
下篇预告:服务端线程模型分析
相关推荐
《Netty源码深入分析》是由美团基础架构部的闪电侠老师所分享的一系列关于Netty源码解析的视频教程。以下将根据标题、描述、标签以及部分内容等信息,对Netty及其源码进行深入剖析。 ### Netty简介 Netty是基于...
Netty是一款高性能的网络应用程序框架,它使用Java编程语言开发,主要用于网络应用程序的快速和易于开发,支持TCP和UDP...通过对Netty源码的深入分析,可以更好地理解其工作机制,对开发高性能的网络应用有极大的帮助。
#### 五、Netty源码分析实战案例 1. **ChannelHandlerContext与ChannelHandlerAdaptor详解**: - 分析`ChannelHandlerContext`的生命周期及其与`ChannelHandler`之间的交互方式。 - 深入理解`...
在深入探讨Netty源码依赖包的相关知识点之前,我们首先需要了解Netty的基本概念及其重要性。Netty是一个高性能、异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器与客户端。它提供了丰富的...
不过,这只是基础,深入研究Netty源码可以帮助我们更好地理解和优化系统性能,例如了解其内部的事件调度机制、缓冲区管理以及线程模型等。 最后,标签中的"源码"提示我们要关注Netty的底层实现,通过阅读和分析源码...
Java Netty 是一个高性能、异步...通过分析这些示例源码,我们可以深入理解 Netty 的工作原理,掌握如何在实际项目中构建高性能的网络应用。同时,这也将有助于我们了解如何处理异常、实现安全性以及优化网络通信性能。
- 分析项目源码,了解ServerBootstrap和Bootstrap的配置过程。 - 理解ChannelHandler和ChannelPipeline的事件处理机制。 - 学习如何编写自定义的Encoder和Decoder。 - 实践客户端和服务器的交互,调试运行聊天...
《Netty源码深入剖析》一书旨在帮助读者深入了解Netty框架的工作原理和技术细节,从基础知识入手,逐步过渡到高级优化技巧,使开发者能够更好地掌握并应用Netty于实际项目中。 ### 一、Netty简介与核心特性 Netty...
Netty是一个高性能、异步事件驱动的网络应用框架...总的来说,这个压缩包提供了一个深入学习Netty源码的机会,通过阅读和实践,开发者能够掌握Netty的设计理念,提升网络编程的能力,并能根据需求定制自己的网络框架。
Netty源码的分析主要围绕服务端和客户端的创建、读写操作等方面进行。服务端的启动涉及到ServerBootstrap类,以及NioServerSocketChannel的注册,新的客户端接入,客户端连接的建立通过Bootstrap类实现。读写操作则...
以下是基于该书第二至第七章的源码分析,涵盖的关键知识点: 1. **NIO基础**: - Netty是基于Java NIO(非阻塞I/O)构建的,理解Java NIO的基本原理,包括通道(Channels)、缓冲区(Buffers)和选择器(Selectors...
通过阅读和分析Netty源码,开发者能够更好地理解其内部工作原理,优化网络应用,以及定制适合自己项目的特性和功能。对于Java开发者来说,深入学习Netty源码不仅可以提升网络编程技能,也有助于解决实际问题,比如...
由于Netty源码较庞大,所以文章建议通过分析几个关键组件来构建对整个框架的理解,从而避免在庞大的源码海洋中迷失方向。通过理解Netty的关键点,包括NIO的使用、事件处理机制、ChannelPipeline的运作以及Handler的...
- **源码分析**: - **初始化过程**:分析如何创建Channel、EventLoop等组件,并配置ChannelPipeline。 - **事件循环机制**:深入了解EventLoop的工作原理,包括如何调度任务、处理I/O事件等。 - **编解码器设计*...
在分析源码时,应关注以下几个关键点: 1. Android客户端如何创建Socket连接并保持连接。 2. 如何在Android中处理网络错误和异常,确保长连接的稳定性。 3. Netty服务器如何处理新连接,以及数据的读写逻辑。 4. ...
源码分析部分可能涵盖以下几个方面: 1. **Channel**:理解Channel接口及其实现类,如何表示网络连接,并进行读写操作。 2. **Bootstrap与ServerBootstrap**:这两者是启动客户端和服务端的配置类,涉及连接建立、...
47_Netty服务器与客户端编码模式回顾及源码分析准备 48_Netty与NIO系统总结及NIO与Netty之间的关联关系分析 49_零拷贝深入剖析及用户空间与内核空间切换方式 50_零拷贝实例深度剖析 51_NIO零拷贝彻底分析与Gather...