(2人评价)
尚硅谷Netty视频教程

Netty视频教程

价格 ¥ 536.00 ¥2680.00 2折
活动
会员免费学 购买课程

4.9、最终会调用doBeginRead方法,也就是AbstractNioChannel类的方法

10、这个地方调试时,请把前面的断点都去掉,然后启动服务器就会停止在doBeginRead

11、执行到这里时,针对这个客户端的连接就完成了,接下来就可以监听读事件了

 

[展开全文]

Netty接收请求过程源码分析

源码剖析

说明:

1)、从之前服务端启动的源码中,我们得知,服务器最终注册了一个Accept事件等待客户端的连接。我们也知道,NioServerSocketChannel将自己注册到了boss线程池(reactor线程)上,也就是EventLoop。

2)、先简单说下EventLoop的逻辑

EventLoop的左右是一个死循环,而这个循环中做3件事情

有条件的等待NIO事件

处理NIO事件

处理消息队列中的任务

3)、仍用前面的项目来分析:进入到NioEventLoop源码中后,在private void processSelectorKey(SelectionKey k,AbstractNioChannel ch)方法开始调试

 

1、断点位置NioEventLoop的如下方法processSelectedKey

2、从断点我们可以看到,readOps是16,也就是Accept事件。

3、这个unsafe是boss线程中NIOServerSOcketChannel的io.netty.channel.nio.AbstractNioMessageChannel.NioMessageUnsafe对象。我们进入到io.netty.channel.nio.AbstractNioMessageChannel.NioMessageUnsafe#read方法中

4、read方法分析

4.1、检查该eventLoop线程是否是当前线程,

assert eventLoop().inEventLoop();

4.2、执行doReadMessages方法,并闯入一个readBuf变量,这个变量是一个List,也就是容器。

4.3、循环容器,执行pipeline.frieChannelRead(readBuf.get(i));

4.4、doReadMessages是读取boss线程中NIOServerSOcketChannel接收到的请求。并把这些请求放到容器。

4.5、循环遍历容器中的所有偶请求,调用pipeline的fireChannelRead方法,用于处理这些接收的请求或者其他事件,在read方法中,循环调用ServerSocket的pipeline的fireChannelRead方法,开始执行管道中的handler的ChannelRead方法

 

 

4.6、追踪doReadMessages方法

说明:

4.6.1、通过工具类,调用NIOServerSOcketChannel内部封装的ServerSocketChannel的accept方法,这是Nio作法。

4.6.2、获取到一个JDK的SOcketChannel进行封装。最后添加到容器中

4.6.3、这样容器buf中就有了NioSocketChannel

 

4.7、回到read方法,继续分析pipeline.fireChannelRead方法

4.7.1、前面分析doReadMessages方法的作用是通过ServerSocket的accept方法获取到tcp连接,然后封装成Netty的NioSocketChannel对象。最后添加到容器中

4.7.2、在read方法中,循环调用ServerSocket的pipeline的fireChannelRead方法,开始执行管道中的handler的ChannelRead方法

4.7.3、经过debug,可以看到会反复执行多个handler的ChannelRead,我们知道,pipeline里面有4个handler,分别是Head,LoggingHandler,ServerBootstrapAcceptor,Tail。

4.7.4、我们重点看看ServerBootstrapAcceptor的channelRead方法

4.7.5、channelRead方法

说明:

4.7.5.1、msg强转成Channel,实际上就是NioSocketChannel。

4.7.5.2、添加NioSocketChannel的pipeline的handler,就是我们main方法里面设置的childHandler方法里的。

4.7.5.3、设置NioSocketChannel的各种属性。

4.7.5.4、将该NioSocketChannel注册到childGroup中的一个EventLoop上,并添加一个监听器。

4.7.5.5、这个childGroup就是我们main方法创建的数组 workerGroup

 

4.8、进入

 

