netty5心跳与业务消息分发实例

馋奶兔 提交于 2019-12-02 13:10:45

  继续基于我们之前的demo(参见netty5自定义私有协议实例 ),这次我们加上连接校验和心跳机制:

  

 

 

   只要校验通过,客户端发送心跳和业务消息是两个不同的事件发送的,彼此互不干扰。针对以上流程,我们需要增加4个handler:客户端请求handler、心跳handler ,服务端校验handler、心跳处理handler。当然,引导类也得添加上面对应的handler。上代码:

  新增客户端首次连接handler:

package com.wlf.netty.nettyclient.handler;

import com.wlf.netty.nettyapi.javabean.Header;
import com.wlf.netty.nettyapi.javabean.NettyMessage;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class ControlClientHandler extends ChannelHandlerAdapter {

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        ctx.writeAndFlush(buildControlReq());
    }

    /**
     * 在处理过程中引发异常时被调用
     *
     * @param ctx
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        log.error("[Client] conrol request error: {}", cause.getMessage());
        ctx.close();
    }

    /**
     * 构造请求消息体
     *
     * @return
     */
    private NettyMessage buildControlReq() {
        NettyMessage nettyMessage = new NettyMessage();
        Header header = new Header();
        byte[] data = buildControlData();
        header.setDelimiter(0xABEF0101);
        header.setLength(data.length);
        header.setType((byte) 0);
        header.setReserved((byte) 0);
        nettyMessage.setHeader(header);

        // 设置数据包
        nettyMessage.setData(data);
        return nettyMessage;
    }

    /**
     * 构造控制请求消息体
     *
     * @return
     */
    private byte[] buildControlData() {
        byte[] result = new byte[2];

        result[0] = (byte) 1;

        result[1] = (byte) 16;
        return result;
    }
}

  服务端校验handler:

package com.wlf.netty.nettyserver.handler;

import com.wlf.netty.nettyapi.javabean.Header;
import com.wlf.netty.nettyapi.javabean.NettyMessage;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import lombok.extern.slf4j.Slf4j;

import java.net.InetSocketAddress;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
public class ControlServerHandler extends ChannelHandlerAdapter {

    // 白名单列表
    private String[] whiteList = new String[]{"127.0.0.1"};

    private Map<String, Boolean> nodeCheck = new ConcurrentHashMap<>();

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        NettyMessage nettyMessage = (NettyMessage) msg;

        // 如果是控制数据格式请求消息,说明是客户端首次请求,校验白名单,否则进入下一个处理流程
        if (nettyMessage.getHeader() != null &&
                nettyMessage.getHeader().getType() == (byte) 0) {
            String nodeIndex = ctx.channel().remoteAddress().toString();
            NettyMessage controlResponse = null;

            if (nodeCheck.containsKey(nodeIndex)) {
                log.warn("request ip : {} has login.", nodeIndex);
                controlResponse = buildResponse(false);
            } else {
                InetSocketAddress address = (InetSocketAddress) ctx.channel().remoteAddress();
                String ip = address.getAddress().getHostAddress();
                boolean isOK = false;

                for (String whiteIp : whiteList) {
                    if (ip.equals(whiteIp)) {
                        isOK = true;
                        break;
                    }
                }

                if (isOK) {
                    nodeCheck.put(nodeIndex, true);
                    // 白名单校验通过,校验是否支持PCM格式
                    byte[] data = nettyMessage.getData();
                    ByteBuf buf = Unpooled.buffer(2);
                    buf.writeBytes(data);
                    byte sample = buf.readByte();

                    if (sample != (byte) 0) {
                        log.error("sample : {} is not 0", sample);
                        controlResponse = buildResponse(false);
                    } else {
                        controlResponse = buildResponse(true);
                    }
                } else {
                    log.error("ip : {} is not in whiteList : {}.", ip, whiteList);
                    controlResponse = buildResponse(false);
                }

            }
            log.info("[server] The control response is : {}, data : {}", controlResponse, controlResponse.getData());
            ctx.writeAndFlush(controlResponse);
        } else {
            ctx.fireChannelRead(msg);
        }
    }

    /**
     * 在处理过程中引发异常时被调用
     *
     * @param ctx
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        log.error("[server] control response error: {}", cause.getMessage());
        // 删除缓存
        nodeCheck.remove(ctx.channel().remoteAddress().toString());
        ctx.close();
    }

    /**
     * 构造响应消息体
     *
     * @param isOk
     * @return
     */
    private NettyMessage buildResponse(boolean isOk) {
        NettyMessage nettyMessage = new NettyMessage();
        Header header = new Header();
        byte[] data = buildData(isOk);
        header.setDelimiter(0xABEF0101);
        header.setLength(data.length);
        header.setType((byte) 0);
        header.setReserved((byte) 0);
        nettyMessage.setHeader(header);
        nettyMessage.setData(data);
        return nettyMessage;
    }

    /**
     * 构建控制数据格式响应消息体
     *
     * @param isOk
     * @return
     */
    private byte[] buildData(boolean isOk) {
        ByteBuf result = null;
        if (isOk) {
            result = Unpooled.buffer(8);
            // 生成sid
            result.writeInt(buildSid());

            // 心跳发送间隔,5000毫秒秒
            result.writeInt(5000);
        } else {
            result = Unpooled.buffer(1);
            result.writeByte((byte) -1);
        }

        return result.array();
    }

    private int buildSid() {
        int max = 100, min = 1;
        long randomNum = System.currentTimeMillis();
        return (int) (randomNum % (max - min) + min);
    }
}

  心跳客户端handler:

