Netty心跳

作者: 诺之林 | 来源:发表于2019-03-11 17:17 被阅读0次

本文的示例代码参考NettyHeartBeat

目录

Introduce

心跳与空闲监测是为了解决连接假死的问题

  • 连接假死的定义
在某一端(服务端或者客户端)看来 底层TCP连接已经断开 但是应用程序并没有捕获到而认为连接仍然存在
  • 连接假死的危害
对服务端来说 连接假死会耗费CPU和内存资源

对客户端来说 连接假死会造成发送数据超时 影响用户体验
  • 连接假死的原因
应用程序出现线程堵塞 无法进行数据的读写

客户端或者服务端网络相关的设备出现故障 比如网卡、机房故障

公网丢包、网络抖动等

Prepare

cp -R NettySticky NettyHeartBeat

cd NettyHeartBeat

关于NettySticky详细参考教程Netty粘拆包

Server Idle Handler

mkdir -p src/main/java/handler

vim src/main/java/handler/IdleHandler.java
package handler;

import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.handler.timeout.IdleStateHandler;

import java.util.concurrent.TimeUnit;

public class IdleHandler extends IdleStateHandler {

    private static final int READER_IDLE_TIME = 15;

    public IdleHandler() {
        super(READER_IDLE_TIME, 0, 0, TimeUnit.SECONDS);
    }

    @Override
    protected void channelIdle(ChannelHandlerContext ctx, IdleStateEvent evt) throws Exception {
        System.out.println(READER_IDLE_TIME + "秒内未读到数据,关闭连接");
        ctx.channel().close();
    }
}
vim src/main/java/server/Server.java
// 省略了未修改的代码
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap
                .group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel.class)
                .option(ChannelOption.SO_BACKLOG, 1024)
                .childOption(ChannelOption.SO_KEEPALIVE, true)
                .childHandler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel ch) throws Exception {
                        ch.pipeline().addLast(new IdleHandler());
                    }
                });
// 省略了未修改的代码
sed -i "" "s/client.Client/server.Server/g" build.gradle && gradle run
# 端口[8888] 绑定成功!
# 15秒内未读到数据,关闭连接

telnet 127.0.0.1 8888
# Connected to localhost.
# Connection closed by foreign host.

Protocol

vim src/main/java/protocol/command/Command.java
package protocol.command;

public interface Command {
    Byte LOGIN = 1;
    Byte HEARTBEAT_REQUEST = 21;
}
vim src/main/java/protocol/request/HeartBeatRequestPacket.java
package protocol.request;

import protocol.Packet;
import protocol.command.Command;

public class HeartBeatRequestPacket extends Packet {
    @Override
    public Byte getCommand() {
        return Command.HEARTBEAT_REQUEST;
    }
}

Client Heart Beat Timer

vim src/main/java/client/ClientHeartBeatTimerHandler.java
package client;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import protocol.request.HeartBeatRequestPacket;

import java.util.concurrent.TimeUnit;

public class ClientHeartBeatTimerHandler extends ChannelInboundHandlerAdapter {
    private static final int HEARTBEAT_INTERVAL = 5;

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        scheduleSendHeartBeat(ctx);

        super.channelActive(ctx);
    }

    private void scheduleSendHeartBeat(ChannelHandlerContext ctx) {
        ctx.executor().schedule(() -> {
            if (ctx.channel().isActive()) {
                System.out.println("发送心跳包");
                ctx.writeAndFlush(PacketCodec.INSTANCE.encode(new HeartBeatRequestPacket()));
                scheduleSendHeartBeat(ctx);
            }
        }, HEARTBEAT_INTERVAL, TimeUnit.SECONDS);
    }
}
vim src/main/java/client/Client.java
// 省略了未修改的代码
        Bootstrap bootstrap = new Bootstrap();
        bootstrap
                .group(workerGroup)
                .channel(NioSocketChannel.class)
                .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
                .option(ChannelOption.SO_KEEPALIVE, true)
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        ch.pipeline().addLast(new ClientHeartBeatTimerHandler());
                    }
                });
// 省略了未修改的代码
sed -i "" "s/client.Client/server.Server/g" build.gradle && gradle run
# 端口[8888] 绑定成功!

sed -i "" "s/server.Server/client.Client/g" build.gradle && gradle run
# 连接成功!
# 发送心跳包 (间隔5s)

参考

相关文章

网友评论

    本文标题:Netty心跳

    本文链接:https://www.haomeiwen.com/subject/srfbpqtx.html