Repository: fzxa/NodeJS-Nucleus-Plus-Internals Branch: master Commit: 00a19d3c42ca Files: 4 Total size: 26.4 KB Directory structure: gitextract_4za_iwvp/ ├── README.md └── chapter1/ ├── chapter1-0.md ├── chapter1-1.md └── chapter1-2.md ================================================ FILE CONTENTS ================================================ ================================================ FILE: README.md ================================================ # NodeJS-Nucleus-Plus-Internals NodeJS源码分析-由浅入深了解架构运行原理 Node版本基于v8.9.3 从运行入口开始深入源码分析,由浅入深,共同学习。 [1-0 NodeJS源码分析-1 Hello World](https://github.com/fzxa/NodeJS-Nucleus-Plus-Internals/blob/master/chapter1/chapter1-0.md) [1-1 NodeJS源码解析 - HTTP Server模块](https://github.com/fzxa/NodeJS-Nucleus-Plus-Internals/blob/master/chapter1/chapter1-1.md) [1-2 NodeJS源码分析 - Stream模块](https://github.com/fzxa/NodeJS-Nucleus-Plus-Internals/blob/master/chapter1/chapter1-2.md) 持续更新.. ### NodeJS系统架构图: ![image](node-system.png) - Javascript V8 Engine: Nodejs javascript运行引擎 - Libuv 是专门为Node.js开发的一个封装库,提供跨平台的异步I/O能力. - C-ares:提供了异步处理 DNS 相关的能力。 - http_parser、OpenSSL、zlib 等:提供包括 http 解析、SSL、数据压缩等其他的能力。 ### NodeJS流程图 ![image](https://github.com/fzxa/NodeJS-Nucleus-Plus-Internals/blob/master/chapter1/images/node-loop.png) ================================================ FILE: chapter1/chapter1-0.md ================================================ ### NodeJS源码分析-1 Hello World #### 简要 Node已经如今发展很快,已经相对稳定和成熟,在某些时候有必要知道其内部运行原理以及运行处理过程。 种一棵树最好的时间是十年前 其次是现在。希望能坚持下去。 ### Nodejs当前最新版本 8.9.4 [NodeJS官方网站下载源码](https://nodejs.org/en/download/) ![image](images/chapter1-0.png) Node.js主要分为四大部分,Node Standard Library,Node Bindings,V8,Libuv 大体流程是这样的: 1. 初始化 V8 、LibUV , OpenSSL 2. 创建 Environment 环境 3. 设置 Process 进程对象 4. 执行 node.js 文件 解压包后代码结构如下: ``` ├── AUTHORS ├── BSDmakefile   # bsd平台makefile文件 ├── BUILDING.md ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── COLLABORATOR_GUIDE.md ├── CONTRIBUTING.md ├── CPP_STYLE_GUIDE.md ├── GOVERNANCE.md ├── LICENSE ├── Makefile   # Linux平台makefile文件 ├── README.md ├── android-configure ├── benchmark ├── common.gypi ├── configure ├── deps         # Node底层核心依赖; 最核心的两块V8 Engine和libuv事件驱动的异步I/O模型库 ├── doc           ├── lib           # Node后端核心库 ├── node.gyp     # Node编译任务配置文件 ├── node.gypi ├── src           # C++内建模块 ├── test         # 测试代码 ├── tools         # 编译时用到的工具 └── vcbuild.bat   # Windows跨平台makefile文件 ``` ### Hello World 底层运行过程 [官方Hello World代码](https://nodejs.org/en/about/) ```js #app.js const http = require('http'); const hostname = '127.0.0.1'; const port = 3000; const server = http.createServer((req, res) => { res.statusCode = 200; res.setHeader('Content-Type', 'text/plain'); res.end('Hello World\n'); }); server.listen(port, hostname, () => { console.log(`Server running at http://${hostname}:${port}/`); }); ``` 一个简单的HelloWorld涉及到多个模块: - global - module - http - event - net ### 1.0 从main执行到js 入口 src/node_main.cc 106行 通过 src/node.cc 调用 node::Start(argc, argv); node_main.cc ```c namespace node { extern bool linux_at_secure; } // namespace node int main(int argc, char *argv[]) { #if defined(__linux__) char** envp = environ; while (*envp++ != nullptr) {} Elf_auxv_t* auxv = reinterpret_cast(envp); for (; auxv->a_type != AT_NULL; auxv++) { if (auxv->a_type == AT_SECURE) { node::linux_at_secure = auxv->a_un.a_val; break; } } #endif // Disable stdio buffering, it interacts poorly with printf() // calls elsewhere in the program (e.g., any logging from V8.) setvbuf(stdout, nullptr, _IONBF, 0); setvbuf(stderr, nullptr, _IONBF, 0);  // main作为入口调用node::Start  return node::Start(argc, argv); } #endif ``` ### 1.1 node::Start 加载js 调用顺序: Start() -> LoadEnviroment() -> ExecuteString() 最终在LoadEnvrioment()里面加载node.js文件,调用ExecuteString() 解析执行node.js文件,返回值是一个f_value 并且在ExecuteString()调用V8的 Script::Compile() 和 Script::Run()两个接口去解析执行js代码。 node.cc ```c # Nodejs启动入口, inline int Start(Isolate* isolate, IsolateData* isolate_data, int argc, const char* const* argv, int exec_argc, const char* const* exec_argv) { HandleScope handle_scope(isolate); Local context = Context::New(isolate); Context::Scope context_scope(context); Environment env(isolate_data, context); CHECK_EQ(0, uv_key_create(&thread_local_env)); uv_key_set(&thread_local_env, &env); env.Start(argc, argv, exec_argc, exec_argv, v8_is_profiling); const char* path = argc > 1 ? argv[1] : nullptr; StartInspector(&env, path, debug_options); if (debug_options.inspector_enabled() && !v8_platform.InspectorStarted(&env)) return 12; // Signal internal error. env.set_abort_on_uncaught_exception(abort_on_uncaught_exception); if (force_async_hooks_checks) { env.async_hooks()->force_checks(); } { Environment::AsyncCallbackScope callback_scope(&env); env.async_hooks()->push_async_ids(1, 0);    //加载nodejs文件后调用ExecuteString()    LoadEnvironment(&env);    env.async_hooks()->pop_async_id(1); } env.set_trace_sync_io(trace_sync_io);  //事件循环池  { SealHandleScope seal(isolate); bool more; PERFORMANCE_MARK(&env, LOOP_START); do { uv_run(env.event_loop(), UV_RUN_DEFAULT); v8_platform.DrainVMTasks(); more = uv_loop_alive(env.event_loop()); if (more) continue; EmitBeforeExit(&env); // Emit `beforeExit` if the loop became alive either after emitting // event, or after running some callbacks. more = uv_loop_alive(env.event_loop()); } while (more == true); PERFORMANCE_MARK(&env, LOOP_EXIT); } env.set_trace_sync_io(false); const int exit_code = EmitExit(&env); RunAtExit(&env); uv_key_delete(&thread_local_env); v8_platform.DrainVMTasks(); WaitForInspectorDisconnect(&env); #if defined(LEAK_SANITIZER) __lsan_do_leak_check(); #endif return exit_code; } ``` ### 核心运行流程 整体运行流程图 ![image](images/node-loop.png) 1. 核心数据结构 default_loop_struct 结构体为struct uv_loop_s 当加载js文件时,如果代码有io操作,调用lib模块->底层C++模块->LibUV(deps uv)->拿到系统返回的一个fd(文件描述符),和 js代码传进来的回调函数callback,封装成一个io观察者(一个uv__io_s类型的对象),保存到default_loop_struct. 2. 进入事件池, default_loop_struct保存对应io观察着,V8 Engine处理js代码, main函数调用libuv进入uv_run(), node进入事件循环 ,判断是否有存活的观察者 - 如果也没有io, Node进程退出 - 如果有io观察者, 执行uv_run()进入epoll_wait()线程挂起,io观察者检测是否有数据返回callback, 没有数据则会一直在epoll_wait()等待执行 server.listen(3000)会挂起一直等待。 ### Module对象 根据CommonJS规范,每一个文件就是一个模块,在每个模块中,都会有一个module对象,这个对象就指向当前的模块。 module对象具有以下属性: - id:当前模块的bi - exports:表示当前模块暴露给外部的值 - parent: 是一个对象,表示调用当前模块的模块 - children:是一个对象,表示当前模块调用的模块 - filename:模块的绝对路径 - paths:从当前文件目录开始查找node_modules目录;然后依次进入父目录,查找父目录下的node_modules目录;依次迭代,直到根目录下的node_modules目录 - loaded:一个布尔值,表示当前模块是否已经被完全加载 示例: ```js module.exports = { name: 'fzxa', getAge: function(age){ console.log(age) } } console.log(module) ``` 执行node module.js 返回如下 ```js Module { id: '.', exports: { name: 'fzxa', getAge: [Function: getAge] }, parent: null, filename: '/Users/fzxa/Documents/study/module.js', loaded: false, children: [], paths: [ '/Users/fzxa/Documents/study/node_modules', '/Users/fzxa/Documents/node_modules', '/Users/fzxa/node_modules', '/Users/node_modules', '/node_modules' ] } ``` module对象具有一个exports属性,该属性就是用来对外暴露变量、方法或整个模块的。当其他的文件require进来该模块的时候,实际上就是读取了该模块module对象的exports属性 ### exports对象 exports和module.exports都是引用类型的变量,而且这两个对象指向同一块内存地址 ``` exports = module.exports = {}; ``` 例子: ```js var module = { exports: {} } var exports = module.exports function change(exports) { //为形参添加属性,是会同步到外部的module.exports对象的 exports.name = "fzxa" //在这里修改了exports的引用,并不会影响到module.exports exports = { age: 24 } console.log(exports) //{ age: 24 } } change(exports) console.log(module.exports) //{exports: {name: "fzxa"}} ``` 直接给exports赋值,会改变当前模块内部的形参exports对象的引用,也就是说当前的exports已经跟外部的module.exports对象没有任何关系了,所以这个改变是不会影响到module.exports的 module.exports就是为了解决上述exports直接赋值,会导致抛出不成功的问题而产生的。有了它,我们就可以这样来抛出一个模块了. ### require方法 Node中引入模块的机制步骤 1. 路径分析 2. 文件定位 3. 编译执行 Node对引入过的模块也会进行缓存。不同的地方是,node缓存的是编译执行之后的对象而不是静态文件 Module._load的源码: ```js Module._load = function(request, parent, isMain) { // 计算绝对路径 var filename = Module._resolveFilename(request, parent); // 第一步:如果有缓存,取出缓存 var cachedModule = Module._cache[filename]; if (cachedModule) { return cachedModule.exports; // 第二步:是否为内置模块 if (NativeModule.exists(filename)) { return NativeModule.require(filename); } // 第三步:生成模块实例,存入缓存 var module = new Module(filename, parent); Module._cache[filename] = module; // 第四步:加载模块 try { module.load(filename); hadException = false; } finally { if (hadException) { delete Module._cache[filename]; } } // 第五步:输出模块的exports属性 return module.exports; }; ``` 在Module._load方法的内部调用了Module._findPath这个方法,这个方法是用来返回模块的绝对路径的,源码如下: ```js Module._findPath = function(request, paths) { // 列出所有可能的后缀名:.js,.json, .node var exts = Object.keys(Module._extensions); // 如果是绝对路径,就不再搜索 if (request.charAt(0) === '/') { paths = ['']; } // 是否有后缀的目录斜杠 var trailingSlash = (request.slice(-1) === '/'); // 第一步:如果当前路径已在缓存中,就直接返回缓存 var cacheKey = JSON.stringify({request: request, paths: paths}); if (Module._pathCache[cacheKey]) { return Module._pathCache[cacheKey]; } // 第二步:依次遍历所有路径 for (var i = 0, PL = paths.length; i < PL; i++) { var basePath = path.resolve(paths[i], request); var filename; if (!trailingSlash) { // 第三步:是否存在该模块文件 filename = tryFile(basePath); if (!filename && !trailingSlash) { // 第四步:该模块文件加上后缀名,是否存在 filename = tryExtensions(basePath, exts); } } // 第五步:目录中是否存在 package.json if (!filename) { filename = tryPackage(basePath, exts); } if (!filename) { // 第六步:是否存在目录名 + index + 后缀名 filename = tryExtensions(path.resolve(basePath, 'index'), exts); } // 第七步:将找到的文件路径存入返回缓存,然后返回 if (filename) { Module._pathCache[cacheKey] = filename; return filename; } } // 第八步:没有找到文件,返回false return false; }; ``` 当我们第一次引入一个模块的时候,require的缓存机制会将我们引入的模块加入到内存中,以提升二次加载的性能。但是,如果我们修改了被引入模块的代码之后,当再次引入该模块的时候,就会发现那并不是我们最新的代码,这是一个麻烦的事情。如何解决呢 require有如下方法: require(): 加载外部模块 require.resolve():将模块名解析到一个绝对路径 require.main:指向主模块 require.cache:指向所有缓存的模块 require.extensions:根据文件的后缀名,调用不同的执行函数 ``` //删除指定模块的缓存 delete require.cache[require.resolve('/*被缓存的模块名称*/')] // 删除所有模块的缓存 Object.keys(require.cache).forEach(function(key) { delete require.cache[key]; }) ``` ### HTTP_Server 首先需要创建一个 http.Server 类的实例,然后监听它的 request 事件 requestListener 回调函数作为观察者,监听了 request 事件, 默认超时时间为2分 lib/_http_server.js ```js function Server(requestListener) { if (!(this instanceof Server)) return new Server(requestListener); net.Server.call(this, { allowHalfOpen: true }); if (requestListener) { this.on('request', requestListener); } // Similar option to this. Too lazy to write my own docs. // http://www.squid-cache.org/Doc/config/half_closed_clients/ // http://wiki.squid-cache.org/SquidFaq/InnerWorkings#What_is_a_half-closed_filedescriptor.3F this.httpAllowHalfOpen = false; this.on('connection', connectionListener); this.timeout = 2 * 60 * 1000; this.keepAliveTimeout = 5000; this._pendingResponseData = 0; this.maxHeadersCount = null; } ``` 观察者 connectionListener 处理 connection 事件。 这时,则需要一个 HTTP parser 来解析通过 TCP 传输过来的数据: lib/_http_server.js ```js function connectionListener(socket) { debug('SERVER new http connection'); httpSocketSetup(socket); // Ensure that the server property of the socket is correctly set. // See https://github.com/nodejs/node/issues/13435 if (socket.server === null) socket.server = this; // If the user has added a listener to the server, // request, or response, then it's their responsibility. // otherwise, destroy on timeout by default if (this.timeout) socket.setTimeout(this.timeout); socket.on('timeout', socketOnTimeout); var parser = parsers.alloc(); parser.reinitialize(HTTPParser.REQUEST); parser.socket = socket; socket.parser = parser; parser.incoming = null; // Propagate headers limit from server instance to parser if (typeof this.maxHeadersCount === 'number') { parser.maxHeaderPairs = this.maxHeadersCount << 1; } else { // Set default value because parser may be reused from FreeList parser.maxHeaderPairs = 2000; } var state = { onData: null, onEnd: null, onClose: null, onDrain: null, outgoing: [], incoming: [], // `outgoingData` is an approximate amount of bytes queued through all // inactive responses. If more data than the high watermark is queued - we // need to pause TCP socket/HTTP parser, and wait until the data will be // sent to the client. outgoingData: 0, keepAliveTimeoutSet: false }; state.onData = socketOnData.bind(undefined, this, socket, parser, state); state.onEnd = socketOnEnd.bind(undefined, this, socket, parser, state); state.onClose = socketOnClose.bind(undefined, socket, state); state.onDrain = socketOnDrain.bind(undefined, socket, state); socket.on('data', state.onData); socket.on('error', socketOnError); socket.on('end', state.onEnd); socket.on('close', state.onClose); socket.on('drain', state.onDrain); parser.onIncoming = parserOnIncoming.bind(undefined, this, socket, state); // We are consuming socket, so it won't get any actual data socket.on('resume', onSocketResume); socket.on('pause', onSocketPause); // Override on to unconsume on `data`, `readable` listeners socket.on = socketOnWrap; // We only consume the socket if it has never been consumed before. var external = socket._handle._externalStream; if (!socket._handle._consumed && external) { parser._consumed = true; socket._handle._consumed = true; parser.consume(external); } parser[kOnExecute] = onParserExecute.bind(undefined, this, socket, parser, state); socket._paused = false; } ``` 未完... 参考链接: ``` https://yjhjstz.gitbooks.io/deep-into-node/chapter1/ http://blog.csdn.net/wuji3390/article/details/71276849 https://feclub.cn/post/content/wq_node ``` ================================================ FILE: chapter1/chapter1-1.md ================================================ ### NodeJS源码解析 - HTTP Server模块 http是nodejs中重要的模块之一,有必要了解它的运行原理 回到helloWorld ,当node在收到一个http请求,会创建一个http.Server,注册并监听request。 ```js var http = require('http'); http.createServer((req, res) => { res.writeHead(200, { 'Content-Type': 'text/plain' }); res.end('Hello World\n'); }).listen(port, hostname, () => { console.log(`Server running at http://${hostname}:${port}/`); }); ``` #### HTTP模块 1. 打开node-v8.9.3/lib/http.js 首先引入的是http模块,模块抛出公共方法调用createServer实际上是返回Server实例, createServer里面的回调函数(参数requestListener) 直接作为了Server的参数requestListener,而这个Server实际上是require('_http_server') ```js 'use strict'; const agent = require('_http_agent'); const { ClientRequest } = require('_http_client'); const common = require('_http_common'); const incoming = require('_http_incoming'); const outgoing = require('_http_outgoing'); //引入私有_http_server模块 const server = require('_http_server'); const { Server } = server; //创建server, 将回调函数作为参数 function createServer(requestListener) { return new Server(requestListener); } function request(options, cb) { return new ClientRequest(options, cb); } function get(options, cb) { var req = request(options, cb); req.end(); return req; } //http模块暴露的所有公共方法 module.exports = { _connectionListener: server._connectionListener, METHODS: common.methods.slice().sort(), STATUS_CODES: server.STATUS_CODES, Agent: agent.Agent, ClientRequest, globalAgent: agent.globalAgent, IncomingMessage: incoming.IncomingMessage, OutgoingMessage: outgoing.OutgoingMessage, Server, ServerResponse: server.ServerResponse,  createServer,  get, request }; ``` 打开文件node-v8.9.3/lib/_http_server.js 260行 实际上是为这个requestListener函数与'request'事件绑定到了一起,而'request '是方法parserOnIncoming里面抛出的一个事件 ```js function Server(requestListener) { if (!(this instanceof Server)) return new Server(requestListener); net.Server.call(this, { allowHalfOpen: true });  //如果有回调函数,对当前实例进行监听,若request有事件触发则调用回调  if (requestListener) { this.on('request', requestListener); } // Similar option to this. Too lazy to write my own docs. // http://www.squid-cache.org/Doc/config/half_closed_clients/ // http://wiki.squid-cache.org/SquidFaq/InnerWorkings#What_is_a_half-closed_filedescriptor.3F this.httpAllowHalfOpen = false;  //当启动server实例时,观察者建立connect事件  this.on('connection', connectionListener); this.timeout = 2 * 60 * 1000; this.keepAliveTimeout = 5000; this._pendingResponseData = 0; this.maxHeadersCount = null; } //net.Server继承Server util.inherits(Server, net.Server); ``` #### res过程? 调用emit方法,将request事件发送给每一个监听的实例,并且传入req,res server.emit('request', req, res); 这个事件也会同时抛出req和res两个对象 req变量与另一个叫做shouldKeepAlive的变量作参同时传入此函数parserOnIncoming _http_server.js 592行 602行 ```js //处理具体解析完毕的请求 function parserOnIncoming(server, socket, state, req, keepAlive) { resetSocketTimeout(server, socket, state); state.incoming.push(req); // If the writable end isn't consuming, then stop reading // so that we don't become overwhelmed by a flood of // pipelined requests that may never be resolved. if (!socket._paused) { var ws = socket._writableState; if (ws.needDrain || state.outgoingData >= ws.highWaterMark) { socket._paused = true; // We also need to pause the parser, but don't do that until after // the call to execute, because we may still be processing the last // chunk. socket.pause(); } }  //服务器通过ServerResponse实例,来个请求方发送数据。包括发送响应表头,发送响应主体 var res = new ServerResponse(req); res._onPendingData = updateOutgoingData.bind(undefined, socket, state); res.shouldKeepAlive = keepAlive; DTRACE_HTTP_SERVER_REQUEST(req, socket); LTTNG_HTTP_SERVER_REQUEST(req, socket); COUNTER_HTTP_SERVER_REQUEST(); if (socket._httpMessage) { // There are already pending outgoing res, append. state.outgoing.push(res); } else { res.assignSocket(socket); } // When we're finished writing the response, check if this is the last // response, if so destroy the socket. res.on('finish', resOnFinish.bind(undefined, req, res, socket, state, server)); if (req.headers.expect !== undefined && (req.httpVersionMajor === 1 && req.httpVersionMinor === 1)) { if (continueExpression.test(req.headers.expect)) { res._expect_continue = true; if (server.listenerCount('checkContinue') > 0) { server.emit('checkContinue', req, res); } else { res.writeContinue(); //送给每一个监听器的实例并传入req&res server.emit('request', req, res); } } else if (server.listenerCount('checkExpectation') > 0) { server.emit('checkExpectation', req, res); } else { res.writeHead(417); res.end(); } } else { //送给每一个监听器的实例并传入req&res    // res实际上是ServerResponse的实例    // var res = new ServerResponse(req); server.emit('request', req, res); } return false; // Not a HEAD response. (Not even a response!) } ``` ServerResponse 实现了 Writable Stream interface,内部也是通过socket来发送信息。 res,发现为ServerResponse()的实例并传入req ```js function ServerResponse(req) { OutgoingMessage.call(this); if (req.method === 'HEAD') this._hasBody = false; this.sendDate = true; this._sent100 = false; this._expect_continue = false; if (req.httpVersionMajor < 1 || req.httpVersionMinor < 1) { this.useChunkedEncodingByDefault = chunkExpression.test(req.headers.te); this.shouldKeepAlive = false; } } //继承自OutgoingMessage,为OM的一个子类,所以回调函数里的res也是OM的一个实例 //来自_http_outgoing私有模块 //const OutgoingMessage = require('_http_outgoing').OutgoingMessage; util.inherits(ServerResponse, OutgoingMessage); ``` 到此res线找到,res为ServerMessage的实例,也是OutgoingMessage的实例 ```js function OutgoingMessage() { Stream.call(this); //返回一些与服务器有关的属性 // Queue that holds all currently pending data, until the response will be // assigned to the socket (until it will its turn in the HTTP pipeline). this.output = []; this.outputEncodings = []; this.outputCallbacks = []; // `outputSize` is an approximate measure of how much data is queued on this // response. `_onPendingData` will be invoked to update similar global // per-connection counter. That counter will be used to pause/unpause the // TCP socket and HTTP Parser and thus handle the backpressure. this.outputSize = 0; this.writable = true; this._last = false; this.upgrading = false; this.chunkedEncoding = false; this.shouldKeepAlive = true; this.useChunkedEncodingByDefault = true; this.sendDate = false; this._removedConnection = false; this._removedContLen = false; this._removedTE = false; this._contentLength = null; this._hasBody = true; this._trailer = ''; this.finished = false; this._headerSent = false; this.socket = null; this.connection = null; this._header = null; this[outHeadersKey] = null; this._onPendingData = noopPendingOutput; } util.inherits(OutgoingMessage, Stream); //继承自Stream ``` 流程图演示: ![image](images/node-server-res.png) #### req 过程 req,在parserOnIncoming()作为参数传入 parserOnIncoming()在哪里被调用? ```js // _http_server.js 345行 function connectionListener(socket) { ... var parser = parsers.alloc(); parser.onIncoming = parserOnIncoming.bind(undefined, this, socket, state); ... } ``` parsers在_http_common.js抛出 onIncoming在skipBody = parser.onIncoming(parser.incoming, shouldKeepAlive)中调用 ``` function parserOnHeadersComplete(...) { ...     //IncomingMessage的实例并将套接字作为参数传入 ,来自_http_common.js模块     parser.incoming = new IncomingMessage(parser.socket); parser.incoming.httpVersionMajor = versionMajor; parser.incoming.httpVersionMinor = versionMinor; parser.incoming.httpVersion = `${versionMajor}.${versionMinor}`; parser.incoming.url = url; ...     //onIncoming 这里被调用 parser.incoming相当于req     skipBody = parser.onIncoming(parser.incoming, shouldKeepAlive); ... } ``` 流程图演示: ![image](images/node-server-req.png) #### Listen 过程 基于ner.js模块 Server Connection事件在net.Server.call(this, { allowHalfOpen: true })触发 connection会在onconnection中触发handle ```js function onconnection(err, clientHandle) { var handle = this; var self = handle.owner; debug('onconnection'); if (err) { self.emit('error', errnoException(err, 'accept')); return; } if (self.maxConnections && self._connections >= self.maxConnections) { clientHandle.close(); return; } var socket = new Socket({ handle: clientHandle, allowHalfOpen: self.allowHalfOpen, pauseOnCreate: self.pauseOnConnect }); socket.readable = socket.writable = true; self._connections++; socket.server = self; socket._server = self; DTRACE_NET_SERVER_CONNECTION(socket); LTTNG_NET_SERVER_CONNECTION(socket); COUNTER_NET_SERVER_CONNECTION(socket);  self.emit('connection', socket); } ``` listen2调用setupListenHandle方法,注册onconnection ```js function setupListenHandle(address, port, addressType, backlog, fd) { ... this._handle.onconnection = onconnection ... } ``` _listen2注册handle, 在listen里被调用 ```js Server.prototype._listen2 = setupListenHandle; server._listen2(address, port, addressType, backlog, fd); ``` listen在Server原型上,所以在代码里的http.createServer()实例上有listen()方法 ```js Server.prototype.listen = function(...args) { ... if (options instanceof TCP) { this._handle = options; this[async_id_symbol] = this._handle.getAsyncId(); listenInCluster(this, null, -1, -1, backlogFromArgs); return this; } ... ``` ```js Socket.prototype.listen = function() { debug('socket.listen'); this.on('connection', arguments[0]); listenInCluster(this, null, null, null); }; ``` Listen流程图: ![image](images/node-server-listen.png) ``` 参考链接: https://yjhjstz.gitbooks.io/deep-into-node/chapter10/chapter10-1.html https://www.cnblogs.com/chyingp/p/node-learning-guide-http.html http://blog.csdn.net/sinat_22996989/article/details/51496010 ``` ================================================ FILE: chapter1/chapter1-2.md ================================================ ### Node Stream模块分析 Stream在平时业务开发时很少用到, 但是很多模块都是基于stream实现的,引用官方文档的解释: 流(stream)在 Node.js 中是处理流数据的抽象接口(abstract interface)。 stream 模块提供了基础的 API 。使用这些 API 可以很容易地来构建实现流接口的对象。 流可以是可读的、可写的,或是可读写的。所有的流都是 EventEmitter 的实例。 #### 为什么应该使用Stream 先来看一段代码: 这段代码有什么问题, 看似是没有问题的。 如果data.txt文件体积非常大,nodejs读入内存当中,然后全部取出 这样会对性能造成很大影响 ```js var http = require('http'); var fs = require('fs'); var server = http.createServer(function (req, res) { fs.readFile(__dirname + '/data.txt', function (err, data) { res.end(data); }); }); server.listen(8000); ``` 经过优化后代码如下: 这段将data.txt一段一段的发送到用户端 这样减少了很多的服务器压力 ```js var http = require('http'); var fs = require('fs'); var server = http.createServer(function (req, res) { let stream = fs.createReadStream(__dirname + '/data.txt');//创造可读流 stream.pipe(res);//将可读流写入response }); server.listen(8000); ``` ### 管道流Pipe 管道提供了一个输出流到输入流的机制, 从获取到数据传入另外一个流 无论哪一种流,都会使用.pipe()方法来实现输入和输出。 读取input.txt文件流 hello World ```js var fs = require("fs"); // 创建一个可读流 var readerStream = fs.createReadStream('input.txt'); // 创建一个可写流 var writerStream = fs.createWriteStream('output.txt'); // 管道读写操作 // 读取 input.txt 文件内容,并将内容写入到 output.txt 文件中 readerStream.pipe(writerStream); console.log("程序执行完毕"); ``` 查看输出文件流output.txt hello World #### 如果多文件还可进行链式操作: 代码如下: ```js a.pipe(b); b.pipe(c); c.pipe(d); //上面代码等价于这样 a.pipe(b).pipe(c).pipe(d) ``` ### readable 可读操作 Readable流可以产出数据流,你可以将这些数据传送到一个writable,transform, duplex,并调用pipe()方法: ```js var Readable = require('stream').Readable; var rs = new Readable; rs.push('beep '); rs.push('boop\n'); rs.push(null); rs.pipe(process.stdout); //输出: beep boop ``` 在上面的代码中rs.push(null)的作用是告诉rs输出数据应该结束了。 ```js var Readable = require('stream').Readable; var rs = Readable(); var c = 97; rs._read = function () { rs.push(String.fromCharCode(c++)); if (c > 'z'.charCodeAt(0)) rs.push(null); }; rs.pipe(process.stdout);//输出 abcdefghijklmnopqrstuvwxyz ``` 还可以通过监听事件readable,触发时手工读取chunk数据: 一旦注册了readable事件,必须手工读取read数据,否则数据就会流失 ```js var Read = require('stream').Readable; var r = new Read(); r.push('hello'); r.push('world'); r.push(null); r.on('readable', function () { var chunk = r.read(); console.log('get data by readable event: ', chunk.toString()) }); // get data by readable event: hello world! ``` #### 注意:process.stdout之前已经将内容推送进readable流rs中,但是所有的数据依然是可写的 ### Readable Stream的模式 Readable Stream 存在两种模式(flowing mode 与 paused mode), 这两种模式决定了chunk数据流动的方式---自动流动还是手工流动。那如何触发这两种模式呢: flowing mode: 注册事件data、调用resume方法、调用pipe方法 paused mode: 调用pause方法(没有pipe方法)、移除data事件 && unpipe所有pipe ```js // data事件触发flowing mode Readable.prototype.on = function(ev, fn) { ... if (ev === 'data' && false !== this._readableState.flowing) { this.resume(); } ... } // resume触发flowing mode Readable.prototype.resume = function() { var state = this._readableState; if (!state.flowing) { debug('resume'); state.flowing = true; resume(this, state); } return this; } // pipe方法触发flowing模式 Readable.prototype.resume = function() { if (!state.flowing) { this.resume() } } ``` 结论 两种方式取决于一个flowing字段:true --> flowing mode;false --> paused mode 三种方式最后均是通过resume方法,将state.flowing = true ``` 参考资料: https://yjhjstz.gitbooks.io/deep-into-node/chapter8/chapter8-1.html http://www.runoob.com/nodejs/nodejs-stream.html https://nodejs.org/api/stream.html ```