关于Netty启动的一个疑问

Posted Netty之家

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了关于Netty启动的一个疑问相关的知识,希望对你有一定的参考价值。

问题:尊敬的李老师,您好!打扰您了!我是来自杭州的一名大四学生,之前购买并阅读过您的《Netty权威指南》,深感这是一本好书!然而我在实际项目中遇到一个令我有点困惑的问题迟迟无法找到答案,因此写邮件非常希望您能在百忙之中抽空帮我解答下疑问!


问题1如下:

当我启动netty服务器时使用阻塞方式绑定监听端口:

bootstrap.bind(PORT)

.sync().channel()

.closeFuture().sync();

假如我注释掉最有一行变成:

bootstrap.bind(PORT)

.sync().channel()

这样的话服务器进程启动后马上就返回退出了,不再继续监听。请问一下这其中的原理是什么?


问题2如下:

假如必须要加上 .closeFuture().sync() 这行代码,能不能采用下面监听器的方法进行监听套接字关闭事件:

f.channel().closeFuture().addListener(new ChannelFutureListener() {

       @Override

        public void operationComplete(ChannelFuture future) throws Exception {

                //业务逻辑处理代码,此处省略...

log.info(future.channel().toString() + " 链路关闭");

         }

}); .

然而我自己测试了一下,这样去监听套接字的关闭事件,好像还是会出现服务器套接字直接关闭,进程退出的情况。请问这又是为什么呢?


解答

在回答你的两个问题之前,先普及下Java Daemon线程,这样你才能够把问题理解透彻:

所谓守护线程(Daemon)就是运行在程序后台的线程,程序的主线程Main不是守护线程。

Daemon Thread在Java里面的定义是,如果虚拟机中只有Daemon thread运行,则虚拟机退出。

  1. 虚拟机中可能会同时有多个线程在运行,只有当所有的非守护线程都结束的时候,虚拟机的进程才会结束,不管在运行的线程是不是main()线程。

  2.  Main主线程结束了,如果此时运行的其它Thread全部是Daemon thread,JVM会使这些Threads停止,同时JVM退出。如果此时正在运行的其它线程有非守护线程,那么必须等所有的非守护线程结束之后,JVM才会退出。

举例如下:守护线程

public static void main(String[] args) throws IllegalArgumentException, InterruptedException {

System.out.println("Start time is : " + new Date());

Thread t = new Thread(new Runnable() {

public void run() {

try {

TimeUnit.DAYS.sleep(Long.MAX_VALUE);

} catch (InterruptedException e) {

e.printStackTrace();}}

}, "Daemon-T");

   //        t.setDaemon(false);

t.setDaemon(true);

t.start();

TimeUnit.SECONDS.sleep(15);

System.out.println("End time is : " + new Date());}}

执行结果:因为是Daemon线程,当主线程执行15S退出之后,进程退出。

举例如下:非守护线程

public static void main(String[] args) throws IllegalArgumentException, InterruptedException {

System.out.println("Start time is : " + new Date());

Thread t = new Thread(new Runnable() {

public void run() {

try {

TimeUnit.DAYS.sleep(Long.MAX_VALUE);

} catch (InterruptedException e) {

e.printStackTrace();}}

}, "Daemon-T");

         t.setDaemon(false);

// t.setDaemon(true);

t.start();

TimeUnit.SECONDS.sleep(15);

System.out.println("End time is : " + new Date());}}

执行结果:因为存在非Daemon线程执行未结束,及时主线程执行15S结束之后,JVM进程仍然不会退出:




接着答复你的问题1:

bootstrap.bind(PORT)

.sync().channel()

在Netty中,绑定端口的操作并不是在主线程中进行的,真正执行它的是NioEventLoop线程,调试堆栈如下所示:

关于Netty启动的一个疑问

它最终的执行结果其实就是调用了Java NIO Socket的绑定操作,如下所示:

 javaChannel().socket().bind(localAddress, config.getBacklog());


