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:082118

评论

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

重磅揭秘!10分钟10TB数据跨云、跨地域传输的技术实践

星汉未来

云原生 数据迁移 东数西算 星汉未来

延时任务-基于netty时间轮算法实现

字母哥哥

Java 架构 后端 Netty

手把手地教你如何建立最好的知识管理体系

Baklib

怎样设计一个协助中心来帮助你的顾客?

Baklib

你可能不知道,自动化元数据管理的“七宗最”?

雨果

元数据

非科班出身,开发五年之后我对编程有了新的领悟

Java永远的神

Java 编程 程序员 程序人生 计算机

云堡垒机和软件堡垒机哪个好?区别是什么?

行云管家

网络安全 数据安全 堡垒机 云堡垒机

学术加油站|面向HTAP数据库的基准评测工具研究进展

OceanBase 数据库

[极致用户体验] 2行代码,让你的UI适配移动端、PC端,快来收藏

HullQin

CSS JavaScript html 前端 8月月更

聊聊 npm 的语义化版本(Semver)

冴羽

JavaScript 前端 npm 项目开发 semver

题目新颖,内容全面!阿里巴巴又一Java面试神册开源!

Java永远的神

Java spring 程序员 面试 JVM

一文看懂流程挖掘是如何工作的

望繁信科技

超全面!字节最新发布22年秋招200道Java面试题(含答案)

Java面试那些事儿

Java 编程 面试 后端 架构师

低/无代码的发展将显著改变银行开发生态

易观分析

代码 银行

阿里云AIoT物联网平台技术集锦

阿里云AIoT

数据挖掘 运维 监控 物联网 消息中间件

一对一直播系统源码——如何只需三步搭建

开源直播系统源码

软件开发 一对一直播源码 直播系统源码 一对一直播系统

开源流式湖仓服务 Arctic 详解:并非另一套 Table Format

网易数帆

大数据 iceberg Hudi Arctic

StarRocks 技术内幕:向量化编程精髓

StarRocks

新手指南|帮助中心应该包含哪些内容?

Geek_da0866

听潮汐,筑灯塔,聚千帆:智慧港口全球创新实验室启航时

脑极体

哪些数据被纳入元数据管理的范畴?

雨果

元数据

堡垒机可以管理哪些网络资产?咨询电话多少?

行云管家

网络安全 数据安全 堡垒机

这么好用的接口工具,请允许我油腻一次!

Liam

Java 开发 Postman API 开放api

爆肝!阿里大佬熬夜38天整合的这份Spring Security源码手册我粉了

Java全栈架构师

Java 程序员 面试 程序人生 springsecurity

前端培训就业后的程序员就业方向有哪些?

小谷哥

数据透视表上线!如何在纯前端实现这个强大的数据分析功能?

葡萄城技术团队

前端

完整实现-通过DelayQueue实现延时任务

字母哥哥

Java 架构 并发编程 后端

toB行业知识管理的重要性

Baklib

高性能实战Alibaba Sentinel笔记,深度还原阿里微服务高并发方案

小柴说Java

Java 编程 架构 面试 后端

从一条更新SQL的执行过程窥探InnoDB之REDOLOG

京东科技开发者

MySQL 数据库

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