本文转载自微信公众号《编程杂技》,作者theanarkh。转载本文请联系编程杂技公众号。昨天在分析http模块相关的代码时,遇到了一个晦涩难懂的逻辑。想了想还是没明白。百度和谷歌搜索了很多帖子都没有找到合适的答案。突然,我看到了一个搜索结果,主题有点眼熟。我在StackOverflow上点了一篇帖子,结果已经是404了,终于通过快照功能成功看到了内容。这篇帖子[1]与我的疑惑无关,却突然给了我一些启发。顺着这个灵感看代码,最后下载了nodejs源码,加了点日志,连夜编译(时间太长,等不及编译完成,只好睡觉了)。早上起床求证,终于解开了疑点。问题源于以下一段代码。functionconnectionListenerInternal(server,socket){socket.server=server;//分配一个http解析器constparser=parsers.alloc();//解析请求消息parser.initialize(HTTPParser.REQUEST,newHTTPServerAsyncResource('HTTPINCOMINGMESSAGE',socket),server.maxHeaderSize||0,server.insecureHTTPParser===undefined?isLenient():server.insecureHTTPParser,);parser.socket=socket;//开始解析header开始时间parser.parsingHeadersStart=nowDate();socket.parser=parser;conststate={onData:null,onEnd:null,onClose:null,onDrain:null,//在同一个tcp连接上,请求和响应队列outgoing:[],incoming:[],outgoingData:0,keepAliveTimeoutSet:false};state.onData=socketOnData.bind(undefined,server,socket,parser,state);socket.on('data',state.onData);if(socket._handle&&socket._handle.isStreamBase&&!socket._handle._consumed){parser._consumed=true;socket._handle._consumed=true;parser.consume(socket._handle);}parser[kOnExecute]=onParserExecute.bind(undefined,server,socket,parser,state);socket._paused=false;}这段代码看起来很多,这是启动http服务器后,有一个新的tcp连接建立连接时执行的回调就是如何处理tcp上数据的到来。上面代码中,nodejs监听了socket的data事件,同时注册了hookkOnExecute。我们都知道数据事件是数据到达流时触发的事件。让我们看看socketOnData做了什么。functionssocketOnData(server,socket,parser,state,d){//交给http解析器处理,返回已解析的字节数constret=parser.execute(d);onParserExecuteCommon(server,socket,parser,state,ret,d);}这个好像没什么问题,socket上有数据,然后交给http解析器处理。http模块源码分析的文章几乎都是这样分析的。我的第一反应是这没问题。kOnExecute是做什么的?kOnExecute钩子函数的值是onParserExecute,好像是用来解析tcp上的数据的。好像和onSocketData功能一样。tcp上的数据有两个消费者吗?让我们看看kOnExecute什么时候被回调。voidOnStreamRead(ssize_tnread,constuv_buf_t&buf)override{Localret=Execute(buf.base,nread);Localcb=object()->Get(env()->context(),kOnExecute).ToLocalChecked();MakeCallback(cb.As(),1,&ret);}是在node_http_parser.cc中的OnStreamRead中回调的,那么OnStreamRead是什么时候回调的呢?OnStreamRead是nodejs中c++层流操作的一个泛型函数,当stream有数据时执行回调。而OnStreamRead也会将数据交给http解析器进行解析。好像真的有两个消费者?这就很奇怪了,为什么一个数据两次交给http解析器处理呢?这时我的想法是,这两个地方一定是互不相容的。但是我一直没能找到它在哪里完成的。终于在connectionListenerInternal的一段代码中找到了答案。if(socket._handle&&socket._handle.isStreamBase&&!socket._handle._consumed){parser._consumed=true;socket._handle._consumed=true;parser.consume(socket._handle);}因为tcp流继承了StreamBase类,所以if成立(后面会详细分析)。我们来看看consume的实现。staticvoidConsume(constFunctionCallbackInfo&args){Parser*parser;ASSIGN_OR_RETURN_UNWRAP(&parser,args.Holder());CHECK(args[0]->IsObject());StreamBase*stream=StreamBase::FromObject(args[0].As