[展开全文]
EventLoopGroup分析
MultithreadEventExecutorGroup
* nThreads:使用的线程数,默认为cpu*2
* executor:执行器:如果传入为null,则采用Netty默认的线程工厂和默认的执行器ThreadPerTaskExecutor
* chooserFactory :单例new DefultEventExecutorChaooserFactory()
* args :在创建执行器的时候传入固定参数

 

 

说明:

1)、如果executor传入为null,则采用Netty默认的线程工厂和默认的执行器ThreadPerTaskExecutor,使用Netty默认的线程工厂

2)、根据传入的线程数(CPU*2)创建一个线程池(单例线程池)数组。

3)、玄幻填充数组中的元素,如果异常,则关闭所有的单例线程。

4)、根据每个线程选择工厂创建一个线程选择器

5)、为每一个单例线程池添加一个关闭监听器

6)、将所有的单例线程池添加到一个HashSet中

 

 

 

 

说明:

1)、链式调用:group方法,将boss和worker传入,boss赋值给parentGroup属性,worker赋值给childGroup属性

2)、channel方法传入NIOServerSOcketChannel class独享。会根据这个class创建channel对象。

3)、option方法传入TCP参数,放在一个LinkedHashMap中。

4)、handler方法传入一个handler中,这个handler只专属ServerSocketChannel而不是SocketChannel

5)、childHandler传入一个handler,这个handler将会在每个客户端连接的时候调用。共SocketChannel使用

 

 

4、绑定端口的分析

4.1、服务器就是在这个bind方法里启动完成的

4.2、bind方法代码,追踪到 创建了一个端口对象,并做了一些空判断,核心代码doBind

[展开全文]

说明:在handler中的代码就使用普通的方法来处理耗时业务。

2)、当我们在调用addLast方法添加线程池后,handler将优先使用这个线程池,如果不添加,将使用IO线程

3)、当走到AbstractChannelHandlerContext的invokeChannelRead方法的时候,executor.inEventLoop()是不会通过的,因为当前线程是IO线程Context(也就是Handler)的executor是业务线程,所以会异步执行

[展开全文]

AbstractChannelHandlerContext  800

1)、在channelRead方法。模拟了一个好事10秒的操作,这里,我们将这个任务提交到了一个自定义的业务线程池中,这样,就不会阻塞Netty的IO

[展开全文]

handler中加入线程池和Context中添加线程池的源码剖析

源码剖析的目的

1)、在Netty中做消耗的,不可预料的操作,比如数据库,网络请求,会严重影响Netty对Socket的处理速度

2)、而解决方法就是将耗时任务添加到异步线程池中。但就是添加线程池这步操作来讲,可以有两种方式,而且这两种方式实现的区别也蛮大的。

3)、处理耗时业务的第一种方式--handler中加入线程池

4)、处理耗时业务的第二种方式--context中添加线程池

5)、

[展开全文]

5、EventLoop作为Netty的核心的运行机制 小结

1)、每次执行execute方法都是向队列中添加任务。当第一次添加时启动线程,执行run方法,而run方法是整个EventLoop的核心,就像EventLoop的名字一样,LoopLoop,不停的Loop,Loop做什么呢》做三件事情

调用selector的select方法,默认阻塞一秒钟,如果有定时任务,则在定时任务剩余时间的基础上在加上0.5秒进行阻塞。当执行execute方法的时候,也就是添加任务的时候,唤醒selector,防止selector阻塞时间过长。

当selector返回的时候,回调用processSelectedKeys方法对selectKey进行处理。

当processSelectKeys方法执行结束后,则按照ioRatio的比例执行runAllTasks方法,默认是IO任务时间和非IO任务时间是相同的,你也可以根据你的应用特点进行调优。比如非IO任务比较多,那么你就将ioRatio调小点,这样非IO任务就能执行的长一点。防止队列积攒过多的任务。

[展开全文]

Netty核心组件EventLoop

源码剖析目的:

说明重点:

