日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(wù)(50元/站),

點(diǎn)擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

背景:一般與服務(wù)端交互頻繁的需求,可以使用輪詢機(jī)制來實(shí)現(xiàn)。然而一些業(yè)務(wù)場景,比如游戲大廳、直播、即時聊天等,這些需求都可以或者說更適合使用長連接來實(shí)現(xiàn),一方面可以減少輪詢帶來的流量浪費(fèi),另一方面可以減少對服務(wù)的請求壓力,同時也可以更實(shí)時的與服務(wù)端進(jìn)行消息交互。

背景知識

HTTP vs WebSocket

名詞解釋

  1. HTTP:是一個用于傳輸超媒體文檔(如html)的應(yīng)用層的無連接、無狀態(tài)協(xié)議。
  2. WebSocket:HTML5開始提供的一種瀏覽器與服務(wù)器進(jìn)行全雙工通訊的網(wǎng)絡(luò)技術(shù),屬于應(yīng)用層協(xié)議,基于TCL傳輸協(xié)議,并復(fù)用HTTP的握手通道。
小程序websocket開發(fā)指南

 

特點(diǎn)

  1. HTTP
  2. WebSocket建立在TCP協(xié)議之上,服務(wù)器端的實(shí)現(xiàn)比較容易;與HTTP協(xié)議有著良好的兼容性。默認(rèn)端口也是80和443,并且握手階段采用HTTP協(xié)議,因此握手時不容易屏蔽,能通過各種HTTP代理服務(wù)器;數(shù)據(jù)格式比較輕量,性能開銷小,通信高效;可以發(fā)送文本(text),也可以發(fā)送二進(jìn)制數(shù)據(jù)(ArrayBuffer);沒有同源限制,客戶端可以與任意服務(wù)器通信;協(xié)議標(biāo)識符是ws(如果加密,則為wss),服務(wù)器網(wǎng)址就是URL;

二進(jìn)制數(shù)組

名詞解釋

  1. ArrayBuffer?對象:代表原始的二進(jìn)制數(shù)據(jù)。代表內(nèi)存中的一段二進(jìn)制數(shù)據(jù),不能直接讀寫,只能通過“視圖”(?TypedArray?和?DataView?)進(jìn)行操作(以指定格式解讀二進(jìn)制數(shù)據(jù))。“視圖”部署了數(shù)組接口,這意味著,可以用數(shù)組的方法操作內(nèi)存。
  2. ?TypedArray?對象:代表確定類型的二進(jìn)制數(shù)據(jù)。用來生成內(nèi)存的視圖,通過9個構(gòu)造函數(shù),可以生成9種數(shù)據(jù)格式的視圖,數(shù)組成員都是同一個數(shù)據(jù)類型,比如:?Unit8Array?:(無符號8位整數(shù))數(shù)組視圖?Int16Array?:(16位整數(shù))數(shù)組視圖?Float32Array?:(32位浮點(diǎn)數(shù))數(shù)組視圖
  • ...
  1. ?DataView?對象:代表不確定類型的二進(jìn)制數(shù)據(jù)。用來生成內(nèi)存的視圖,可以自定義格式和字節(jié)序,比如第一個字節(jié)是?Uint8?(無符號8位整數(shù))、第二個字節(jié)是?Int16?(16位整數(shù))、第三個字節(jié)是?Float32?(32位浮點(diǎn)數(shù))等等,數(shù)據(jù)成員可以是不同的數(shù)據(jù)類型。

舉個栗子

?ArrayBuffer?也是一個構(gòu)造函數(shù),可以分配一段可以存放數(shù)據(jù)的連續(xù)內(nèi)存區(qū)域

var buf = new ArrayBuffer(32); // 生成一段32字節(jié)的內(nèi)存區(qū)域,每個字節(jié)的值默認(rèn)都是0
為了讀寫buf,需要為它指定視圖。
  1. ?DataView?視圖,是一個構(gòu)造函數(shù),需要提供?ArrayBuffer?對象實(shí)例作為參數(shù):
var dataView = new DataView(buf); // 不帶符號的8位整數(shù)格式
dataView.getUnit8(0) // 0
  1. ?TypedArray?視圖,是一組構(gòu)造函數(shù),代表不同的數(shù)據(jù)格式。
