基于Netty实现私有化协议(序列化数据结构协议ProtoBuf)

本文介绍如何使用Google的ProtoBuf和Netty框架自定义通信协议,包括序列化、编码和解码过程,以及客户端和服务端的实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

因为项目需要,需要自定义通信协议。序列化协议使用到了Google的ProtoBuf,这里也是通过一个案例来实现基于Netty的私有化协议的开发。

protoBuf 介绍

Google Protocol Buffer(protoBuf)是一种平台无关,语言无关,可扩展且轻便高效的序列化数据结构的协议,
相比传统的XML、JSON序列化的方式,更小、序列化更快、传输速度更快。只需要定义一次你的结构化文件,就可以自己
生成源代码,实现轻松的读写结构化文件。并且可以使用各种语言.

优点

  • 跨平台、跨语言,支持Java、Python、C++、Go、JavaScript;
  • 序列化更快(比xml、json方式提升20~100倍);
  • 代码自动生成,生成不同语言代码;

缺点

  • 通用性较差,没有json、xml普及;
  • 数据结构化没有json、xml表达能力那么强;
  • 以二进制数据流方式存储(不可读),需要通过.proto文件 才能了解到数据结构;

总结:protoBuf更侧重数据序列化,应用场景更明确,xml、json的应用场景更为丰富。

protoBuf 语法

这里就不一一介绍语法,后面会开专门章节来讲,直接贴上一段定义好的.proto消息文件。

syntax = "proto2";
package com.elisland.customprotocol;
option optimize_for = SPEED; //文件属性
option java_package = "com.elisland.customprotocol";
option java_outer_classname = "CustomMessageData";

message MessageData{
    required int64 length = 1;
    optional Content content = 2;
    enum DataType {
        REQ_LOGIN = 0;  //上线登录验证环节 等基础信息上报
        RSP_LOGIN = 1;  //返回上线登录状态与基础信息
        PING = 2;  //心跳
        PONG = 3;  //心跳
        REQ_ACT = 4;  //动作请求
        RSP_ACT = 5;  //动作响应
        REQ_CMD = 6;  //指令请求
        RSP_CMD = 7;  //指令响应
        REQ_LOG = 8 ;//日志请求
        RSP_LOG = 9;  //日志响应
    }
    optional DataType order = 3;
    message Content{
        optional int64 contentLength = 1;
        optional string data = 2;
    }
}

文件生成,这里使用Java语言生成,java文件。语法 protoc --java_out=生成java文件位置 .proto文件位置
代码生成完成后,接下来就是基于Netty来传输protoBuf协议。

Netty 使用

Netty简介:高效的Java NIO框架,通过对传统NIO的封装,提供更加便捷高效的非阻塞式开发。 后面会开具体的章节来讲解Netty的学习。本次案例,通过Netty构建客户端、服务端之间的数据传输、以及通过我们自定义编解码器的方式来解析和编码我们使用的.protoBuf消息。闲话少说,后面直接上代码。

服务器Server端
服务端
public class MyServer {
    public static void main(String[] args) throws InterruptedException {
        EventLoopGroup bossGroup = new NioEventLoopGroup();//接收连接,将连接发送给worker
        EventLoopGroup workerGroup = new NioEventLoopGroup();//处理连接
        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            pipeline.addLast(new MyDecode());
                            pipeline.addLast(new MyEncode());
                            pipeline.addLast(new MyServerHandle());
                        }
                    });
            ChannelFuture sync = serverBootstrap.bind(8899).sync();
            sync.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();//关闭
            workerGroup.shutdownGracefully();
        }
    }
}
自定义编码器
public class MyEncode extends MessageToByteEncoder<CustomMessageData.MessageData> {

    /**
     * 编码器
     * @param ctx
     * @param msg
     * @param out
     * @throws Exception
     */
    @Override
    protected void encode(ChannelHandlerContext ctx, CustomMessageData.MessageData msg, ByteBuf out) throws Exception {
        System.out.println("MyEncode invoke...");
        out.writeInt(((int) (msg.getLength())));
        out.writeByte((byte)msg.getOrder().getNumber());
        out.writeBytes(msg.getContent().getData().getBytes());
    }
}
自定义解码器
public class MyDecode extends ReplayingDecoder {
    /**
     * 解码器
     * @param ctx
     * @param in
     * @param out
     * @throws Exception
     */
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        System.out.println("Mydecode invoke...");
        int length = in.readInt();
        byte order = in.readByte();
        byte[] content = new byte[length];
        in.readBytes(content);