1)、ScheduledExcutorService接口表示是一个定时任务接口,EventLoop可以接受定时任务

2)、EventLoop接口:Netty接口文档说明该接口作用:一旦Channel注册了。就处理该Channel对应的所有IO操作。

3)、SingleThreadEventExecutor表示这是一个单个线程的线程池

4)、EventLoop是一个单例的线程池,里面含有一个死循环的线程不断的做着3件事情:监听端口,处理端口事件,处理队列事件。每个EventLoop都可以绑定多个Channel,而每个Channel始终只能由一个EventLoop来处理

 

SingleThreadEventExecutor的

public void execute(Runnable task) {
    if (task == null) {
        throw new NullPointerException("task");
    } else {
        boolean inEventLoop = this.inEventLoop();
        if (inEventLoop) {
            this.addTask(task);
        } else {
            this.startThread();
            this.addTask(task);
            if (this.isShutdown() && this.removeTask(task)) {
                reject();
            }
        }

        if (!this.addTaskWakesUp) {
            this.wakeup(inEventLoop);
        }

    }
}

说明:

1)、首先判断该EventLoop的线程是否是当前线程,如果是,直接添加到任务队列中去,如果不是,则尝试启动线程(但由于线程是单个的,因此只能启动一次),随后再将任务添加到队列中去。

2)、如果线程已经停止,并且删除任务失败,则执行拒绝策略,默认是抛出异常

3)、如果addTaskWakesUp是false,并且任务不是NonWakeUpRunnable类型的,就尝试唤醒selector。这个时候,阻塞在selector的线程就会立即返回

 

 

说明:

1)、首先调用excutor的execute方法,这个executor就是在创建EventLoopGroup的时候创建的ThreadPerTaskExecutor类。该execute方法会将Runnable包装成Netty的FastThtreadLocalThread.

2)、任务中,首先判断线程中断状态,然后设置最后一次的执行时间。

3)、执行当前NioEventLoop的run方法,注意:这个方法是个死循环,是整个EventLoop的核心

4)、在finally块中,使用CAS不断修改state状态,改成ST_SHUTTING_DOWN。也就是当前线程Loop结束的时候。关闭线程,最后还要死循环确认是否关系,否则不会break。然后,执行cleanup操作,更新状态为ST_TERMINATED

5)、ST_TERMINATED,并释放当前线程锁。如果任务队列不是空,则打印队列中海油多少个未完成的任务,并回调terminationFuture方法

6)、其实最核心的就是EventLoop自身的run方法。在继续深入run方法

[展开全文]

1.新建一个 serverSocketChnnel 

2. 开启 一个 seletor

 

3. bind 端口

 

4. 配置 为 非阻塞

 

5. serverSo

[展开全文]

1)、得到用户设置的超时时间

2)、如果读取操作结束了(执行了channelReadComplete方法设置),就用当前时间减去给定时间和最后一次读取操作的时间(执行了channelReadComplete方法设置),如果小于0,就触发事件。反之,继续放入队列。间隔时间是新的计算时间

3)、触发的逻辑是:首先将任务再次放到队列,时间是刚开始设置的时间,返回一个promise对象,用于做取消操作,然后,设置first属性为false,表示,下次读取不在是第一次了,这个属性在channelRead方法会被改成true。

4)、创建一个IdelStateEvent类型的写事件对象,将此对象传播给用户的UserEventTriggered方法,完成触发事件的操作

5)、总的来说,每次读取操作都会记录一个时间,定时任务时间到了,会计算当前时间和最后一次读的时间的间隔,如果间隔超过了设置的时间,就会触发UserEventTriggered方法。

 

 

 

小结Netty的心跳机制

1)、IdleStatehandler可以实现心跳功能,当服务器和客户端没有任何读写交互时,并超过了给定时间,则会触发用户handler的userEventTriggered方法,用户可以在这个方法中尝试向对方发送消息,如果发送失败,则关闭连接。

