electron 从一到二
〇、介绍
上一节介绍了 electron 的基础使用,介绍的比较简单,照着文章一步步基本可以打出一个简单的原型项目啦。
这篇文章介绍一下 electron IM 应用开发中要考虑的一些问题。
本文主要包括:
- 消息加密解密
- 消息序列化
- 网络传输协议
- 私有数据通信协议
- 多进程优化
- 消息本地存储
- 新消息 tray 图标闪烁
- 项目自动更新
- 进程间通信
- 其他
一、消息加密解密
背景
对聊天软件而言,消息的保密性就比较重要了,谁也不希望自己的聊天内容泄露甚至暴露在众人的前面。所以在收发信息的时候,我们需要对信息做一些加密解密操作,保证信息在网络中传输的时候是加密的状态。
简单的实现方法
可能大家马上就想这还不简单,项目里写个加密解密的方法。收到消息时候先解密,发送消息时候先加密,服务端收到加密消息直 接存储起来。
这样写理论上也没有问题,不过客户端直接写加解密方法有一些不好的地方。
- 容易被逆向。前端代码比较容易被逆向。
- 性能较差。在公司中可能加了很多项目的群组,各个群组中都会收到很多消息,前端处理起来比较慢。
- 类似的如果都在客户端实现加解密算法,那么 ios, android 等不同客户端,因为使用的开发语言不同,都要要分别实现相同的算法,增加维护成本。
我们的方案
我们使用C++ Addons 提供的能力,在 c++ sdk 中实现加解密算法,让 js 可以像调用 Node 模块一样去调用 c++ sdk 模块。这样就一次性解决了上面提到的所有问题。
开发完 addon, 使用 node-gyp 来构建 C++ Addons. node-gyp 会根据 binding.gyp 配置文件调用各平台上的编译工具集来进行编译。如果要实现跨平台,需要按不同平台编译 nodejs addon,在 binding.gyp
中按平台配置加解密的静态链接库。
{
"targets": [{
"conditions": [
["OS=='mac'", {
"libraries": [
"<(module_root_dir)/lib/mac/security.a"
]
}],
["OS=='win'", {
"libraries": [
"<(module_root_dir)/lib/win/security.lib"
]
}],
...
]
...
}]
当然也可以根据需要添加更多平台的支持,如 linux、unix。
对 c++ 代码进程封装 addon 的时候,可以使用 node-addon-api. node-addon-api 包对 N-API 做了封装,并抹平了 nodejs 版本间的兼容问题。封装大大降低了非职业 c++ 开发编写 node addon 的成本。关于 node-addon-api、N-API、NAN 等概念可以参考死月同学的文章从暴力到 NAN 再到 NAPI——Node.js 原生模块开发方式变迁
打包出 .node 文 件后,可以在 electron 应用运行时,调用 process.platform
判断运行的平台,分别加载对应平台的 addon。
if (process.platform === "win32") {
addon = require("../lib/security_win.node");
} else {
addon = require("../lib/security_mac.node");
}
二、消息序列化和反序列化
背景
聊天消息直接通过 JSON 解码和传输效率都比较低。
我们的方案
这里我们引入谷歌的 Protocol Buffer 提升效率。关于 Protocol Buffer 更多的介绍,可以查看底部的参考文章。
node 环境中使用 Protocol Buffer 可以用 protobufjs 包。
npm i protobuff -S
然后通过 pbjs 命令将 proto 文件转换成 pbJson.js
shell pbjs -t json-module --sparse --force-long -w commonjs -o src/im/data/pbJson.js proto/*.proto
要在 js 中支持后端 int64 格式数据,需要使用 long 包配置下 protobuf。
var Long = require("long");
$protobuf.util.Long = Long;
$protobuf.configure();
$protobuf.util.LongBits.prototype.toLong = function toLong(unsigned) {
return new $protobuf.util.Long(
this.lo | 0,
this.hi | 0,
Boolean(unsigned)
).toString();
};
后面就是消息的压缩转换了,将 js 字符串转成 bp 格式。
import PbJson from "./path/to/src/im/data/pbJson.js";
// 封装数据
let encodedMsg = PbJson.lookupType("pb-api").ctor.encode(data).finish();
// 解封数据
let decodedMsg = PbJson.lookupType("pb-api").ctor.decode(buff);