工具&环境
1.vscode https://code.visualstudio.com
2.node.js http://nodejs.cn/download/
3.npm(node.js)
4.Google.Protobuf https://github.com/protocolbuffers/protobuf
5.protoc (windows平台编译工具)https://github.com/protocolbuffers/protobuf/releases/download/v3.17.3/protoc-3.17.3-win64.zip
6.protobuf Csharp https://github.com/protocolbuffers/protobuf/tree/master/csharp
7.Protoc Gen Typescript (转ts配置)https://www.npmjs.com/package/protoc-gen-ts
无关知识点
1)package.json
npm项目配置 文档
npm init //创建默认package.json配置
以下是我本地项目配置
{
"name": "protobufdemo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"google-protobuf": "^3.17.0",
"ts-node": "^10.2.1",
"typescript": "^4.3.5"
},
"devDependencies": {
"source-map-support": "^0.5.19",
"@types/google-protobuf": "^3.15.2",
"ts-protoc-gen": "^0.15.1-pre.a71b34e",
"@swc-node/core": "^1.3.0",
"fs-extra": "^9.1.0",
"uglify-js": "^3.13.1"
}
}
注意两种依赖
dependencies,在生产环境中需要用到的依赖
devDependencies,在开发、测试环境中用到的依赖
在开发新项目时 运行 npm install
就会根据配置自动下载安装依赖文件。
在配置外还有两种安装方式
npm install xxx
本地安装
npm install -g xxx
全局安装 (默认会安装到npm全局目录下:C:\Users\jsfa\AppData\Roaming\npm\node_modules
)
如果安装时同时还想将module添加到package依赖 npm install xxx -save
添加到Dev的命令类似 npm install xxx -save -dev
自定义npm 命令 script
在package目录控制台命令
npm run test
就会输出一个我们定义的报错信息通过这样的方式可以做很多事情,比如快捷打包,自动执行,复制文件
2)Vscode配置文件 settings.json
自定义一些设置,文件筛选啊之类的
调试(debug)和启动(run)的配置项 launch.json
如果我想调试typescript代码,添加下面的代码
"version": "0.2.0",
"configurations": [
{
"name": "Current TS File",
"type": "node",
"request": "launch",
"args": [
"${workspaceRoot}/TsProj/Test/Test.ts"
],
"runtimeArgs": [
"--nolazy",
"-r",
"ts-node/register"
],
"sourceMaps": true,
"cwd": "${workspaceRoot}/TsProj",
"protocol": "inspector",
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen"
}
}
点击调试就会自动编译运行工作空间下Test.ts脚本
如果新项目没有这个脚本,添加的方式很多比如
Create a launch.json file
如果想调试Unity 可以安装Vscode 插件Debugger for Unity
安装好之后 选择创建json.file 在下拉列表中选择unityDebugger
然后当当当当(三声),多了一堆东西
调试
运行调试 运行unity 就可以愉快打断点调试了
有可能还要先Attach一下
3)TypeScript 配置文件 tsconfig.json
文档链接
初始化配置文件
1.在跟目录手动创建tsconfig.json文件
2控制台运行 tsc --init
命令
这是我本地项目配置
{
"compilerOptions": {
"target": "esnext",
"module": "commonjs",
"jsx": "react",
"sourceMap": true,
"noImplicitAny": true,
"typeRoots": [
"../Assets/Puerts/Typing",
"../Assets/Gen/Typing",
"./node_modules/@types"
],
"outDir": "dist",
"suppressImplicitAnyIndexErrors": true,
"experimentalDecorators": true
}
}
还记得上面package配置里的Scripts: build
执行
npm run build
就可以以我配置的选项执行编译typescript脚本。
Protobuf 使用(本文主题)
protobuf的概念上面有文档链接
-
创建proto文件
TestData.proto 这就是我们需要的protobuf源文件了
可以下载这个插件,方便编写protobuf协议
syntax = "proto3";
option csharp_namespace = "Dx";
message TestData {
string name = 1;
int32 id = 2; // Unique ID number for this person.
string email = 3;
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
message PhoneNumber {
string number = 1;
PhoneType type = 2;
}
repeated PhoneNumber phones = 4;
}
- 将.proto生成特定语言能够使用的数据结构
这是我们下载好的编译文件,将bin目录添加到环境变量中,然后控制台测试 protoc --version
在vs目录也好,控制台也好
a.C#
protoc --csharp_out=输出目录 源文件目录\*.proto
b.js
protoc --js_out=输出目录 源文件目录\*.proto
c.TypeScript
这个比较特殊 我还没找到原有的生成方式,所以这里要用到Protoc Gen Typescript
可以通过npm直接安装
npm install ts-protoc-gen
然后就可以执行
protoc -I=sourcedir --ts_out=dist myproto.proto \\生成ts的protobuf结构
这里有可能会报错,找不到google.protobuf 模块,但项目里明明有,可以通过全局安装
npm install -g google-protobuf
解决 (这里的问题我还没搞明白,但暂时这么解决了)
ts-protoc-gen有两种转化方式
protoc --ts_out=.\Assets\Proto .\Assets\Proto\TestData.proto
protoc --plugin=protoc-gen-ts=.\node_modules\ts-protoc-gen\bin\protoc-gen-ts.cmd --proto_path=. --ts_out=.\Assets\Proto .\Assets\Proto\TestData.proto
这两种方式生成的ts结构是不同的
TestData.ts
ts的协议文件包含所有的变量 类型 序列化 反序列化方法。。。
/**
* Generated by the protoc-gen-ts. DO NOT EDIT!
* compiler version: 3.7.1
* source: Assets/Proto/TestData.proto
* git: https://github.com/thesayyn/protoc-gen-ts
* buymeacoffee: https://www.buymeacoffee.com/thesayyn
* */
import * as pb_1 from "google-protobuf";
export class TestData extends pb_1.Message {
constructor(data?: any[] | {
name?: string;
id?: number;
email?: string;
phones?: TestData.PhoneNumber[];
}) {
super();
pb_1.Message.initialize(this, Array.isArray(data) ? data : [], 0, -1, [4], []);
if (!Array.isArray(data) && typeof data == "object") {
if ("name" in data && data.name != undefined) {
this.name = data.name;
}
if ("id" in data && data.id != undefined) {
this.id = data.id;
}
if ("email" in data && data.email != undefined) {
this.email = data.email;
}
if ("phones" in data && data.phones != undefined) {
this.phones = data.phones;
}
}
}
get name() {
return pb_1.Message.getField(this, 1) as string;
}
set name(value: string) {
pb_1.Message.setField(this, 1, value);
}
get id() {
return pb_1.Message.getField(this, 2) as number;
}
set id(value: number) {
pb_1.Message.setField(this, 2, value);
}
get email() {
return pb_1.Message.getField(this, 3) as string;
}
set email(value: string) {
pb_1.Message.setField(this, 3, value);
}
get phones() {
return pb_1.Message.getRepeatedWrapperField(this, TestData.PhoneNumber, 4) as TestData.PhoneNumber[];
}
set phones(value: TestData.PhoneNumber[]) {
pb_1.Message.setRepeatedWrapperField(this, 4, value);
}
...还有好长好长的代码...
serializeBinary(): Uint8Array {
return this.serialize();
}
static deserializeBinary(bytes: Uint8Array): PhoneNumber {
return PhoneNumber.deserialize(bytes);
}
}
}
TestData_pb.ts
ts 协议的声明文件 同样包含所有的 变量 类型 序列化反序列化方法
// package:
// file: TestData.proto
import * as jspb from "google-protobuf";
export class TestData extends jspb.Message {
getName(): string;
setName(value: string): void;
getId(): number;
setId(value: number): void;
getEmail(): string;
setEmail(value: string): void;
clearPhonesList(): void;
getPhonesList(): Array<TestData.PhoneNumber>;
setPhonesList(value: Array<TestData.PhoneNumber>): void;
addPhones(value?: TestData.PhoneNumber, index?: number): TestData.PhoneNumber;
serializeBinary(): Uint8Array;
toObject(includeInstance?: boolean): TestData.AsObject;
static toObject(includeInstance: boolean, msg: TestData): TestData.AsObject;
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
static serializeBinaryToWriter(message: TestData, writer: jspb.BinaryWriter): void;
static deserializeBinary(bytes: Uint8Array): TestData;
static deserializeBinaryFromReader(message: TestData, reader: jspb.BinaryReader): TestData;
}
export namespace TestData {
export type AsObject = {
name: string,
id: number,
email: string,
phonesList: Array<TestData.PhoneNumber.AsObject>,
}
export class PhoneNumber extends jspb.Message {
getNumber(): string;
setNumber(value: string): void;
getType(): TestData.PhoneTypeMap[keyof TestData.PhoneTypeMap];
setType(value: TestData.PhoneTypeMap[keyof TestData.PhoneTypeMap]): void;
serializeBinary(): Uint8Array;
toObject(includeInstance?: boolean): PhoneNumber.AsObject;
static toObject(includeInstance: boolean, msg: PhoneNumber): PhoneNumber.AsObject;
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
static serializeBinaryToWriter(message: PhoneNumber, writer: jspb.BinaryWriter): void;
static deserializeBinary(bytes: Uint8Array): PhoneNumber;
static deserializeBinaryFromReader(message: PhoneNumber, reader: jspb.BinaryReader): PhoneNumber;
}
export namespace PhoneNumber {
export type AsObject = {
number: string,
type: TestData.PhoneTypeMap[keyof TestData.PhoneTypeMap],
}
}
export interface PhoneTypeMap {
MOBILE: 0;
HOME: 1;
WORK: 2;
}
export const PhoneType: PhoneTypeMap;
}
在import 对应路径下的 TestData之后 ,就可以开始使用了
import { TestData } from "./Assets/Proto/TestData_pb";//这里要指向使用的协议文件,我这里指向的协议文件
class main{
public x:TestData=new TestData();
constructor(id:number,name:string){
this.x.setId(id);
this.x.setName(name);
}
}
function Read(data:ArrayBuffer){
let ret = TestData.deserializeBinary(new Uint8Array(data));
console.log(ret.getId());
console.log(ret.getName());
}
let m = new main(12,"dx");
Read(m.x.serializeBinary());
我们可以直接在ts脚本中创建对应的协议数据类型,读取写入,序列化之后传输。
但当我们将ts编译为js文件时,两种方式的流程有所不同
TestData.ts
main.ts
通过tsc 编译后,将自动将生成TestData.js脚本,里面包含全部的协议信息,所以编译过后,能正常调用,程序正常运行。
而TestData_pb.ts
由于它只是声明文件,
所以我们在编程阶段可以正常使用方法,添加数据而不会报错,但将主程序编译之后,并不会生成对应的协议.js
文件,所以运行程序会出现
所以需要我们主动生成对应的
.js
脚本:在生成协议时同时生成js脚本
D:\SoftWare\protoc-3.7.1-win64\bin\protoc.exe --plugin=protoc-gen-ts=D:\Project\UnityProject\ProtobufDemo\node_modules/.bin/protoc-gen-ts.cmd --proto_path=. --ts_out=. --js_out=import_style=commonjs,binary:. .\TestData.proto
程序目录\protoc.exe
--plugin=protoc-gen-ts=
protoc-gen-ts.cmd目录\protoc-gen-ts.cmd
--proto_path=
依赖的proto文件目录(import"**.proto"
)
--ts_out=
声明文件输出目录
--js_out=import_style=commonjs,binary:
js文件输出格式及目录
协议文件输入路径
这样我们就同时得到声明文件.d.ts
和.js
文件了,可以正常编译使用
一般在应用场景中,我们更多的使用第二种方式来在ts部分使用protobuf协议,因为声明文件更简洁明了,诸如此类巴拉巴拉(反正用就完事了)
d. 其他语言请查看官方文档
3 ) proto文件有了,对应平台转化的协议也有了,现在就是使用了,以Unity为例
将转换的协议 TestData.cs导入Unity
然后报错
因为我们项目中缺少 Google.Protobuf
打开github,选择C#,download
bulidall
将以下文件导入unity plugins\protobuf 目录
编写测试代码
main.cs
using Google.Protobuf; //数据转二进制要用到 ToByteArray() 方法
public class Main : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
TestData data = new TestData();
data.Name="dx";
data.Id=1234;
var s = data.ToByteArray();
SendS(s);
}
public void SendS(byte[] data){
Debug.Log(data);
RecvS(data);
}
public void RecvS(byte[] data){
TestData rData = TestData.Parser.ParseFrom(data);
Debug.Log(rData.Name);
Debug.Log(rData.Id);
}
}
结果
成功!
其他语言也类似,只是序列化和反序列化的方法可以稍微有差别
补充(大概率是我数据转换处理基础太差造成的原因)
这里出现了Cs端Ts端传输byte[] 数据 协议转化失败,没法正常取数据的情况,最后通过Puerts
的ArrayBuffer
将cs端的二进制数据先转为ArrayBuffer,最后在ts部分转换为协议数据解决
public static Puerts.ArrayBuffer GetPlayerRecord()
{
Protocol.PlayerRecord data = PlayerRecord.Instance.GetPlayerRecordPb();
if (data != null) return new Puerts.ArrayBuffer(data.ToByteArray());
return null;
}
export function InitPlayerRecord (data: ArrayBuffer) {
let playerRecord = PlayerRecord .deserializeBinary(new Uint8Array(data));
}
抛开协议也能通过在ts端通过jsonobj的方式解析数据,前提是你完全知道协议内容
let obj = JSON.parse(data);
let mrlist = obj['mapRecords'];
for (let i = 0; i < mrlist.length; i++) {
let record=mrlist[i];
UpdateRecordByJson(record["area"],record["floor"],record["status"],record["time"],record["resetTime"],record["boughttimes"],record["weeklyboughttimes"]);
}