2)、IdleStateHandler的实现基于EventLoop的定时任务,每次读写都会记录一个值,在定时任务运行的时候,通过计算当前时间和设置时间和上次时间发生的时间的结果,来判断是否空闲

3)、内部由3个定时任务,分别对应读事件,写事件,读写事件,通常使用监听读写事件就足够了。

4)、同事,IdleStateHandler内部也考虑了一些极端情况,客户端接收缓慢,一次接收数据的速度超过了设置的空闲时间,Netty通过构造器方法中的observerOutput属性来决定是否对出站缓冲区的情况进行判断

5)、如果出站缓慢,Netty不认为这是空闲,也就是不触发空闲时间,但第一次无论如何也是要触发的,因为第一次无法判断是出站缓慢还是空闲,当然,出站缓慢的话,可能造成OOM,OOM比空闲的问题更大。

6)、所以,当你的应用出现了内存溢出,OOM之类,并且写空闲极少发生(使用observerOutput为true),那么就需要注意是不是数据出站速度过慢

7)、还有一个注意的地方,就是ReadTimeOutHandler,它继承自IdleStateHandler,当触发读写空闲事件的时候,就触发ctx.fireExceptionCaugth方法,兵传入一个ReadTimeOutException,然后关闭Socket。

8)、而WriteTimeOutHandler的实现不是基于IdleStateHandler的,他的原来是,当调用write方法的时候,会创建一个定时任务,任务内容是根据传入的promise的完后才能情况来判断是否超出了写的时间,当定时任务根据指定时间开始运行,发现promise的isDone方法返回false,表明还没有写完,说明超市了,则抛出异常,当write方法完成后,会打断定时任务。

[展开全文]

ChannelPipeline调度handler的源码分析

ChannelPipeline调度handler梳理:

1)、Context包装handler,多个Context在pipeline中形成了双向链表,入站方向叫inbound,由head节点开始,出站方法叫outbound,由taul节点开始

2)、而节点中间的传递通过AbstractChannelHandlerContext类的内部的fire系列方法,找到当前节点的下一个节点不断的循环传播,是一个过滤形式完成对handler的调度

[展开全文]

对上图的解释说明

这是一个handler的list,handler用于处理或拦截入站事件和出站事件,pipeline实现了过滤器的高级形式,以便用户控制事件如何处理handler的pipeline中如何交互。

上图描述了一个典型的handler在pipeline中处理IO事件由InboundHandler或者OutboundHandler处理,并通过调用ChannelHandlerContext.fireChannelRead方法转发给其他最近的处理程序

入站事件由入站处理程序以自下而上的方向处理,如图所示,入站处理程序处理由图底部的IO线程生成入站数据,入站数据通常从如SocketChannel.read(ByteBuffer)获取。

通常一个pipeline有多个handler,例如,一个典型的服务器在每个通道中都会有以下处理程序协议编码器,将java对象转换为二进制数据。业务处理程序,执行实际逻辑(例如数据库访问)

你的业务程序不能将线程阻塞,会影响IO速度,进而影响整个Netty程序的性能,如果你的业务逻辑程序很快,就可以放在IO线程中,反之,你需要一部执行,或者在添加handler的时候添加一个线程

[展开全文]

Netty接收请求过来源码剖析

Netty接受请求过程梳理

总体流程:接受连接----》创建一个新的NioSocketChannel-----》注册到一个worker EventLoop上-----》注册selector read事件

1)、服务器轮询accept事件,获取事件后调用unsafe的read方法,这个unsafe是ServerSocket的内部类,该方法内部由两部分组成

2)、doReadMessage用于创建NioSocketChannel对象,该对象包装JDK的Nio Channel客户端,该方法会像创建ServerSocketChannel类似创建相关的pipeline、unsafe、config

3)、随后执行pipeline.fireChannelRead方法,并将自己绑定到一个chooser选择器的workerGroup中的一个EventLoop,并且注册一个0,表示注册成功,但并没有注册读(1)事件

 

 