package com.wlf.netty.nettyclient.handler;

import com.wlf.netty.nettyapi.constant.Delimiter;
import com.wlf.netty.nettyapi.constant.MessageType;
import com.wlf.netty.nettyapi.javabean.Header;
import com.wlf.netty.nettyapi.javabean.NettyMessage;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.concurrent.ScheduledFuture;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;

@Slf4j
public class HeartBeatClientHandler extends ChannelHandlerAdapter {
    private volatile int interval = 5000;
    private volatile ScheduledFuture<?> heartBeat;

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        NettyMessage nettyMessage = (NettyMessage) msg;

        // 接收控制数据响应消息成功,发送心跳给服务端
        if (nettyMessage.getHeader() != null && nettyMessage.getHeader().getType() == (byte) 0) {
            byte[] data = nettyMessage.getData();
            ByteBuf buf = Unpooled.buffer(8);
            buf.writeBytes(data);
            int sid = buf.readInt();
            interval = buf.readInt();
            log.info("[client] control response is OK, header : {}. sid : {}, interval : {}", nettyMessage.getHeader(), sid, interval);

            // 每interval(默认5000)豪秒发送一次心跳请求到服务端
            heartBeat = ctx.executor().scheduleAtFixedRate(new Runnable() {
                                                               @Override
                                                               public void run() {
                                                                   NettyMessage heartBeat = buildHeartBeat(sid);
                                                                   log.info("[client] Client send heart beat message to server : ----> {}", heartBeat);
                                                                   ctx.writeAndFlush(heartBeat);
                                                               }
                                                           },
                    0, interval, TimeUnit.MILLISECONDS);

            // 消息继续向后传
            ctx.fireChannelRead(msg);
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        log.error("[Client] heart request error: {}", cause.getMessage());
        if (heartBeat != null) {
            heartBeat.cancel(true);
            heartBeat = null;
        }
        ctx.close();
    }

    /**
     * 构造心跳请求消息体
     *
     * @return
     */
    private NettyMessage buildHeartBeat(int sid) {
        NettyMessage message = new NettyMessage();
        Header header = new Header();
        byte[] data = buildData(sid);
        header.setDelimiter(0xABEF0101);
        header.setLength(data.length);
        header.setType((byte) 3);
        header.setReserved((byte) 0);
        message.setHeader(header);

        // 设置数据包
        message.setData(data);
        return message;
    }

   /**
     * 构建心跳响应消息体
     *
     * @param sid
     * @return
     */
    private byte[] buildData(int sid) {
        ByteBuf result = Unpooled.buffer(4);
        result.writeInt(sid);
        return result.array();
    }

}

  服务端心跳handler:

package com.wlf.netty.nettyserver.handler;

