zoukankan      html  css  js  c++  java
  • 自定义兼容多种Protobuf协议的编解码器

    《从零开始搭建游戏服务器》自定义兼容多种Protobuf协议的编解码器

    直接在protobuf序列化数据的前面,加上一个自定义的协议头,协议头里包含序列数据的长度和对应的数据类型,在数据解包的时候根据包头来进行反序列化。

    1.协议头定义

    关于这一块,我打算先采取比较简单的办法,结构如下: 
     
    协议号是自定义的一个int类型的枚举(当然,假如协议吧比较少的话,可以用一个short来代替int以缩小数据包),这个协议号与协议类型是一一对应的,而协议头通常使用数据总长度来填入,具体过程如下:

    • 当客户端向服务器发送数据时,会根据协议类型加上协议号,然后使用protobuf序列化之后再发送给服务器;
    • 当服务器发送数据给客户端时,根据协议号,确定protobuf协议类型以反序列化数据,并调用相应回调方法。

    2.自定义的编码器和解码器

    编码器: 
    参考netty自带的编码器ProtobufEncoder可以发现,被绑定到ChannelPipeline上用于序列化协议数据的编码器,必须继承MessageToByteEncoder<MessageLite>这个基类,并通过重写protected void encode(ChannelHandlerContext ctx, MessageLite msg, ByteBuf out)这个方法来实现自定义协议格式的目的:

    package com.tw.login.tools;
    import com.google.protobuf.MessageLite;
    import com.tw.login.proto.CsEnum.EnmCmdID;
    import com.tw.login.proto.CsLogin;
    import io.netty.buffer.ByteBuf;
    import io.netty.channel.ChannelHandlerContext;
    import io.netty.handler.codec.MessageToByteEncoder;
    /**
     * 自定义编码器
     * @author linsh
     *
     */
    public class PackEncoder extends MessageToByteEncoder<MessageLite> {
        /**
         * 传入协议数据,产生携带包头之后的数据
         */
        @Override
        protected void encode(ChannelHandlerContext ctx, MessageLite msg, ByteBuf out) throws Exception {
            // TODO Auto-generated method stub
            byte[] body = msg.toByteArray();
            byte[] header = encodeHeader(msg, (short)body.length);
            out.writeBytes(header);
            out.writeBytes(body);
            return;
        }
        /**
         * 获得一个协议头
         * @param msg
         * @param bodyLength
         * @return
         */
        private byte[] encodeHeader(MessageLite msg,short bodyLength){
            short _typeId = 0;
            if(msg instanceof CsLogin.CSLoginReq){
                _typeId = EnmCmdID.CS_LOGIN_REQ_VALUE;
            }else if(msg instanceof CsLogin.CSLoginRes){
                _typeId = EnmCmdID.CS_LOGIN_RES_VALUE;
            }
            //存放两个short数据
            byte[] header = new byte[4];
            //前两位放数据长度
            header[0] = (byte) (bodyLength & 0xff);
            header[1] = (byte) ((bodyLength >> 8) & 0xff);
            //后两个字段存协议id
            header[2] = (byte) (_typeId & 0xff);
            header[3] = (byte) ((_typeId >> 8) & 0xff);
            return header;
        }
    }
    

     解码器: 
    参考netty自带的编码器ProtobufDecoder可以发现,被绑定到ChannelPipeline上用于序列化协议数据的解码器,必须继承ByteToMessageDecoder这个基类,并通过重写protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out)这个方法来实现解析自定义协议格式的目的:

    package com.tw.login.tools;
    import java.util.List;
    import com.google.protobuf.MessageLite;
    import com.tw.login.proto.CsEnum.EnmCmdID;
    import com.tw.login.proto.CsLogin.CSLoginReq;
    import com.tw.login.proto.CsLogin.CSLoginRes;
    import io.netty.buffer.ByteBuf;
    import io.netty.channel.ChannelHandlerContext;
    import io.netty.handler.codec.ByteToMessageDecoder;
    public class PackDecoder extends ByteToMessageDecoder {
        @Override
        protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
            // 获取包头中的body长度
            byte low = in.readByte();
            byte high = in.readByte();
            short s0 = (short) (low & 0xff);
            short s1 = (short) (high & 0xff);
            s1 <<= 8;
            short length = (short) (s0 | s1);
            // 获取包头中的protobuf类型
            byte low_type = in.readByte();
            byte high_type = in.readByte();
            short s0_type = (short) (low_type & 0xff);
            short s1_type = (short) (high_type & 0xff);
            s1_type <<= 8;
            short dataTypeId = (short) (s0_type | s1_type);
            // 如果可读长度小于body长度,恢复读指针,退出。
            if (in.readableBytes() < length) {
                in.resetReaderIndex();
                return;
            }
            //开始读取核心protobuf数据
            ByteBuf bodyByteBuf = in.readBytes(length);
            byte[] array;
            //反序列化数据的起始点
            int offset;
            //可读的数据字节长度
            int readableLen= bodyByteBuf.readableBytes();
            //分为包含数组数据和不包含数组数据两种形式
            if (bodyByteBuf.hasArray()) {
                array = bodyByteBuf.array();
                offset = bodyByteBuf.arrayOffset()   bodyByteBuf.readerIndex();
            } else {
                array = new byte[readableLen];
                bodyByteBuf.getBytes(bodyByteBuf.readerIndex(), array, 0, readableLen);
                offset = 0;
            }
            //反序列化
            MessageLite result = decodeBody(dataTypeId, array, offset, readableLen);
            out.add(result);
        }
        /**
         * 根据协议号用响应的protobuf类型来解析协议数据
         * @param _typeId
         * @param array
         * @param offset
         * @param length
         * @return
         * @throws Exception
         */
        public MessageLite decodeBody(int _typeId,byte[] array,int offset,int length) throws Exception{
            if(_typeId == EnmCmdID.CS_LOGIN_REQ_VALUE){
                return CSLoginReq.getDefaultInstance().getParserForType().parseFrom(array,offset,length);
            }
            else if(_typeId == EnmCmdID.CS_LOGIN_RES_VALUE){
                return CSLoginRes.getDefaultInstance().getParserForType().parseFrom(array,offset,length);
            }
            return null;
        }
    }
    

    3.修改Socket管道绑定的编解码器:

    在创建Socket管道的时候,将编解码器替换为自定义的编解码器,而具体数据发送和接受过程无需做任何修改:

    ChannelPipeline pipeline = ch.pipeline();
    // 协议数据的编解码器
    pipeline.addLast("frameDecoder",new ProtobufVarint32FrameDecoder());
    pipeline.addLast("protobufDecoder",new PackDecoder());
    pipeline.addLast("frameEncoder",new ProtobufVarint32LengthFieldPrepender());
    pipeline.addLast("protobufEncoder", new PackEncoder());
    pipeline.addLast("handler",new SocketServerHandler());
    
  • 相关阅读:
    HDU 5119 Happy Matt Friends(DP || 高斯消元)
    URAL 1698. Square Country 5(记忆化搜索)
    POJ 2546 Circular Area(两个圆相交的面积)
    URAL 1430. Crime and Punishment(数论)
    HDU 1111 Secret Code (DFS)
    HDU 1104 Remainder (BFS求最小步数 打印路径)
    URAL 1091. Tmutarakan Exams(容斥原理)
    PDO连接mysql8.0报PDO::__construct(): Server sent charset (255) unknown to the client. Please, report to the developers错误
    swoole简易实时聊天
    centos安装netcat
  • 原文地址:https://www.cnblogs.com/zeroone/p/8595464.html
Copyright © 2011-2022 走看看