Netty4 Tcp长连接、断开重连、心跳监测、Msgpack编码解码

Netty4 Tcp长连接、断开重连、心跳监测、Msgpack编码解码

结构

这里写图片描述


目录

  1. 导入Msgpack
  2. 创建对象,命名DeviceValue
  3. 创建type标记,命名TypeData
  4. 创建msgpack编码器
  5. 创建msgpack解码器
  6. 创建相同Server端和Client端共有的消息处理类
  7. 创建Client端消息处理类
  8. 创建Server端消息处理类
  9. 创建Client
  10. 创建Server
  11. 运行
  12. 结果

  • 导入Msgpack
compile 'org.msgpack:msgpack:0.6.12'
  • 创建对象,命名DeviceValue(注意:需要在对象头上注入@Message)
package com.zmm.netty4msgpacktest.domain;

import org.msgpack.annotation.Message;

@Message
public class DeviceValue {

    private int type;

    private int seatId;

    private int speed;

    private int angle;

    public int getType() {
        return type;
    }

    public DeviceValue() {
    }

    public void setType(int type) {
        this.type = type;
    }

    public int getSeatId() {
        return seatId;
    }

    public void setSeatId(int seatId) {
        this.seatId = seatId;
    }

    public int getSpeed() {
        return speed;
    }

    public void setSpeed(int speed) {
        this.speed = speed;
    }

    public int getAngle() {
        return angle;
    }

    public void setAngle(int angle) {
        this.angle = angle;
    }


    @Override
    public String toString() {
        return "DeviceValue{" +
                "type=" + type +
                ", seatId=" + seatId +
                ", speed=" + speed +
                ", angle=" + angle +
                '}';
    }
}
  • 创建type标记,命名TypeData(目的:通过DeviceValue中的不同type来区分数据类型)
package com.zmm.netty4msgpacktest.domain;

public interface TypeData {


    //模式
    byte PING = 1;

    byte PONG = 2;

    byte CUSTOME = 3;

    //*******************************
    byte PING_SEAT = 100;

    byte PONG_SEAT = 101;

    byte SERVER_RESPONSE = 102;

    byte SERVER_RESISTANT = 103;
}
  • 创建msgpack编码器(Msgpack的具体用法可以去查一下,网上很多,其传输效率真的很高,很适合大量数据传递的情况)
package com.zmm.netty4msgpacktest.code;

import org.msgpack.MessagePack;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;

public class MsgPackEncode extends MessageToByteEncoder<Object> {


    @Override
    protected void encode(ChannelHandlerContext ctx, Object msg, ByteBuf out) throws Exception {
        MessagePack msgPack = new MessagePack();
        byte[] raw = msgPack.write(msg);
        out.writeBytes(raw);
    }
}
  • 创建msgpack解码器
package com.zmm.netty4msgpacktest.code;

import com.zmm.netty4msgpacktest.domain.DeviceValue;

import org.msgpack.MessagePack;

import java.util.List;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageDecoder;

public class MsgPackDecode extends MessageToMessageDecoder<ByteBuf> {


    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception {
        final byte[] array;
        final int length=msg.readableBytes();
        array=new byte[length];
        msg.getBytes(msg.readerIndex(), array,0,length);
        MessagePack msgpack=new MessagePack();
        out.add(msgpack.read(array, DeviceValue.class));
    }

}
  • 创建相同Server端和Client端共有的消息处理类(通过传递的type,执行不同的逻辑)
package com.zmm.netty4msgpacktest.common;

import com.zmm.netty4msgpacktest.domain.DeviceValue;
import com.zmm.netty4msgpacktest.domain.TypeData;

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

public abstract class CustomHeartbeatHandler extends ChannelInboundHandlerAdapter {


    protected String name;
    private int heartbeatCount = 0;

    public CustomHeartbeatHandler(String name) {
        this.name = name;
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        DeviceValue deviceValue = (DeviceValue) msg;
        int type = deviceValue.getType();
        System.out.println("CustomHeartbeatHandler type="+type);

        switch (type){
            case 1:
                sendPongMsg(ctx);
                break;

            case 2:
                System.out.println(name + " get pong msg from " + ctx.channel().remoteAddress());

                break;

            case 3:
                handleData(ctx, msg);

                break;
        }

    }

