nodejs v8中的回调机制
2014-11-03 16:31
267 查看
本文地址 http://blog.csdn.net/wangjia184/article/details/18940165
如果要在nodejs中调用动态链接库中的导出方法,或者从动态链接库中回调nodejs中的某个方法,可以采用 node-ffi(https://github.com/rbranson/node-ffi )。不过我试了很久都没有成功,貌似ffi对于回调的支持有问题,无法正确区分
_stdcall 与 _cdecl。而另一种实现方式就非常简单直接了,通过编写nodejs addon的方式直接实现。
nodejs中的addon使用C编写,其编译链接的工具链不是常见的makefile autoconf之类,而是从Chromium移植来的node-gyp。所以,如果直接将复杂的C/C++代码在addon中实现,容易产生编译或者链接冲突。比较简单的方式是,addon只作为adapter使用,在addon中通过dlopen/LoadLibrary去操纵动态链接库或者回调js.
http://nodejs.org/api/addons.html 有实现Addon的基本讲解。
这是一个“空”的addon,啥事都没干。NODE_MODULE宏的第一个参数是该模块的名称;第二个参数是初始化函数init,此函数在addon加载后调用。
然后在adapter.cc同目录中新建文件building.gyp,它的内容是JSON格式
然后就可以在此目录下,使用gyp编译了
编译的结果在build/release目录下,文件的扩展名是 *.node, 而文件名就是之前指定的模块名。
将此*.node文件拷贝到nodejs工程中的node_modules文件夹下,就可以进行加载了。
从javascript这样的弱类型语言向C强类型语言传递参数,在输入时需要做好类型检查与类型转换。
在Addon中,将传递进来的回调函数进行保存
在Addon中,当需要回调此函数的时候,直接调用即可。如
首先需要定义 uv_async_t 变量
而onCallback方法则在主线程中,通知发生后执行
在任何线程中,都可以通过uv_async_send来触发此回调的执行。
当不再需要回调的时候,可以调用uv_close来取消注册此回调方法。
这里特别需要注意的是,uv_async_send触发回调的次数并不是一一对应的。它只能保证最少一次的触发。可能会出现这样一种情况,连续调用了3次uv_async_send方法,但回调只被触发了一次(调用第1、2、3次的时候,NodeJS的主循环可能忙于其它处理而直到检测到此通知时,3次调用都已经发生了,而此时只会进行一次回调)。针对这种情况,应该设计相应的队列结构来传递数据到回调中依次处理。
例如,JS调用如下导出方法
在AddOn中,定义一个结构体在异步调用中传递数据。
导出方法首先保存回调函数,并验证和解析传入参数
在lookupIpCountryAsyc函数中,进行阻塞调用。这里要注意,此函数不是在主线程中运行,所以不能访问或者调用任何V8有关的函数或数据。
当此函数执行完后,lookupIpCountryCompleted函数会在主线程中被执行,完成回调和清理工作。
本文地址 http://blog.csdn.net/wangjia184/article/details/18940165
如果要在nodejs中调用动态链接库中的导出方法,或者从动态链接库中回调nodejs中的某个方法,可以采用 node-ffi(https://github.com/rbranson/node-ffi )。不过我试了很久都没有成功,貌似ffi对于回调的支持有问题,无法正确区分
_stdcall 与 _cdecl。而另一种实现方式就非常简单直接了,通过编写nodejs addon的方式直接实现。
nodejs中的addon使用C编写,其编译链接的工具链不是常见的makefile autoconf之类,而是从Chromium移植来的node-gyp。所以,如果直接将复杂的C/C++代码在addon中实现,容易产生编译或者链接冲突。比较简单的方式是,addon只作为adapter使用,在addon中通过dlopen/LoadLibrary去操纵动态链接库或者回调js.
http://nodejs.org/api/addons.html 有实现Addon的基本讲解。
AddOn的基本结构
首先新建adapter.cc, 贴图如下代码<span style="font-size:18px;">#include <node.h> #include <v8.h> using namespace v8; void init(Handle<Object> exports) { } NODE_MODULE(mq, init)</span>
这是一个“空”的addon,啥事都没干。NODE_MODULE宏的第一个参数是该模块的名称;第二个参数是初始化函数init,此函数在addon加载后调用。
然后在adapter.cc同目录中新建文件building.gyp,它的内容是JSON格式
<span style="font-size:18px;">{ "targets": [ { "target_name": "mq", "sources": [ "adapter.cc" ] } ] }</span>需要注意的是,target_name必须和NODE_MODULE的第一个参数相同。
然后就可以在此目录下,使用gyp编译了
<span style="font-size:18px;">node-gyp configure node-gyp rebuild</span>
编译的结果在build/release目录下,文件的扩展名是 *.node, 而文件名就是之前指定的模块名。
将此*.node文件拷贝到nodejs工程中的node_modules文件夹下,就可以进行加载了。
<span style="font-size:18px;">var MQ = require('mq.node'); console.log(MQ);</span>
Addon中注册方法供NodeJS调用
在Addon中被NodeJS调用的函数原型必须是 Handle<Value> method(const Arguments& args), 在模块初始化的时候注册此方法。如:<span style="font-size:18px;">Handle<Value> XXXXXX(const Arguments& args) { HandleScope scope; return scope.Close(Undefined()); } void init(Handle<Object> exports) { exports->Set(String::NewSymbol("XXXXXX"), FunctionTemplate::New(XXXXXX<span style="font-family:Arial, Helvetica, sans-serif;">)->GetFunction());</span> }</span>在nodejs中即可调用此方法
<span style="font-size:18px;">var MQ = require('mq.node'); MQ.XXXXXX( 2, false, 'Text');</span>
从javascript这样的弱类型语言向C强类型语言传递参数,在输入时需要做好类型检查与类型转换。
Addon中回调NodeJS方法
首先在NodeJS中将需要被回调的函数地址通过参数传入。<span style="font-size:18px;">MQ.setLogCallback(function (level, message) { console.log('[' + level + '] : ' + message) });</span>
在Addon中,将传递进来的回调函数进行保存
<span style="font-size:18px;">static Persistent<Function> s_logCallback; Handle<Value> setLogCallback(const Arguments& args) { HandleScope scope; if (args.Length() < 1 || !args[0]->IsFunction() ) { return ThrowException(Exception::TypeError(String::New("Invalid parameter."))); } s_logCallback.Dispose(); s_logCallback = Persistent<Function>::New(Local<Function>::Cast(args[0])); return scope.Close(Undefined()); } </span>
在Addon中,当需要回调此函数的时候,直接调用即可。如
<span style="font-size:18px;">if( !s_logCallback.IsEmpty() ){ const unsigned argc = 2; Local<Value> argv[argc] = { Local<Value>::New(Number::New(1)) , Local<Value>::New(String::New("Test Message")) }; s_logCallback->Call(Context::GetCurrent()->Global(), argc, argv); }</span>
多线程环境下回调
NodeJS中的V8引擎是以单线程执行的,回调JS方法也必须在V8的主线程中进行,否则会发生未知的后果甚至crash掉整个进程。NodeJS底层的libuv提供了相应的通知机制来实现主线程中的调用。首先需要定义 uv_async_t 变量
<span style="font-size:18px;">static uv_async_t s_async = {0};</span>在主线程中初始化此变量,并注册在主线程中此通知触发时回调的方法。此步骤可以在init中执行。
<span style="font-size:18px;"><a target=_blank name="baidusnap1"></a><strong style="color:black;background-color:#A0FFFF">uv_async_init</strong>( uv_default_loop(), &s_async, onCallback);</span>
而onCallback方法则在主线程中,通知发生后执行
<span style="font-size:18px;">void onCallback(uv_async_t* handle, int status){ if( !s_logCallback.IsEmpty() ){ const unsigned argc = 2; Local<Value> argv[argc] = { Local<Value>::New(Number::New(1)) , Local<Value>::New(String::New("Callback is happening")) }; s_logCallback->Call(Context::GetCurrent()->Global(), 4000 argc, argv); } }</span>
在任何线程中,都可以通过uv_async_send来触发此回调的执行。
<span style="font-size:18px;">uv_async_send(&s_async);</span>
当不再需要回调的时候,可以调用uv_close来取消注册此回调方法。
<span style="font-size:18px;">uv_close( &s_async, NULL);</span>
这里特别需要注意的是,uv_async_send触发回调的次数并不是一一对应的。它只能保证最少一次的触发。可能会出现这样一种情况,连续调用了3次uv_async_send方法,但回调只被触发了一次(调用第1、2、3次的时候,NodeJS的主循环可能忙于其它处理而直到检测到此通知时,3次调用都已经发生了,而此时只会进行一次回调)。针对这种情况,应该设计相应的队列结构来传递数据到回调中依次处理。
异步调用
NodeJS的主线程只负责event loop和V8的执行,如果addon中某个导出方法在调用时会发生阻塞,会严重地影响到NodeJS的整体性能。因此,libuv设计了异步调用的方式--将阻塞类操作放入其它线程中处理,在处理完成后回调。例如,JS调用如下导出方法
<span style="font-size:18px;">AddOn.lookupIpCountry( ip, function(countryCode){ // get the country code // ... });</span>
在AddOn中,定义一个结构体在异步调用中传递数据。
<span style="font-size:18px;">struct LookupIpCountryBaton { uv_work_t work; Persistent<Function> callback; char ip[IP_LEN]; char country_code[COUNTRY_CODE_LEN]; };</span>
导出方法首先保存回调函数,并验证和解析传入参数
<span style="font-size:18px;">// lookup country by ip // 1st argument is ip address // 2nd argument is the callback function Handle<Value> lookupIpCountry(const Arguments& args) { HandleScope scope; if (args.Length() < 2 || !args[1]->IsFunction() || (!args[0]->IsStringObject() && !args[0]->IsString()) ) { return ThrowException(Exception::TypeError(String::New("Invalid parameter."))); } String::Utf8Value param1(args[0]->ToString()); char * ip = *param1; LookupIpCountryBaton * baton = new LookupIpCountryBaton(); baton->work.data = baton; memset( baton->country_code, 0, COUNTRY_CODE_LEN); memset( baton->ip, 0, IP_LEN); strncpy( baton->ip, ip, IP_LEN); baton->callback = Persistent<Function>::New(Local<Function>::Cast(args[1])); uv_queue_work( uv_default_loop(), &baton->work, lookupIpCountryAsync, lookupIpCountryCompleted); return Undefined(); } </span>这里最关键的是uv_queue_work, 它将请求压入队列交由其它线程执行,同时指定在线程中执行的函数(lookupIpCountryAsyc),亦指定了调用结束后完成的函数(lookupIpCountryCompleted)
在lookupIpCountryAsyc函数中,进行阻塞调用。这里要注意,此函数不是在主线程中运行,所以不能访问或者调用任何V8有关的函数或数据。
<span style="font-size:18px;">void lookupIpCountryAsync(uv_work_t * work){ LookupIpCountryBaton * baton = (LookupIpCountryBaton*)work->data; // block thread for 3 seconds sleep(3); // save the result strncpy( baton->country_code, "CN", COUNTRY_CODE_LEN - 1); }</span>
当此函数执行完后,lookupIpCountryCompleted函数会在主线程中被执行,完成回调和清理工作。
<span style="font-size:18px;">void lookupIpCountryCompleted(uv_work_t * work, int){ LookupIpCountryBaton * baton = (LookupIpCountryBaton*)work->data; const unsigned argc = 1; Local<Value> argv[argc] = { Local<Value>::New(String::New(baton->country_code)) , }; baton->callback->Call(Context::GetCurrent()->Global(), argc, argv); baton->callback.Dispose(); delete baton; }</span>
本文地址 http://blog.csdn.net/wangjia184/article/details/18940165
相关文章推荐
- nodejs的v8引擎垃圾回收机制学习
- ucGUI的视窗管理回调机制学习
- Java回调机制
- Android 回调机制
- Android开发学习之路-回调机制学习笔记
- Java回调机制探讨
- C++中实现回调机制的几种方式
- C#回调机制
- Java回调机制
- 异步消息的传递-回调机制
- Android回调机制总结
- 回调机制的实现,即细看setOnClickListener怎么运行
- java接口回调机制
- Java回调机制解析
- 一个经典例子让你彻彻底底理解java回调机制
- Java回调机制
- 彻底理解NodeJs中的回调(Callback)函数
- 细数Objective-C中的回调机制
- Java学习之 回调机制
- C#委托的回调机制