某个产品的Mac客户端计划基于electron实现,因为现有SDK有C API,原理上用NodeJS Addon来封装成JS API就可使用了。但涉及到与Addon多线程交互,翻找资料没能找到到底该怎么做,只好翻看NodeJS实现找到实现思路。
实现思路上,NodeJS V8 引擎是libuv单线程的,客户端前台页面逻辑跑在libuv事件循环中,后台SDK跑在独立的线程上。业务需要前后台线程之间互相通信,前台访问后台直接API调用,然后异步回调、后台通知则需要唤醒libuv线程来完成。
libuv 唯一线程安全的接口是 uv_async_send, 用它可以唤醒libuv线程执行指定代码。
后台SDK -> NodeJS 调用流程:
0. SDK 实例初试化时
初试化一个uv_async_t:
uv_async_init(uv_default_loop(), &uv_async, node_event_process);
注意必须在libuv线程线程执行,node_event_process 是被唤醒时执行的方法。
1. 将参数临时存放在SDK实例
2. 唤醒libuv线程
uv_async_send(&uv_async)
libuv 在UNIX下的实现是,uv_async_init时创建一个namepipe fd, 在epoll等待,send时就是往fd写入数据,epoll就会返回。
要注意的是uv_async_send每次调用并不保证都会执行回调,只保证能唤醒线程,不能作为调用方式。
3. SDK线程执行 std::condition_variable.wait() 挂起等待libuv线程执行结果
4. libuv线程唤醒,读取SDK实例临时参数执行node_event_process调用
通过V8接口,调用JSON库,将参数解析为V8 Object对象
通过CallID查找事务回调的CallBack Function,或者提供bind方法绑定的Callback Function,执行调用就会返回Javascript世界开始执行
5. 执行完成,将返回值临时放到SDK实例上
返回v8::Local
6. std::condition_variable.notify_one() 唤醒SDK线程继续执行
7. SDK wait() 继续执行,读取返回值
参考代码如下:
class RcsSdk : public Nan::ObjectWrap {
public:
static NAN_MODULE_INIT(Init);
void addCallback(int cid, Nan::Callback* callback);
const Nan::Callback* removeCallback(int cid);
const Nan::Callback* getBindMethod(const char* method);
void notifyMainLoop();
#ifdef CALL_NODE_USE_ASYNC_QUEUE
std::queue asyncEventQueue;
#else
void waitForEventProcess(EventItem* item);
void notifyEventComplete();
EventItem* syncEventItem;
char* syncEventResult;
#endif
std::mutex eventSyncMutex;
rcs_state *state;
private:
explicit RcsSdk(const char* appId, const char* number, const char* imei,
const char* devicevendor,const char* devicemodel,
const char* deviceos,const char* deviceosversion,
const char* clientVendor, const char* clientVersion,
const char* storage, const char* privateStorage, int clientType);
~RcsSdk();
static NAN_METHOD(New);
static NAN_METHOD(bind);
static void addApiMethods(v8::Local &tpl);
static Nan::Persistent constructor;
std::condition_variable eventCondition;
uv_async_t uv_async;
std::map callbacks;
std::map bindMethods;
};
static std::map sdkMap;
static std::mutex globalMutex;
static RcsSdk* node_get_sdk(rcs_state *R){
RcsSdk *sdk = NULL;
globalMutex.lock();
std::map::iterator iterator = sdkMap.find(R);
if(iterator != sdkMap.end()){
sdk = iterator->second;
}
globalMutex.unlock();
return sdk;
}
char* node_event_deliver(struct rcs_state *R, const char* event, const char* json){
//LuaSdk 后台线程
LOG_DEBUG("node_event_deliver begin, event:%s json:%s", event, json);
RcsSdk* sdk = node_get_sdk(R);
char* result = NULL;
if(NULL == sdk){
LOG_DEBUG("node_event_deliver sdk NULL");
return NULL;
}
EventItem item;
item.event = event;
item.data = (char *)json;
sdk->waitForEventProcess(&item);
sdk->eventSyncMutex.lock();
result = sdk->syncEventResult;
sdk->eventSyncMutex.unlock();
LOG_DEBUG("node_event_deliver end");
return result;
}
static int node_get_cid(v8::Handle json){
int cid = -1;
if(json->IsObject()){
v8::Handle ojson = json->ToObject();
cid = ojson->Get(Nan::New("id").ToLocalChecked())->Int32Value();
}
return cid;
}
static v8::Handle parseJson(v8::Handle jsonString) {
// Call JSON.parse.apply(JSON, jsonString)
v8::Handle context = Nan::GetCurrentContext();
v8::Handle global = context->Global();
v8::Handle JSON = global->Get(Nan::New("JSON").ToLocalChecked())->ToObject();
v8::Handle JSON_parse =v8::Handle::Cast(JSON->Get(Nan::New("parse").ToLocalChecked()));
return JSON_parse->Call(JSON, 1, &jsonString);
}
static v8::Local node_do_event_process(RcsSdk* sdk, EventItem *item){
v8::Handle json = parseJson(Nan::New(item->data).ToLocalChecked());
v8::Local argv[] = {json};
v8::Local result;
if(!std::strcmp(item->event, "callback")){
int cid = node_get_cid(json);
LOG_DEBUG("node api callback cid:%d", cid);
const Nan::Callback * callback = sdk->removeCallback(cid);
if(NULL == callback){
LOG_ERROR("node api callback=NULL");
}else{
result = callback->Call(1, argv);
delete callback;
}
}else{
LOG_DEBUG("node_do_event_process:%s %s", item->event, item->data);
const Nan::Callback * callback = sdk->getBindMethod(item->event);
if(NULL == callback){
LOG_DEBUG("node listener callback=NULL");
}else{
result = callback->Call(1, argv);
}
}
return result;
}
// SDK线程调用 uv_async_send 后,主线程被唤醒后执行
// uv_async_send 是libuv唯一线程安全的API,但不保证每次调用都通知到,只是能确保至少一次的唤醒主线程
// 参见: http://docs.libuv.org/en/v1.x/async.html
// int uv_async_send(uv_async_t* async)
// Wakeup the event loop and call the async handle’s callback.
//
// Note It’s safe to call this function from any thread. The callback will be
// called on the loop thread.
// Warning libuv will coalesce calls to uv_async_send(), that is, not every call to
// it will yield an execution of the callback. For example: if uv_async_send() is
// called 5 times in a row before the callback is called, the callback will only be
// called once. If uv_async_send() is called again after the callback was called, it
// will be called again.
static void node_event_process(uv_async_t *handle){
//nodejs 主线程
LOG_DEBUG("node_event_process begin");
Nan::HandleScope scope;
RcsSdk *sdk = (RcsSdk*)handle->data;
sdk->eventSyncMutex.lock();
EventItem *item = sdk->syncEventItem;
sdk->syncEventItem = NULL;
sdk->eventSyncMutex.unlock();
if(NULL == item){
LOG_ERROR("node_event_process sync syncEventItem=NULL");
}else{
v8::Local ret = node_do_event_process(sdk, item);
char *result = NULL;
// 暂只支持返回String类型(必须在主线程解析v8对象)
if(!ret.IsEmpty() && ret->IsString()){
result = *Nan::Utf8String(v8::Local::Cast(ret));
}
// 此处分配内存,由下次调用前释放,最多保留上一次的结果占用的内存
if(result != NULL){
char* cpy = (char*)RCS_MALLOC(strlen(result)+1);
assert(cpy);
strcpy(cpy, result);
result = cpy;
}
sdk->eventSyncMutex.lock();
sdk->syncEventResult = result;
sdk->eventSyncMutex.unlock();
}
sdk->notifyEventComplete();
LOG_DEBUG("node_event_process end");
}
// RcsSdk Imps
Nan::Persistent RcsSdk::constructor;
NAN_MODULE_INIT(RcsSdk::Init) {
v8::Local tpl = Nan::New(New);
tpl->SetClassName(Nan::New("RcsSdk").ToLocalChecked());
//这个数只要>0即可
tpl->InstanceTemplate()->SetInternalFieldCount(1);
//构建Js 的ProtoType
Nan::SetPrototypeMethod(tpl, "bind", bind);
addApiMethods(tpl);
constructor.Reset(Nan::GetFunction(tpl).ToLocalChecked());
Nan::Set(target, Nan::New("RcsSdk").ToLocalChecked(), Nan::GetFunction(tpl).ToLocalChecked());
}
RcsSdk::RcsSdk(const char* number, const char* imei, const char* devicevendor,
const char* devicemodel, const char* deviceos, const char* deviceosversion,
const char* clientVendor, const char* clientVersion, const char* storage,
const char* appId, const char* syspath, int clientType)
#ifndef CALL_NODE_USE_ASYNC_QUEUE
:syncEventItem(NULL),
syncEventResult(NULL)
#endif
{
LOG_DEBUG("begin initSdk: %s %s %s %s %s", appId, number, clientVendor, clientVersion, storage);
state = rcs_state_new();
rcs_listeners listeners;
node_get_listeners(listeners);
rcs_set_listeners(state, &listeners);
rcs_callbacks callbacks;
node_get_callbacks(callbacks);
rcs_set_callbacks(state, &callbacks);
// 每个State上有且只有一个用于中断主线程的 uv_async_t
uv_async_init(uv_default_loop(), &uv_async, node_event_process);
uv_async.data = this;
rcs_init(state, number, imei, "node-imsi",
devicevendor, devicemodel, deviceos, deviceosversion,
clientVendor,clientVersion, storage, appId, syspath, clientType);
rcs_start(state);
globalMutex.lock();
sdkMap[state] = this;
globalMutex.unlock();
}
RcsSdk::~RcsSdk() {
if(state){
rcs_stop(state, 0);
globalMutex.lock();
sdkMap.erase(state);
globalMutex.unlock();
state = NULL;
}
}
void RcsSdk::addCallback(int cid, Nan::Callback* callback){
callbacks[cid] = callback;
}
const Nan::Callback* RcsSdk::removeCallback(int cid){
std::map::iterator iterator = callbacks.find(cid);
const Nan::Callback* callback = NULL;
if(iterator != callbacks.end()){
callback = iterator->second;
callbacks.erase(iterator);
}
return callback;
}
const Nan::Callback* RcsSdk::getBindMethod(const char* method){
std::map::iterator iterator = bindMethods.find(method);
if(iterator != bindMethods.end()){
return iterator->second;
}
return NULL;
}
void RcsSdk::notifyMainLoop(){
uv_async_send(&uv_async);
}
NAN_METHOD(RcsSdk::New) {
if (!info.IsConstructCall()) {
// Invoked as plain function `RcsSdk(...)`, turn into construct call.
const int argc = info.Length();
v8::Local argv[12];
for(int i=0; i cons = Nan::New(constructor);
info.GetReturnValue().Set(cons->NewInstance(argc, argv));
} else {
// Invoked as constructor: `new RcsSdk(...)`
if(info.Length() != 12){
Nan::ThrowError(Nan::Error("Wrong number of arguments"));
return;
}
CHECK_ARGS_STRING(info, 0, "number");
CHECK_ARGS_STRING(info, 1, "imei");
CHECK_ARGS_STRING(info, 2, "devicevendor");
CHECK_ARGS_STRING(info, 3, "devicemodel");
CHECK_ARGS_STRING(info, 4, "deviceos");
CHECK_ARGS_STRING(info, 5, "deviceosversion");
CHECK_ARGS_STRING(info, 6, "clientVendor");
CHECK_ARGS_STRING(info, 7, "clientVersion");
CHECK_ARGS_STRING(info, 8, "storage");
CHECK_ARGS_STRING(info, 9, "appId");
CHECK_ARGS_STRING(info, 10, "sysPath");
CHECK_ARGS_NUMBER(info, 11, "clientType");
const char* number = *Nan::Utf8String(info[0]);
const char* imei = *Nan::Utf8String(info[1]);
const char* devicevendor = *Nan::Utf8String(info[2]);
const char* devicemodel = *Nan::Utf8String(info[3]);
const char* deviceos = *Nan::Utf8String(info[4]);
const char* deviceosversion = *Nan::Utf8String(info[5]);
const char* clientVendor = *Nan::Utf8String(info[6]);
const char* clientVersion = *Nan::Utf8String(info[7]);
const char* storage = *Nan::Utf8String(info[8]);
const char* appId = *Nan::Utf8String(info[9]);
const char* sysPath = *Nan::Utf8String(info[10]);
int clientType = info[11]->Uint32Value();
RcsSdk *sdk = new RcsSdk(number, imei, devicevendor, devicemodel, deviceos, deviceosversion,
clientVendor, clientVersion, storage, appId, sysPath, clientType);
sdk->Wrap(info.This());
info.GetReturnValue().Set(info.This());
}
}
NAN_METHOD(RcsSdk::bind) {
RcsSdk* sdk = Nan::ObjectWrap::Unwrap(info.This());
if(info.Length() != 2 || ! info[0]->IsString() || !info[1]->IsFunction()){
Nan::ThrowError(Nan::Error("Wrong arguments"));
return;
}
const char* method = *Nan::Utf8String(info[0]);
Nan::Callback *callback = new Nan::Callback(info[1].As());
sdk->bindMethods[method] = callback;
}