    protected void sendPingMsg(ChannelHandlerContext context) {
        DeviceValue deviceValue = new DeviceValue();
        deviceValue.setType(TypeData.PING);
        deviceValue.setSpeed(0);
        deviceValue.setAngle(15);
        deviceValue.setSeatId(TypeData.PING_SEAT);
        context.channel().writeAndFlush(deviceValue);
        heartbeatCount++;
        System.out.println(name + " sent ping msg to " + context.channel().remoteAddress() + ", count: " + heartbeatCount);
    }

    private void sendPongMsg(ChannelHandlerContext context) {
        DeviceValue deviceValue = new DeviceValue();
        deviceValue.setType(TypeData.PONG);
        deviceValue.setSpeed(0);
        deviceValue.setAngle(15);
        deviceValue.setSeatId(TypeData.PONG_SEAT);
        context.channel().writeAndFlush(deviceValue);
        heartbeatCount++;
        System.out.println(name + " sent pong msg to " + context.channel().remoteAddress() + ", count: " + heartbeatCount);
    }

    protected abstract void handleData(ChannelHandlerContext channelHandlerContext, Object msg);

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        // IdleStateHandler 所产生的 IdleStateEvent 的处理逻辑.
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent e = (IdleStateEvent) evt;
            switch (e.state()) {
                case READER_IDLE:
                    handleReaderIdle(ctx);
                    break;
                case WRITER_IDLE:
                    handleWriterIdle(ctx);
                    break;
                case ALL_IDLE:
                    handleAllIdle(ctx);
                    break;
                default:
                    break;
            }
        }
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.err.println("---" + ctx.channel().remoteAddress() + " is active---");
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        System.err.println("---" + ctx.channel().remoteAddress() + " is inactive---");
    }

    protected void handleReaderIdle(ChannelHandlerContext ctx) {
        System.err.println("---READER_IDLE---");
    }

    protected void handleWriterIdle(ChannelHandlerContext ctx) {
        System.err.println("---WRITER_IDLE---");
    }

    protected void handleAllIdle(ChannelHandlerContext ctx) {
        System.err.println("---ALL_IDLE---");
    }
}
  • 创建Client端消息处理类(因为在Client里面每隔2s发送一次数据给服务器,这里接收后就不发送了)
package com.zmm.netty4msgpacktest.client;

import com.zmm.netty4msgpacktest.common.CustomHeartbeatHandler;
import com.zmm.netty4msgpacktest.domain.DeviceValue;
import com.zmm.netty4msgpacktest.domain.TypeData;

import io.netty.channel.ChannelHandlerContext;

public class ClientHandler extends CustomHeartbeatHandler {
    private Client client;
    public ClientHandler(Client client) {
        super("client");
        this.client = client;
    }


    @Override
    protected void handleData(ChannelHandlerContext channelHandlerContext, Object msg) {
        DeviceValue deviceValue = (DeviceValue) msg;
        System.out.println("client 接收数据:"+deviceValue.toString());

//        DeviceValue s = new DeviceValue();
//        s.setType(TypeData.CUSTOME);
//        s.setSpeed(0);
//        s.setAngle(15);
//        s.setSeatId(TypeData.SERVER_RESPONSE);
//        channelHandlerContext.writeAndFlush(s);
    }

    @Override
    protected void handleAllIdle(ChannelHandlerContext ctx) {
        super.handleAllIdle(ctx);
        sendPingMsg(ctx);
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        super.channelInactive(ctx);
        client.doConnect();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println(name + " exception"+cause.toString());

    }
}
  • 创建Server端消息处理类(服务端收到数据后,返回固定数据给客户端)
package com.zmm.netty4msgpacktest.server;

import com.zmm.netty4msgpacktest.common.CustomHeartbeatHandler;
import com.zmm.netty4msgpacktest.domain.DeviceValue;
import com.zmm.netty4msgpacktest.domain.TypeData;

import io.netty.channel.ChannelHandlerContext;

public class ServerHandler extends CustomHeartbeatHandler {

    public ServerHandler() {
        super("server");
    }

    @Override
    protected void handleData(ChannelHandlerContext channelHandlerContext, Object msg) {
        DeviceValue deviceValue = (DeviceValue) msg;
        System.out.println("server 接收数据:"+deviceValue.toString());

        DeviceValue s = new DeviceValue();
        s.setType(TypeData.CUSTOME);
        s.setSpeed(0);
        s.setAngle(15);
        s.setSeatId(TypeData.SERVER_RESPONSE);
        channelHandlerContext.writeAndFlush(s);
        System.out.println("server 发送数据:"+s.toString());
    }