var x1 = new Init32Array(buf); // 32位帶符號整數(shù)
x1[0] = 1;
var x2 = new Unit8Array(buf); // 8位不帶符號整數(shù)
x2[0] = 2;
x1[0] // 2 兩個視圖對應(yīng)同一段內(nèi)存,一個視圖修改底層內(nèi)存,會影響另一個視圖
TypedArray(buffer, byteOffset=0, length?)
  • buffer:必需,視圖對應(yīng)的底層?ArrayBuffer?對象
  • byteOffset:可選,視圖開始的字節(jié)序號,默認(rèn)從0開始,必須與所要建立的數(shù)據(jù)類型一致,否則會報錯
var buffer = new ArrayBuffer(8);
var i16 = new Int16Array(buffer, 1);
// Uncaught RangeError: start offset of Int16Array should be a multiple of 2

因?yàn)椋瑤Х柕?6位整數(shù)需要2個字節(jié),所以byteOffset參數(shù)必須能夠被2整除。

  • length:可選,視圖包含的數(shù)據(jù)個數(shù),默認(rèn)直到本段內(nèi)存區(qū)域結(jié)束

note:如果想從任意字節(jié)開始解讀?ArrayBuffer?對象,必須使用?DataView?視圖,因?yàn)?TypedArray?視圖只提供9種固定的解讀格式。

?TypedArray?視圖的構(gòu)造函數(shù),除了接受?ArrayBuffer?實(shí)例作為參數(shù),還可以接受正常數(shù)組作為參數(shù),直接分配內(nèi)存生成底層的?ArrayBuffer?實(shí)例,并同時完成對這段內(nèi)存的賦值。

var typedArray = new Unit8Array([0, 1, 2]);
typedArray.length // 3
typedArray[0] = 5;
typedArray // [5, 1, 2]

總結(jié)

?ArrayBuffer?是一(大)塊內(nèi)存,但不能直接訪問?ArrayBuffer?里面的字節(jié)。?TypedArray?只是一層視圖,本身不儲存數(shù)據(jù),它的數(shù)據(jù)都儲存在底層的?ArrayBuffer?對象之中,要獲取底層對象必須使用buffer屬性。其實(shí)?ArrayBuffer? 跟 ?TypedArray? 是一個東西,前者是一(大)塊內(nèi)存,后者用來訪問這塊內(nèi)存。

Protocol Buffers

我們編碼的目的是將結(jié)構(gòu)化數(shù)據(jù)寫入磁盤或用于網(wǎng)絡(luò)傳輸,以便他人來讀取,寫入方式有多種選擇,比如將數(shù)據(jù)轉(zhuǎn)換為字符串,然后將字符串寫入磁盤。也可以將需要處理的結(jié)構(gòu)化數(shù)據(jù)由 .proto 文件描述,用 Protobuf 編譯器將該文件編譯成目標(biāo)語言。

名詞解釋

Protocol Buffers 是一種輕便高效的結(jié)構(gòu)化數(shù)據(jù)存儲格式,可以用于結(jié)構(gòu)化數(shù)據(jù)串行化,或者說序列化。它很適合做數(shù)據(jù)存儲或 RPC 數(shù)據(jù)交換格式。可用于通訊協(xié)議、數(shù)據(jù)存儲等領(lǐng)域的語言無關(guān)、平臺無關(guān)、可擴(kuò)展的序列化結(jié)構(gòu)數(shù)據(jù)格式。

基本原理

一般情況下,采用靜態(tài)編譯模式,先寫好 .proto 文件,再用 Protobuf 編譯器生成目標(biāo)語言所需要的源代碼文件,將這些生成的代碼和應(yīng)用程序一起編譯。

讀寫數(shù)據(jù)過程是將對象序列化后生成二進(jìn)制數(shù)據(jù)流,寫入一個 fstream 流,從一個 fstream 流中讀取信息并反序列化。

優(yōu)缺點(diǎn)

  • 優(yōu)點(diǎn)

