Node.js之HelloWorld背后的大坑2

填坑


上一篇文章留了个扣子,本来不想继续写的,因为这样写实在是又累又耗时间。而且中间Node.js还发布了v4.0.0版本,一下子没动力了有木有,人家v4.0.0都发布了,我还分析v0.12.7干吗?于是便在这段时间写了两篇自我感觉良好的小文,奈何文笔太差,完全没人看。倍受打击之余,默默的在多看上买了一套《诺贝尔文学奖作品典藏书系(共28册)》,至今还躺在书架上供每日瞻仰使用。还是多写技术文章吧,于是便回来继续填坑。

新版本出了并不代表老版本就没有价值了,我想只有充分了解了老版本才能更好的体会新版本,于是乎欣然继续分析v0.12.7。温习一下git命令,大神略过。

% git checkout v0.12.7

想说Hello不容易


上回书说到,Node.js已经摆好姿势,“'Hello World”已经含在嘴里,伺机待发,但是有句歌词是这么唱的“让我怎么说出口……”,充分体现了这种状态,真是不简单。看了代码之后,充分被Node.js秀了一把继承和异步,一个乱字已然无法形容,所以必须先做几点温馨提示。

  • 首先最好先重新温习一下网络方面的知识,百度一下TCP/IP、HTTP、Socket、Stream,随便翻翻前几页,有个大概了解就行,要不有些东西真的理解不了。
  • 其次要了解一下相关类的继承关系,撇开多继承,这种程度的继承关系在Java里不算复杂,在IDE里也非常容易查看,但是放到JavaScript里只能用一坨来形容,不梳理出来一定会陷入其中。
Stream Inheritance
  • 然后一定要按照之前提到的方法,把V8和Debug日志全部打开,对比日志逐行分析代码,这样分析起来会事半功倍。
  • 最后就是去触发一下程序,毕竟人家已经listen了这么久。这里不推荐浏览器,因为在地址栏里直接输入请求地址会有个问题,浏览器会默认先请求favicon.ico,这样日志里会出现大量的干扰,不利于分析。我在这里被坑了半天之后,不由哼出了周董的旋律,“快使用命令行!轻松!愉快!”
% curl http://127.0.0.1:1337/

SayHello


连接过程
SayHello1

图中有一个细节需要注意,左上角的第一个调用是空心箭头->,这代表了异步调用,我们真正开始接触异步代码了。等等,为什么EventEmitter.emit()是普通的实心箭头?没错,他确实只是一个同步调用。我一直以为他是一个Node.js经典的异步操作,OMG,我居然被自己骗了这么长时间。以我目前阅读的代码和日志来看,好像只有libuvprocess.nextTick()发起的调用是异步的,特此说明。

代码是如何从libuv到C++再到Node.js的呢?

  • tcp_wrap.cc中有如下代码
void TCPWrap::Initialize(Handle<Object> target,
                         Handle<Value> unused,
                         Handle<Context> context) {
...
  t->InstanceTemplate()->Set(String::NewFromUtf8(env->isolate(),
                                                 "onconnection"),
                             Null(env->isolate()));
...
}
void TCPWrap::Listen(const FunctionCallbackInfo<Value>& args) {
...
  int err = uv_listen(reinterpret_cast<uv_stream_t*>(&wrap->handle_),
                      backlog,
                      OnConnection);
  args.GetReturnValue().Set(err);
}
void TCPWrap::OnConnection(uv_stream_t* handle, int status) {
  ...
  tcp_wrap->MakeCallback(env->onconnection_string(), ARRAY_SIZE(argv), argv);
}
  • env.h中有如下代码
#define PER_ISOLATE_STRING_PROPERTIES(V)                                      \
...
  V(onconnection_string, "onconnection")                                      \
...
  • net.js中有如下代码
Server.prototype._listen2 = function(address, port, addressType, backlog, fd) {
...
  self._handle.onconnection = onconnection;
...
}
function onconnection(err, clientHandle) {
...
}

简单说就是tcp_wrap.cc在用V8定义JS对象TCP的时候定义了onconnection属性,并且在调用uv_listen的时候设置了OnConnection回调,而在OnConnection的最后会调用onconnection属性所设置的方法,最后net.js设置了这个属性为onconnection方法。

请求处理响应过程
SayHello2-1
SayHello2-2

图中小人形状代表第三方库deps/uvdeps/http_parser。图SayHello2-1和图SayHello2-2其实是一次同步顺序调用,画不开了才分成两个,也就是说图SayHello2-1右边的小人和图SayHello2-2左边的小人其实是一个人。

仔细看,http_parser竟然是个同步库,再一次纠正了我先入为主的认识——Node.js全身都是异步的。Node.js以异步闻名于江湖没错,但是要加上个主语——I/O,异步I/O才是其必杀技。回想一下跟他过招的这么长时间里,好像确实是异步多发于I/O。

看了这两张图,欣喜的发现,“Hello World”终于脱口而出了,可喜可贺~

善后过程

此时并没有完,如果将时间静止于此,命令行里应该已经显示出了“Hello world”,但是程序还没有退出。后面还要干什么呢,大概跟吃完饭要刷碗,说完话嘴不能一直张着类似。再次恢复时间的流动,又会哗哗哗的输出一大堆日志,仔细查看可知,其实是在还前面的债,统统的异步回调。前面的时序图基本都忽略了异步调用,是故意的,因为实在没有找到办法和地方把他们画出来。

还好剩下的东西不多了,而且也有一些窍门。重新查看前面两个流程的代码,把C++中调用uv_xxx并且设置了回调的方法记下来,把JS中process.nextTick()的调用记下来。V8的日志中一段完整的调用总是有以下形式。

  1: xxx {
  2:   xxx { 
  3:     xxx {
  .
  .
  .
  3:     } -> xxx
  2:   } -> xxx
  1: } -> xxx

所以把前面记录下来的方法调用和V8日志里为1:的段落进行对比,肯定能一一对上的,这里就不详细说明了。但是V8日志我没有收集全,最后面一段莫名其妙的就没了,不知道为什么。

回顾


回想源码分析过程,异常痛苦,只是用着Sublime Text的基本功能,翻来翻去,跳来跳去,虽然他的基本功能已经很强大了,但是还是感觉效率低下。“工欲善其事必先利其器”,还是应该把环境弄得高效趁手一些。工具放一边,其实不能随心所欲Debug源码才是硬伤,否则就不用假装有时间机器了,也许是被Java宠坏了,落差巨大。不过我想Node.js的核心开发人员不可能像我这么费劲,所以这方面还需要进一步了解。虽然重新认识了Node.js的异步,初步了解了其幕后英雄libuv和V8的机制,体系结构更加清晰明了,但是底层细节还是需要深入研究。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,658评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,482评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,213评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,395评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,487评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,523评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,525评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,300评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,753评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,048评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,223评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,905评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,541评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,168评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,417评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,094评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,088评论 2 352

推荐阅读更多精彩内容