2天时间,聊今年最热的 Agent、上下文工程、AI 产品创新等话题。2025 年最后一场~ 了解详情
写点什么

实现微信小程序编译和运行环境系列 (核心篇二)

  • 2020-06-22
  • 本文字数:4005 字

    阅读完需:约 13 分钟

实现微信小程序编译和运行环境系列(核心篇二)

在上文中我们有点到小程序开发者工具里面的消息是通过 websocket 协议发送和接受处理的,当然这个不是凭空而说的,是在小程序的逻辑层 appservice.js 源码里面有代码表明的,至于它的消息格式还有一部分我没有列出来,比如它的数据分析和上报他们自己服务器的一些消息格式可以先先需要关注。


下面还是先给大家展示一下流程找到 appservice.js 源码文件



可以看到它的链接地址,数据发送和接收的部分代码,由于图片尺寸问题我折叠了部分代码,大家可以自己去细看看。


我还是先简述一些 webstocket 的知识,可能部分同学对这方面不是很熟悉。细节 webstocket 内容不会在本文描述,后期会写一篇专门的介绍。

websocket 是什么

其实这些内容我们通过谷歌搜索可以查阅很多材料,但有没有真正理解可以在自己项目里进行灵活设计运用还是只是简单使用文档 api,还是要靠自己多探索思考一些。


下图为webstockrt协议



可以理解为:WebSocket 协议允许在运行于受控环境中的不受信任代码的用户代理与已选择从该代码进行通信的远程主机之间进行双向通信。简单点描述就是:客户端和服务器之间存在持久连接,而且双方都可以随时随地相互发送数据

为什么用 websocket

一项新规范或者一门新技术的诞生肯定是为了解决或者完善前面方案的不足,这样才能一直进步下去。


在没有 websocket 之前我们采用 http 用的很好,但是随着一些应用的要求像聊天 股票 游戏 这种对实时性数据要求高的系统,才用 HTTP 协议发送数据的话只能有客户端单方面进行请求,服务端响应获取最新数据,如果服务端的数据变换很快比如股票的信息,因此只能定时去请求,就出出现效率低 浪费资源而且数据还不实时同步的情况,为了解决这些问题通过研究 websocket 协议就闪亮登场了。


websocket 具备的一些优点:


  • 支持双向通信,具有很强的实时性

  • 对二进制的支持比较友好

  • 相比与 http 协议的控制开销要少很多

  • 用户可以自由的扩展协议,自定义子协议例如(wss)

如何使用 websocket

这个点比较广泛一个新方案新技术的产生都会经过由浅入深的过程发展,主要看大家门自己的具体设计和使用了,下面一些链接知识点可以让大家先了解这个概念和基础使用,本章节不在这里衍生更多 websocket 相关内容。


(大家如果想对 websocket 深入学习感兴趣 希望可以关注我后面的 websocket 专栏文章)




这个是一个比较简单的可以在线看效果的网页


如果有同学希望自己动手试试的话,我在自己的 github 仓库写了一个最简化的服务端和客户端的案例,一共 10 多行代码比较方便,有兴趣的朋友可以看下案例地址


执行 index.js 后效果如下:



下面的内容我会结合在实现这个小程序运行环境里面的对于 websocket 的一些运用设计和部分代码展示。


我们回到主题先在源码 appservice.js 的发送和接收的地方添加了一些日志保存,这里一定要彻底退出工具进程在打开不然是不起作用的。


然后我们从新进入开发者工具打开一个小程序项目,我打开的是一个官方的云开发项目列子可以看到:



通过这个图我们可以看出一些信息先给大家简单介绍一下:


数据发送部分


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}}
复制代码


可以观察到一些字段和对象(这个是一个普通云开发项目默认打开的时候的状态,不做任何操作是个例子对象是比较复杂的)


  • command

  • data

  • api

  • args

  • data

  • api_name

  • qbase_api_name

  • qbase_req

  • callbackID


看到这个 api operateWXData 可能大家不是很熟悉,因为这个 api 微信没有对外的是内部使用的,这个不是我们现在要讲的重点,我们现在要描述的是 webstocket 相关的,至于 api 的实现会在下文如何实现小程序对外 api 来描述讲解,我们在这里只要知道他的消息传输格式就可以了


  • command 消息类型

  • data 各种数据组合

  • 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":20receive===>"command":"APPSERVICE_INVOKE_CALLBACK" "callbackID":20
复制代码


APPSERVICE_INVOKE 的消息类型是 service 层发送给 service 进行接收处理