绑定操作执行完成之后,main函数主线程就不会阻塞,如果后续没有代码,main线程就会退出,main线程退出是否意味着JVM进程退出? 非也。

之前已经讲过,待所有非守护线程全部执行完成之后,进程才会退出。那此时是否还存在其它非守护线程运行吗,我们首先看下线程状态:


我们发现,Main主线程已经运行结束,但是Netty的NioEventLoop还处于运行状态,因此JVM进程并没有退出。



通过对NioEventLoop源码分析(此处略,大家可以参考《Netty权威指南》的源码分析章节),我们发现:

1、NioEventLoop是非守护线程

2、NioEventLoop运行之后,不会主动退出

3、只有调用shutdownGracefully方法,NioEventLoop才会主动退出。

按照这个分析,及时main()函数执行结束,进程也不会退出啊,为啥注释掉.closeFuture().sync();方法之后,进程退出了呢?

这个问题很简单,因为Netty官方的Demo和《Netty权威指南》中,标准的代码都是这样写的:

            ChannelFuture f = b.bind(port).sync();

            // Wait until the server socket is closed.

            f.channel().closeFuture().sync();

          } finally {

            // Shut down all event loops to terminate all threads.

            bossGroup.shutdownGracefully();

            workerGroup.shutdownGracefully();

        }

如果注释掉f.channel().closeFuture().sync();则端口绑定成功之后,Main()函数退出之前会执行bossGroup.shutdownGracefully();它会使所有的NioEventLoop优雅退出,导致所有非守护进程执行结束,最终JVM进程退出。

假如我们把代码修改成这样:

            ChannelFuture f = b.bind(port).sync();

            // Wait until the server socket is closed.

           // f.channel().closeFuture().sync();

          } finally {

            // Shut down all event loops to terminate all threads.

            // bossGroup.shutdownGracefully();

           // workerGroup.shutdownGracefully();

        }

你会发现JVM进程不再退出:



接着回答第二个问题,你把代码做了修改,不调用.closeFuture().sync(),而是改用监听器尝试监听链路关闭事件。

监听器实际是一种异步回调方式,如果你不想通过同步阻塞的方式等待结果,而是希望程序继续往下走,当执行结束之后通过系统回调的方式通知你,由你来实现自己的逻辑。

通过上面的分析可以得出这样的结论:

1、改用监听器监听链路关闭事件,不会阻塞Main()线程

2、端口绑定成功之后,Main线程继续向下执行

3、由于你在finnal中增加了线程池关闭代码,所以NioEventLoop线程主动退出

4、系统没有正在运行的非守护线程,JVM进程退出。


总结

这个案例非常典型,有很多读者都咨询过我类似的问题。从问题本身看,该读者对如下几个领域或者特性还不太熟悉:

1、Java Deamon线程工作机制

2、Netty的NioEventLoop线程工作原理

3、Netty NIO监听器的使用方式以及原理

希望广大读者能够理论结合实践,从实践中再方向理解理论,特别是对《Netty权威指南》中的源码分析章节、服务端和客户端工作原理章节多读几遍,认真学习下。书读百遍、其义自见。

当真正理解Netty核心工作原理之后,才能在实际工作中得心应手的使用它。

最后再给大家遗留一个问题,请大家思考下,如果我把代码修改成这样,进程又会退出,为啥?

    ChannelFuture f = b.bind(port).sync();

//         f.channel().closeFuture().sync();

        } finally {

            // Shut down all event loops to terminate all threads.

            bossGroup.shutdownGracefully();

//          workerGroup.shutdownGracefully();}




以上是关于关于Netty启动的一个疑问的主要内容,如果未能解决你的问题,请参考以下文章

关于这个 Spring Batch @Scheduled() 注解以及如何手动启动 Spring Batch 作业的一些疑问?

SpringBoot基于WebSocket进行推送

netty源码之启动流程

netty源码之启动流程

springboot启动成功后启动netty服务端

Netty:server启动流程解析