Netty启动过程梳理

1)、创建2个EventLoopGroup线程池数组。数组默认大小CPU*2,方便chooser选择线程池时提高性能

2)、Bootstrap将boss设置为group属性,将worker设置为childer属性

3)、通过bind方法启动,内部重要方法为inAndRegister和dobind方法

4)、initAndRegister方法会反射创建NIOServerSocketChannel及其相关的NIO的对象。pipeline,unsafe,同时也为pipeline初始了head节点和tail节点。

5)、在register0方法成功以后调用在doBind方法中调用doBind0方法,该方法会调用NioServerSocketChannel的doBind方法对JDK的channel和端口进行绑定,完成Netty服务器的所有启动,并开始监听连接事件

[展开全文]

说明:

1)、addList方法,在DefaultChannelPipeline类中

2)、addList方法就是pipeline方法的核心

3)、检查handler是否符合标准

4)、创建一个AbstractChannelContext对象,这里说明一下,ChannelHandlerContext对象是ChannelHandler和ChannelPipeline之间的关联,每当有ChannelHandler添加到Pipeline中时,都会创建context,context的主要功能是管理它所关联的Handler和同一个Pipeline中的其他Handler之间的交互

5)、将context添加到链表中,也就是追加到tail节点的前面

6)、最后,同步或者异步或延迟异步的调用callHandlerAdded()方法

 

说明:channelfactory.newChannel()方法的作用,通过ServerBootstrap的通道工厂反射创建一个NIOServerSocketChannel,具体追踪源码可以得到结论:

(1)、通过NIO的SelectorProvider的openServerSocketChannel方法得到JDK的channel。目的是让Netty包装JDK的channel。

(2)、创建一个唯一的ChannelId,创建了一个NIOMessageUnsafe,用于操作消息,创建了一个DefaultChannelPipeline管道,是个双向链表结构,用于过滤所有的进出的消息。

(3)、创建了一个NioServerSocketChannelConfig对象,用于对外展示一些配置。

 

说明:init初始化这个NIOServerSOcketChannel,具体追踪源码可以得出如下结论

1)、init方法,这是个抽象方法(AbstractBootstrap类的),由ServerBootstrap实现(可以追踪一下源码//setChannelOptions(channel,option,logger))。

2)、设置NIOServerSOcketChannel的TCP属性。

3)、由于LinkedHashMap是非线程安全的,使用同步进行处理。

4)、对NIOServerSOcketChannel的ChannelPipeline添加ChannelInitializer处理器。

5)、可以看出,init的方法的核心作用在和ChannelPipeline相关。

6)、从NIOServerSOcketChannel的初始化过程中,我们知道,pipeline是一个双向链表,并且,他本身就初始化了head和tail,这里调用了他的addLast放啊,也就是将整个handler插入到tail的前面,因为tail永远会在后面,需要做一些系统的固定的工作。

 

说明:

1)、基本说明:initAndRegister()初始化NIOServerSOcketChannel通道并注册各个handler,返回一个future

2)、通过ServerBootstrap的通道工厂反射创建一个NIOServerSOcketChannel。

3)、init初始化这个NIOServerSOcketChannel

4)、config().group().register(channel)通过ServerBootstrap的bossGroup注册NIOServerSOcketChannel。

5)、最后,返回这个异步执行的占位符即regFuture。

 

说明:

1)、addList方法,在DefaultChannelPipeline类中

2)、addList方法这就是pipeline方法的核心

3)、检查handler是否符合标准

4)、创建一个AbstractChannelHandlerContext对象,这是说一下,ChannelHandlerContext对象是ChannelHandler和ChannelPipeline之间的关联,每当有ChannelHandler添加Pipeline中时,都会创建Context。Context的主要功能是管理他所关联的Handler和同一个Pipeline中的其他Handler之间的交互。