代码实现浏览器运行环境 websocket 服务通信设计

这边采用 node 方式来启动的服务先创建一个服务端:


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; }}
复制代码


创建客户端链接发送和接收:


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)); } }}
复制代码


上面两个类文件就是比较简单的服务和客户端的创建。这里创建了一个 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)); }};
复制代码


新添加的一个 addSocketClient 方法


表示如果 SocketClient 不存在,则根据 ws 创建一个新的 SocketClient,否则,将旧的 ws 替换为新的 ws,这样消息队列中的消息就可以被替换后立即发送到新的 ws,保证可用性。


getSocketClient 方法


调用这个函数总是可以返回一个 SocketClient 实例,以便用户可以在任何时候发送消息。


上文点主要关注的就是消息的格式内容组成和几个接收方和发送方的顺序,下篇我通过几个大家常用的对外 api,用具体代码实现来给大家描述下具体过程。


2020-06-22 11:082103

评论

发布
暂无评论
发现更多内容

二分发代码模板

小兵

甲方日常 59

句子

工作 随笔杂谈 日常

磁盘到底是怎样工作的?一文理解硬盘结构

Guanngxu

操作系统

《华为数据之道》读书笔记:第 7章 打造“数字孪生”的数据全量感知能力

方志

数据中台 数字化转型

今年最火的 Golang 云原生开源项目,可能就是它了!

孙健波

Kubernetes k8s OAM KubeVela CloudNative

区块链开发落地,联盟链系统平台搭建

t13823115967

区块链 区块链开发落地 联盟链系统平台搭建

前嗅教你大数据:常见几种编码介绍

前嗅大数据

大数据 编码 编码指南

微博和B站屏蔽马保国相关信息:自媒体蹭热度要适可而止

石头IT视角

架构师训练营第一期-第十周学习总结

卖猪肉的大叔

极客大学架构师训练营

免费下载O’Reilly出版社全新之作《建立机器学习流水线》

计算机与AI

学习

监控之美——监控系统选型分析及误区探讨

华章IT

运维 云原生 监控 Prometheus

5分钟学会6个阿里内部编程的方法

Java架构师迁哥

阿里内部“高并发通关秘籍”曝光,看完带给你独一无二的认知!

比伯

Java 编程 架构 面试 计算机

CPU飙高问题排查

程序猿玄微子

Spring 源码学习 02:关于 Spring IoC 和 Bean 的概念

程序员小航

spring 源码 源码分析 ioc

现在Php、Java、Python横行霸道的市场,C++程序员们都在干什么呢?

ShenDu_Linux

c++ 程序员 编程语言 C语言 软件工程师

区块链政务系统开发解决方案

t13823115967

区块链+ 区块链开发落地 政务系统开发解决方案

【薪火计划】06 - 你推崇的领导方式是怎么样的?

码上生长

管理

根治可扩展、高可用、高性能“神器”:SpringCloud+Nginx高并发编程手册

Java架构追梦

Java nginx 架构 面试 微服务

Nginx的反向代理与负载均衡--配置Nginx

Linux服务器开发

nginx 负载均衡 反向代理 后端 Linux服务器

架构师训练营第 1 期 - 第 10 周 - 学习总结

wgl

极客大学架构师训练营

三万字无坑搭建基于Docker+K8S+GitLab/SVN+Jenkins+Harbor持续集成交付环境!!

冰河

Docker 云原生 k8s

《华为数据之道》读书笔记:第 8 章 打造“清洁数据”的质量综合管理能力

方志

数字化转型 数据质量管理

苹果开始告别英特尔

罗燕珊

macOS Big Sur 芯片 苹果 MacBook 英特尔

一枚程序猿的MacBook M1详细体验报告

Zhendong

使用 Go 实现 Async/Await 模式

Roc

channel goroutines Async Go 语言

《华为数据之道》读书笔记:第 6 章 面向“自助消费”的数据服务建设

方志

数据中台 数据仓库 数字化转型 数据治理

聊聊销售背后的策略

吴晨曦

创业 销售管理

JVM调优不知道怎么回答,阿里总结四大模块,学不会就背过来

小Q

Java 学习 架构 面试 JVM

面试无忧:源码+实践,讲到MySQL调优的底层算法实现

小Q

Java 数据库 学习 面试 算法

Spock单元测试框架实战指南四 - 异常测试

Java老k

单元测试 spock

实现微信小程序编译和运行环境系列(核心篇二)_语言 & 开发_风逝_InfoQ精选文章