Protocol Buffers 在序列化數(shù)據(jù)方面,它是靈活的,高效的。相比于 XML 來說,Protocol Buffers 更加小巧,更加快速,更加簡單。一旦定義了要處理的數(shù)據(jù)的數(shù)據(jù)結(jié)構(gòu)之后,就可以利用 Protocol Buffers 的代碼生成工具生成相關(guān)的代碼。甚至可以在無需重新部署程序的情況下更新數(shù)據(jù)結(jié)構(gòu)。只需使用 Protobuf 對數(shù)據(jù)結(jié)構(gòu)進(jìn)行一次描述,即可利用各種不同語言或從各種不同數(shù)據(jù)流中對你的結(jié)構(gòu)化數(shù)據(jù)輕松讀寫。

Protocol Buffers 很適合做數(shù)據(jù)存儲或 RPC 數(shù)據(jù)交換格式。可用于通訊協(xié)議、數(shù)據(jù)存儲等領(lǐng)域的語言無關(guān)、平臺無關(guān)、可擴(kuò)展的序列化結(jié)構(gòu)數(shù)據(jù)格式。

  • 缺點(diǎn)

消息結(jié)構(gòu)可讀性不高,序列化后的字節(jié)序列為二進(jìn)制序列不能簡單的分析有效性;

字節(jié)消息通道(Frontier)系統(tǒng)

整體設(shè)計(jì)

為了維護(hù)用戶在線狀態(tài),需要和服務(wù)端保持長連接,決定采用websocket來跟服務(wù)端進(jìn)行通信,同時使用消息通道系統(tǒng)來轉(zhuǎn)發(fā)消息。

時序圖

小程序websocket開發(fā)指南

 

技術(shù)要點(diǎn)

交互協(xié)議

  • connectSocket:創(chuàng)建一個WebSocket連接實(shí)例,并通過返回的?socketTask?操作該連接。
const wsUrl = `${domain}/ws/v2?aid=2493&device_id=${did}&fpid=100&access_key=${access_key}&code=${code}`
let socketTask = tt.connectSocket({
    url: wsUrl,
    protocols: ['p1']
});
  • ?wsUrl?遵循?Frontier?的交互協(xié)議:
  • aid:應(yīng)用id,不是宿主App的appid,由服務(wù)端指定
  • fpid:由服務(wù)端指定
  • device_id:設(shè)備id,服務(wù)端通過aid+userid+did來維護(hù)長連接
  • access_key:用于防止攻擊,一般用md5加密算法生成(?md5.hexMD5(fpid + appkey + did + salt);?)
  • code:調(diào)用?tt.login?獲取的code,服務(wù)端通過 code2Session 可以將其轉(zhuǎn)化為open_id,然后進(jìn)一步轉(zhuǎn)化為user_id用于標(biāo)識用戶的唯一性。
  • note:由于code具有時效性,每次重新建立?websocket?連接時,需要調(diào)用?tt.login?重新獲取code。

數(shù)據(jù)協(xié)議

前面介紹了那么多關(guān)于?Protobuf?的內(nèi)容,小程序的?webSocket?接口發(fā)送數(shù)據(jù)的類型支持?ArrayBuffer?,再加上?Frontier?對?Protobuf?支持得比較好,因此和服務(wù)端商定采用?Protobuf?作為整個長連接的數(shù)據(jù)通信協(xié)議。

想要在小程序中使用?Protobuf?,首先將.proto文件轉(zhuǎn)換成js能解析的json,這樣也比直接使用.proto文件更輕量,可以使用pbjs工具進(jìn)行解析:

  1. 安裝pbjs工具
  • 基于node.js,首先安裝protobufjs
$ npm install -g protobufjs
  • 安裝 pbjs需要的庫 命令行執(zhí)行下“pbjs”就ok
$ pbjs
  1. 使用pbjs轉(zhuǎn)換.proto文件
  • 和服務(wù)端約定好的.proto文件
// awesome.proto
package wenlipackage;
syntax = "proto2";
message Header {
required string key = 1;
required string value = 2;
}
message Frame {
required uint64 SeqID = 1;
required uint64 LogID = 2; 
required int32 service = 3;
required int32 method = 4;
repeated Header headers = 5;
optional string payload_encoding = 6;
optional string payload_type = 7;
optional bytes payload = 8;
}
  • 轉(zhuǎn)換awesome.proto文件
$ pbjs -t json awesome.proto > awesome.json

生成如下的awesom.json文件:

