nodejs v8中的回調機制

本文地址 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(), 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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章