自定义兼容多种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());
自定义兼容多种Protobuf协议的编解码器的更多相关文章
- SQL 横转竖 、竖专横 (转载) 使用Dapper.Contrib 开发.net core程序,兼容多种数据库 C# 读取PDF多级书签 Json.net日期格式化设置 ASPNET 下载共享文件 ASPNET 文件批量下载 递归,循环,尾递归 利用IDisposable接口构建包含非托管资源对象 《.NET 进阶指南》读书笔记2------定义不可改变类型
SQL 横转竖 .竖专横 (转载) 普通行列转换 问题:假设有张学生成绩表(tb)如下: 姓名 课程 分数 张三 语文 74 张三 数学 83 张三 物理 93 李四 语文 74 李四 数学 84 ...
- 使用Dapper.Contrib 开发.net core程序,兼容多种数据库
关于Dapper的介绍,我想很多人都对它有一定的了解,这个类似一个轻型的ORM框架是目前应用非常火的一个东西,据说各方面的性能都不错,而且可以支持多种数据库,在开始介绍这个文章之前,我花了不少功夫来学 ...
- Protobuf 协议语言指南
l 定义一个消息(message)类型 l 标量值类型 l Optional 的字段及默认值 l 枚举 l 使用其他消息类型 l 嵌套类型 l 更新一个消息类型 l 扩展 l 包(p ...
- Dapper.Contrib 开发.net core程序,兼容多种数据库
Dapper.Contrib 开发.net core程序,兼容多种数据库 https://www.cnblogs.com/wuhuacong/p/9952900.html 使用Dapper.Contr ...
- Protobuf协议应用干货
Protobuf应用广泛,尤其作为网络通讯协议最为普遍.本文将详细描述几个让人眼前一亮的protobuf协议设计,对准备应用或已经应用protobuf的开发者会有所启发,甚至可以直接拿过去用. 这里描 ...
- 开源项目SMSS开源项目(三)——protobuf协议设计
本文的第一部分将介绍protobuf使用基础以及如何利用protobuf设计通信协议.第二部分会给出smss项目的协议设计规范和源码讲解. 一.Protobuf使用基础 什么是protobuf pro ...
- protobuf 协议浅析
目录 Protobuf 协议浅析 1. Protobuf 介绍 1.1 Protobuf 基本概念 1.2 Protobuf 的优点 1.3 Protobuf, JSON, XML 的区别 2. Pr ...
- Google的Protobuf协议分析
protobuf和thrift类似,也是一个序列化的协议实现,简称PB(下文出现的PB代表protobuf). Github:https://github.com/google/protobuf 上图 ...
- 真正的让iframe自适应高度 兼容多种浏览器随着窗口大小改变
今天有朋友问到我关于"iframe自适应高度"的问题,原本以为是很简单的问题,没想到折腾了20分钟才搞定.期间遇到几个问题,要么是高度自适应了,但是当窗口改变时会出现滚动条.也就是 ...
随机推荐
- HighCharts、EChart图表X轴纵向显示
HighCharts 回调javascript函数来格式化标签,值通过this.value获得,this的其他属性还包括axis, chart, isFirst and isLast. 默认为: fu ...
- https-配置使用HTTPS的ASP.NET Web应用
有关HTTPS.SSL以及SSL证书的工作原理,参见 <HTTPS-HTTPS原理> <HTTPS-SSL证书> <HTTPS-攻击实例与防御> 本文将演示如何在I ...
- 《NodeJS开发指南》第五章微博实例开发总结
所有文章搬运自我的个人主页:sheilasun.me <NodeJS开发指南>这本书用来NodeJS入门真是太好了,而且书的附录部分还讲到了闭包.this等JavaScript常用特性.第 ...
- 【AtCoder】ARC088
C - Multiple Gift 题解 首项是X,每次乘个2,暴力统计 代码 #include <bits/stdc++.h> #define fi first #define se s ...
- VMware虚拟机三种联网方法及原理
VMware虚拟机三种联网方法及原理 一.Brigde——桥接:默认使用VMnet0 1.原理: Bridge 桥"就是一个主机,这个机器拥有两块网卡,分别处于两个局域网中,同时 ...
- 微信小程序实现即时通信聊天功能的实例代码
项目背景:小程序中实现实时聊天功能 一.服务器域名配置 配置流程 配置参考URL:https://developers.weixin.qq.com/miniprogram/dev/api/api-ne ...
- 005.SMB之user级别配置
一 配置文件修改 注意: 1 share级别权限访问即,无需要用户名和密码才能访问. 2 新版samba4已取消share级别,可默认采用user级别. 1.1 全局配置文件修改 [global] w ...
- 《编写可维护的javascript》读书笔记(上)
最近在读<编写可维护的javascript>这本书,为了加深记忆,简单做个笔记,同时也让没有读过的同学有一个大概的了解. 一.编程风格 程序是写给人读的,所以一个团队的编程风格要保持一致. ...
- Docker化高可用redis集群
最近遇到部分系统因为redis服务挂掉,导致部分服务不可用.所以希望搭建一个redis集群镜像,把原先散落各处的redis服务器统一管理起来,并且保障高可用和故障自动迁移. 一:redis集群分类 大 ...
- Android-Window(一)——初识Window
Android-Window(一)--初识Window 学习自 <Android开发艺术探索> https://blog.csdn.net/qian520ao/article/detail ...