nodejs教程.docx
- 文档编号:8216736
- 上传时间:2023-01-29
- 格式:DOCX
- 页数:16
- 大小:27.40KB
nodejs教程.docx
《nodejs教程.docx》由会员分享,可在线阅读,更多相关《nodejs教程.docx(16页珍藏版)》请在冰豆网上搜索。
nodejs教程
一、编写Node.js原生扩展
Node.js是一个强大的平台,理想状态下一切都都可以用javascript写成。
然而,你可能还会用到许多遗留的库和系统,这样的话使用c++编写Node.JS扩展会是一个不错的注意。
以下所有例子的源代码可在node扩展示例中找到。
编写Node.jsC++扩展很大程度上就像是写V8的扩展;Node.js增加了一些接口,但大部分时间你都是在使原始的V8数据类型和方法,为了理解以下的代码,你必须首先阅读V8引擎嵌入指南。
Javascript版本的HelloWorld
在讲解C++版本的例子之前,先让我们来看看在Node.js中用Javascript编写的等价模块是什么样子。
这是一个最简单的HelloWorld,也不是通过HTTP,但它展示了node模块的结构,而其接口也和大多数C++扩展要提供的接口差不多:
HelloWorldJs=function(){
this.m_count=0;
};
HelloWorldJs.prototype.hello=function()
{
this.m_count++;
return"HelloWorld";
};
exports.HelloWorldJs=HelloWorldJs;
正如你所看到的,它使用prototype为HelloWorldJs类创建了一个新的方法。
请注意,上述代码通过将HelloWorldJS添加到exports变量来暴露构造函数。
要在其他地方使用该模块,请使用如下代码:
varhelloworld=require('helloworld_js');
varhi=newhelloworld.HelloWorldJs();
console.log(hi.hello());//prints"HelloWorld"tostdout
C++版本的HelloWorld
要开始编写C++扩展,首先要能够编译Node.js(请注意,我们使用的是Node.js2.0版本)。
本文所讲内容应该兼容所有未来的0.2.x版本。
一旦编译安装完node,编译模块就不在需要额外的东西了。
完整的源代码可以在这里找到。
在使用Node.js或V8之前,我们需要包括相关的头文件:
#include
#include
usingnamespacenode;
usingnamespacev8;
在本例子中我直接使用了V8和node的命名空间,使代码更易于阅读。
虽然这种用法和谷歌的自己的C++编程风格指南相悖,但由于你需要不停的使用V8定义的类型,所以目前为止的大多数node的扩展仍然使用了V8的命名空间。
接下来,声明HelloWorld类。
它继承自node:
:
ObjectWrap类,这个类提供了几个如引用计数、在V8内部传递contex等的实用功能。
一般来说,所有对象应该继承ObjectWrap:
classHelloWorld:
ObjectWrap
{
private:
intm_count;
public:
声明类之后,我们定义了一个静态成员函数,用来初始化对象并将其导入Node.js提供的target对象中。
设个函数基本上是告诉Node.js和V8你的类是如何创建的,和它将包含什么方法:
staticPersistent
staticvoidInit(Handle
{
HandleScopescope;
Local
:
New(New);
s_ct=Persistent
:
New(t);
s_ct->InstanceTemplate()->SetInternalFieldCount
(1);
s_ct->SetClassName(String:
:
NewSymbol("HelloWorld"));
NODE_SET_PROTOTYPE_METHOD(s_ct,"hello",Hello);
target->Set(String:
:
NewSymbol("HelloWorld"),
s_ct->GetFunction());
}
在上面这个函数中target参数将是模块对象,即你的扩展将要载入的地方。
(译著:
这个函数将你的对象及其方法连接到这个模块对象,以便外界可以访问)首先我们为New方法创建一个FunctionTemplate,将于稍后解释。
我们还为该对象添加一个内部字段,并命名为HelloWorld。
然后使用NODE_SET_PROTOTYPE_METHOD宏将hello方法绑定到该对象。
最后,一旦我们建立好这个函数模板后,将他分配给target对象的HelloWorld属性,将类暴露给用户。
接下来的部分是一个标准的C++构造函数:
HelloWorld():
m_count(0)
{
}
~HelloWorld()
{
}
接下来,在:
:
New方法中V8引擎将调用这个简单的C++构造函数:
staticHandle
{
HandleScopescope;
HelloWorld*hw=newHelloWorld();
hw->Wrap(args.This());
returnargs.This();
}
此段代码相当于上面Javascript代码中使用的构造函数。
它调用newHelloWorld创造了一个普通的C++对象,然后调用从ObjectWrap继承的Wrap方法,它将一个C++HelloWorld类的引用保存到args.This()的值中。
在包装完成后返回args.This(),整个函数的行为和javascript中的new运算符类似,返回this指向的对象。
现在我们已经建立了对象,下面介绍在Init函数中被绑定到hello的函数:
staticHandle
{
HandleScopescope;
HelloWorld*hw=ObjectWrap:
:
Unwrap
hw->m_count++;
Local
:
New("HelloWorld");
returnscope.Close(result);
}
函数中首先使用ObjectWrap模板的方法提取出指向HelloWorld类的指针,然后和javascript版本的HelloWorld一样递增计数器。
我们新建一个内容为“HelloWorld”的v8字符串对象,然后在关闭本地作用域的时候返回这个字符串。
上面的代码实际上只是针对v8的接口,最终我们还需要让Node.js知道如何动态加载我们的代码。
为了使Node.js的扩展可以在执行时从动态链接库加载,需要有一个dlsym函数可以识别的符号,所以执行编写如下代码:
extern"C"{
staticvoidinit(Handle
{
HelloWorld:
:
Init(target);
}
NODE_MODULE(helloworld,init);
}
由于c++的符号命名规则,我们使用externC,以便该符号可以被dysym识别。
init方法是Node.js加载模块后第一个调用的函数,如果你有多个类型,请全部在这里初始化。
NODE_MODULE宏用来填充一个用于存储模块信息的结构体,存储的信息如模块使用的API版本。
这些信息可以用来防止未来因API不兼容导致的崩溃。
到此,我们已经完成了一个可用的C++NodeJS扩展。
Node.js也提供了一个用于构建模块的简单工具:
node-waf首先编写一个包含扩展编译方法的wscript文件,然后执行node-wafconfigure&&node-wafbuild完成模块的编译和链接工作。
对于这个helloworld的例子来说,wscript内容如下:
defset_options(opt):
opt.tool_options("compiler_cxx")
defconfigure(conf):
conf.check_tool("compiler_cxx")
conf.check_tool("node_addon")
defbuild(bld):
obj=bld.new_task_gen("cxx","shlib","node_addon")
obj.cxxflags=["-g","-D_FILE_OFFSET_BITS=64","-D_LARGEFILE_SOURCE","-Wall"]
obj.target="helloworld"
obj.source="helloworld.cc"
异步IO的HelloWorld
对于实际的应用来说,HelloWorld的示例太过简单了一些,Node.js主要的优势是提供异步IO。
Node.js内部通过libeio将会产生阻塞的操作全都放入线程池中执行。
如果需要和遗留的c库交互,通常需要使用异步IO来为javascript代码提供回调接口。
通常的模式是提供一个回调,在异步操作完成时被调用——你可以在整个Node.js的API中看到这种模式。
Node.js的filesystem模块提供了一个很好的例子,其中大多数的函数都在操作完成后通过调用回调函数来传递数据。
和许多传统的GUI框架一样,Node.js只在主线程中执行JavaScript,因此主线程以外的任何操作都不应该直接和V8或Javascript交互。
同样helloworld_eio.cc源代码在GitHub上。
我只强调和原来HelloWorld之间的差异,其中大部分代码保持不变,变化集中在Hello方法中:
staticHandle
{
HandleScopescope;
REQ_FUN_ARG(0,cb);
HelloWorldEio*hw=ObjectWrap:
:
Unwrap
在Hello函数的入口处,我们使用宏从参数列表的第一个位置获取回调函数,在下一节中将详细介绍。
然后,我们使用相同的Unwarp方法提取指向类对象的指针。
hello_baton_t*baton=newhello_baton_t();
baton->hw=hw;
baton->increment_by=2;
baton->sleep_for=1;
baton->cb=Persistent
:
New(cb);
这里我们创建一个baton结构,并将各种参数保存在里面。
请注意,我们为回调函数创建了一个永久引用,因为我们想要在超出当前函数作用域的地方使用它。
如果不这么做,在本函数结束后将无法再调用回调函数。
hw->Ref();
eio_custom(EIO_Hello,EIO_PRI_DEFAULT,EIO_AfterHello,baton);
ev_ref(EV_DEFAULT_UC);
returnUndefined();
}
如下代码是真正的重点。
首先,我们增加HelloWorld对象的引用计数,这样在其他线程执行的时候他就不会被回收。
函数eio_custom接受两个函数指针作为参数。
EIO_Hello函数将在线程池中执行,然后EIO_AfterHello函数将回到在“主线程”中执行。
我们的baton结构也被传递进各函数,这些函数可以使用baton结构中的数据完成相关的操作。
同时,我们也增加eventloop的引用。
这很重要,因为如果eventloop无事可做,Node.js就会退出。
最终,函数返回Undefined,因为真正的工作将在其他线程中完成。
staticintEIO_Hello(eio_req*req)
{
hello_baton_t*baton=static_cast
sleep(baton->sleep_for);
baton->hw->m_count+=baton->increment_by;
return0;
}
这个回调函数将在libeio管理的线程中执行。
首先,解析出baton结构,这样可以访问之前设置的各种参数。
然后sheepbaton->sleep_for秒,这么做是安全的,因为这个函数运行在独立的线程中并不会阻塞主线程中javascript的执行。
然后我们的增计数器,在实际的系统中,这些操作通常需要使用Lock/Mutex进行同步。
当上述方法返回后,libeio将会通知主线程它需要在主线成上执行代码,此时EIO_AfterHello将会被调用。
staticintEIO_AfterHello(eio_req*req)
{
HandleScopescope;
hello_baton_t*baton=static_cast
ev_unref(EV_DEFAULT_UC);
baton->hw->Unref();
进度此函数时,我们提取出baton结构,删除事件循环的引用,并减少HelloWorld对象的引用。
Local
argv[0]=String:
:
New("HelloWorld");
TryCatchtry_catch;
baton->cb->Call(Context:
:
GetCurrent()->Global(),1,argv);
if(try_catch.HasCaught()){
FatalException(try_catch);
}
新建要传递给回调函数的字符串参数,并放入字符串数组中。
然后我们调用回调传递一个参数,并检测可能抛出的异常。
baton->cb.Dispose();
deletebaton;
return0;
}
在执行过回调之后,应该销毁持久引用,然后删除之前创建的baton结构。
最后,你可以使用如下形式在Javascript中使用该模块:
varhelloeio=require('./helloworld_eio');
hi=newhelloeio.HelloWorldEio();
hi.hello(function(data){
console.log(data);
});
参数传递与解析
除了HelloWorld之外,你还需要理解最后一个问题:
参数的处理。
在helloWorldEIO例子中,我们使用一个REQ_FUN_ARG宏,然我们看看这个宏到底都做些什么。
#defineREQ_FUN_ARG(I,VAR)\
if(args.Length()<=(I)||!
args[I]->IsFunction())\
returnThrowException(Exception:
:
TypeError(\
String:
:
New("Argument"#I"mustbeafunction")));\
Local
:
Cast(args[I]);
就像Javascript中的argument变量,v8使用数组传递所有的参数。
由于没有严格的类型限制,所以传递给函数的参数数目可能和期待的不同。
为了对用户友好,使用如下的宏检测一下参数数组的长度并判断参数是否是正确的类型。
如果传递了错误的参数类型,该宏将会抛出TypeError异常。
为简化参数的解析,目前为止大多数的Node.js扩展都有一些本地作用域内的宏,用于特定类型参数的检测。
二、揭秘node.js事件
要使用NodeJS,你需要知道一个重要的东西:
事件(events)。
Node中有很多对象都可以触发事件,Node的文档中有很多示例。
但文档也许并不能清晰的讲解如何编写自定义事件以及监听函数。
对于一些简单的程序你可以不使用自定义事件,但这样很难应对复杂的应用。
那么如何编写自定义事件?
首先需要了解的是在node.js中的’events’模块。
快速概览
要访问此模块,只需使用如下语句:
require(‘events’)
requires(‘events’).EventEmitter
特别说明,node中所有能触发事件的对象基本上都是后者的实例。
让我们创建一个简单的演示程序Dummy:
dummy.js
viewplaincopytoclipboardprint?
1.// basic imports
2.var events = require('events');
3.
4.// for us to do a require later
5.module.exports = Dummy;
6.
7.function Dummy() {
8. events.EventEmitter.call(this);
9.}
10.
11.// inherit events.EventEmitter
12.Dummy.super_ = events.EventEmitter;
13.Dummy.prototype = Object.create(events.EventEmitter.prototype, {
14. constructor:
{
15. value:
Dummy,
16. enumerable:
false
17. }
18.});
//basicimports
varevents=require('events');
//forustodoarequirelater
module.exports=Dummy;
functionDummy(){
events.EventEmitter.call(this);
}
//inheritevents.EventEmitter
Dummy.super_=events.EventEmitter;
Dummy.prototype=Object.create(events.EventEmitter.prototype,{
constructor:
{
value:
Dummy,
enumerable:
false
}
});
上述代码中重点展示如何使用EventEmitter扩充对象,并从中继承所有的原型对象,方法…等等。
现在,我们假设Dummy有一个cooking()的方法,一旦把食物做熟之后它会触发’cooked’事件,并调用一个名为’eat’的回调函数。
dummy-cooking.js
viewplaincopytoclipboardprint?
1.Dummy.prototype.cooking = function(chicken) {
2. var self = this;
3. self.chicken = chicken;
4. self.cook = cook(); // assume dummy function that'll do the cooking
5. self.cook(chicken, function(cooked_chicken) {
6. self.chicken = cooked_chicken;
7. self.emit('cooked', self.chicken);
8. });
9.
10. return self;
11.}
Dummy.prototype.cooking=function(chicken){
varself=this;
self.chicken=chicken;
self.cook=cook();//assumedummyfunctionthat'lldothecooking
self.cook(chicken,function(cooked_chicken){
self.chicken=cooked_chicken;
self.emit('cooked',self.chicken);
});
returnself;
}
现在,这个模块已经完成了。
我们可以在主程序中使用它。
dummy-node.js
viewplaincopytoclipboardprint?
1.// A nonsensical node.js program
2.
3.var Dummy = require('./dummy');
4.var kenny = new Dummy();
5.var dinner = kenny.cooking(fried_chix);
6.dinner.on('cooked', function(chicken) {
7. // eat up!
8.}>
//Anonsensicalnode.jsprogram
varDummy=require('./dummy');
varkenny=newDummy();
vardinner=kenny.cooking(fried_chix);
dinner.on('cooked',function(chicken){
//eatup!
}>
所以基本上,node.js执行脚本,然后等待’cooked’事件被触发,并在事件触发之后调用回调函数并传递返回的参数。
还有什么要注意的
值得注意的是,例子中使用的“子类”和事件有一些极端(abitofanoverkill)。
EventEmitter每次只触发一个事件(EventEmitterforthingsthatonlyfireoneeventonce)。
如果只创造少数几个实例,可以将方法直接加入到实例本身,如果要触发底层事件,可能实用异步函数会更好一些。
关于events.EventEmitter,你还需注意一个特别的事件:
’error’。
任何错误发生时此事件都会触发,并且当没有监听程序监听这个事件时,node将会抛出异常并结束应用程序。
(感谢Tim指出这一点)
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- nodejs 教程