import com.wlf.netty.nettyapi.constant.MessageType;
import com.wlf.netty.nettyapi.javabean.NettyMessage;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class HeartBeatServerHandler extends ChannelHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        NettyMessage nettyMessage = (NettyMessage) msg;

        // 接收到心跳请求,打印心跳消息,否则进入下一处理流程
        if (nettyMessage.getHeader() != null &&
                nettyMessage.getHeader().getType() == (byte) 3) {
            log.info("[server] Receive client heart beat message : ----> {}", nettyMessage);
        } else {
            ctx.fireChannelRead(msg);
        }
    }

    /**
     * 在处理过程中引发异常时被调用
     *
     * @param ctx
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        log.error("[server] heart response error: {}", cause.getMessage());

        ctx.close();
    }

}

  客户端发送业务消息NettyClientHandler修改,发送触发从channelAcitve事件改为channelRead事件:

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        NettyMessage nettyMessage = (NettyMessage) msg;

        // 接收控制数据响应消息成功,每5秒发送pcm数据
        if (nettyMessage.getHeader() != null && nettyMessage.getHeader().getType() == (byte) 0) {
            ctx.writeAndFlush(buildClientRequest());
        }

//    @Override
//    public void channelActive(ChannelHandlerContext ctx) throws Exception {
//        ctx.writeAndFlush(buildClientRequest());
//    }

 

  客户端引导类NettyClient修改,新增handler:

    public void connect(int port, String host) throws Exception {
        NioEventLoopGroup workGroup = new NioEventLoopGroup();
        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(workGroup).channel(NioSocketChannel.class).option(ChannelOption.TCP_NODELAY, true)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel channel) throws Exception {
                            channel.pipeline().addLast(new NettyMessageDecoder());
                            channel.pipeline().addLast(new NettyMessageEncoder());
                            channel.pipeline().addLast(new ControlClientHandler());
                            channel.pipeline().addLast(new HeartBeatClientHandler());
                            channel.pipeline().addLast(new NettyClientHandler());
                        }
                    });
            ChannelFuture future = bootstrap.connect(host, port).sync();
            future.channel().closeFuture().sync();
        } finally {
            workGroup.shutdownGracefully();
        }
    }

  服务端引导类修改:

    public void bind(int port) throws Exception {
        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup, workGroup).channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG, 100)
                    .handler(new LoggingHandler(LogLevel.INFO))
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel channel) throws Exception {
                            channel.pipeline().addLast(new NettyMessageDecoder());
                            channel.pipeline().addLast(new NettyMessageEncoder());
                            channel.pipeline().addLast(new ControlServerHandler());
                            channel.pipeline().addLast(new HeartBeatServerHandler());
                            channel.pipeline().addLast(new NettyServerHandler());
                        }
                    });
            // 绑定端口
            ChannelFuture channelFuture = serverBootstrap.bind(port).sync();
            channelFuture.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workGroup.shutdownGracefully();
        }
    }

  我们试着跑起来看看:

  客户端输出:

22:55:33.725 [nioEventLoopGroup-1-0] INFO com.wlf.netty.nettyserver.handler.ControlServerHandler - [server] The control response is : NettyMessage{header=Header{delimiter=-1410399999, length=8, type=0, reserved=0}, data=[B@65aac359}, data : [0, 0, 0, 18, 0, 0, 19, -120]
22:55:33.741 [nioEventLoopGroup-1-0] INFO com.wlf.netty.nettyserver.handler.HeartBeatServerHandler - [server] Receive client heart beat message : ----> NettyMessage{header=Header{delimiter=-1410399999, length=4, type=3, reserved=0}, data=[B@79a9b61d}
22:55:38.747 [nioEventLoopGroup-1-0] INFO com.wlf.netty.nettyserver.handler.HeartBeatServerHandler - [server] Receive client heart beat message : ----> NettyMessage{header=Header{delimiter=-1410399999, length=4, type=3, reserved=0}, data=[B@77e50de}
22:55:43.746 [nioEventLoopGroup-1-0] INFO com.wlf.netty.nettyserver.handler.HeartBeatServerHandler - [server] Receive client heart beat message : ----> NettyMessage{header=Header{delimiter=-1410399999, length=4, type=3, reserved=0}, data=[B@75305725}
22:55:48.769 [nioEventLoopGroup-1-0] INFO com.wlf.netty.nettyserver.handler.HeartBeatServerHandler - [server] Receive client heart beat message : ----> NettyMessage{header=Header{delimiter=-1410399999, length=4, type=3, reserved=0}, data=[B@29515fc1}
22:55:53.754 [nioEventLoopGroup-1-0] INFO com.wlf.netty.nettyserver.handler.HeartBeatServerHandler - [server] Receive client heart beat message : ----> NettyMessage{header=Header{delimiter=-1410399999, length=4, type=3, reserved=0}, data=[B@1dfc1da8}
22:55:58.739 [nioEventLoopGroup-1-0] INFO com.wlf.netty.nettyserver.handler.HeartBeatServerHandler - [server] Receive client heart beat message : ----> NettyMessage{header=Header{delimiter=-1410399999, length=4, type=3, reserved=0}, data=[B@68f15669}
22:56:03.738 [nioEventLoopGroup-1-0] INFO com.wlf.netty.nettyserver.handler.HeartBeatServerHandler - [server] Receive client heart beat message : ----> NettyMessage{header=Header{delimiter=-1410399999, length=4, type=3, reserved=0}, data=[B@1f8fad2d}
22:56:08.742 [nioEventLoopGroup-1-0] INFO com.wlf.netty.nettyserver.handler.HeartBeatServerHandler - [server] Receive client heart beat message : ----> NettyMessage{header=Header{delimiter=-1410399999, length=4, type=3, reserved=0}, data=[B@3c83464a}

Process finished with exit code -1

  服务端输出:

22:55:33.725 [nioEventLoopGroup-1-0] INFO com.wlf.netty.nettyclient.handler.HeartBeatClientHandler - [client] control response is OK, header : Header{delimiter=-1410399999, length=8, type=0, reserved=0}. sid : 18, interval : 5000
22:55:33.725 [nioEventLoopGroup-1-0] INFO com.wlf.netty.nettyclient.handler.HeartBeatClientHandler - [client] Client send heart beat message to server : ----> NettyMessage{header=Header{delimiter=-1410399999, length=4, type=3, reserved=0}, data=[B@b1db7b2}
22:55:38.747 [nioEventLoopGroup-1-0] INFO com.wlf.netty.nettyclient.handler.HeartBeatClientHandler - [client] Client send heart beat message to server : ----> NettyMessage{header=Header{delimiter=-1410399999, length=4, type=3, reserved=0}, data=[B@4958706c}
22:55:43.746 [nioEventLoopGroup-1-0] INFO com.wlf.netty.nettyclient.handler.HeartBeatClientHandler - [client] Client send heart beat message to server : ----> NettyMessage{header=Header{delimiter=-1410399999, length=4, type=3, reserved=0}, data=[B@1fb0fa50}
22:55:48.769 [nioEventLoopGroup-1-0] INFO com.wlf.netty.nettyclient.handler.HeartBeatClientHandler - [client] Client send heart beat message to server : ----> NettyMessage{header=Header{delimiter=-1410399999, length=4, type=3, reserved=0}, data=[B@4aeea171}
22:55:53.754 [nioEventLoopGroup-1-0] INFO com.wlf.netty.nettyclient.handler.HeartBeatClientHandler - [client] Client send heart beat message to server : ----> NettyMessage{header=Header{delimiter=-1410399999, length=4, type=3, reserved=0}, data=[B@2c282fb9}
22:55:58.739 [nioEventLoopGroup-1-0] INFO com.wlf.netty.nettyclient.handler.HeartBeatClientHandler - [client] Client send heart beat message to server : ----> NettyMessage{header=Header{delimiter=-1410399999, length=4, type=3, reserved=0}, data=[B@2679e140}
22:56:03.738 [nioEventLoopGroup-1-0] INFO com.wlf.netty.nettyclient.handler.HeartBeatClientHandler - [client] Client send heart beat message to server : ----> NettyMessage{header=Header{delimiter=-1410399999, length=4, type=3, reserved=0}, data=[B@3e03eda5}
22:56:08.742 [nioEventLoopGroup-1-0] INFO com.wlf.netty.nettyclient.handler.HeartBeatClientHandler - [client] Client send heart beat message to server : ----> NettyMessage{header=Header{delimiter=-1410399999, length=4, type=3, reserved=0}, data=[B@1d9363d}

  我们看到心跳机制是正常的(这里还没有实现心跳超时服务端断连),可是消息并未向后传播,没有进入NettyClientHandler,事件循环被心跳消息IO阻塞了,除非发送完所有心跳消息,否则永远不会进入下个handler。怎么办?我们不在channelRead事件中执行定时发送心跳,换个事件channelReadComplete发。我们设置一个标志,如果在channelRead中读取到服务端响应,说明可以发送心跳了,同时进入下个handler,发送的事情就交给channelReadComplete了:

@Slf4j
public class HeartBeatClientHandler extends ChannelHandlerAdapter {

    // 新增发送标志
    private volatile boolean startSendHeartNow = false;
    private volatile int interval = 5000;
    private volatile ScheduledFuture<?> heartBeat;

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        // 新增读取服务端消息后的处理    
        if (ctx.channel().isRegistered() && ctx.channel().isActive() && startSendHeartNow) {
            heartBeat = ctx.executor().scheduleAtFixedRate(new Runnable() {
                                                               @Override
                                                               public void run() {
                                                                   NettyMessage heartBeat = buildHeartBeat(interval);
                                                                   log.info("[client] Client send heart beat message to server : ----> {}", heartBeat);
                                                                   ctx.writeAndFlush(heartBeat);
                                                               }
                                                           },
                    0, interval, TimeUnit.MILLISECONDS);
            startSendHeartNow = false;
        }
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        NettyMessage nettyMessage = (NettyMessage) msg;

        // 接收控制数据响应消息成功,发送心跳给服务端
        if (nettyMessage.getHeader() != null && nettyMessage.getHeader().getType() == (byte) 0) {
            byte[] data = nettyMessage.getData();
            ByteBuf buf = Unpooled.buffer(8);
            buf.writeBytes(data);
            int sid = buf.readInt();
            interval = buf.readInt();
            log.info("[client] control response is OK, header : {}. sid : {}, interval : {}", nettyMessage.getHeader(), sid, interval);

            // 开始发送心跳
            startSendHeartNow = true;

            // 每interval(默认5000)豪秒发送一次心跳请求到服务端
//            heartBeat = ctx.executor().scheduleAtFixedRate(new Runnable() {
//                                                               @Override
//                                                               public void run() {
//                                                                   NettyMessage heartBeat = buildHeartBeat(sid);
//                                                                   log.info("[client] Client send heart beat message to server : ----> {}", heartBeat);
//                                                                   ctx.writeAndFlush(heartBeat);
//                                                               }
//                                                           },
//                    0, interval, TimeUnit.MILLISECONDS);

            // 消息继续向后传
            ctx.fireChannelRead(msg);
        }
    }

}

  再跑一次:

  服务端输出:

23:51:36.516 [nioEventLoopGroup-1-0] INFO com.wlf.netty.nettyserver.handler.ControlServerHandler - [server] The control response is : NettyMessage{header=Header{delimiter=-1410399999, length=8, type=0, reserved=0}, data=[B@37729cbd}, data : [0, 0, 0, 76, 0, 0, 19, -120]
23:51:36.532 [nioEventLoopGroup-1-0] INFO com.wlf.netty.nettyserver.handler.NettyServerHandler - [server] server receive client message : NettyMessage{header=Header{delimiter=-1410399999, length=8, type=1, reserved=0}, data=[B@75f76fa3}
23:51:36.532 [nioEventLoopGroup-1-0] INFO com.wlf.netty.nettyserver.handler.NettyServerHandler - data length: 8
23:51:36.532 [nioEventLoopGroup-1-0] INFO com.wlf.netty.nettyserver.handler.NettyServerHandler - startTime: 1572105096532
23:51:36.532 [nioEventLoopGroup-1-0] INFO com.wlf.netty.nettyserver.handler.HeartBeatServerHandler - [server] Receive client heart beat message : ----> NettyMessage{header=Header{delimiter=-1410399999, length=4, type=3, reserved=0}, data=[B@5d175611}
23:51:41.551 [nioEventLoopGroup-1-0] INFO com.wlf.netty.nettyserver.handler.HeartBeatServerHandler - [server] Receive client heart beat message : ----> NettyMessage{header=Header{delimiter=-1410399999, length=4, type=3, reserved=0}, data=[B@75a120b5}
23:51:46.552 [nioEventLoopGroup-1-0] INFO com.wlf.netty.nettyserver.handler.HeartBeatServerHandler - [server] Receive client heart beat message : ----> NettyMessage{header=Header{delimiter=-1410399999, length=4, type=3, reserved=0}, data=[B@591bb469}
23:51:51.553 [nioEventLoopGroup-1-0] INFO com.wlf.netty.nettyserver.handler.HeartBeatServerHandler - [server] Receive client heart beat message : ----> NettyMessage{header=Header{delimiter=-1410399999, length=4, type=3, reserved=0}, data=[B@175f529e}

  现在客户端同时发送了心跳和业务消息了。

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!