工具&環(huán)境
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 (轉(zhuǎn)ts配置)https://www.npmjs.com/package/protoc-gen-ts
無關(guān)知識點
1)package.json
npm項目配置 文檔
npm init //創(chuàng)建默認(rèn)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鸯乃,在生產(chǎn)環(huán)境中需要用到的依賴
devDependencies,在開發(fā)、測試環(huán)境中用到的依賴
在開發(fā)新項目時 運行 npm install
就會根據(jù)配置自動下載安裝依賴文件。
在配置外還有兩種安裝方式
npm install xxx
本地安裝
npm install -g xxx
全局安裝 (默認(rèn)會安裝到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
就會輸出一個我們定義的報錯信息通過這樣的方式可以做很多事情在孝,比如快捷打包,自動執(zhí)行运沦,復(fù)制文件
2)Vscode配置文件 settings.json
自定義一些設(shè)置击孩,文件篩選啊之類的
調(diào)試(debug)和啟動(run)的配置項 launch.json
如果我想調(diào)試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"
}
}
點擊調(diào)試就會自動編譯運行工作空間下Test.ts腳本
如果新項目沒有這個腳本挟冠,添加的方式很多比如
Create a launch.json file
如果想調(diào)試Unity 可以安裝Vscode 插件Debugger for Unity
安裝好之后 選擇創(chuàng)建json.file 在下拉列表中選擇unityDebugger
然后當(dāng)當(dāng)當(dāng)當(dāng)(三聲)于购,多了一堆東西
調(diào)試
運行調(diào)試 運行unity 就可以愉快打斷點調(diào)試了
有可能還要先Attach一下
3)TypeScript 配置文件 tsconfig.json
文檔鏈接
初始化配置文件
1.在跟目錄手動創(chuàng)建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
執(zhí)行
npm run build
就可以以我配置的選項執(zhí)行編譯typescript腳本。
Protobuf 使用(本文主題)
protobuf的概念上面有文檔鏈接
-
創(chuàng)建proto文件
TestData.proto 這就是我們需要的protobuf源文件了
可以下載這個插件,方便編寫protobuf協(xié)議
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生成特定語言能夠使用的數(shù)據(jù)結(jié)構(gòu)
這是我們下載好的編譯文件知染,將bin目錄添加到環(huán)境變量中肋僧,然后控制臺測試 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
然后就可以執(zhí)行
protoc -I=sourcedir --ts_out=dist myproto.proto \\生成ts的protobuf結(jié)構(gòu)
這里有可能會報錯嫌吠,找不到google.protobuf 模塊,但項目里明明有掺炭,可以通過全局安裝
npm install -g google-protobuf
解決 (這里的問題我還沒搞明白辫诅,但暫時這么解決了)
ts-protoc-gen有兩種轉(zhuǎn)化方式
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結(jié)構(gòu)是不同的
TestData.ts
ts的協(xié)議文件包含所有的變量 類型 序列化 反序列化方法。涧狮。炕矮。
/**
* 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 協(xié)議的聲明文件 同樣包含所有的 變量 類型 序列化反序列化方法
// 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 對應(yīng)路徑下的 TestData之后 ,就可以開始使用了
import { TestData } from "./Assets/Proto/TestData_pb";//這里要指向使用的協(xié)議文件者冤,我這里指向的協(xié)議文件
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腳本中創(chuàng)建對應(yīng)的協(xié)議數(shù)據(jù)類型肤视,讀取寫入,序列化之后傳輸涉枫。
但當(dāng)我們將ts編譯為js文件時邢滑,兩種方式的流程有所不同
TestData.ts
main.ts
通過tsc 編譯后,將自動將生成TestData.js腳本,里面包含全部的協(xié)議信息拜银,所以編譯過后殊鞭,能正常調(diào)用,程序正常運行尼桶。
而TestData_pb.ts
由于它只是聲明文件操灿,
所以我們在編程階段可以正常使用方法,添加數(shù)據(jù)而不會報錯泵督,但將主程序編譯之后趾盐,并不會生成對應(yīng)的協(xié)議.js
文件,所以運行程序會出現(xiàn)
所以需要我們主動生成對應(yīng)的
.js
腳本:在生成協(xié)議時同時生成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文件輸出格式及目錄
協(xié)議文件輸入路徑
這樣我們就同時得到聲明文件.d.ts
和.js
文件了,可以正常編譯使用
一般在應(yīng)用場景中,我們更多的使用第二種方式來在ts部分使用protobuf協(xié)議,因為聲明文件更簡潔明了,諸如此類巴拉巴拉(反正用就完事了)
d. 其他語言請查看官方文檔
3 ) proto文件有了,對應(yīng)平臺轉(zhuǎn)化的協(xié)議也有了,現(xiàn)在就是使用了藤滥,以Unity為例
將轉(zhuǎn)換的協(xié)議 TestData.cs導(dǎo)入Unity
然后報錯
因為我們項目中缺少 Google.Protobuf
打開github,選擇C#,download
bulidall
將以下文件導(dǎo)入unity plugins\protobuf 目錄
編寫測試代碼
main.cs
using Google.Protobuf; //數(shù)據(jù)轉(zhuǎn)二進制要用到 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);
}
}
結(jié)果
成功融虽!
其他語言也類似,只是序列化和反序列化的方法可以稍微有差別
補充(大概率是我數(shù)據(jù)轉(zhuǎn)換處理基礎(chǔ)太差造成的原因)
這里出現(xiàn)了Cs端Ts端傳輸byte[] 數(shù)據(jù) 協(xié)議轉(zhuǎn)化失敗截亦,沒法正常取數(shù)據(jù)的情況,最后通過Puerts
的ArrayBuffer
將cs端的二進制數(shù)據(jù)先轉(zhuǎn)為ArrayBuffer,最后在ts部分轉(zhuǎn)換為協(xié)議數(shù)據(jù)解決
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));
}
拋開協(xié)議也能通過在ts端通過jsonobj的方式解析數(shù)據(jù),前提是你完全知道協(xié)議內(nèi)容
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"]);
}