在Java项目中main方法启动Netty项目之后,netty马上就退出了。这个问题一直困扰这我。最近终于吧问题理清楚了。下面是一些总结。
Java项目中JVM如果当前所有的线程都是守护线程的时候,会关闭服务器的。那么Netty主线程完成之后调用的是NioEventLoop线程,这个问题有可能会导致服务器关闭。
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
final EchoServerHandler serverHandler = new EchoServerHandler();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 100)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
p.addLast(new IdleStateHandler(10,10,10));
p.addLast(new LoggingHandler(LogLevel.INFO));
p.addLast(serverHandler);
}
});
// Start the server.
ChannelFuture f = b.bind(PORT).sync();
} finally {
// Shut down all event loops to terminate all threads.
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
b.bind(PORT).sync()并没有将主线程给阻塞掉,因此最终还是会调用finally方法块,从而关闭EventLoop线程的导致。
监听端口的方法最终调用的是
@Override
protected void doBind(SocketAddress localAddress) throws Exception {
if (PlatformDependent.javaVersion() >= 7) {
javaChannel().bind(localAddress, config.getBacklog());
} else {
javaChannel().socket().bind(localAddress, config.getBacklog());
}
}
这里的整个方法都是不会阻塞main方法的,如果没有其他方法来阻塞main方法,那么整个main方法都会执行,最终执行到finally方法块中将服务器关闭。 而且因为NioEventLoop线程并不是守护线程,因此也可以排查因为主线程退出之后,剩下的所有线程都是守护线程导致服务器退出的问题了。
根据上面的逻辑我们知道有两种方法来解决这种问题。
在netty的启动的过程中有一个关闭channel的方法可以阻塞当前线程,并关服的时候会调用其operationComplete()方法。
具体代码如下
// Start the server.
ChannelFuture f = b.bind(PORT);
// Wait until the server socket is closed.
ChannelFuture sync = f.channel().closeFuture();
sync.addListener(new ChannelFutureListener() {
public void operationComplete(ChannelFuture future) throws Exception {
System.out.println("链路断开:"+future.channel().toString());
}
}).sync();
这里必须注意要加上.sync()同步方法,不然并不会阻塞main方法。
将finally块中的代码丢到某个地方,我们直接用上面的方案1的即可当然此时我们需要将.sync()方法中去掉即可,因为不需要阻塞main方法。直接让main方法执行完毕退出即可。
// Start the server.
ChannelFuture f = b.bind(PORT);
// Wait until the server socket is closed.
ChannelFuture sync = f.channel().closeFuture();
sync.addListener(new ChannelFutureListener() {
public void operationComplete(ChannelFuture future) throws Exception {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
System.out.println("链路断开:"+future.channel().toString());
}
})
当然我们也可以不丢在这里,我们自己写一个方法来调用优雅关服代码即可。当然,Netty的推荐方法还是用上面的两种方案来处理。
服务器关服无非就是要让channel调用close()方法。这里的close方法并不是ChannelHandler中的close。
ChannelHandler的事件回调方法中,ctx.close() 或 ctx.channel().close()只能关闭与某个客户端连接的channel
当时我们可以通过ChannelHandler事件回调中的链接的父亲来获取整个服务器的channel。
ctx.channel().parent()
因此可以使用方法关闭服务器ctx.channel().parent().close();
还有一种方法来关闭服务器那就是在启动的时候保存一个静态变量来保存服务器的channel如下
public static Channel serverChannel = null;
...
...
...
// Start the server.
ChannelFuture f = b.bind(PORT).sync();
//将ServerChannel保存起来方便后续直接调用关服
serverChannel = f.channel();
在需要关服的地方直接嗲用serverChannel.close()关服即可。