{
"nested": {
"wenlipackage": {
"nested": {
"Header": {
"fields": {
            ...
          }
        },
"Frame": {
"fields": {
            ...
          }
        }
      }
    }
  }
}
  • 此時的json文件還不能直接使用,必須采用?module.exports?的方式將其導(dǎo)出去,可生成如下的awesome.js文件供小程序引用。
module.exports = {
"nested": {
"wenlipackage": {
"nested": {
"Header": {
"fields": {
            ...
          }
        },
"Frame": {
"fields": {
            ...
          }
        }
      }
    }
  }
}
  1. 采用Protobuf庫編/解碼數(shù)據(jù)
// 引入protobuf模塊
import * as protobuf from './weichatPb/protobuf'; 
// 加載awesome.proto對應(yīng)的json
import awesomeConfig from './awesome.js'; 
// 加載JSON descriptor
const AwesomeRoot = protobuf.Root.fromJSON(awesomeConfig);
// Message類,.proto文件中定義了Frame是消息主體
const AwesomeMessage = AwesomeRoot.lookupType("Frame");
const payload = {test: "123"};
const message = AwesomeMessage.create(payload);
const array = AwesomeMessage.encode(message).finish();
// unit8Array => ArrayBuffer
const enMessage = array.buffer.slice(array.byteOffset, array.byteLength + array.byteOffset)
console.log("encodeMessage", enMessage);
// buffer 表示通過小程序this.socketTask.onMessage((msg) => {});接收到的數(shù)據(jù)
const deMessage = AwesomeMessage.decode(new Uint8Array(buffer));
console.log("decodeMessage", deMessage);

消息通信

一個?websocket?實(shí)例的生成需要經(jīng)過以下步驟:

  1. 建立連接
  • 建立連接后會返回一個websoket實(shí)例
  1. 連接打開
  • 連接建立->連接打開是一個異步的過程,在這段時間內(nèi)是監(jiān)聽不到消息,更是無法發(fā)送消息的
  1. 監(jiān)聽消息
  • 監(jiān)聽的時機(jī)比較關(guān)鍵,只有當(dāng)連接建立并生成websocket實(shí)例后才能監(jiān)聽
  1. 發(fā)送消息
  • 發(fā)送當(dāng)時機(jī)也很關(guān)鍵,只有當(dāng)連接真正打開后才能發(fā)送消息

將小程序WebSocket的一些功能封裝成一個類,里面包括建立連接、監(jiān)聽消息、發(fā)送消息、心跳檢測、斷線重連等等常用的功能。

  1. 封裝websocket類
export default class websocket {
constructor({ heartCheck, isReconnection }) {
this.socketTask = null;// websocket實(shí)例
this._isLogin = false;// 是否連接
this._netWork = true;// 當(dāng)前網(wǎng)絡(luò)狀態(tài)
this._isClosed = false;// 是否人為退出
this._timeout = 10000;// 心跳檢測頻率
this._timeoutObj = null;
this._connectNum = 0;// 當(dāng)前重連次數(shù)
this._reConnectTimer = null;
this._heartCheck = heartCheck;// 心跳檢測和斷線重連開關(guān),true為啟用,false為關(guān)閉
this._isReconnection = isReconnection;
  }
  _reset() {}// 心跳重置
_start() {} // 心跳開始
  onSocketClosed(options) {}  // 監(jiān)聽websocket連接關(guān)閉
  onSocketError(options) {}  // 監(jiān)聽websocket連接關(guān)閉
  onNetworkChange(options) {}  // 檢測網(wǎng)絡(luò)變化
  _onSocketOpened() {}  // 監(jiān)聽websocket連接打開
  onReceivedMsg(callBack) {}  // 接收服務(wù)器返回的消息
  initWebSocket(options) {}  // 建立websocket連接
  sendWebSocketMsg(options) {}  // 發(fā)送websocket消息
  _reConnect(options) {}  // 重連方法,會根據(jù)時間頻率越來越慢
  closeWebSocket(){}  // 關(guān)閉websocket連接
}

 

  1. 多個page使用同一個?websocket?對象

引入?vuex?維護(hù)一個全局?websocket?對象?globalWebsocket?,通過?mapMutations?的?changeGlobalWebsocket?方法改變?nèi)?websocket?對象:

methods: {
    ...mapMutations(['changeGlobalWebsocket']),
    linkWebsocket(websocketUrl) {
// 建立連接
this.websocket.initWebSocket({
        url: websocketUrl,
success(res) { console.log('連接建立成功', res) },
fail(err) { console.log('連接建立失敗', err) },
complate: (res) => {
this.changeGlobalWebsocket(res);
        }
      })
    }
}
  • 通過WebSocket類建立連接,將tt.connectSocket返回的websocket實(shí)例透傳出來,全局共享。
computed: {
    ...mapState(['globalWebsocket']),
    newGlobalWebsocket() {
// 只有當(dāng)連接建立并生成websocket實(shí)例后才能監(jiān)聽
if (this.globalWebsocket && this.globalWebsocket.socketTask) {
if (!this.hasListen) {
this.globalWebsocket.onReceivedMsg((res, data) => {
// 處理服務(wù)端發(fā)來的各類消息
this.handleServiceMsg(res, data);
          });
this.hasListen = true;
        }
if (this.globalWebsocket.socketTask.readyState === 1) {
// 當(dāng)連接真正打開后才能發(fā)送消息
        }
      }
return this.globalWebsocket;
    },
},
watch: {
    newGlobalWebsocket(newVal, oldVal) {
if(oldVal && newVal.socketTask && newVal.socketTask !== oldVal.socketTask) {
// 重新監(jiān)聽
this.globalWebsocket.onReceivedMsg((res, data) => {
this.handleServiceMsg(res, data);
        });
      }
    },
  },

由于需要監(jiān)聽?websocket?的連接與斷開,因此需要新生成一個computed屬性?newGlobalWebsocket?,直接返回全局的?globalWebsocket?對象,這樣才能watch到它的變化,并且在重新監(jiān)聽的時候需要控制好條件,只有?globalWebsocket?對象socketTask真正發(fā)生改變的時候才進(jìn)行重新監(jiān)聽邏輯,否則會收到重復(fù)的消息。

問題總結(jié)

  1. 直接引入google官方Protobuf庫(protobuf.js)將json => pb,在開發(fā)者工具能正常使用,真機(jī)卻報錯:
小程序websocket開發(fā)指南

 


小程序websocket開發(fā)指南

 

原因是protobufjs 代碼里面有用到 Function() {} 來執(zhí)行一段代碼,在小程序中Function 和 eval 相關(guān)的動態(tài)執(zhí)行代碼方式都給屏蔽了,是不允許開發(fā)者使用的,導(dǎo)致這個庫不能正常使用。

解決辦法:搜了一圈github,找到有人專門針對這個問題,修改了dcodeIO 的protobuf.js部分實(shí)現(xiàn)方式,寫了一個能在小程序中運(yùn)行的 protobuf.js 。

  1. ?ArrayBuffer? vs ?Unit8Array? 到底是個什么關(guān)系??!
  • 受小程序框架、protobuf.js工具以及Frontier系統(tǒng)限制,發(fā)送消息和接收消息的格式如下
小程序websocket開發(fā)指南

 


小程序websocket開發(fā)指南

 

可以看到:

  • 發(fā)送消息經(jīng)過protobuf.js編碼后的消息是?Unit8Array?格式的
  • 接收到的服務(wù)器原始消息是?ArrayBuffer?格式的

上文介紹了?TyedArray?和?ArrayBuffer?的區(qū)別,?Unit8Array?是?TypedArray?對象的一種類型,用來表示?ArrayBuffer?的視圖,用來讀寫?ArrayBuffer?,要訪問?ArrayBuffer?的底層對象,必須使用?Unit8Array?的buffer屬性。

  • 一開始跟服務(wù)端調(diào)websocket的連通性,發(fā)現(xiàn)用?AwesomeMessage.decode?解析服務(wù)端消息會解析失敗:
小程序websocket開發(fā)指南

 

const msg = xxx; // ArrayBuffer類型
const res = AwesomeMessage.decode(msg); // 直接解析ArrayBuffer會報錯
const res = AwesomeMessage.decode(new Uint8Array(msg)); // ArrayBuffer => Unit8Array => decode => JSON