        CustomMessageData.MessageData decodeData = CustomMessageData.MessageData.newBuilder()
                .setLength(length)
                .setOrder(CustomMessageData.MessageData.DataType.forNumber(order))
                .setContent(CustomMessageData.MessageData.Content.newBuilder()
                        .setData(new String(content, Charset.forName("utf-8")))).build();
         out.add(decodeData);
    }
}
服务端Handler
public class MyServerHandle extends SimpleChannelInboundHandler<CustomMessageData.MessageData> {
    int count;//记录接收数据数量

    /**
     * 接受client发送来的消息
     * @param ctx
     * @param msg
     * @throws Exception
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, CustomMessageData.MessageData msg) throws Exception {
        long length = msg.getLength();
        CustomMessageData.MessageData.DataType order = msg.getOrder();
        String ContentData = msg.getContent().getData();
        System.out.println("服务端接收到的数据长度:" + length);
        System.out.println("服务端接收到的数据指令:" + order);
        System.out.println("服务端接收到的数据内容:" + ContentData);
        System.out.println("服务端接收到的数据数量:" + (++count));

        String sendClientMessage = UUID.randomUUID().toString();
        int sendClientMessageLength = sendClientMessage.getBytes("utf-8").length;
        //每次收到客户端消息后,向客户端返回UUID字符串
        CustomMessageData.MessageData message = CustomMessageData.MessageData.newBuilder()
                .setLength(sendClientMessageLength)
                .setOrder(CustomMessageData.MessageData.DataType.forNumber(1))
                .setContent(CustomMessageData.MessageData.Content.newBuilder().setData(sendClientMessage)).build();
        ctx.writeAndFlush(message);

    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        super.exceptionCaught(ctx, cause);
        ctx.close();
    }
}
服务器Client端
客户端
public class MyClient {
    public static void main(String[] args) throws InterruptedException {
        EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(eventLoopGroup)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {

                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            pipeline.addLast(new MyDecode());
                            pipeline.addLast(new MyEncode());
                            pipeline.addLast(new MyClientHandle());
                        }
                    });

            ChannelFuture channelFuture = bootstrap.connect("localhost", 8899).sync();
            channelFuture.channel().closeFuture().sync();
        } finally {
            eventLoopGroup.shutdownGracefully();
        }
    }
}
客户端handler
public class MyClientHandle extends SimpleChannelInboundHandler<CustomMessageData.MessageData> {
    int count;

    /**
     * 客户端和服务端建立连接后,向服务端发送消息
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        for (int i = 0; i < 10; i++) {
            String message = "message for client";
            int length = message.getBytes().length;
            MessageProtocol messageProtocol = new MessageProtocol();
            messageProtocol.setLength(length);
            messageProtocol.setContent(message);
            //构建消息内容
            CustomMessageData.MessageData messageData = CustomMessageData.MessageData.newBuilder()
                    .setLength(length)
                    .setOrder(CustomMessageData.MessageData.DataType.forNumber(1))
                    .setContent(CustomMessageData.MessageData.Content.newBuilder().setData(message)).build();
            ctx.writeAndFlush(messageData);
        }
    }

    /**
     * 接受响应服务端消息
     * @param ctx
     * @param msg
     * @throws Exception
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, CustomMessageData.MessageData msg) throws Exception {
        long length = msg.getLength();
        CustomMessageData.MessageData.DataType order = msg.getOrder();
        String data = msg.getContent().getData();
        System.out.println("客户端接收到的数据长度:" + length);
        System.out.println("客户端接收到的数据指令:" + order);
        System.out.println("客户端接收到的数据内容:" + data);
        System.out.println("客户端接收到的数据数量:" + (++count));
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        super.exceptionCaught(ctx, cause);
        ctx.close();
    }
}

* .protoBuf生成的java文件未贴出。
流程:服务端启动后,监听客户端,当客户端启动完成后,客户端handler中的channelActive()方法会连续向服务器端发送10条消息。在传输过程中,先经过自定义的Encode编码器,服务器端接受到发送过来的消息时,再通过自定义解码器去解析消息。服务器向客户端返回应答消息(UUID字符串),同样是通过编码器将消息编码,客户端收到消息后再通过解码器将消息解析打印。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值