實現(xiàn)微信小程序編譯和運行環(huán)境系列 (核心篇二)
動手實現(xiàn)微信小程序和小游戲編譯打包和運行環(huán)境平臺 (核心篇二)
在上文中我們有點到小程序開發(fā)者工具里面的消息是通過 websocket 協(xié)議發(fā)送和接受處理的,當然這個不是憑空而說的,是在小程序的邏輯層 appservice.js 源碼里面有代碼表明的,至于它的消息格式還有一部分我沒有列出來,比如它的數(shù)據(jù)分析和上報他們自己服務器的一些消息格式可以先需要關注。
下面還是先給大家展示一下流程找到 appservice.js 源碼文件
可以看到它的鏈接地址,數(shù)據(jù)發(fā)送和接收的部分代碼,由于圖片尺寸問題我折疊了部分代碼,大家可以自己去細看看。
我還是先簡述一些 webstocket 的知識,可能部分同學對這方面不是很熟悉。細節(jié) webstocket 內容不會在本文描述,后期會寫一篇專門的介紹。
websocket 是什么
其實這些內容我們通過谷歌搜索可以查閱很多材料,但有沒有真正理解可以在自己項目里進行靈活設計運用還是只是簡單使用文檔 api,還是要靠自己多探索思考一些。
下圖為webstockrt協(xié)議:
可以理解為:WebSocket 協(xié)議允許在運行于受控環(huán)境中的不受信任代碼的用戶代理與已選擇從該代碼進行通信的遠程主機之間進行雙向通信。簡單點描述就是:客戶端和服務器之間存在持久連接,而且雙方都可以隨時隨地相互發(fā)送數(shù)據(jù)
為什么用 websocket
一項新規(guī)范或者一門新技術的誕生肯定是為了解決或者完善前面方案的不足,這樣才能一直進步下去。
在沒有 websocket 之前我們采用 http 用的很好,但是隨著一些應用的要求像聊天 股票 游戲 這種對實時性數(shù)據(jù)要求高的系統(tǒng),才用 HTTP 協(xié)議發(fā)送數(shù)據(jù)的話只能有客戶端單方面進行請求,服務端響應獲取最新數(shù)據(jù),如果服務端的數(shù)據(jù)變換很快比如股票的信息,因此只能定時去請求,就出出現(xiàn)效率低 浪費資源而且數(shù)據(jù)還不實時同步的情況,為了解決這些問題通過研究 websocket 協(xié)議就閃亮登場了。
websocket 具備的一些優(yōu)點:
支持雙向通信,具有很強的實時性
對二進制的支持比較友好
相比與 http 協(xié)議的控制開銷要少很多
用戶可以自由的擴展協(xié)議,自定義子協(xié)議例如(wss)
如何使用 websocket
這個點比較廣泛一個新方案新技術的產生都會經(jīng)過由淺入深的過程發(fā)展,主要看大家門自己的具體設計和使用了,下面一些鏈接知識點可以讓大家先了解這個概念和基礎使用,本章節(jié)不在這里衍生更多 websocket 相關內容。
(大家如果想對 websocket 深入學習感興趣 希望可以關注我后面的 websocket 專欄文章)
webStocket Api(https://html.spec.whatwg.org/multipage/web-sockets.html)
[MDN]
如果想在線測試的話可以試下這個websocket demo(http://websocket.org/echo.html)
這個是一個比較簡單的可以在線看效果的網(wǎng)頁
如果有同學希望自己動手試試的話,我在自己的 github 倉庫寫了一個最簡化的服務端和客戶端的案例,一共 10 多行代碼比較方便,有興趣的朋友可以看下案例地址(https://github.com/gongmw/js-example/tree/master/websocket)
執(zhí)行 index.js 后效果如下:
下面的內容我會結合在實現(xiàn)這個小程序運行環(huán)境里面的對于 websocket 的一些運用設計和部分代碼展示。
我們回到主題先在源碼 appservice.js 的發(fā)送和接收的地方添加了一些日志保存,這里一定要徹底退出工具進程在打開不然是不起作用的。
然后我們重新進入開發(fā)者工具打開一個小程序項目,我打開的是一個官方的云開發(fā)項目例子可以看到:
通過這個圖我們可以看出一些信息先給大家簡單介紹一下:
send===>{"command":"APPSERVICE_INVOKE","data":{"api":"operateWXData","args":{"data":{"api_name":"qbase_commapi","data":{"qbase_api_name":"tcbapi_init","qbase_req":"{\"trace_user\":true}","qbase_options":{},"qbase_meta":{"session_id":"1587696384156","sdk_version":"wx-miniprogram-sdk/2.9.5 (1578926697000)"},"cli_req_id":"1587696386661_0.5287857917854695"},"operate_directly":false},"isImportant":false,"requestInQueue":false,"apiName":"qbase_commapi","reqData":{"qbase_api_name":"tcbapi_init","qbase_req":"{\"trace_user\":true}","qbase_options":{},"qbase_meta":{"session_id":"1587696384156","sdk_version":"wx-miniprogram-sdk/2.9.5 (1578926697000)"},"cli_req_id":"1587696386661_0.5287857917854695"}},"callbackID":20}}
可以觀察到一些字段和對象(這個是一個普通云開發(fā)項目默認打開的時候的狀態(tài),不做任何操作是個例子對象是比較復雜的)
command
data
api
args
data
api_name
qbase_api_name
qbase_req
callbackID
看到這個 api operateWXData 可能大家不是很熟悉,因為這個 api 微信沒有對外的是內部使用的,這個不是我們現(xiàn)在要講的重點,我們現(xiàn)在要描述的是 webstocket 相關的,至于 api 的實現(xiàn)會在下文如何實現(xiàn)小程序對外 api 來描述講解,我們在這里只要知道他的消息傳輸格式就可以了。
command 消息類型
data 各種數(shù)據(jù)組合
callbackID 標示這個很重要
<====12receive {"command":"APPSERVICE_INVOKE_CALLBACK","data":{"callbackID":20,"res":{"errMsg":"operateWXData:ok","data":{"data":"{\"baseresponse\":{\"errcode\":0,\"stat\":{\"qbase_cost_time\":141}},\"tcb_api_list\":[{\"apiname\":\"tcbapi_db_adddocument\",\"status\":1},{\"apiname\":\"tcbapi_callfunction\",\"status\":1},{\"apiname\":\"tcbapi_component_gettempfileurl\",\"status\":1},{\"apiname\":\"tcbapi_db_countdocument\",\"status\":1},{\"apiname\":\"tcbapi_db_deletedocument\",\"status\":1},{\"apiname\":\"tcbapi_deletefile\",\"status\":1},{\"apiname\":\"tcbapi_downloadfile\",\"status\":1},{\"apiname\":\"tcbapi_gettempfileurl\",\"status\":1},{\"apiname\":\"tcbapi_db_querydocument\",\"status\":1},{\"apiname\":\"tcbapi_db_setdocument\",\"status\":1},{\"apiname\":\"tcbapi_slowcallfunction\",\"status\":1},{\"apiname\":\"tcbapi_slowcallfunction_v2\",\"status\":1},{\"apiname\":\"tcbapi_traceuser\",\"status\":1},{\"apiname\":\"tcbapi_uploadfile\",\"status\":1},{\"apiname\":\"tcbapi_db_updatedocument\",\"status\":1},{\"apiname\":\"tcbapi_init\",\"status\":1}],\"config\":{\"db_doc_size_limit\":524288,\"upload_max_file_size\":52428800,\"get_temp_file_url_max_requests\":50,\"call_function_poll_max_retry\":10,\"call_function_max_req_data_size\":5242880,\"call_function_client_poll_timeout\":15000,\"call_function_valid_start_retry_gap\":100000}}"}}}}
對比可以看出在上面核心篇里面講的內容:
send===> "command":"APPSERVICE_INVOKE" "callbackID":20 receive===>"command":"APPSERVICE_INVOKE_CALLBACK" "callbackID":20
APPSERVICE_INVOKE 的消息類型是 service 層發(fā)送給 service 進行接收處理
代碼實現(xiàn)瀏覽器運行環(huán)境 websocket 服務通信設計
這邊采用 node 方式來啟動的服務先創(chuàng)建一個服務端:
const ws = require('ws'); const EventEmitter = require('events'); class SocketServer extends EventEmitter { constructor (options) { super(); this.port = options.port; this.wss = new ws.Server({ port: this.port }); this.socketClientMap = new SocketClientMap(); } async start () { this.wss.on('connection', ws => { this.socketClientMap.addSocketClient(ws); ws.on('close', () => { this.socketClientMap.removeSocketClient(ws.protocol); }); ws.on('message', async message => { await this.handle(message); }); }); this.on(SEND_MSG_TO_CONTROLLER, (message) => { this.sendMessageToController(message); }); this.on(SEND_MSG_TO_SPECIAL_WEBVIEW, ({ webviewId, message }) => { this.sendMessageToSpecialWebview(webviewId, message); }); this.running = true; }}
創(chuàng)建客戶端鏈接發(fā)送和接收:
const WebSocket = require('ws'); class SocketClient { constructor (ws) { this.ws = ws; this.msgQueue = []; } setWebSocket (ws) { this.ws = ws; this.msgQueue.forEach(msg => { this.ws.send(JSON.stringify(msg)); }); this.msgQueue = []; } removeWebSocket () { this.ws = null; } send (msg) { if (!this.ws || this.ws.readyState !== WebSocket.OPEN) { this.msgQueue.push(msg); } else { this.ws.send(JSON.stringify(msg)); } } }
上面兩個類文件就是比較簡單的服務和客戶端的創(chuàng)建。這里創(chuàng)建了一個 client 集合類:
class SocketClientMap { constructor () { this.socketClients = new Map(); } addSocketClient (ws) { let socketClient = this.socketClients.get(ws.protocol); if (!socketClient) { socketClient = new SocketClient(ws); } else { socketClient.setWebSocket(ws); } this.socketClients.set(ws.protocol, socketClient); } getSocketClient (protocol) { let socketClient = this.socketClients.get(protocol); if (!socketClient) { socketClient = new SocketClient(protocol); this.socketClients.set(protocol, socketClient); } return socketClient; } removeSocketClient (protocol) { this.socketClients.delete(protocol); } loop (cb) { this.socketClients.forEach((value, key) => cb(value, key)); } };
表示如果 SocketClient 不存在,則根據(jù) ws 創(chuàng)建一個新的 SocketClient,否則,將舊的 ws 替換為新的 ws,這樣消息隊列中的消息就可以被替換后立即發(fā)送到新的 ws,保證可用性。
調用這個函數(shù)總是可以返回一個 SocketClient 實例,以便用戶可以在任何時候發(fā)送消息。
上文點主要關注的就是消息的格式內容組成和幾個接收方和發(fā)送方的順序,下篇我通過幾個大家常用的對外 api,用具體代碼實現(xiàn)來給大家描述下具體過程。
websocket 小程序
版權聲明:本文內容由網(wǎng)絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實的內容,請聯(lián)系我們jiasou666@gmail.com 處理,核實后本網(wǎng)站將在24小時內刪除侵權內容。