    @Override
    protected void handleReaderIdle(ChannelHandlerContext ctx) {
        super.handleReaderIdle(ctx);
        System.err.println("---client " + ctx.channel().remoteAddress().toString() + " reader timeout, close it---");
        ctx.close();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println(name+" exception"+cause.toString());
    }
}
  • 创建Client(这里执行了多个程序,包括连接服务器、重连、定时发送等)
package com.zmm.netty4msgpacktest.client;

import com.zmm.netty4msgpacktest.code.MsgPackDecode;
import com.zmm.netty4msgpacktest.code.MsgPackEncode;
import com.zmm.netty4msgpacktest.domain.DeviceValue;
import com.zmm.netty4msgpacktest.domain.TypeData;

import java.util.Random;
import java.util.concurrent.TimeUnit;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.timeout.IdleStateHandler;

public class Client {
    private NioEventLoopGroup workGroup = new NioEventLoopGroup(4);
    private Channel channel;
    private Bootstrap bootstrap;

    public static void main(String[] args) throws Exception {
        Client client = new Client();
        client.start();
        client.sendData();
    }

    public void start() {
        try {
            bootstrap = new Bootstrap();
            bootstrap
                    .group(workGroup)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            ChannelPipeline p = socketChannel.pipeline();
                            p.addLast(new IdleStateHandler(0, 0, 5));
//                            p.addLast(new LengthFieldBasedFrameDecoder(1024, 0, 4, -4, 0));
                            p.addLast(new MsgPackDecode());
                            p.addLast(new MsgPackEncode());
                            p.addLast(new ClientHandler(Client.this));
                        }
                    });
            doConnect();

        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 重连机制,每隔2s重新连接一次服务器
     */
    protected void doConnect() {
        if (channel != null && channel.isActive()) {
            return;
        }

        ChannelFuture future = bootstrap.connect("127.0.0.1", 12345);

        future.addListener(new ChannelFutureListener() {
            public void operationComplete(ChannelFuture futureListener) throws Exception {
                if (futureListener.isSuccess()) {
                    channel = futureListener.channel();
                    System.out.println("Connect to server successfully!");
                } else {
                    System.out.println("Failed to connect to server, try connect after 2s");

                    futureListener.channel().eventLoop().schedule(new Runnable() {
                        @Override
                        public void run() {
                            doConnect();
                        }
                    }, 2, TimeUnit.SECONDS);
                }
            }
        });
    }

    /**
     * 发送数据 每隔2秒发送一次
     * @throws Exception
     */
    public void sendData() throws Exception {
        Random random = new Random(System.currentTimeMillis());
        for (int i = 0; i < 10000; i++) {
            if (channel != null && channel.isActive()) {

                DeviceValue deviceValue = new DeviceValue();
                deviceValue.setType(TypeData.CUSTOME);
                deviceValue.setAngle(i%15);
                deviceValue.setSeatId(i%30);
                deviceValue.setSpeed(i%120);

                System.out.println("client 发送数据:"+deviceValue.toString());

                channel.writeAndFlush(deviceValue);
            }

            Thread.sleep(random.nextInt(20000));
        }
    }


}
  • 创建Server(Server开启一次就够了,不需要重复开启,若是服务器断开,Server和Client不会报异常,Client会继续不断的搜索服务器,直到搜索到为止)
package com.zmm.netty4msgpacktest.server;

import com.zmm.netty4msgpacktest.code.MsgPackDecode;
import com.zmm.netty4msgpacktest.code.MsgPackEncode;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.timeout.IdleStateHandler;


public class Server {
    public static void main(String[] args) {
        NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
        NioEventLoopGroup workGroup = new NioEventLoopGroup(4);
        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap
                    .group(bossGroup, workGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            ChannelPipeline p = socketChannel.pipeline();
                            p.addLast(new IdleStateHandler(10, 0, 0));
//                            p.addLast(new LengthFieldBasedFrameDecoder(1024, 0, 4, -4, 0));
                            p.addLast(new MsgPackDecode());
                            p.addLast(new MsgPackEncode());
                            p.addLast(new ServerHandler());
                        }
                    });

            Channel ch = bootstrap.bind(12345).sync().channel();

            System.out.println("------Server Start------");

            ch.closeFuture().sync();
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            bossGroup.shutdownGracefully();
            workGroup.shutdownGracefully();
        }
    }
}
  • 运行
  • 结果

Server端日志:
这里写图片描述

Client端日志:
这里写图片描述

Github源码:Netty4MsgPackTest

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值