为什么 netty 客户端表现得像僵尸?
Why a netty client performs as zombie?
我使用 netty 作为套接字客户端:
public void run() {
isRunning = true;
EventLoopGroup group = new NioEventLoopGroup(EventLoopsPerGetter);
Bootstrap b = new Bootstrap();
b.group(group).channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
p.addLast(
new ProtobufVarint32FrameDecoder(),
ZlibCodecFactory.newZlibDecoder(ZlibWrapper.GZIP),
new ProtobufDecoder(Protocol.Packet.getDefaultInstance()),
new ProtobufVarint32LengthFieldPrepender(),
ZlibCodecFactory.newZlibEncoder(ZlibWrapper.GZIP),
new ProtobufEncoder(),
session
);
}
});
try {
while(isRunning) {
try {
b.connect(host, port).sync().channel().closeFuture().sync();
} catch(Exception e) {
if (e instanceof InterruptedException) {
throw e;
}
retryLogger.warn("try to connect to " + host + " : " + port + " , but", e);
}
if(isRunning) {
retryLogger.info("netty connection lost, retry!");
Thread.sleep(RetryInterval);
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
group.shutdownGracefully();
}
}
session代码很简单,发送Get-packet到服务器,得到响应,写入文件,然后发送下一个Get-packet。
在这个程序中,我启动了两个 netty 客户端线程,但是 运行 几天后,其中一个表现得像僵尸线程,也就是说,即使我杀死了 netty 服务器,僵尸客户端也会打印没有日志,而另一个客户端打印想要的日志。顺便说一下,jstack 文件显示两个线程都处于活动状态,而不是死机。
我正在使用 netty 5.
您没有任何读取超时机制,发生的情况是 10~(取决于路由器型号)分钟内没有流量,并且路由器中的 NAT table 认为连接正常完成,并关闭连接。
您有多种方法可以解决这个问题:
使用ReadTimeoutHandler
ReadTimeoutHandler
关闭通道并抛出一个 ReadTimeoutException
if a timeout is detected. You can catch this exception if needed via the exceptionCaught
。以你现有的逻辑,你不需要捕捉这个。
此处理程序还可以与 WriteTimeoutHandler
结合使用,以将 "ping" 消息写入远程。但是,以下解决方案更适合此目的。
使用IdleStateHandler
您也可以为此使用 IdleStateHandler
,此处理程序有 3 个参数,分别代表 readerIdleTime
、writeIdleTime
和 allIdleTime
。这个 class 的优点是它不会抛出异常并使用 Netty userEventTriggered
来调度它的调用,虽然这使得 class 更难使用,但你可以用它做更多的事情它。
例如,如果您的协议支持 ping 消息,则可以使用此 class 来发送那些 ping 消息。这个 class 真的很容易使用,可以在处理程序中使用,如下所示:
public class MyChannelInitializer extends ChannelInitializer<Channel> {
@Override
public void initChannel(Channel channel) {
channel.pipeline().addLast("idleStateHandler", new IdleStateHandler(60, 30, 0));
channel.pipeline().addLast("myHandler", new MyHandler());
}
}
// Handler should handle the IdleStateEvent triggered by IdleStateHandler.
public class MyHandler extends ChannelHandlerAdapter {
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof IdleStateEvent) {
IdleStateEvent e = (IdleStateEvent) evt;
if (e.state() == IdleState.READER_IDLE) {
ctx.close();
} else if (e.state() == IdleState.WRITER_IDLE) {
ctx.writeAndFlush(new PingMessage());
}
}
}
}
我使用 netty 作为套接字客户端:
public void run() {
isRunning = true;
EventLoopGroup group = new NioEventLoopGroup(EventLoopsPerGetter);
Bootstrap b = new Bootstrap();
b.group(group).channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
p.addLast(
new ProtobufVarint32FrameDecoder(),
ZlibCodecFactory.newZlibDecoder(ZlibWrapper.GZIP),
new ProtobufDecoder(Protocol.Packet.getDefaultInstance()),
new ProtobufVarint32LengthFieldPrepender(),
ZlibCodecFactory.newZlibEncoder(ZlibWrapper.GZIP),
new ProtobufEncoder(),
session
);
}
});
try {
while(isRunning) {
try {
b.connect(host, port).sync().channel().closeFuture().sync();
} catch(Exception e) {
if (e instanceof InterruptedException) {
throw e;
}
retryLogger.warn("try to connect to " + host + " : " + port + " , but", e);
}
if(isRunning) {
retryLogger.info("netty connection lost, retry!");
Thread.sleep(RetryInterval);
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
group.shutdownGracefully();
}
}
session代码很简单,发送Get-packet到服务器,得到响应,写入文件,然后发送下一个Get-packet。
在这个程序中,我启动了两个 netty 客户端线程,但是 运行 几天后,其中一个表现得像僵尸线程,也就是说,即使我杀死了 netty 服务器,僵尸客户端也会打印没有日志,而另一个客户端打印想要的日志。顺便说一下,jstack 文件显示两个线程都处于活动状态,而不是死机。
我正在使用 netty 5.
您没有任何读取超时机制,发生的情况是 10~(取决于路由器型号)分钟内没有流量,并且路由器中的 NAT table 认为连接正常完成,并关闭连接。
您有多种方法可以解决这个问题:
使用ReadTimeoutHandler
ReadTimeoutHandler
关闭通道并抛出一个 ReadTimeoutException
if a timeout is detected. You can catch this exception if needed via the exceptionCaught
。以你现有的逻辑,你不需要捕捉这个。
此处理程序还可以与 WriteTimeoutHandler
结合使用,以将 "ping" 消息写入远程。但是,以下解决方案更适合此目的。
使用IdleStateHandler
您也可以为此使用 IdleStateHandler
,此处理程序有 3 个参数,分别代表 readerIdleTime
、writeIdleTime
和 allIdleTime
。这个 class 的优点是它不会抛出异常并使用 Netty userEventTriggered
来调度它的调用,虽然这使得 class 更难使用,但你可以用它做更多的事情它。
例如,如果您的协议支持 ping 消息,则可以使用此 class 来发送那些 ping 消息。这个 class 真的很容易使用,可以在处理程序中使用,如下所示:
public class MyChannelInitializer extends ChannelInitializer<Channel> {
@Override
public void initChannel(Channel channel) {
channel.pipeline().addLast("idleStateHandler", new IdleStateHandler(60, 30, 0));
channel.pipeline().addLast("myHandler", new MyHandler());
}
}
// Handler should handle the IdleStateEvent triggered by IdleStateHandler.
public class MyHandler extends ChannelHandlerAdapter {
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof IdleStateEvent) {
IdleStateEvent e = (IdleStateEvent) evt;
if (e.state() == IdleState.READER_IDLE) {
ctx.close();
} else if (e.state() == IdleState.WRITER_IDLE) {
ctx.writeAndFlush(new PingMessage());
}
}
}
}