Node.js的由V8 engine,内建的模块以及libuv组成。 结合Node.js源代码以及图1,可以看出几点
- Node对外提供了标准的JS库,这些库包括了从HTTP,DNS等网络服务到File System, Child Processes等基础服务的一系列库
- 这些库是依赖于由C/C++写成的其它内建模块对外提供服务的
- 由libuv完成了最终的异步调用的工作
图1: Node.js overview
图2是我之前发表的一篇文章( 一个小例子浅析Node.JS架构)中所画的图。该图从V8 engine角度出发,描述了建立HTTP server时,从JS到C++的完整穿越过程。图2的过程既包括了V8所执行的JS和它所处的context,也包括了异步调用后端的参与者(Node.js TCPWrap以及libuv)。
对于图2,需要强调的是,图中的1,2,3,4,5这5个步骤是一个同步调用的过程。该过程始于server.listen(),终于第5步,也就是server.listen()的下一行JS代码。 这5步执行完后,server.listen()所发起的请求其实并没有被处理完。V8就急忙动身执行后面的JS代码了。 当server.listen()发起的请求最终处理完后,从libuv发起了一个逆向的callback调用过程。该逆向过程始于libuv的event loop,终于V8 engine调用server.listen()所设置的callback函数。而在这段时间内,V8已经不知道执行完多少JS代码,完成了多少其它任务了,这就是异步调用的魅力所在,这也是Node.js高并发的原因。
需要澄清的是,高并发不等于高实时性。以网站响应为例,高并发意味着可以同时接受很高数量的页面请求,但从接受每个请求到把最终数据返回给请求者的时间确是响应速度的问题。这就好像我们很多人同时去饭馆吃饭,如果饭馆接待员反应灵敏,调度得当,她会让我们每个人都能得到一个座位,并且开始点菜,但这不表示我们能在很快时间内吃上可口的菜。
本文将图2所示的过程进一步细化,从V8执行JS代码创建一个HTTP server开始分析,帮助需要的人从更细更完整的角度看完整个过程。
图2:从V8到libuv异步调用过程
用Node.js建立一个HTTP server可谓方便至极也简单至极。寥寥十几行代码就完成了一个server的雏形。然而代码看似简单,背后必然发生了很多故事。暴露给应用开发者的接口越少,说明平台或者框架代替开发者做了越多的事情。
图3:创建http server的JS code
套用著名技术作家侯捷的一句话“只会用一样东西,不明白他的道理,实在不高明”。作为一个探索欲望挺强的人,我按捺不住好奇心深入框架研究了一番,努力做一个高明的人。
我的研究从代码http.createServer()开始。 图4展示了从V8开始执行图3的JS代码到TCPWrap的序列图。
图4:http server创建序列图
序列图从上至下分为2部分。第一部分创建了一个Server()的实例。这部分还是集中在JS部分。其中http.js, _http_server.js以及net.js是Node.js JS库中的一部分。这部分比较重要的细节是 _http_server.js中的Server()的prototype部分从net.js Server()集成了若干API(),这部分API会为之后提供服务。
第二部分开始从调用this.listen()开始。可以看到,第二部分直接调用net.js提供的API。让我们把注意力直接放到序列图的net.js 最后一步。
createTCP()通过process.binding(‘tcp_wrap’).TCP进入到Node.js C++部分。也就是图2所示的TCPWrap部分。new TCPWrap()是最先被调用的代码。从JS 到 C++如何穿越的问题,请参考V8 engine相关文档。
我们再来看看图4 net.js的倒数第二步 handle.open(fd)。这部分代码最终调用到C++部分的TCPWrap::Open()。
文章分析至此结束,后续部分大家可以按代码索骥,找到背后的故事。
转载本文请注明作者和出处,请勿用于任何商业用途。如需帮助,请QQ联系作者:229848501