[自己做个游戏服务器]搞清楚游戏通信协议之protobuf的方方面面
最近准备启动写个gameserver的计划,所以开始准备,网络协议方面准备使用protobuf ,这也是现在最流行的,最多使用的,所以今天就写下protobuf,虽然在项目中也一直在使用,但是也只是惯性使然,今天就全面的复习下,避免在使用的时候卡壳。开始吧。
注:因为使用java的缘故,所以我就我的经验写下protobuf在java中的使用,因为其他的语言不是很熟,同时经验不多,但是大同小异。
1、protobuf 环境搭建
1.1 protobuf 的版本
最新版本地址:https://github.com/protocolbuffers/protobuf/releases
Maven 依赖配置,直接放进去,不用想太多
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.18.0</version>
</dependency>
1.2 proto编译器
将主页一直往下拉,可以看到protoc的各种系统版本,根据自己的操作系统选择,我的是64位的win10 ,所以选择了win64,下载后解压就好,等会使用
2、protobuf 语法
proto3 的语法文档 :https://developers.google.com/protocol-buffers/docs/proto3
syntax = "proto3";
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 result_per_page = 3;
}
2.1 注释规则
为你的.proto 添加注释,使用 C/C++风格的 // 或者 /* ... */ 语法.
/* SearchRequest represents a search query, with pagination options to
* indicate which results to include in the response. */
message SearchRequest {
string query = 1;
int32 page_number = 2; // Which page number do we want?
int32 result_per_page = 3; // Number of results to return per page.
}
2.2 数据类型
基本上也都是常规的数据类型
数值: int32,int64,float ,double ,s开头(可变的,也就是编码的时候更紧凑),u开头(也就是无符号的,只能是正值),fix(固定长度的,一般不怎么用)
布尔值:bool
字符串:string
枚举 : enum
列表:repeated
二进制:bytes
基本上常用的也就是这些简单的类型,没什么特殊的。
2.3 默认值规则
定义了数据类型的,在不传的时候的默认值的规则是什么,下面以Java 为主
-
string:认值是”“
-
bool : 默认值 是false
-
数值类型:默认值是0,0.f或者0.D.
-
枚举:默认值是第一个枚举值,也就是0
-
message 符合类型:在Java中时null.
message SearchResponse { repeated Result results = 1; } message Result { string url = 1; string title = 2; repeated string snippets = 3; }
2.4 protobuf 选项
protobuf 提供了一些选项设置生成的proto文件
option java_package = "com.example.foo";
option java_outer_classname = "Ponycopter";
option java_multiple_files = true;
option optimize_for = CODE_SIZE;
option java_package = "com.example.foo";
生成的java文件所在的包名
option java_outer_classname = "Ponycopter";
生成的类的名字
option java_multiple_files = true;
如果是false 则整个proto文件生成在一个java文件中,true 则一个message 生成一个java文件,注:不写的时候默认是false
option optimize_for = CODE_SIZE;
可以设置为 SPEED
, CODE_SIZE
, LITE_RUNTIM
SPEED : 代码的序列和转换等速度优先
CODE_SIZE:优化生成的代码大小
LITE_RUNTIME:生成的代码需要更少的运行时,一般不选用此选项。
2.5 protoc 生成java文件
拷贝你下载的protoc.exe 到你的proto 文件的所在地址,在文件夹shfit + 右键 ,选择打开cmd 窗口 运行下面的
protoc --java_out=./ XX.proto
--java_out
就是生成的输出地址
xx.proto
就是你要编译的proto 文件
3、idea 生成插件
java中开发最常用的就是IDEA 了,因为idea 的强大插件体系是真的好用,protobuf 的开发在idea中也是很方便,这里推荐两个proto的插件,助你在开发的时候如虎添翼。
3.1 GenProtobuf
生成proto 每次都输入命令有点烦的,所以有人写了插件genprotobuf ,在idea中点点就可以了,
安装:很简单,File -> Settings->Plugins,然后点击install 就可以了,等安装完成后就可以了
配置:Tools -> Config GenProtobuf
生成java文件:选择需要生成的proto文件,然后选择 quick gen protobuf rules ,可以按上一步配置的规则生成java文件
3.2 protobuf
protobuf 插件是支持proto文件的语法,对关键字进行高亮
4、序列化和 反序列化
4.1 常规序列化和反序列化
常规的使用
SimpleMessage.Builder builder = SimpleMessage.newBuilder();
builder.setName("香菜");
builder.setId(1);
builder.setIsSimple(true);
byte[] result=builder.build().toByteArray();//序列化
SimpleMessage msg;
try {
msg = SimpleMessage.parseFrom(result);
System.out.println(msg);
} catch (InvalidProtocolBufferException e) {
e.printStackTrace();
}
4.2 通用的反序列化
使用parser 进行反序列化,保存每个消息的parser,可以在启动的时候对消息进行扫描,将消息id 对应的协议进行保存
import com.google.protobuf.Parser;
import com.xin.msg.login.SimpleMessage;
import java.util.HashMap;
import java.util.Map;
public class MsgMgr {
public static Map<Integer, Parser> msgMap = new HashMap<>();
static {
// msgId - parser
msgMap.put(1, SimpleMessage.parser());
}
}
在进行解析的时候根据消息Id获取对应的parser 转换为 对应的消息,统一转换
Parser parser = MsgMgr.msgMap.get(headId);
int canReadBytes = decode.readableBytes();
byte[] data = new byte[canReadBytes];
decode.readBytes(data);
Object o = parser.parseFrom(data);
5、protostuff
使用protobuf 需要写proto 文件,这个文件是为了和客户端同步,其实有点烦,再把它编译成目标语言,这样使用起来就很麻烦。但是现在有了protostuff之后,就不需要依赖.proto文件了,他可以直接对POJO进行序列化和反序列化,使用起来非常简单。
github 地址 :https://github.com/protostuff/protostuff
maven 依赖,加到你的pom.xml就可以了
<dependency>
<groupId>io.protostuff</groupId>
<artifactId>protostuff-core</artifactId>
<version>1.7.4</version>
</dependency>
<dependency>
<groupId>io.protostuff</groupId>
<artifactId>protostuff-runtime</artifactId>
<version>1.7.4</version>
</dependency>
实例;
public final class Foo
{
String name;
int id;
public Foo(String name, int id)
{
this.name = name;
this.id = id;
}
}
static void roundTrip()
{
Foo foo = new Foo("foo", 1);
// this is lazily created and cached by RuntimeSchema
// so its safe to call RuntimeSchema.getSchema(Foo.class) over and over
// The getSchema method is also thread-safe
Schema<Foo> schema = RuntimeSchema.getSchema(Foo.class);
// Re-use (manage) this buffer to avoid allocating on every serialization
LinkedBuffer buffer = LinkedBuffer.allocate(512);
// ser
final byte[] protostuff;
try
{
protostuff = ProtostuffIOUtil.toByteArray(foo, schema, buffer);
}
finally
{
buffer.clear();
}
// deser
Foo fooParsed = schema.newMessage();
ProtostuffIOUtil.mergeFrom(protostuff, fooParsed, schema);
}
protostuff 就不具体介绍了,官方地址也贴上了,同时我也没在项目中使用,原因就是proto 文件还要发给客户短使用,proto是和客户端进行协议沟通的桥梁,要不然就要客户端自己写proto文件了,容易造成协议不一致。
6、总结
- 点赞
- 收藏
- 关注作者
评论(0)