5)、将Context添加到链表中。也就是追加到tail节点的前面。

6)、最后,同步或者异步或者晚点异步的调用callHandlerAdded()方法

 

说明:

1)、该方法的参数为initAndRegister的future,NIOServerSOcketChannel,端口地址,NIOServerSOcketChannel的promise

2)、这里就可以根据当前下的断点,一直debug

 

可以看到,这里最终的方法就是doBind方法,执行成功后,执行通道的fireChannelActive方法,告诉所有的handler,已经绑定成功

 

继续追踪,服务器就会进入NioEventLoop类的一个循环代码,进行监听

4.7、回到bind方法,最后一步,safeSetSUccess(promise),告诉promise任务成功了。其可以执行监听器的方法了。到此整个启动过程已经结束了,ok了

 

并且要Debug程序到NioEventLoop类的run代码,无限循环,在服务器端运行

 

 

[展开全文]

Netty启动过程源码分析

源码剖析:

说明:

1)、源码需要剖析到Netty 调用doBind()方法,追踪到NioServerSocketChannel的doBind

2)、并且要Debug程序到NioEventLoop类的run代码,无限循环,在服务端运行。

 

说明:

1)、先看启动类,main方法中,首先创建了关于SSL的配置类。

2)、重点分析下 创建了两个EventLoopGroup对象

EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();

(1)、这两个对象是整个Netty的核心对象,可以说,整个Netty的运作都依赖于他们。bossGroup用于接受Tcp请求,他会将请求交给workerGroup,workerGroup会获取到正真的连接,然后和连接进行通信,比如读写解码编码等操作

(2)、EventLoopGroup是时间循环组(线程组)含有多个EventLoop,可以注册channel,用于在事件循环中去进行选择(和选择器相关)

(3)、new NioEventLoopGroup(1);这个1表示bossGroup事件组有1个线程你可以指定,如果new NioEventLoopGroup()会含有默认线程cpu核数*2,即可以充分的利用多核的优势

会创建EventExecutor数组,children=new EventExcutor[nThreads];

每个元素的类型就是NioEventLoop,NIOEventLoop实现了EventLoop接口,和Executor接口 try快中创建了一个ServerBootstrap对象,他是一个引导类,用于启动服务器和引导整个程序的初始化(看源码allows

 easy bootstrapt of{@link ServerChannel})。它和ServerChannel关联。而ServerChannel继承了Channel,有一些方法remoteAddress等

随后,变量b调用了group方法将两个group放入了自己的字段中,用于后期引导使用[debug 下代码group]

 

(4)、然后添加了一个channel,其中参数一个class对象,引导类将通过这个class对象反射创建ChannelFactory。然后添加一些TCP的参数。[说明:Channel的创建在bind方法,可以debug下bind,会找到channel=channelFactory.newChannel()]

(5)、再添加了一个服务器专属的日志处理器handler

(6)、再添加一个SocketChannel(不是ServerSocketChannel)的handler。

(7)、然后绑定端口兵阻塞至连接成功

(8)、最后main线程阻塞等待关闭

(9)、finally块中的代码将在服务器关闭时优雅关闭所有资源

[展开全文]

如果浏览器出现中文乱码问题,需在

TestHttpServerHandler.java文件做如下修改

[展开全文]

TCP粘包和拆包解决方案

1)、使用自定义协议+编解码器 来解决

2)、关键就是要解决 服务器每次读取长度的问题,这个问题解决,就不会出现服务器多度和少读数据的问题,从而避免的TCP粘包、拆包。

 

看一个具体的实例

1)、要求客户端发送5个Message对象,客户端每次发送一个Message对象

2)、服务器端每次接收一个Message,分5此进行编码,没读取一个Message,会回复一个Message对象给客户端

[展开全文]
业界大牛亲自授课
前沿技术实时更新
足不出户学编程
关注尚硅谷微信 一键下载全部视频教程

关注尚硅谷微信

一键下载全部视频教程