原因是原始msg是?ArrayBuffer?類型,protobuf.js在解碼的時候限制了類型是?TypedArray?類型,否則解析失敗,因此需要將其轉(zhuǎn)換為?TypedArray?對象,選擇?Uint8Array?子類型,才能解析成前端能讀取的json對象。

  • 在開發(fā)者工具調(diào)通協(xié)議后,轉(zhuǎn)到真機(jī),發(fā)現(xiàn)后端解析不了前端發(fā)的消息:
小程序websocket開發(fā)指南

 


小程序websocket開發(fā)指南

 

【開發(fā)者工具抓包消息】

小程序websocket開發(fā)指南

 

【真機(jī)抓包消息】

抓包發(fā)現(xiàn)在開發(fā)者工具發(fā)送的消息是二進(jìn)制(Binary)類型的,真機(jī)卻是文本(Text)類型,這就很奇怪了,仔細(xì)翻了下小程序文檔:

小程序websocket開發(fā)指南

 

小程序框架對發(fā)送的消息類型進(jìn)行了限制,只能是string(Text)或arraybuffer(Binary)類型的,真機(jī)為啥被轉(zhuǎn)成了text類型呢,首先肯定不是主動發(fā)送的string類型,一種可能就是發(fā)送的消息不是arraybuffer類型,默認(rèn)被轉(zhuǎn)成了string。看了下代碼:

const encodeMsg = (msg) => {
const message = AwesomeMessage.create(msg);
const array = AwesomeMessage.encode(message).finish();// unit8Array
return array;
};

發(fā)現(xiàn)發(fā)送的類型直接是?Unit8Array?,開發(fā)者工具沒有對其進(jìn)行轉(zhuǎn)換,這個數(shù)據(jù)是能直接被服務(wù)端解析的,然而在真機(jī)被轉(zhuǎn)換成了String,導(dǎo)致服務(wù)端解析不了,更改代碼,將?Unit8Array?轉(zhuǎn)換成?ArrayBuffer?,問題得到解決,在真機(jī)和開發(fā)者工具都正常:

const encodeMsg = (msg) => {
const message = AwesomeMessage.create(msg);
const array = AwesomeMessage.encode(message).finish();
  console.log('加密后即將發(fā)送的消息', array);
// unit8Array => ArrayBuffer,只支持ArrayBuffer
return array.buffer.slice(array.byteOffset, array.byteLength + array.byteOffset)
};

其實(shí)還發(fā)現(xiàn)一個現(xiàn)象:

小程序websocket開發(fā)指南

 

即收到的服務(wù)端原始消息最外層是?ArrayBuffer?類型的,解密后的業(yè)務(wù)數(shù)據(jù)payload卻是?Unit8Array?類型的,結(jié)合發(fā)送消息時encdoe后的類型也是?Unit8Array?類型,得出如下結(jié)論:

  • protobuf.js庫和Frontier對數(shù)據(jù)的處理是以?Unit8Array?類型為準(zhǔn),服務(wù)端同時支持?ArrayBuffer?和?Unit8Array?兩種類型數(shù)據(jù)的解析;
  • 小程序框架只支持?ArrayBuffer?和?String?類型數(shù)據(jù),其余類型會默認(rèn)當(dāng)成?String?類型;

上述兩個規(guī)則限制導(dǎo)致在數(shù)據(jù)傳輸過程中,需要將數(shù)據(jù)格式轉(zhuǎn)成標(biāo)準(zhǔn)的?ArrayBuffer?即小程序框架支持的數(shù)據(jù)格式。

ps:至于為啥開發(fā)者工具和真機(jī)表現(xiàn)不一致,這是因?yàn)殚_發(fā)者工具其實(shí)是一個web,和小程序的運(yùn)行時并不太一樣,同時由于兩者不統(tǒng)一,導(dǎo)致在開發(fā)調(diào)試過程中踩了許多的坑。 ‍♀?

參考文獻(xiàn)

小程序WebSocket接口文檔:

https://developer.toutiao.com/docs/api/connectSocket.html#%E8%BE%93%E5%85%A5

protocol buffers介紹:

https://halfrost.com/protobuf_encode/

 

作者:byte

出處:https://segmentfault.com/a/1190000024456875

分享到:
標(biāo)簽:websocket
用戶無頭像

網(wǎng)友整理

注冊時間:

網(wǎng)站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學(xué)四六

運(yùn)動步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績評定2018-06-03

通用課目體育訓(xùn)練成績評定