node.js 官方文档中文版 - v1.1


前言前言 > 本文档翻译自 Node.js (https://nodejs.org/api/) 官方文档,适用于 V0.12.2。 本文档从引用参考和概念两个方面全面的解释了 Node.js API。每个章节描述了一个模块或高级概念。 一般情况下,属性、方法参数,及事件都会列在主标题下的列表中。 每个 .html 文档都有对应的 .json ,它们包含相同的结构化内容。这些东西目前还是实验性的,主要为各种集 成开发环境(IDE)和开发工具提供便利。 每个 .html 和 .json 文件都和 doc/api/ 目录下的 .markdown 文件相对应。这些文档使用 tools/doc/gener ate.js 程序生成。 HTML 模板位于 doc/template.html 。 稳定性标志稳定性标志 在文档中,你会看到每个章节的稳定性标志。Node.js API 还在改进中,成熟的部分会比其他章节值得信赖。经 过大量验证和依赖的 API 是一般是不会变的。其他新增的,试验性的,或被证明具有危险性的部分正重新设计。 稳定性标志包括以下内容: 稳定性(Stability): 0 - 抛弃 这部分内容有问题,并已计划改变。不要使用这些内容,可能会引起警告。不要想什么向后兼容性了。 稳定性(Stability): 1 - 试验 这部分内容最近刚引进,将来的版本可能会改变也可能会被移除。你可以试试并提供反馈。如果你用到的部分对你来说非常重要,可以告诉 node 的核心团队。 稳定性(Stability): 2 - 不稳定 这部分 API 正在调整中,还没在实际工作测试中达到满意的程度。如果合理的话会保证向后兼容性。 稳定性(Stability): 3 - 稳定 这部分 API 验证过基本能令人满意,但是清理底层代码时可能会引起小的改变。保证向后的兼容性。 稳定性(Stability): 4 - API 冻结 这部分的 API 已经在产品中广泛试验,不太可能被改变。 稳定性(Stability): 5 - 锁定 除非发现了严重 bug,否则这部分代码永远不会改变。请不要对这部分内容提出更改建议,否则会被拒绝。 JSON 输出JSON 输出 稳定性(Stability): 1 - 试验 每个通过 markdown 生成的 HTML 文件都有相应的 JSON 文件。 这个特性从 v0.6.12 开始,是试验性功能。 更新日期更新日期 更新内容更新内容 2015-04-21 第一版发布,翻译自 Node.js V0.12.2 官方文档 目录目录 前言前言 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 第 1 章第 1 章 概述概述 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55 第 2 章第 2 章 断言测试断言测试 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77 第 3 章第 3 章 BufferBuffer. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1111 第 4 章第 4 章 C/C++ 插件C/C++ 插件 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3232 第 5 章第 5 章 子进程子进程. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4848 第 6 章第 6 章 集群集群 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6565 第 7 章第 7 章 控制台控制台. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8080 第 8 章第 8 章 加密加密 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8383 第 9 章第 9 章 调试器调试器. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101101 第 10 章第 10 章 DNSDNS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106106 第 10 章第 10 章 dns.lookupService(address, port, callback)dns.lookupService(address, port, callback). . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109109 第 11 章第 11 章 域域 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115115 第 12 章第 12 章 事件事件 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125125 第 13 章第 13 章 文件系统文件系统 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129129 第 14 章第 14 章 全局对象全局对象 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 150150 第 15 章第 15 章 HTTPHTTP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155155 第 16 章第 16 章 HTTPSHTTPS. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 178178 第 17 章第 17 章 模块模块 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 184184 第 18 章第 18 章 网络网络 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 195195 第 19 章第 19 章 系统系统 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 209209 第 20 章第 20 章 路径路径 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 214214 第 21 章第 21 章 进程进程 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 221221 第 22 章第 22 章 PunycodePunycode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 238238 第 23 章第 23 章 Query StringQuery String . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241241 第 24 章第 24 章 逐行读取逐行读取 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 244244 第 25 章第 25 章 REPLREPL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 252252 第 26 章第 26 章 SmallocSmalloc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 258258 第 27 章第 27 章 流流 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 263263 第 28 章第 28 章 字符串解码器字符串解码器 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 287287 第 29 章第 29 章 定时器定时器. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 289289 第 30 章第 30 章 TLS/SSLTLS/SSL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 292292 第 31 章第 31 章 TTYTTY . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 311311 第 32 章第 32 章 UDP/Datagram SocketsUDP/Datagram Sockets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 314314 第 33 章第 33 章 URLURL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 322322 第 34 章第 34 章 实用工具实用工具 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 326326 第 35 章第 35 章 虚拟机虚拟机. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 334334 第 36 章第 36 章 ZlibZlib . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 341341 11 概述概述 第一个 服务器 () 的例子就从 “Hello World” 开始: var http = require('http'); http.createServer(function (request, response) { response.writeHead(200, {'Content-Type': 'text/plain'}); response.end('Hello World\n'); }).listen(8124); console.log('Server running at http://127.0.0.1:8124/'); 把代码拷贝到 example.js 文件里,用 node 程序执行 > node example.js Server running at http://127.0.0.1:8124/ 文档中所有的例子都可以这么执行。 第 1 章 概述 | 6 22 断言测试断言测试 稳定性: 5 - 锁定 这个模块可用于应用的单元测试,通过 require('assert') 可以使用这个模块。 assert.fail(actual, expected, message, operator)assert.fail(actual, expected, message, operator) 使用参数 operator 测试参数 actual (实际值) 和 expected (期望值)是否相等。 assert(value[, message]), assert.ok(value[, message])assert(value[, message]), assert.ok(value[, message]) 测试参数 value 是否为 true ,此函数和 assert.equal(true, !!value, message); 等价。 assert.equal(actual, expected[, message])assert.equal(actual, expected[, message]) 判断实际值 actual 和期望值 expected 是否相等。 assert.notEqual(actual, expected[, message])assert.notEqual(actual, expected[, message]) 判断实际值 actual 和期望值 expected 是否不等。 assert.deepEqual(actual, expected[, message])assert.deepEqual(actual, expected[, message]) 执行深度比较,判断实际值 actual 和期望值 expected 是否相等。 assert.notDeepEqual(actual, expected[, message])assert.notDeepEqual(actual, expected[, message]) 深度比较两个参数是否不相等。 assert.strictEqual(actual, expected[, message])assert.strictEqual(actual, expected[, message]) 深度比较两个参数是否相等。 第 2 章 断言测试 | 8 assert.notStrictEqual(actual, expected[, message])assert.notStrictEqual(actual, expected[, message]) 此函数使用操作符 ‘!==’ 严格比较是否两参数不相等。 assert.throws(block[, error][, message])assert.throws(block[, error][, message]) 声明一个 block 用来抛出错误( error ), error 可以是构造函数,正则表达式或其他验证器。 使用构造函数验证实例: assert.throws( function() { throw new Error("Wrong value"); }, Error ); 使用正则表达式验证错误信息: assert.throws( function() { throw new Error("Wrong value"); }, /value/ ); 用户自定义的错误验证器: assert.throws( function() { throw new Error("Wrong value"); }, function(err) { if ( (err instanceof Error) && /value/.test(err) ) { return true; } }, "unexpected error" ); 第 2 章 断言测试 | 9 assert.doesNotThrow(block[, message])assert.doesNotThrow(block[, message]) 声明 block 不抛出错误,详细信息参见 assert.throws 。 assert.ifError(value)assert.ifError(value) 判断参数 value 是否为 false ,如果是 true 抛出异常。通常用来测试回调中第一个参数 error。 第 2 章 断言测试 | 10 33 BufferBuffer 稳定性: 3 - 稳定 纯 Javascript 语言对 Unicode 友好,但是难以处理二进制数据。在处理 TCP 流和文件系统时经常需要操作字 节流。Node 提供了一些机制,用于操作、创建、以及消耗字节流。 在 Buffer 类实例化中存储了原始数据。 Buffer 类似于一个整数数组,在 V8 堆(the V8 heap)原始存储空间 给它分配了内存。一旦创建了 Buffer 实例,则无法改变大小。 Buffer 类是全局对象,所以访问它不必使用 require('buffer') 。 Buffers 和 Javascript 字符串对象之间转换时需要一个明确的编码方法。下面是字符串的不同编码。 • 'ascii' - 7位的 ASCII 数据。这种编码方式非常快,它会移除最高位内容。 • 'utf8' - 多字节编码 Unicode 字符。大部分网页和文档使用这类编码方式。 • 'utf16le' - 2个或4个字节, Little Endian (LE) 编码 Unicode 字符。编码范围 (U+10000 到 U+10FFFF) 。 • 'ucs2' - 'utf16le' 的子集。 • 'base64' - Base64 字符编码。 • 'binary' - 仅使用每个字符的头8位将原始的二进制信息进行编码。在需使用 Buffer 的情况下,应该尽量避 免使用这个已经过时的编码方式。这个编码方式将会在未来某个版本中弃用。 • 'hex' - 每个字节都采用 2 进制编码。 在 Buffer 中创建一个数组,需要注意以下规则: 1. Buffer 是内存拷贝,而不是内存共享。 2. Buffer 占用内存被解释为一个数组,而不是字节数组。比如, new Uint32Array(new Buffer([1,2,3,4])) 创 建了4个 Uint32Array ,它的成员为 [1,2,3,4] ,而不是 [0x1020304] 或 [0x4030201] 。 注意:Node.js v0.8 只是简单的引用了 array.buffer 里的 buffer ,而不是克隆(cloning)。 介绍一个高效的方法, ArrayBuffer#slice() 拷贝了一份切片,而 Buffer#slice() 新建了一份。 类: Buffer类: Buffer Buffer 类是全局变量类型,用来直接处理2进制数据。 它能够使用多种方式构建。 第 3 章 Buffer | 12 new Buffer(size)new Buffer(size) • size Number 类型 分配一个新的 size 大小单位为8位字节的 buffer。 注意, size 必须小于 kMaxLength (页 0),否则,将会抛 出异常 RangeError 。 new Buffer(array)new Buffer(array) • array Array 使用一个8位字节 array 数组分配一个新的 buffer。 new Buffer(buffer)new Buffer(buffer) • buffer {Buffer} 拷贝参数 buffer 的数据到 Buffer 实例。 new Buffer(str[, encoding])new Buffer(str[, encoding]) • str String 类型 - 需要编码的字符串。 • encoding String 类型 - 编码方式, 可选。 分配一个新的 buffer ,其中包含着传入的 str 字符串。 encoding 编码方式默认为 'utf8' 。 类方法: Buffer.isEncoding(encoding)类方法: Buffer.isEncoding(encoding) • encoding {String} 用来测试给定的编码字符串 如果参数编码 encoding 是有效的,返回 true,否则返回 false。 类方法: Buffer.isBuffer(obj)类方法: Buffer.isBuffer(obj) • obj 对象 • 返回: Boolean 第 3 章 Buffer | 13 obj 如果是 Buffer 返回 true, 否则返回 false。 类方法: Buffer.byteLength(string[, encoding])类方法: Buffer.byteLength(string[, encoding]) • string String 类型 • encoding String 类型, 可选, 默认: 'utf8' • 返回: Number 类型 将会返回这个字符串真实字节长度。 encoding 编码默认是: utf8 。 这和 String.prototype.length 不一样,因 为那个方法返回这个字符串中字符的数量。 例如: str = '\u00bd + \u00bc = \u00be'; console.log(str + ": " + str.length + " characters, " + Buffer.byteLength(str, 'utf8') + " bytes"); // ½ + ¼ = ¾: 9 characters, 12 bytes 类方法: Buffer.concat(list[, totalLength])类方法: Buffer.concat(list[, totalLength]) • list {Array} 用来连接的数组 • totalLength {Number 类型} 数组里所有对象的总buffer大小 返回一个buffer对象,它将参数 buffer 数组中所有 buffer 对象拼接在一起。 如果传入的数组没有内容,或者 totalLength 是 0,那将返回一个长度为 0 的buffer。 如果数组长度为 1,返回数组第一个成员。 如果数组长度大于 0,将会创建一个新的 Buffer 实例。 如果没有提供 totalLength 参数,会根据 buffer 数组计算,这样会增加一个额外的循环计算,所以提供一个准确 的 totalLength 参数速度更快。 类方法: Buffer.compare(buf1, buf2)类方法: Buffer.compare(buf1, buf2) • buf1 {Buffer} 第 3 章 Buffer | 14 • buf2 {Buffer} 和 buf1.compare(buf2) (页 0)一样。 用来对数组排序: var arr = [Buffer('1234'), Buffer('0123')]; arr.sort(Buffer.compare); buf.lengthbuf.length • Number 类型 返回这个 buffer 的 bytes 数。注意这未必是 buffer 里面内容的大小。 length 是 buffer 对象所分配的内存 数,它不会随着这个 buffer 对象内容的改变而改变。 buf = new Buffer(1234); console.log(buf.length); buf.write("some string", 0, "ascii"); console.log(buf.length); // 1234 // 1234 length 不能改变,如果改变 length 将会导致不可以预期的结果。如果想要改变 buffer 的长度,需要使用 bu f.slice 来创建新的 buffer。 buf = new Buffer(10); buf.write("abcdefghj", 0, "ascii"); console.log(buf.length); // 10 buf = buf.slice(0,5); console.log(buf.length); // 5 buf.write(string[, offset][, length][, encoding])buf.write(string[, offset][, length][, encoding]) • string String 类型 - 写到 buffer 里 • offset Number 类型, 可选参数, 默认值: 0 • length Number 类型, 可选参数, 默认值: buffer.length - offset • encoding String 类型, 可选参数, 默认值: 'utf8' 根据参数 offset 偏移量和指定的 encoding 编码方式,将参数 string 数据写入buffer。 offset 偏移量默认值是 0, encoding 编码方式默认是 utf8 。 length 长度是将要写入的字符串的 bytes 大小。 返回 number 类型,表 第 3 章 Buffer | 15 示写入了多少 8 位字节流。如果 buffer 没有足够的空间来放整个 string,它将只会只写入部分字符串。 length 默认是 buffer.length - offset。 这个方法不会出现写入部分字符。 buf = new Buffer(256); len = buf.write('\u00bd + \u00bc = \u00be', 0); console.log(len + " bytes: " + buf.toString('utf8', 0, len)); buf.writeUIntLE(value, offset, byteLength[, noAssert])buf.writeUIntLE(value, offset, byteLength[, noAssert]) buf.writeUIntBE(value, offset, byteLength[, noAssert])buf.writeUIntBE(value, offset, byteLength[, noAssert]) buf.writeIntLE(value, offset, byteLength[, noAssert])buf.writeIntLE(value, offset, byteLength[, noAssert]) buf.writeIntBE(value, offset, byteLength[, noAssert])buf.writeIntBE(value, offset, byteLength[, noAssert]) • value {Number 类型} 准备写到 buffer 字节数 • offset {Number 类型} 0 <= offset <= buf.length • byteLength {Number 类型} 0 < byteLength <= 6 • noAssert {Boolean} 默认值: false • 返回: {Number 类型} 将 value 写入到 buffer 里, 它由 offset 和 byteLength 决定,支持 48 位计算,例如: var b = new Buffer(6); b.writeUIntBE(0x1234567890ab, 0, 6); // noAssert 值为 true 时,不再验证 value 和 offset 的有效性。 默认是 false 。 buf.readUIntLE(offset, byteLength[, noAssert])buf.readUIntLE(offset, byteLength[, noAssert]) buf.readUIntBE(offset, byteLength[, noAssert])buf.readUIntBE(offset, byteLength[, noAssert]) buf.readIntLE(offset, byteLength[, noAssert])buf.readIntLE(offset, byteLength[, noAssert]) buf.readIntBE(offset, byteLength[, noAssert])buf.readIntBE(offset, byteLength[, noAssert]) • offset {Number 类型} 0 <= offset <= buf.length 第 3 章 Buffer | 16 • byteLength {Number 类型} 0 < byteLength <= 6 • noAssert {Boolean} 默认值: false • 返回: {Number 类型} 支持 48 位以下的数字读取。 例如: var b = new Buffer(6); b.writeUint16LE(0x90ab, 0); b.writeUInt32LE(0x12345678, 2); b.readUIntLE(0, 6).toString(16); // 指定为 6 bytes (48 bits) // 输出: '1234567890ab' noAssert 值为 true 时, offset 不再验证是否超过 buffer 的长度,默认为 false 。 buf.toString([encoding][, start][, end])buf.toString([encoding][, start][, end]) • encoding String 类型, 可选参数, 默认值: 'utf8' • start Number 类型, 可选参数, 默认值: 0 • end Number 类型, 可选参数, 默认值: buffer.length 根据 encoding 参数(默认是 'utf8')返回一个解码过的 string 类型。还会根据传入的参数 start (默认是 0) 和 e nd (默认是 buffer.length)作为取值范围。 buf = new Buffer(26); for (var i = 0 ; i < 26 ; i++) { buf[i] = i + 97; // 97 is ASCII a } buf.toString('ascii'); // 输出: abcdefghijklmnopqrstuvwxyz buf.toString('ascii',0,5); // 输出: abcde buf.toString('utf8',0,5); // 输出: abcde buf.toString(undefined,0,5); // encoding defaults to 'utf8', 输出 abcde 查看上面 buffer.write() 例子。 buf.toJSON()buf.toJSON() 返回一个 JSON 表示的 Buffer 实例。 JSON.stringify 将会默认调用字符串序列化这个 Buffer 实例。 例如: 第 3 章 Buffer | 17 var buf = new Buffer('test'); var json = JSON.stringify(buf); console.log(json); // '{"type":"Buffer","data":[116,101,115,116]}' var copy = JSON.parse(json, function(key, value) { return value && value.type === 'Buffer' ? new Buffer(value.data) : value; }); console.log(copy); // buf[index]buf[index] 获取或设置指定 index 位置的 8 位字节。这个值是指单个字节,所以必须在合法的范围取值,16 进制的 0x00 到 0xFF,或者 0 到 255。 例如: 拷贝一个 ASCII 编码的 string 字符串到一个 buffer, 一次一个 byte 进行拷贝: str = "node.js"; buf = new Buffer(str.length); for (var i = 0; i < str.length ; i++) { buf[i] = str.charCodeAt(i); } console.log(buf); // node.js buf.equals(otherBuffer)buf.equals(otherBuffer) • otherBuffer {Buffer} 如果 this 和 otherBuffer 拥有相同的内容,返回 true。 buf.compare(otherBuffer)buf.compare(otherBuffer) • otherBuffer {Buffer} 第 3 章 Buffer | 18 返回一个数字,表示 this 在 otherBuffer 之前,之后或相同。 buf.copy(targetBuffer[, targetStart][, sourceStart][, sourceEnd])buf.copy(targetBuffer[, targetStart][, sourceStart][, sourceEnd]) • targetBuffer Buffer 对象 - Buffer to copy into • targetStart Number 类型, 可选参数, 默认值: 0 • sourceStart Number 类型, 可选参数, 默认值: 0 • sourceEnd Number 类型, 可选参数, 默认值: buffer.length buffer 拷贝,源和目标可以相同。 targetStart 目标开始偏移和 sourceStart 源开始偏移默认都是 0。 sourc eEnd 源结束位置偏移默认是源的长度 buffer.length 。 例如:创建 2 个Buffer,然后把 buf1 的 16 到 19 位内容拷贝到 buf2 第 8 位之后。 buf1 = new Buffer(26); buf2 = new Buffer(26); for (var i = 0 ; i < 26 ; i++) { buf1[i] = i + 97; // 97 is ASCII a buf2[i] = 33; // ASCII ! } buf1.copy(buf2, 8, 16, 20); console.log(buf2.toString('ascii', 0, 25)); // !!!!!!!!qrst!!!!!!!!!!!!! 例如: 在同一个buffer中,从一个区域拷贝到另一个区域 buf = new Buffer(26); for (var i = 0 ; i < 26 ; i++) { buf[i] = i + 97; // 97 is ASCII a } buf.copy(buf, 0, 4, 10); console.log(buf.toString()); // efghijghijklmnopqrstuvwxyz 第 3 章 Buffer | 19 buf.slice([start][, end])buf.slice([start][, end]) • start Number 类型, 可选参数, 默认值: 0 • end Number 类型, 可选参数, 默认值: buffer.length 返回一个新的buffer,这个buffer将会和老的buffer引用相同的内存地址,根据 start (默认是 0 ) 和 end (默 认是 buffer.length ) 偏移和裁剪了索引。 负的索引是从 buffer 尾部开始计算的。 修改这个新的 buffer 实例 slice 切片,也会改变原来的 buffer!修改这个新的 buffer 实例 slice 切片,也会改变原来的 buffer! 例如: 创建一个 ASCII 字母的 Buffer,进行 slice 切片,然后修改源 Buffer 上的一个 byte。 var buf1 = new Buffer(26); for (var i = 0 ; i < 26 ; i++) { buf1[i] = i + 97; // 97 is ASCII a } var buf2 = buf1.slice(0, 3); console.log(buf2.toString('ascii', 0, buf2.length)); buf1[0] = 33; console.log(buf2.toString('ascii', 0, buf2.length)); // abc // !bc buf.readUInt8(offset[, noAssert])buf.readUInt8(offset[, noAssert]) • offset Number 类型 • noAssert Boolean, 可选参数, 默认值: false • 返回: Number 类型 从这个 buffer 对象里,根据指定的偏移量,读取一个有符号 8 位整数 整形。 若参数 noAssert 为 true 将不会验证 offset 偏移量参数。 如果这样 offset 可能会超出buffer 的末尾。默认 是 false 。 例如: var buf = new Buffer(4); 第 3 章 Buffer | 20 buf[0] = 0x3; buf[1] = 0x4; buf[2] = 0x23; buf[3] = 0x42; for (ii = 0; ii < buf.length; ii++) { console.log(buf.readUInt8(ii)); } // 0x3 // 0x4 // 0x23 // 0x42 buf.readUInt16LE(offset[, noAssert])buf.readUInt16LE(offset[, noAssert]) buf.readUInt16BE(offset[, noAssert])buf.readUInt16BE(offset[, noAssert]) • offset Number 类型 • noAssert Boolean, 可选参数, 默认值: false • 返回: Number 类型 从 buffer 对象里,根据指定的偏移量,使用特殊的 endian 字节序格式读取一个有符号 16 位整数。 若参数 noAssert 为 true 将不会验证 offset 偏移量参数。 这意味着 offset 可能会超出 buffer 的末尾。默认 是 false 。 例如: var buf = new Buffer(4); buf[0] = 0x3; buf[1] = 0x4; buf[2] = 0x23; buf[3] = 0x42; console.log(buf.readUInt16BE(0)); console.log(buf.readUInt16LE(0)); console.log(buf.readUInt16BE(1)); console.log(buf.readUInt16LE(1)); console.log(buf.readUInt16BE(2)); console.log(buf.readUInt16LE(2)); 第 3 章 Buffer | 21 // 0x0304 // 0x0403 // 0x0423 // 0x2304 // 0x2342 // 0x4223 buf.readUInt32LE(offset[, noAssert])buf.readUInt32LE(offset[, noAssert]) buf.readUInt32BE(offset[, noAssert])buf.readUInt32BE(offset[, noAssert]) • offset Number 类型 • noAssert Boolean, 可选参数, 默认值: false • 返回: Number 类型 从这个 buffer 对象里,根据指定的偏移量,使用指定的 endian 字节序格式读取一个有符号 32 位整数。 若参数 noAssert 为 true 将不会验证 offset 偏移量参数。 这意味着 offset 可能会超出buffer 的末尾。默认 是 false 。 例如: var buf = new Buffer(4); buf[0] = 0x3; buf[1] = 0x4; buf[2] = 0x23; buf[3] = 0x42; console.log(buf.readUInt32BE(0)); console.log(buf.readUInt32LE(0)); // 0x03042342 // 0x42230403 buf.readInt8(offset[, noAssert])buf.readInt8(offset[, noAssert]) • offset Number 类型 • noAssert Boolean, 可选参数, 默认值: false • 返回: Number 类型 第 3 章 Buffer | 22 从这个 buffer 对象里,根据指定的偏移量,读取一个 signed 8 位整数。 若参数 noAssert 为 true 将不会验证 offset 偏移量参数。 这意味着 offset 可能会超出 buffer 的末尾。默认 是 false 。 返回和 buffer.readUInt8 一样,除非 buffer 中包含了有作为2 的补码的有符号值。 buf.readInt16LE(offset[, noAssert])buf.readInt16LE(offset[, noAssert]) buf.readInt16BE(offset[, noAssert])buf.readInt16BE(offset[, noAssert]) • offset Number 类型 • noAssert Boolean, 可选参数, 默认值: false • 返回: Number 类型 从这个 buffer 对象里,根据指定的偏移量,使用特殊的 endian 格式读取一个 signed 16 位整数。 若参数 noAssert 为 true 将不会验证 offset 偏移量参数。 这意味着 offset 可能会超出 buffer 的末尾。默认 是 false 。 返回和 buffer.readUInt16 一样,除非 buffer 中包含了有作为 2 的补码的有符号值。 buf.readInt32LE(offset[, noAssert])buf.readInt32LE(offset[, noAssert]) buf.readInt32BE(offset[, noAssert])buf.readInt32BE(offset[, noAssert]) • offset Number 类型 • noAssert Boolean, 可 位选参数, 默认值: false • 返回: Number 类型 从这个 buffer 对象里,根据指定的偏移量,使用指定的 endian 字节序格式读取一个 signed 32 位整数。 若参数 noAssert 为 true 将不会验证 offset 偏移量参数。 这意味着 offset 可能会超出buffer 的末尾。默认 是 false 。 和 buffer.readUInt32 一样返回,除非 buffer 中包含了有作为 2 的补码的有符号值。 第 3 章 Buffer | 23 buf.readFloatLE(offset[, noAssert])buf.readFloatLE(offset[, noAssert]) buf.readFloatBE(offset[, noAssert])buf.readFloatBE(offset[, noAssert]) • offset Number 类型 • noAssert Boolean, 可选参数, 默认值: false • 返回: Number 类型 从这个 buffer 对象里,根据指定的偏移量,使用指定的 endian 字节序格式读取一个 32 位浮点数。 若参数 noAssert 为 true 将不会验证 offset 偏移量参数。 这意味着 offset 可能会超出buffer的末尾。默认 是 false 。 例如: var buf = new Buffer(4); buf[0] = 0x00; buf[1] = 0x00; buf[2] = 0x80; buf[3] = 0x3f; console.log(buf.readFloatLE(0)); // 0x01 buf.readDoubleLE(offset[, noAssert])buf.readDoubleLE(offset[, noAssert]) buf.readDoubleBE(offset[, noAssert])buf.readDoubleBE(offset[, noAssert]) • offset Number 类型 • noAssert Boolean, 可选参数, 默认值: false • 返回: Number 类型 从这个 buffer 对象里,根据指定的偏移量,使用指定的 endian字节序格式读取一个 64 位double。 若参数 noAssert 为 true 将不会验证 offset 偏移量参数。 这意味着 offset 可能会超出buffer 的末尾。默认 是 false 。 例如: 第 3 章 Buffer | 24 var buf = new Buffer(8); buf[0] = 0x55; buf[1] = 0x55; buf[2] = 0x55; buf[3] = 0x55; buf[4] = 0x55; buf[5] = 0x55; buf[6] = 0xd5; buf[7] = 0x3f; console.log(buf.readDoubleLE(0)); // 0。3333333333333333 buf.writeUInt8(value, offset[, noAssert])buf.writeUInt8(value, offset[, noAssert]) • value Number 类型 • offset Number 类型 • noAssert Boolean, 可选参数, 默认值: false 根据传入的 offset 偏移量将 value 写入 buffer。注意: value 必须是一个合法的有符号 8 位整数。 若参数 noAssert 为 true 将不会验证 offset 偏移量参数。 这意味着 value 可能过大,或者 offset 可能会 超出 buffer 的末尾从而造成 value 被丢弃。 除非你对这个参数非常有把握,否则不要使用。默认是 false 。 例如: var buf = new Buffer(4); buf.writeUInt8(0x3, 0); buf.writeUInt8(0x4, 1); buf.writeUInt8(0x23, 2); buf.writeUInt8(0x42, 3); console.log(buf); // 第 3 章 Buffer | 25 buf.writeUInt16LE(value, offset[, noAssert])buf.writeUInt16LE(value, offset[, noAssert]) buf.writeUInt16BE(value, offset[, noAssert])buf.writeUInt16BE(value, offset[, noAssert]) • value Number 类型 • offset Number 类型 • noAssert Boolean, 可选参数, 默认值: false 根据传入的 offset 偏移量和指定的 endian 格式将 value 写入 buffer。注意: value 必须是一个合法的有符 号 16 位整数。 若参数 noAssert 为 true 将不会验证 value 和 offset 偏移量参数。 这意味着 value 可能过大,或者 offs et 可能会超出buffer的末尾从而造成 value 被丢弃。 除非你对这个参数非常有把握,否则尽量不要使用。默认 是 false 。 例如: var buf = new Buffer(4); buf.writeUInt16BE(0xdead, 0); buf.writeUInt16BE(0xbeef, 2); console.log(buf); buf.writeUInt16LE(0xdead, 0); buf.writeUInt16LE(0xbeef, 2); console.log(buf); // // buf.writeUInt32LE(value, offset[, noAssert])buf.writeUInt32LE(value, offset[, noAssert]) buf.writeUInt32BE(value, offset[, noAssert])buf.writeUInt32BE(value, offset[, noAssert]) • value Number 类型 • offset Number 类型 • noAssert Boolean, 可选参数, 默认值: false 第 3 章 Buffer | 26 根据传入的 offset 偏移量和指定的 endian 格式将 value 写入buffer。注意: value 必须是一个合法的有符 号 32 位整数。 若参数 noAssert 为 true 将不会验证 value 和 offset 偏移量参数。 这意味着 value 可能过大,或者offset 可能会超出buffer的末尾从而造成 value 被丢弃。 除非你对这个参数非常有把握,否则尽量不要使用。默认是 false 。 例如: var buf = new Buffer(4); buf.writeUInt32BE(0xfeedface, 0); console.log(buf); buf.writeUInt32LE(0xfeedface, 0); console.log(buf); // // buf.writeInt8(value, offset[, noAssert])buf.writeInt8(value, offset[, noAssert]) • value Number 类型 • offset Number 类型 • noAssert Boolean, 可选参数, 默认值: false 根据传入的 offset 偏移量将 value 写入 buffer 。注意: value 必须是一个合法的 signed 8 位整数。 若参数 noAssert 为 true 将不会验证 value 和 offset 偏移量参数。 这意味着 value 可能过大,或者 offse t 可能会超出 buffer 的末尾从而造成 value 被丢弃。 除非你对这个参数非常有把握,否则尽量不要使用。默认 是 false 。 和 buffer.writeUInt8 一样工作,除非是把有2的补码的 有符号整数 有符号整形写入buffer。 buf.writeInt16LE(value, offset[, noAssert])buf.writeInt16LE(value, offset[, noAssert]) buf.writeInt16BE(value, offset[, noAssert])buf.writeInt16BE(value, offset[, noAssert]) • value Number 类型 第 3 章 Buffer | 27 • offset Number 类型 • noAssert Boolean, 可选参数, 默认值: false 根据传入的 offset 偏移量和指定的 endian 格式将 value 写入 buffer。注意: value 必须是一个合法的 sign ed 16 位整数。 若参数 noAssert 为 true 将不会验证 value 和 offset 偏移量参数。 这意味着 value 可能过大,或者 offse t 可能会超出 buffer 的末尾从而造成 value 被丢弃。 除非你对这个参数非常有把握,否则尽量不要使用。默认 是 false 。 和 buffer.writeUInt16* 一样工作,除非是把有2的补码的有符号整数 有符号整形写入 buffer 。 buf.writeInt32LE(value, offset[, noAssert])buf.writeInt32LE(value, offset[, noAssert]) buf.writeInt32BE(value, offset[, noAssert])buf.writeInt32BE(value, offset[, noAssert]) • value Number 类型 • offset Number 类型 • noAssert Boolean, 可选参数, 默认值: false 根据传入的 offset 偏移量和指定的 endian 格式将 value 写入 buffer。注意: value 必须是一个合法的 sign ed 32 位整数。 若参数 noAssert 为 true 将不会验证 value 和 offset 偏移量参数。 这意味着 value 可能过大,或者 offs et 可能会超出 buffer 的末尾从而造成 value 被丢弃。 除非你对这个参数非常有把握,否则尽量不要使用。默 认是 false 。 和 buffer.writeUInt32* 一样工作,除非是把有2的补码的有符号整数 有符号整形写入buffer。 buf.writeFloatLE(value, offset[, noAssert])buf.writeFloatLE(value, offset[, noAssert]) buf.writeFloatBE(value, offset[, noAssert])buf.writeFloatBE(value, offset[, noAssert]) • value Number 类型 • offset Number 类型 • noAssert Boolean, 可选参数, 默认值: false 第 3 章 Buffer | 28 根据传入的 offset 偏移量和指定的 endian 格式将 value 写入 buffer 。注意:当 value 不是一个 32 位浮点 数类型的值时,结果将是不确定的。 若参数 noAssert 为 true 将不会验证 value 和 offset 偏移量参数。 这意味着 value可能过大,或者 offset 可能会超出 buffer 的末尾从而造成 value 被丢弃。 除非你对这个参数非常有把握,否则尽量不要使用。默认是 false 。 例如: var buf = new Buffer(4); buf.writeFloatBE(0xcafebabe, 0); console.log(buf); buf.writeFloatLE(0xcafebabe, 0); console.log(buf); // // buf.writeDoubleLE(value, offset[, noAssert])buf.writeDoubleLE(value, offset[, noAssert]) buf.writeDoubleBE(value, offset[, noAssert])buf.writeDoubleBE(value, offset[, noAssert]) • value Number 类型 • offset Number 类型 • noAssert Boolean, 可选参数, 默认值: false 根据传入的 offset 偏移量和指定的 endian 格式将 value 写入 buffer。注意: value 必须是一个有效的 64 位double 类型的值。 若参数 noAssert 为 true 将不会验证 value 和 offset 偏移量参数。 这意味着 value 可能过大,或者 offse t 可能会超出 buffer 的末尾从而造成 value 被丢弃。 除非你对这个参数非常有把握,否则尽量不要使用。默认是 false 。 例如: var buf = new Buffer(8); buf.writeDoubleBE(0xdeadbeefcafebabe, 0); 第 3 章 Buffer | 29 console.log(buf); buf.writeDoubleLE(0xdeadbeefcafebabe, 0); console.log(buf); // // buf.fill(value[, offset][, end])buf.fill(value[, offset][, end]) • value • offset Number 类型, Optional • end Number 类型, Optional 使用指定的 value 来填充这个 buffer。如果没有指定 offset (默认是 0) 并且 end (默认是 buffer.length ) ,将会填充整个buffer。 var b = new Buffer(50); b.fill("h"); buffer.iNSPECT_MAX_BYTESbuffer.iNSPECT_MAX_BYTES • Number 类型, 默认值: 50 设置当调用 buffer.inspect() 方法后,将会返回多少 bytes 。用户模块重写这个值可以。 注意这个属性是 require('buffer') 模块返回的。这个属性不是在全局变量 Buffer 中,也不在 buffer 的实例里。 类: SlowBuffer类: SlowBuffer 返回一个不被池管理的 Buffer 。 大量独立分配的 Buffer 容易带来垃圾,为了避免这个情况,小于 4KB 的空间都是切割自一个较大的独立对 象。这种策略既提高了性能也改善了内存使用率。V8 不需要跟踪和清理过多的 Persistent 对象。 当开发者需要将池中一小块数据保留一段时间,比较好的办法是用 SlowBuffer 创建一个不被池管理的 Buffer 实 例,并将相应数据拷贝出来。 第 3 章 Buffer | 30 // need to keep around a few small chunks of memory var store = []; socket。on('readable', function() { var data = socket。read(); // allocate for retained data var sb = new SlowBuffer(10); // copy the data into the new allocation data。copy(sb, 0, 0, 10); store。push(sb); }); 请谨慎使用,仅作为经常发现他们的应用中过度的内存保留时的最后手段。 第 3 章 Buffer | 31 44 C/C++ 插件C/C++ 插件 插件 Addons 是动态链接的共享对象。他提供了 C/C++ 类库能力。这些API比较复杂,他包以下几个类库: • V8 JavaScript, C++ 类库。用来和 JavaScript 交互,比如创建对象,调用函数等等。 在 v8.h 头文件中 (目录地址 deps/v8/include/v8.h ),线上地址 online (http://izs.me/v8-docs/main.html) 。 • libuv (https://github.com/joyent/libuv) ,C 事件循环库。 等待文件描述符变为可读,等待定时器,等待信 号时,会和 libuv 打交道。或者说,如果你需要和 I/O 打交道,就会用到 libuv。 • 内部 Node 类库。 其中最重要的类 node::ObjectWrap ,你会经常派生自它。 • 其他的参见 deps/ 。 Node 已经将所有的依赖编译成可以执行文件,所以你不必当心这些类库的链接问题。 以下所有例子可以在 download (https://github.com/rvagg/node-addon-examples) 下载,也许你可以从中 找一个作为你的扩展插件。 Hello worldHello world 现在我们来写一个 C++ 插件的小例子,它的效果和以下 JS 代码一致: module.exports.hello = function() { return 'world'; }; 创建 hello.cc 文件: // hello.cc #include using namespace v8; void Method(const FunctionCallbackInfo& args) { Isolate* isolate = Isolate::GetCurrent(); HandleScope scope(isolate); args.GetReturnValue().Set(String::NewFromUtf8(isolate, "world")); } void init(Handle exports) { NODE_SET_METHOD(exports, "hello", Method); } NODE_MODULE(addon, init) 注意:所有的 Node 插件必须输出一个初始化函数: 第 4 章 C/C++ 插件 | 33 void Initialize (Handle exports); NODE_MODULE(module_name, Initialize) NODE_MODULE 之后的代码没有分号,因为它不是一个函数 (参见 node.h )。 module_name 必须和二进制文件名字一致 (后缀是 .node)。 源文件会编译成 addon.node 二进制插件。 为此我们创建了一个很像 JSON 的 binding.gyp 文件, 它包含配 置信息,这个文件用node-gyp (https://github.com/TooTallNate/node-gyp) 编译。 { "targets": [ { "target_name": "addon", "sources": [ "hello.cc" ] } ] } 下一步创建一个 node-gyp configure 工程,在平台上生成这些文件。 创建后,在 build/ 文件夹里拥有一个 Makefile (Unix 系统) 文件或者 vcxproj 文件 (Windows 系统)。 接着调 用 node-gyp build 命令编译,生成 .node 文件。 这些文件在 build/Release/ 目录里。 现在,你能在 Node 工程中使用这些 2 进制扩展插件,在 hello.js 中声明 require 之前编译的 hello.node : // hello.js var addon = require('./build/Release/addon'); console.log(addon.hello()); // 'world' 更多的信息请参考https://github.com/arturadib/node-qt 。 插件模式插件模式 下面是一些 addon 插件的模式,帮助你开始编码。v8 reference (http://izs.me/v8-docs/main.html) 文档里 包含 v8 的各种接口,Embedder's Guide (http://code.google.com/apis/v8/embed.html) 这个文档包含各种 说明,比如 handles, scopes, function templates, 等等。 在使用这些例子前,你需要先用 node-gyp 编译。 创建 binding.gyp 文件: { "targets": [ { 第 4 章 C/C++ 插件 | 34 "target_name": "addon", "sources": [ "addon.cc" ] } ] } 将文件名加入到 sources 数组里就可以使用多个 .cc 文件,例如 : "sources": ["addon.cc", "myexample.cc"] 准备好 binding.gyp 文件后, 你就能配置并编译插件: $ node-gyp configure build 函数参数函数参数 从以下模式中解释了如何从 JavaScript 函数中读取参数,并返回结果。仅需要一个 addon.cc 文件: // addon.cc #include using namespace v8; void Add(const FunctionCallbackInfo& args) { Isolate* isolate = Isolate::GetCurrent(); HandleScope scope(isolate); if (args.Length() < 2) { isolate->ThrowException(Exception::TypeError( String::NewFromUtf8(isolate, "Wrong number of arguments"))); return; } if (!args[0]->IsNumber() || !args[1]->IsNumber()) { isolate->ThrowException(Exception::TypeError( String::NewFromUtf8(isolate, "Wrong arguments"))); return; } double value = args[0]->NumberValue() + args[1]->NumberValue(); Local num = Number::New(isolate, value); args.GetReturnValue().Set(num); } 第 4 章 C/C++ 插件 | 35 void Init(Handle exports) { NODE_SET_METHOD(exports, "add", Add); } NODE_MODULE(addon, Init) 可以用以下的 JavaScript 代码片段测试: // test.js var addon = require('./build/Release/addon'); console.log( 'This should be eight:', addon.add(3,5) ); 回调Callbacks回调Callbacks 你也能传 JavaScript 函数给 C++ 函数,并执行它。 在 addon.cc 中: // addon.cc #include using namespace v8; void RunCallback(const FunctionCallbackInfo& args) { Isolate* isolate = Isolate::GetCurrent(); HandleScope scope(isolate); Local cb = Local::Cast(args[0]); const unsigned argc = 1; Local argv[argc] = { String::NewFromUtf8(isolate, "hello world") }; cb->Call(isolate->GetCurrentContext()->Global(), argc, argv); } void Init(Handle exports, Handle module) { NODE_SET_METHOD(module, "exports", RunCallback); } NODE_MODULE(addon, Init) 注意,这个例子中使用了 Init() 里的 2 个参数, module 对象是第二个参数。它允许 addon 使用一个函数完全 重写 exports 。 可以用以下的代码来测试: // test.js var addon = require('./build/Release/addon'); 第 4 章 C/C++ 插件 | 36 addon(function(msg){ console.log(msg); // 'hello world' }); 对象工厂对象工厂 在 addon.cc 模式里,你能用 C++ 函数创建并返回一个新的对象,这个对象所包含的 msg 属性是由 createO bject() 函数传入: // addon.cc #include using namespace v8; void CreateObject(const FunctionCallbackInfo& args) { Isolate* isolate = Isolate::GetCurrent(); HandleScope scope(isolate); Local obj = Object::New(isolate); obj->Set(String::NewFromUtf8(isolate, "msg"), args[0]->ToString()); args.GetReturnValue().Set(obj); } void Init(Handle exports, Handle module) { NODE_SET_METHOD(module, "exports", CreateObject); } NODE_MODULE(addon, Init) 使用 JavaScript 测试: // test.js var addon = require('./build/Release/addon'); var obj1 = addon('hello'); var obj2 = addon('world'); console.log(obj1.msg+' '+obj2.msg); // 'hello world' 工厂模式工厂模式 这个模式里展示了如何创建并返回一个 JavaScript 函数,它是由 C++ 函数包装的 : 第 4 章 C/C++ 插件 | 37 // addon.cc #include using namespace v8; void MyFunction(const FunctionCallbackInfo& args) { Isolate* isolate = Isolate::GetCurrent(); HandleScope scope(isolate); args.GetReturnValue().Set(String::NewFromUtf8(isolate, "hello world")); } void CreateFunction(const FunctionCallbackInfo& args) { Isolate* isolate = Isolate::GetCurrent(); HandleScope scope(isolate); Local tpl = FunctionTemplate::New(isolate, MyFunction); Local fn = tpl->GetFunction(); // omit this to make it anonymous fn->SetName(String::NewFromUtf8(isolate, "theFunction")); args.GetReturnValue().Set(fn); } void Init(Handle exports, Handle module) { NODE_SET_METHOD(module, "exports", CreateFunction); } NODE_MODULE(addon, Init) 测试: // test.js var addon = require('./build/Release/addon'); var fn = addon(); console.log(fn()); // 'hello world' 包装 C++ 对象包装 C++ 对象 以下会创建一个 C++对象的包装 MyObject ,这样 他就能再 JavaScript 中用 new 实例化。首先在 addon.c c 中准备主要模块: 第 4 章 C/C++ 插件 | 38 // addon.cc #include #include "myobject.h" using namespace v8; void InitAll(Handle exports) { MyObject::Init(exports); } NODE_MODULE(addon, InitAll) 接着在 myobject.h 创建包装,它继承自 node::ObjectWrap : // myobject.h #ifndef MYOBJECT_H #define MYOBJECT_H #include #include class MyObject : public node::ObjectWrap { public: static void Init(v8::Handle exports); private: explicit MyObject(double value = 0); ~MyObject(); static void New(const v8::FunctionCallbackInfo& args); static void PlusOne(const v8::FunctionCallbackInfo& args); static v8::Persistent constructor; double value_; }; #endif 在 myobject.cc 中实现各种暴露的方法,通过给构造函数添加 prototype 属性来暴露 plusOne 方法: // myobject.cc #include "myobject.h" using namespace v8; Persistent MyObject::constructor; 第 4 章 C/C++ 插件 | 39 MyObject::MyObject(double value) : value_(value) { } MyObject::~MyObject() { } void MyObject::Init(Handle exports) { Isolate* isolate = Isolate::GetCurrent(); // Prepare constructor template Local tpl = FunctionTemplate::New(isolate, New); tpl->SetClassName(String::NewFromUtf8(isolate, "MyObject")); tpl->InstanceTemplate()->SetInternalFieldCount(1); // Prototype NODE_SET_PROTOTYPE_METHOD(tpl, "plusOne", PlusOne); constructor.Reset(isolate, tpl->GetFunction()); exports->Set(String::NewFromUtf8(isolate, "MyObject"), tpl->GetFunction()); } void MyObject::New(const FunctionCallbackInfo& args) { Isolate* isolate = Isolate::GetCurrent(); HandleScope scope(isolate); if (args.IsConstructCall()) { // Invoked as constructor: `new MyObject(...)` double value = args[0]->IsUndefined() ? 0 : args[0]->NumberValue(); MyObject* obj = new MyObject(value); obj->Wrap(args.This()); args.GetReturnValue().Set(args.This()); } else { // Invoked as plain function `MyObject(...)`, turn into construct call. const int argc = 1; Local argv[argc] = { args[0] }; Local cons = Local::New(isolate, constructor); args.GetReturnValue().Set(cons->NewInstance(argc, argv)); } } void MyObject::PlusOne(const FunctionCallbackInfo& args) { Isolate* isolate = Isolate::GetCurrent(); HandleScope scope(isolate); 第 4 章 C/C++ 插件 | 40 MyObject* obj = ObjectWrap::Unwrap(args.Holder()); obj->value_ += 1; args.GetReturnValue().Set(Number::New(isolate, obj->value_)); } 测试: // test.js var addon = require('./build/Release/addon'); var obj = new addon.MyObject(10); console.log( obj.plusOne() ); // 11 console.log( obj.plusOne() ); // 12 console.log( obj.plusOne() ); // 13 包装对象工厂包装对象工厂 当你想创建本地对象,又不想在 JavaScript 中严格的使用 new 初始化的时候,以下方法非常实用。 var obj = addon.createObject(); // instead of: // var obj = new addon.Object(); 在 addon.cc 中注册 createObject 方法: // addon.cc #include #include "myobject.h" using namespace v8; void CreateObject(const FunctionCallbackInfo& args) { Isolate* isolate = Isolate::GetCurrent(); HandleScope scope(isolate); MyObject::NewInstance(args); } void InitAll(Handle exports, Handle module) { MyObject::Init(); NODE_SET_METHOD(module, "exports", CreateObject); } NODE_MODULE(addon, InitAll) 第 4 章 C/C++ 插件 | 41 在 myobject.h 中有静态方法 NewInstance ,他能实例化对象 (它就像 JavaScript 的 new ): // myobject.h #ifndef MYOBJECT_H #define MYOBJECT_H #include #include class MyObject : public node::ObjectWrap { public: static void Init(); static void NewInstance(const v8::FunctionCallbackInfo& args); private: explicit MyObject(double value = 0); ~MyObject(); static void New(const v8::FunctionCallbackInfo& args); static void PlusOne(const v8::FunctionCallbackInfo& args); static v8::Persistent constructor; double value_; }; #endif 这个实现方法和 myobject.cc 类似: // myobject.cc #include #include "myobject.h" using namespace v8; Persistent MyObject::constructor; MyObject::MyObject(double value) : value_(value) { } MyObject::~MyObject() { } void MyObject::Init() { Isolate* isolate = Isolate::GetCurrent(); // Prepare constructor template 第 4 章 C/C++ 插件 | 42 Local tpl = FunctionTemplate::New(isolate, New); tpl->SetClassName(String::NewFromUtf8(isolate, "MyObject")); tpl->InstanceTemplate()->SetInternalFieldCount(1); // Prototype NODE_SET_PROTOTYPE_METHOD(tpl, "plusOne", PlusOne); constructor.Reset(isolate, tpl->GetFunction()); } void MyObject::New(const FunctionCallbackInfo& args) { Isolate* isolate = Isolate::GetCurrent(); HandleScope scope(isolate); if (args.IsConstructCall()) { // Invoked as constructor: `new MyObject(...)` double value = args[0]->IsUndefined() ? 0 : args[0]->NumberValue(); MyObject* obj = new MyObject(value); obj->Wrap(args.This()); args.GetReturnValue().Set(args.This()); } else { // Invoked as plain function `MyObject(...)`, turn into construct call. const int argc = 1; Local argv[argc] = { args[0] }; Local cons = Local::New(isolate, constructor); args.GetReturnValue().Set(cons->NewInstance(argc, argv)); } } void MyObject::NewInstance(const FunctionCallbackInfo& args) { Isolate* isolate = Isolate::GetCurrent(); HandleScope scope(isolate); const unsigned argc = 1; Handle argv[argc] = { args[0] }; Local cons = Local::New(isolate, constructor); Local instance = cons->NewInstance(argc, argv); args.GetReturnValue().Set(instance); } void MyObject::PlusOne(const FunctionCallbackInfo& args) { Isolate* isolate = Isolate::GetCurrent(); HandleScope scope(isolate); 第 4 章 C/C++ 插件 | 43 MyObject* obj = ObjectWrap::Unwrap(args.Holder()); obj->value_ += 1; args.GetReturnValue().Set(Number::New(isolate, obj->value_)); } 测试: // test.js var createObject = require('./build/Release/addon'); var obj = createObject(10); console.log( obj.plusOne() ); // 11 console.log( obj.plusOne() ); // 12 console.log( obj.plusOne() ); // 13 var obj2 = createObject(20); console.log( obj2.plusOne() ); // 21 console.log( obj2.plusOne() ); // 22 console.log( obj2.plusOne() ); // 23 传递包装对象传递包装对象 除了包装并返回 C++ 对象,你可以使用 Node 的 node::ObjectWrap::Unwrap 帮助函数来解包。在下面的 ad don.cc 中,我们介绍了一个 add() 函数,它能获取2个 MyObject 对象: // addon.cc #include #include #include "myobject.h" using namespace v8; void CreateObject(const FunctionCallbackInfo& args) { Isolate* isolate = Isolate::GetCurrent(); HandleScope scope(isolate); MyObject::NewInstance(args); } void Add(const FunctionCallbackInfo& args) { Isolate* isolate = Isolate::GetCurrent(); HandleScope scope(isolate); MyObject* obj1 = node::ObjectWrap::Unwrap( 第 4 章 C/C++ 插件 | 44 args[0]->ToObject()); MyObject* obj2 = node::ObjectWrap::Unwrap( args[1]->ToObject()); double sum = obj1->value() + obj2->value(); args.GetReturnValue().Set(Number::New(isolate, sum)); } void InitAll(Handle exports) { MyObject::Init(); NODE_SET_METHOD(exports, "createObject", CreateObject); NODE_SET_METHOD(exports, "add", Add); } NODE_MODULE(addon, InitAll) 介绍 myobject.h 里的一个公开方法,它能在解包后使用私有变量: // myobject.h #ifndef MYOBJECT_H #define MYOBJECT_H #include #include class MyObject : public node::ObjectWrap { public: static void Init(); static void NewInstance(const v8::FunctionCallbackInfo& args); inline double value() const { return value_; } private: explicit MyObject(double value = 0); ~MyObject(); static void New(const v8::FunctionCallbackInfo& args); static v8::Persistent constructor; double value_; }; #endif myobject.cc 的实现方法和之前的类似: 第 4 章 C/C++ 插件 | 45 // myobject.cc #include #include "myobject.h" using namespace v8; Persistent MyObject::constructor; MyObject::MyObject(double value) : value_(value) { } MyObject::~MyObject() { } void MyObject::Init() { Isolate* isolate = Isolate::GetCurrent(); // Prepare constructor template Local tpl = FunctionTemplate::New(isolate, New); tpl->SetClassName(String::NewFromUtf8(isolate, "MyObject")); tpl->InstanceTemplate()->SetInternalFieldCount(1); constructor.Reset(isolate, tpl->GetFunction()); } void MyObject::New(const FunctionCallbackInfo& args) { Isolate* isolate = Isolate::GetCurrent(); HandleScope scope(isolate); if (args.IsConstructCall()) { // Invoked as constructor: `new MyObject(...)` double value = args[0]->IsUndefined() ? 0 : args[0]->NumberValue(); MyObject* obj = new MyObject(value); obj->Wrap(args.This()); args.GetReturnValue().Set(args.This()); } else { // Invoked as plain function `MyObject(...)`, turn into construct call. const int argc = 1; Local argv[argc] = { args[0] }; Local cons = Local::New(isolate, constructor); args.GetReturnValue().Set(cons->NewInstance(argc, argv)); } } void MyObject::NewInstance(const FunctionCallbackInfo& args) { 第 4 章 C/C++ 插件 | 46 Isolate* isolate = Isolate::GetCurrent(); HandleScope scope(isolate); const unsigned argc = 1; Handle argv[argc] = { args[0] }; Local cons = Local::New(isolate, constructor); Local instance = cons->NewInstance(argc, argv); args.GetReturnValue().Set(instance); } 测试: // test.js var addon = require('./build/Release/addon'); var obj1 = addon.createObject(10); var obj2 = addon.createObject(20); var result = addon.add(obj1, obj2); console.log(result); // 30 第 4 章 C/C++ 插件 | 47 55 子进程子进程 稳定性: 3 - 稳定 Node 通过 child_process 模块提供了 popen(3) 数据流。 它能在非阻塞的方式中,通过 stdin , stdout , 和 stderr 传递数据。 (请注意,某些程序使用内部线性缓冲 I/ O, 它并不妨碍 node.js,只是你发送给子进程的数据不会被立即消。) 使用 require('child_process').spawn() 或 require('child_process').fork() 创建一个子进程。这两种方法有区 别,底下会解释 below (页 0). 开发过程中查看 synchronous counterparts (页 0) 效率会更高。 类: ChildProcess类: ChildProcess ChildProcess 是一个 EventEmitter (events.html#events_class_events_eventemitter) 。 子进程有三个相关的流 child.stdin , child.stdout , 和 child.stderr 。他们可能和会父进程的 stdio streams 共 享, 也可作为独立的对象。 不能直接调用 ChildProcess 类,使用 spawn() , exec() , execFile() , 或 fork() 方法来创建子进程的实例。 事件: 'error'事件: 'error' • err {Error Object} 错误。 发生于: 1. 无法创建进程。 2. 无法杀死进程。 3. 无论什么原因导致给子进程发送消息失败。 注意, exit 事件有可能在错误发生后调用,也可能不调用,所以如果你监听这两个事件来触发函数,记得预防函 数会被调用2次。 参考 ChildProcess#kill() (页 0) 和 ChildProcess#send() (页 0)。 事件: 'exit'事件: 'exit' • code {Number} 退出代码, 正常退出时才有效。 • signal {String} 如果是被父进程杀死,则它为传递给子进程的信号 第 5 章 子进程 | 49 子进程结束的时候触发这个事件。如果子进程正常终止,则 code 为最终的退出代码,否则为 null 。如果是由 signal 引起的终止,则 signal 为字符串,否则为 null 。 注意,子进程的 stdio 流可能仍为开启模式。 注意,node 为 'SIGINT' 和 'SIGTERM ' 建立句柄,所以当信号来临的时候,他们不会终止而是退出。 参见 waitpid(2) 。 事件: 'close'事件: 'close' • code {Number} 退出代码, 正常退出时才有效。 • signal {String} 如果是被父进程杀死,则它为传递给子进程的信号。 子进程里所有 stdio 流都关闭时触发这个事件。要和 'exit' 区分开, 因为多进程可以共享一个 stdio 流。 Event: 'disconnect'Event: 'disconnect' 父进程或子进程中调用 .disconnect() 方法后触发这个事件。 断开后不会在互发消息,并且 .connected 属性 值为 false。 Event: 'message'Event: 'message' • message {Object} 一个解析过的 JSON 对象,或者一个原始值 • sendHandle {Handle object} 一个 Socket 或 Server 对象 通过 .send(message, [sendHandle]) 传递消息。 child.stdinchild.stdin • {Stream object} 子进程的 stdin 是 Writable Stream (可写流)。 如果子进程在等待输入,它就会暂停直到通过调用 end() 来关闭。 child.stdin 是 child.stdio[0] 的缩写。这两个都指向同一个对象,或者 null。 第 5 章 子进程 | 50 child.stdoutchild.stdout • {Stream object} 子进程的 stdout 是 Readable Stream (可读流)。 child.stdout 是 child.stdio[1] 的缩写。 这两个都指向同一个对象,或者 null。 child.stderrchild.stderr • {Stream object} 子进程的 stderr 是 Readable Stream (可写流)。 child.stderr 是 child.stdio[2] 缩写。 这两个都指向同一个对象,或者 null。 child.stdiochild.stdio • {Array} 子进程的管道数组和 spawn (页 0) 的 stdio (页 0) 里设置为 'pipe' 的内容次序相对应。 注意,流[0-2]也能分别用 ChildProcess.stdin,ChildProcess.stdout, 和 ChildProcess.stderrNote 来表示。 在下面的例子里,只有子进程的 fd 1 设置为 pipe管道,所以父进程的 child.stdio[1] 是流(stream), 数组里 其他值为 null 。 child = child_process.spawn("ls", { stdio: [ 0, // use parents stdin for child 'pipe', // pipe child's stdout to parent fs.openSync("err.out", "w") // direct child's stderr to a file ] }); assert.equal(child.stdio[0], null); assert.equal(child.stdio[0], child.stdin); assert(child.stdout); assert.equal(child.stdio[1], child.stdout); 第 5 章 子进程 | 51 assert.equal(child.stdio[2], null); assert.equal(child.stdio[2], child.stderr); child.pidchild.pid • {Integer} 子进程的 PID。 例子: var spawn = require('child_process').spawn, grep = spawn('grep', ['ssh']); console.log('Spawned child pid: ' + grep.pid); grep.stdin.end(); child.connectedchild.connected • {Boolean} 调用 `.disconnect' 后设置为 false 如果 .connected 为 false, 消息不再可用。 child.kill([signal])child.kill([signal]) • signal {String} 发送信号给子进程。如果没有参数,会发送 'SIGTERM' ,参见 signal(7) 里的可用的信号列表。 var spawn = require('child_process').spawn, grep = spawn('grep', ['ssh']); grep.on('close', function (code, signal) { console.log('child process terminated due to receipt of signal '+signal); }); // send SIGHUP to process grep.kill('SIGHUP'); 当信号无法传递的时候会触发 'error' 事件。给已经终止的进程发送信号不会触发 'error' 事件,但是可以能引 起不可预知的后果: 因为有可能 PID (进程 ID) 已经重新分配给其他进程, 信号就会被发送到新的进程里,无法想 象这样会引发什么样的事情。 第 5 章 子进程 | 52 注意,当函数调用 kill 信号的时候,它实际并并不会杀死进程,只是发送信号给进程。 参见 kill(2) child.send(message[, sendHandle])child.send(message[, sendHandle]) • message {Object} • sendHandle {Handle object} 使用 child_process.fork() 的时候,你能用 child.send(message, [sendHandle]) 给子进程写数据,子进程通 过 'message' 接收消息。 例如: var cp = require('child_process'); var n = cp.fork(__dirname + '/sub.js'); n.on('message', function(m) { console.log('PARENT got message:', m); }); n.send({ hello: 'world' }); 子进程的代码 'sub.js' : process.on('message', function(m) { console.log('CHILD got message:', m); }); process.send({ foo: 'bar' }); 子进程代码里的 process 对象拥有 send() 方法,当它通过信道接收到信息时会触发,并返回对象。 注意,父进程和子进程 send() 是同步的,不要用来发送大块的数据(可以用管道来代替,参见 child_proces s.spawn (页 0))。 不过发送 {cmd: 'NODE_foo'} 消息是特殊情况。所有包含 NODE_ 前缀的消息都不会被触发,因为它们是 no de 的内部的核心消息,它们会在 internalMessage 事件里触发,尽量避免使用这个特性。 child.send() 里的 sendHandle 属性用来发送 TCP 服务或 socket 对象给其他的进程,子进程会用接收到的 对象作为 message 事件的第二个参数。 第 5 章 子进程 | 53 如果不能发出消息会触发 'error' 事件,比如子进程已经退出。 例子: 发送 server 对象例子: 发送 server 对象 以下是例子: var child = require('child_process').fork('child.js'); // Open up the server object and send the handle. var server = require('net').createServer(); server.on('connection', function (socket) { socket.end('handled by parent'); }); server.listen(1337, function() { child.send('server', server); }); 子进程将会收到这个 server 对象: process.on('message', function(m, server) { if (m === 'server') { server.on('connection', function (socket) { socket.end('handled by child'); }); } }); 注意,现在父子进程共享了server,某些连接会被父进程处理,某些会被子进程处理。 dgram 服务器,工作流程是一样的,监听的是 message 事件,而不是 connection ,使用 server.bind 而 不是 server.listen 。(目前仅支持 UNIX 平台) 例子: 发送 socket 对象例子: 发送 socket 对象 以下是发送 socket 对象的例子。 他将会创建 2 个子线程,并且同时处理连接,一个将远程地址 74.125.127.10 0 当做 VIP 发送到一个特殊的子进程,另外一个发送到正常进程。 var normal = require('child_process').for k('child.js', ['normal']); var special = require('child_process').fork('child.js', ['special']); // Open up the server and send sockets to child var server = require('net').createServer(); server.on('connection', function (socket) { // if this is a VIP if (socket.remoteAddress === '74.125.127.100') { special.send('socket', socket); return; } 第 5 章 子进程 | 54 // just the usual dudes normal.send('socket', socket); }); server.listen(1337); child.js 代码如下: process.on('message', function(m, socket) { if (m === 'socket') { socket.end('You were handled as a ' + process.argv[2] + ' person'); } }); 注意,当 socket 发送给子进程后,如果这个 socket 被销毁,父进程不再跟踪它,相应的 .connections 属性 会变为 null 。这种情况下,不建议使用 .maxConnections 。 child.disconnect()child.disconnect() 关闭父子进程间的所有 IPC 通道,能让子进程优雅的退出。调用这个方法后,父子进程里的 .connected 标志会 变为 false ,之后不能再发送消息。 当进程里没有消息需要处理的时候,会触发 'disconnect' 事件。 注意,在子进程还有 IPC 通道的情况下(如 fork() ),也可以调用 process.disconnect() 来关闭它。 创建异步处理创建异步处理 这些方法遵从常用的异步处理模式(比如回调,或者返回一个事件处理)。 child_process.spawn(command[, args][, options])child_process.spawn(command[, args][, options]) • command {String} 要运行的命令 • args {Array} 字符串参数表 • options {Object} • cwd {String} 子进程的工作目录 • env {Object} 环境 • stdio {Array|String} 子进程的 stdio 配置。 (见 below (页 0)) • customFds {Array} DeprecatedDeprecated 作为子进程 stdio 使用的 文件标示符。(见 below (页 0)) 第 5 章 子进程 | 55 • detached {Boolean} 子进程将会变成一个进程组的领导者。(参见 below (页 0)) • uid {Number} 设置用户进程的ID。 (参见 setuid(2)) • gid {Number} 设置进程组的ID。 (参见 setgid(2)) • 返回: {ChildProcess object} 用指定的 command 发布一个子进程, args 是命令行参数。如果忽略, args 是空数组。 第三个参数用来指定附加设置,默认值: { cwd: undefined, env: process.env } 创建的子进程里使用 cwd 指定工作目录,如果没有指定,默认继承自当前的工作目录。 使用 env 来指定新进程可见的环境变量。默认是 process.env 。 例如,运行 ls -lh /usr , 获取 stdout , stderr , 和退出代码: var spawn = require('child_process').spawn, ls = spawn('ls', ['-lh', '/usr']); ls.stdout.on('data', function (data) { console.log('stdout: ' + data); }); ls.stderr.on('data', function (data) { console.log('stderr: ' + data); }); ls.on('close', function (code) { console.log('child process exited with code ' + code); }); 例如: 一个非常精巧的方法执行 'ps ax | grep ssh' var spawn = require('child_process').spawn, ps = spawn('ps', ['ax']), grep = spawn('grep', ['ssh']); ps.stdout.on('data', function (data) { grep.stdin.write(data); }); 第 5 章 子进程 | 56 ps.stderr.on('data', function (data) { console.log('ps stderr: ' + data); }); ps.on('close', function (code) { if (code !== 0) { console.log('ps process exited with code ' + code); } grep.stdin.end(); }); grep.stdout.on('data', function (data) { console.log('' + data); }); grep.stderr.on('data', function (data) { console.log('grep stderr: ' + data); }); grep.on('close', function (code) { if (code !== 0) { console.log('grep process exited with code ' + code); } }); options.stdiooptions.stdio stdio 可能是以下几个参数之一: • 'pipe' - ['pipe', 'pipe', 'pipe'] , 默认值 • 'ignore' - ['ignore', 'ignore', 'ignore'] • 'inherit' - [process.stdin, process.stdout, process.stderr] 或 [0,1,2] child_process.spawn() 里的 'stdio' 参数是一个数组,它和子进程的 fd 相对应,它的值如下: 1. 'pipe' - 创建在父进程和子进程间的 pipe。管道的父进程端以 child_process 的属性形式暴露给父进 程,例如 ChildProcess.stdio[fd] 。为 fds 0 - 2 创建的管道也可以通过 ChildProcess.stdin, ChildProce ss.stdout 和 ChildProcess.stderr 来独立的访问。 2. 'ipc' - 在父进程和子进程间创建一个 IPC 通道来传递消息/文件描述符。 一个子进程最多有1个1个 IPC stdio 文件标识。设置这个选项会激活 ChildProcess.send() 方法。如果子进程向此文件标识写入 JSON 消 第 5 章 子进程 | 57 息,则会触发 ChildProcess.on('message') 。如果子进程是 Node.js 程序,那么 IPC 通道会激活 proces s.send() 和 process.on('message')。 3. 'ignore' - 在子进程里不要设置这个文件标识,注意,Node 总会为其 spawn 的进程打开 fd 0-2。如果任 何一个被 ignored,node 将会打开 /dev/null 并赋给子进程的 fd。 4. Stream 对象 - 共享一个tty, file, socket, 或刷(pipe)可读或可写流给子进程。该流底层(underlyin g)的文件标识在子进程中被复制给stdio数组索引对应的文件标识(fd)。 5. 正数 - 这个整数被理解为一个在父进程中打开的文件标识,它和子进程共享,就和共享 Stream 对象类 似。 6. null , undefined - 使用默认值。 对于 stdio fds 0, 1 and 2 (换句话说, stdin, stdout, and stderr) ,pip e管道被建立。 对于 fd 3 及之后, 默认是 'ignore' 。 例如: var spawn = require('child_process').spawn; // Child will use parent's stdios spawn('prg', [], { stdio: 'inherit' }); // Spawn child sharing only stderr spawn('prg', [], { stdio: ['pipe', 'pipe', process.stderr] }); // Open an extra fd=4, to interact with programs present a // startd-style interface. spawn('prg', [], { stdio: ['pipe', null, null, null, 'pipe'] }); options.detachedoptions.detached 如果设置了 detached 选项, 子进程将会被作为新进程组的leader,这使得子进程可以在父进程退出后继续运 行。 缺省情况下父进程会等 detached 的子进程退出。要阻止父进程等待一个这样的子进程,调用 child.unref() 方 法,则父进程的事件循环引用计数中将不会包含这个子进程。 detaching 一个长期运行的进程,并重新将输出指向文件: var fs = require('fs'), spawn = require('child_process').spawn, out = fs.openSync('./out.log', 'a'), err = fs.openSync('./out.log', 'a'); 第 5 章 子进程 | 58 var child = spawn('prg', [], { detached: true, stdio: [ 'ignore', out, err ] }); child.unref(); 使用 detached 选项来启动一个长时间运行的进程时,进程不会在后台保持运行,除非他提供了一个不连接到父 进程的 stdio 。如果继承了父进程的 stdio ,则子进程会继续控制终端。 options.customFdsoptions.customFds 已废弃, customFds 允许指定特定文件描述符作为子进程的 stdio 。该 API 无法移植到所有平台,因此被废 弃。使用 customFds 可以将新进程的 [ stdin , stdout , stderr ] 钩到已有流上;-1 表示创建新流。自己承担 使用风险。 参见: child_process.exec() and child_process.fork() child_process.exec(command[, options], callback)child_process.exec(command[, options], callback) • command {String} 要执行的命令,空格分割 • options {Object} • cwd {String} 子进程的当前工作目录 • env {Object} 环境变量 • encoding {String} (默认: 'utf8') • shell {String} 运行命令的 shell (默认: '/bin/sh' UNIX, 'cmd.exe' Windows, 该 shell 必须接收 UNIX 上的 -c 开关 ,或者 Windows上的 /s /c 开关 。Windows 上,命令解析必须兼容 cmd.exe 。) • timeout {Number} (默认: 0) • maxBuffer {Number} (默认: 200*1024 ) • killSignal {String} (默认: 'SIGTERM') • uid {Number} 设置进程里的用户标识。 (见 setuid(2)。) • gid {Number} 设置进程里的群组标识。(见 setgid(2)。) • callback {Function} 进程终止的时候调用 第 5 章 子进程 | 59 • error {Error} • stdout {Buffer} • stderr {Buffer} • 返回: ChildProcess 对象 在 shell 里执行命令,并缓冲输出。 var exec = require('child_process').exec, child; child = exec('cat *.js bad_file | wc -l', function (error, stdout, stderr) { console.log('stdout: ' + stdout); console.log('stderr: ' + stderr); if (error !== null) { console.log('exec error: ' + error); } }); 回调参数是 (error, stdout, stderr) 。 如果成功 , error 值为 null 。 如果失败, error 变为 Error 的实例, error.code 等于子进程退出码, 并且 error.signal 会被设置为结束进程的信号名。 第二个参数可以设置一些选项。 缺省是: { encoding: 'utf8', timeout: 0, maxBuffer: 200*1024, killSignal: 'SIGTERM', cwd: null, env: null } 如果 timeout 大于 0, 子进程运行时间超过 timeout 时会被终止。 killSignal (默认: 'SIGTERM' ) 能杀死子进 程。 maxBuffer 设定了 stdout 或 stderr 的最大数据量,如果子进程的数量量超过了,将会被杀死。 (file[, args][, options][, callback])(file[, args][, options][, callback]) • file {String} 要运行的程序的文件名 • args {Array} 参数列表 • options {Object} 第 5 章 子进程 | 60 • cwd {String} 子进程的工作目录 • env {Object} 环境 • encoding {String} (默认: 'utf8') • timeout {Number} (默认: 0) • maxBuffer {Number} (默认: 200*1024) • killSignal {String} (默认: 'SIGTERM') • uid {Number} 设置进程里的用户标识。 (见 setuid(2)。) • gid {Number} 设置进程里的群组标识。(见 setgid(2)。) • callback {Function} 进程终止的时候调用 • error {Error} • stdout {Buffer} • stderr {Buffer} • 返回: ChildProcess 对象 和 child_process.exec() 类似,不同之处在于这是执行一个指定的文件,因此它比 child_process.exec 精简 些,参数相同。 child_process.fork(modulePath[, args][, options])child_process.fork(modulePath[, args][, options]) • modulePath {String} 子进程里运行的模块 • args {Array} 参数列表 • options {Object} • cwd {String} 子进程的工作目录 • env {Object} 环境 • execPath {String} 执行文件路径 • execArgv {Array} 执行参数 (默认: process.execArgv ) • silent {Boolean} 如果是 true ,子进程将会用父进程的 stdin, stdout, and stderr ,否则,将会继承 自父进程, 更多细节,参见 spawn() 的 stdio 参数里的 "pipe" 和 "inherit" 选项(默认 false) • uid {Number} 设置进程里的用户标识。 (见 setuid(2)。) 第 5 章 子进程 | 61 • gid {Number} 设置进程里的群组标识。 (见 setgid(2)。) • 返回: ChildProcess 对象 这是 spawn() 的特殊例子,用于派生 Node 进程。除了拥有子进程的所有方法,它的返回对象还拥有内置通讯 通道。参见 child.send(message, [sendHandle]) 。 这些 Nodes 是全新的 V8 实例化,假设每个 Node 最少需要 30ms 的启动时间,10mb 的存储空间,可想而 知,创建几千个 Node 是不太现实的。 options 对象中的 execPath 属性可以用于执行文件(非当前 node )创建子进程。这需要小心使用,缺省 情况下 fd 表示子进程的 NODE_CHANNEL_FD 环境变量。该 fa 的输入和输出是以行分割的 JSON 对象。 创建同步进程创建同步进程 以下这些方法是同步同步的,意味着他会阻塞阻塞事件循环,并暂停执行代码,直到 spawned 的进程退出。 同步方法简化了任务进程,比如大为简化在应用初始化加载/处理过程。 child_process.spawnSync(command[, args][, options])child_process.spawnSync(command[, args][, options]) • command {String} 要执行的命令 • args {Array} 参数列表 • options {Object} • cwd {String} 子进程的当前工作目录 • input {String|Buffer} 传递给spawned 进程的值,这个值将会重写 stdio[0] • stdio {Array} 子进程的 stdio 配置。 • env {Object} 环境变量 • uid {Number} 设置用户进程的ID。 (参见 setuid(2)。) • gid {Number} 设置进程组的ID。 (参见 setgid(2)。) • timeout {Number} 子进程运行最大毫秒数。 (默认: undefined) • killSignal {String} 用来终止子进程的信号。 (默认: 'SIGTERM') • maxBuffer {Number} • encoding {String} stdio 输入和输出的编码方式。 (默认: 'buffer') 第 5 章 子进程 | 62 • 返回: {Object} • pid {Number} 子进程的 pid • output {Array} stdio 输出的结果数组 • stdout {Buffer|String} output[1] 的内容 • stderr {Buffer|String} output[2] 的内容 • status {Number} 子进程的退出代码 • signal {String} 用来杀死子进程的信号 • error {Error} 子进程错误或超时的错误代码 spawnSync 直到子进程关闭才会返回。超时或者收到 killSignal 信号,也不会返回,直到进程完全退出。进 程处理完 SIGTERM 信号后并不会结束,直到子进程完全退出。 child_process.execFileSync(command[, args][, options])child_process.execFileSync(command[, args][, options]) • command {String} 要执行的命令 • args {Array} 参数列表 • options {Object} • cwd {String} 子进程的当前工作目录 • input {String|Buffer}传递给spawned 进程的值,这个值将会重写 stdio[0] • stdio {Array}子进程的 stdio 配置。 (默认: 'pipe') • stderr 默认情况下会输出给父进程的' stderr 除非指定了 stdio • env {Object} 环境变量 • uid {Number} 设置用户进程的ID。 (参见 setuid(2)。) • gid {Number} 设置进程组的ID。 (参见 setgid(2)。) • timeout {Number} 进程运行最大毫秒数。 (默认: undefined) • killSignal {String} 用来终止子进程的信号。 (默认: 'SIGTERM') • maxBuffer {Number} • encoding {String} stdio 输入和输出的编码方式。 (默认: 'buffer') • 返回: {Buffer|String} 来自命令的 stdout 第 5 章 子进程 | 63 直到子进程完全退出, execFileSync 才会返回。超时或者收到 killSignal 信号,也不会返回,直到进程完全退 出。进程处理完 SIGTERM 信号后并不会结束,直到子进程完全退出。 如果进程超时,或者非正常退出,这个方法将会抛出异常。 Error 会包含整个 child_process.spawnSync (页 0)结果。 child_process.execSync(command[, options])child_process.execSync(command[, options]) • command {String} 要执行的命令 • options {Object} • cwd {String} 子进程的当前工作目录 • input {String|Buffer} 传递给spawned 进程的值,这个值将会重写 stdio[0] • stdio {Array} 子进程的 stdio 配置。 (默认: 'pipe') • stderr 默认情况下会输出给父进程的' stderr 除非指定了 stdio • env {Object} 环境变量 • uid {Number} 设置用户进程的ID。 (参见 setuid(2)。) • gid {Number} 设置进程组的ID。 (参见 setgid(2)。) • timeout {Number} 进程运行最大毫秒数。 (默认: undefined) • killSignal {String} 用来终止子进程的信号。 (默认: 'SIGTERM') • maxBuffer {Number} • encoding {String} stdio 输入和输出的编码方式。 (默认: 'buffer') • 返回: {Buffer|String} 来自命令的 stdout 直到子进程完全退出, execSync 才会返回。超时或者收到 killSignal 信号,也不会返回,直到进程完全退 出。进程处理完 SIGTERM 信号后并不会结束,直到子进程完全退出。 如果进程超时,或者非正常退出,这个方法将会抛出异常。 Error 会包含整个 child_process.spawnSync (页 0)结果。 第 5 章 子进程 | 64 66 集群集群 稳定性: 2 - 不稳定 单个 Node 实例运行在一个线程中。为了更好的利用多核系统的能力,可以启动 Node 集群来处理负载。 在集群模块里很容易就能创建一个共享所有服务器接口的进程。 var cluster = require('cluster'); var http = require('http'); var numCPUs = require('os').cpus().length; if (cluster.isMaster) { // Fork workers. for (var i = 0; i < numCPUs; i++) { cluster.fork(); } cluster.on('exit', function(worker, code, signal) { console.log('worker ' + worker.process.pid + ' died'); }); } else { // Workers can share any TCP connection // In this case its a HTTP server http.createServer(function(req, res) { res.writeHead(200); res.end("hello world\n"); }).listen(8000); } 运行 Node 后,将会在所有工作进程里共享 8000 端口。 % NODE_DEBUG=cluster node server.js 23521,Master Worker 23524 online 23521,Master Worker 23526 online 23521,Master Worker 23523 online 23521,Master Worker 23528 online 这个特性是最近才引入的,大家可以试试并提供反馈。 还要注意,在 Windows 系统里还不能在工作进程中创建一个被命名的管道服务器。 如何工作如何工作 child_process.fork 方法派生工作进程,所以它能通过 IPC 和父进程通讯,并相互传递句柄。 集群模块通过2种分发模式来处理连接。 第 6 章 集群 | 66 第一种(默认方法,除了 Windows 平台)为循环式。主进程监听一个端口,接收新的连接,再轮流的分发给工 作进程。 第二种,主进程监听 socket,并发送给感兴趣的工作进程,工作进程直接接收连接。 第二种方法理论上性能最高。实际上,由于操作系统各式各样,分配往往分配不均。列如,70%的连接终止于2 个进程,实际上共有8个进程。 因为 server.listen() 将大部分工作交给了主进程,所以一个普通的 Node.js 进程和一个集群工作进程会在三种 情况下有所区别: 1. server.listen({fd: 7}) 由于消息被传回主进程,所以将会监听主进程主进程里的文件描述符,而不是其他工作进程 里的文件描述符 7。 2. server.listen(handle) 监听一个明确地句柄,会使得工作进程使用指定句柄,而不是与主进程通讯。如果工 作进程已经拥有了该句柄,前提是您知道在做什么。 3. server.listen(0) 通常它会让服务器随机监听端口。然而在集群里每个工作进程 listen(0) 时会收到相同的 端口。实际上仅第一次是随机的,之后是可预测的。如果你想监听一个特定的端口,可以根据集群的工作进 程的ID生产一个端口ID 。 在 Node.js 或你的程序里没有路由逻辑,工作进程见也没有共享状态。因此,像登录和会话这样的工作,不要设 计成过度依赖内存里的对象。 因为工作线程都是独立的,你可以根据需求来杀死或者派生而不会影响其他进程。只要仍然有工作进程,服务器 还会接收连接。Node 不会自动管理工作进程的数量,这是你的责任,你可以根据自己需求来管理。 cluster.schedulingPolicycluster.schedulingPolicy 调度策略 cluster.SCHED_RR 表示轮流制, cluster.SCHED_NONE 表示操作系统处理。这是全局性的设 定,一旦你通过 cluster.setupMaster() 派生了第一个工作进程,它就不可更改了。 SCHED_RR 是除 Windows 外所有系统的默认设置。只要 libuv 能够有效地分配 IOCP 句柄并且不产生巨大 的性能损失,Windows 也会改为 SCHED_RR 方式。 cluster.schedulingPolicy 也可通过环境变量 NODE_CLUSTER_SCHED_POLICY 来更改。有效值为 "rr" 和 "none" 。 第 6 章 集群 | 67 cluster.settingscluster.settings • {Object} • execArgv {Array} 传给可执行的 Node 的参数列表(默认= process.execArgv ) • exec {String} 执行文件的路径。 (默认= process.argv[1] ) • args {Array} 传给工作进程的参数列表(默认= process.argv.slice(2) ) • silent {Boolean}是否将输出发送给父进程的 stdio。 (默认= false ) • uid {Number} 设置用户进程的ID。 (See setuid(2)。) • gid {Number} 设置进程组的ID。 (See setgid(2)。) 调用 .setupMaster() (或 .fork() ) 方法后,这个 settings 对象会包含设置内容,包括默认值。 设置后会立即冻结,因为 .setupMaster() 只能调用一次。 这个对象不应该被手动改变或设置。 cluster.isMastercluster.isMaster • {Boolean} 如果是主进程,返回 true。如果 process.env.NODE_UNIQUE_ID 未定义, isMaster 为 true 。 cluster.isWorkercluster.isWorker • {Boolean} 如果不是主进程返回 true (和 cluster.isMaster 相反)。 事件: 'fork'事件: 'fork' • worker {Worker object} 当一个新的工作进程被分支出来,集群模块会产生 'fork' 事件。它可用于记录工作进程,并创建自己的超时管 理。 第 6 章 集群 | 68 var timeouts = []; function errorMsg() { console.error("Something must be wrong with the connection ..."); } cluster.on('fork', function(worker) { timeouts[worker.id] = setTimeout(errorMsg, 2000); }); cluster.on('listening', function(worker, address) { clearTimeout(timeouts[worker.id]); }); cluster.on('exit', function(worker, code, signal) { clearTimeout(timeouts[worker.id]); errorMsg(); }); 事件: 'online'事件: 'online' • worker {Worker object} 分支出一个新的工作进程后,它会响应在线消息。当主线程接收到在线消息后,它会触发这个事件。'fork' 和 'onli ne' 之间的区别在于,主进程分支一个工作进程后会调用 fork,而工作进程运行后会调用 emitted。 cluster.on('online', function(worker) { console.log("Yay, the worker responded after it was forked"); }); 事件: 'listening'事件: 'listening' • worker {Worker object} • address {Object} 工作进程调用 listen() 时,服务器会触发'listening'事件,同时也会在主进程的集群里触发。 事件处理函数有两个参数, worker 包含工作进程对象, address 包含以下属性: address , port 和 addre ssType 。如果工作进程监听多个地址的时候,这些东西非常有用。 cluster.on('listening', function(worker, address) { console.log("A worker is now connected to " + address.address + ":" + address.port); }); addressType 是以下内容: 第 6 章 集群 | 69 • 4 (TCPv4) • 6 (TCPv6) • -1 (unix domain socket) • "udp4" or "udp6" (UDP v4 or v6)* 事件: 'disconnect'事件: 'disconnect' • worker {Worker object} 当一个工作进程的 IPC 通道关闭时会触发这个事件。当工作进程正常退出,被杀死,或者手工关闭(例如worke r.disconnect())时会调用。 disconnect 和 exit 事件间可能存在延迟。 这些事件可以用来检测进程是否卡在清理过程中,或者存在长连 接。 cluster.on('disconnect', function(worker) { console.log('The worker #' + worker.id + ' has disconnected'); }); 事件: 'exit'事件: 'exit' • worker {Worker object} • code {Number} 如果正常退出,则为退出代码. • signal {String} 使得进程被杀死的信号名 (比如. 'SIGHUP' ) 当任意一个工作进程终止的时候,集群模块会触发 'exit' 事件。 可以调用 .fork() 重新启动工作进程。 cluster.on('exit', function(worker, code, signal) { console.log('worker %d died (%s). restarting...', worker.process.pid, signal || code); cluster.fork(); }); 参见 child_process event: 'exit' (https://nodejs.org/api/child_process.html#child_process_event_exit) . 第 6 章 集群 | 70 事件: 'setup'事件: 'setup' • settings {Object} 调用 .setupMaster() 后会被触发。 settings 对象就是 cluster.settings 对象。 详细内容参见 cluster.settings 。 cluster.setupMaster([settings])cluster.setupMaster([settings]) • settings {Object} • exec {String} 执行文件的路径。 (默认= process.argv[1] ) • args {Array}传给工作进程的参数列表(默认= process.argv.slice(2) ) • silent {Boolean} 是否将输出发送给父进程的 stdio. setupMaster 用来改变默认的 'fork' 。 一旦调用,settings值将会出现在 cluster.settings 里。 注意: • 改变任何设置,仅会对未来的工作进程产生影响,不会影响对目前已经运行的进程 • 工作进程里,仅能改变传递给 .fork() 的 env 属性。 • 以上的默认值,仅在第一次调用的时候有效,之后的默认值是调用 cluster.setupMaster() 后的值。 例如: var cluster = require('cluster'); cluster.setupMaster({ exec: 'worker.js', args: ['--use', 'https'], silent: true }); cluster.fork(); // https worker cluster.setupMaster({ args: ['--use', 'http'] }); cluster.fork(); // http worker 第 6 章 集群 | 71 仅能在主进程里调用。 cluster.fork([env])cluster.fork([env]) • env {Object} 添加到子进程环境变量中的键值。 • return {Worker object} 派生一个新的工作进程。 仅能在主进程里调用。 cluster.disconnect([callback])cluster.disconnect([callback]) • callback {Function} 当所有工作进程都断开连接,并且关闭句柄后被调用。 cluster.workers 里的每个工作进程可调用 .disconnect() 关闭。 关闭所有的内部句柄连接,并且没有任何等待处理的事件时,允许主进程优雅的退出。 这个方法有一个可选参数,会在完成时被调用。 仅能在主进程里调用。 cluster.workercluster.worker • {Object} 对当前工作进程对象的引用。主进程中不可用。 var cluster = require('cluster'); if (cluster.isMaster) { console.log('I am master'); cluster.fork(); cluster.fork(); } else if (cluster.isWorker) { console.log('I am worker #' + cluster.worker.id); } 第 6 章 集群 | 72 cluster.workerscluster.workers • {Object} 存储活跃工作对象的哈希表,主键是 id ,能方便的遍历所有工作进程,仅在主进程可用。 当工作进程关闭连接并退出后,将会从 cluster.workers 里移除。这两个事件的次序无法确定,仅能保证从clust er.workers 移除会发生在 'disconnect' 或 'exit' 之后。 // Go through all workers function eachWorker(callback) { for (var id in cluster.workers) { callback(cluster.workers[id]); } } eachWorker(function(worker) { worker.send('big announcement to all workers'); }); 如果希望通过通讯通道引用工作进程,那么使用工作进程的 id 来查询最简单。 socket.on('data', function(id) { var worker = cluster.workers[id]; }); Class: WorkerClass: Worker 一个 Worker 对象包含工作进程所有公开的信息和方法。在主进程里可用通过 cluster.workers 来获取,在工作 进程里可以通过 cluster.worker 来获取。 worker.idworker.id • {String} 每一个新的工作进程都有独立的唯一标示,它就是 id 。 当工作进程可用时, id 就是 cluster.workers 里的主键。 第 6 章 集群 | 73 worker.processworker.process • {ChildProcess object} 所有工作进程都是通用 child_process.fork() 创建的,该函数返回的对象被储存在 process 中。 参见: Child Process module () 注意,当 process 和 .suicide 不是 true 的时候,会触发 'disconnect' 事件,并使得工作进程调用 proces s.exit(0) 。它会保护意外的连接关闭。 worker.suicideworker.suicide • {Boolean} 调用 .kill() 或 .disconnect() 后设置,在这之前是 undefined 。 worker.suicide 能让你区分出是自愿的还是意外退出,主进程可以根据这个值,来决定是否是重新派生成工作进 程。 cluster.on('exit', function(worker, code, signal) { if (worker.suicide === true) { console.log('Oh, it was just suicide\' – no need to worry'). } }); // kill worker worker.kill(); worker.send(message[, sendHandle])worker.send(message[, sendHandle]) • message {Object} • sendHandle {Handle object} 这个函数和 child_process.fork() 提供的 send 方法相同。主进程里你必须使用这个函数给指定工作进程发消 息。 在工作进程里,你也可以用 process.send(message) 。 这个例子会回应所有来自主进程的消息: 第 6 章 集群 | 74 if (cluster.isMaster) { var worker = cluster.fork(); worker.send('hi there'); } else if (cluster.isWorker) { process.on('message', function(msg) { process.send(msg); }); } worker.kill([signal='SIGTERM'])worker.kill([signal='SIGTERM']) • signal {String}发送给工作进程的杀死信号的名称 这个函数会杀死工作进程。在主进程里,它会关闭 worker.process ,一旦关闭会发送杀死信号。在工作进程 里,关闭通道,退出,返回代码 0 。 会导致 .suicide 被设置。 为了保持兼容性,这个方法的别名是 worker.destroy() 。 注意,在工作进程里有 process.kill() ,于此不同。 worker.disconnect()worker.disconnect() 在工作进程里,这个函数会关闭所有服务器,等待 'close' 事件,关闭 IPC 通道。 在主进程里,发给工作进程一个内部消息,用来调用 .disconnect() 会导致 .suicide 被设置。 注意,服务器关闭后,不再接受新的连接,但可以接受新的监听。已经存在的连接允许正常退出。当连接为空得 时候,工作进程的 IPC 通道运行优雅的退出。 以上仅能适用于服务器的连接,客户端的连接由工作进程关闭。 注意,在工作进程里,存在 process.disconnect ,但并不是这个函数,它是 disconnect。 由于长连接可能会阻塞进程关闭连接,有一个较好的办法是发消息给应用,这样应用会想办法关闭它们。超时管 理也是不错,如果超过一定时间后还没有触发 disconnect 事件,将会杀死进程。 if (cluster.isMaster) { var worker = cluster.fork(); 第 6 章 集群 | 75 var timeout; worker.on('listening', function(address) { worker.send('shutdown'); worker.disconnect(); timeout = setTimeout(function() { worker.kill(); }, 2000); }); worker.on('disconnect', function() { clearTimeout(timeout); }); } else if (cluster.isWorker) { var net = require('net'); var server = net.createServer(function(socket) { // connections never end }); server.listen(8000); process.on('message', function(msg) { if(msg === 'shutdown') { // initiate graceful close of any connections to server } }); } worker.isDead()worker.isDead() 工作进程结束,返回 true , 否则返回 false 。 worker.isConnected()worker.isConnected() 当工作进程通过 IPC 通道连接主进程时,返回 true ,否则 false 。工作进程创建后会连接到主进程。当 disc onnect 事件触发后会关闭连接。 事件: 'message'事件: 'message' • message {Object} 第 6 章 集群 | 76 该事件和 child_process.fork() 所提供的一样。在主进程中您应当使用该事件,而在工作进程中您也可以使用 process.on('message') 。 例如,有一个集群使用消息系统在主进程中统计请求的数量: var cluster = require('cluster'); var http = require('http'); if (cluster.isMaster) { // Keep track of http requests var numReqs = 0; setInterval(function() { console.log("numReqs =", numReqs); }, 1000); // Count requestes function messageHandler(msg) { if (msg.cmd && msg.cmd == 'notifyRequest') { numReqs += 1; } } // Start workers and listen for messages containing notifyRequest var numCPUs = require('os').cpus().length; for (var i = 0; i < numCPUs; i++) { cluster.fork(); } Object.keys(cluster.workers).forEach(function(id) { cluster.workers[id].on('message', messageHandler); }); } else { // Worker processes have a http server. http.Server(function(req, res) { res.writeHead(200); res.end("hello world\n"); // notify master about the request process.send({ cmd: 'notifyRequest' }); }).listen(8000); } 第 6 章 集群 | 77 事件: 'online'事件: 'online' 和 cluster.on('online') 事件类似, 仅能在特定工作进程里触发。 cluster.fork().on('online', function() { // Worker is online }); 不会在工作进程里触发。 事件: 'listening'事件: 'listening' • address {Object} 和 cluster.on('listening') 事件类似, 仅能在特定工作进程里触发。 cluster.fork().on('listening', function(address) { // Worker is listening }); 不会在工作进程里触发。 事件: 'disconnect'事件: 'disconnect' 和 cluster.on('disconnect') 事件类似, 仅能在特定工作进程里触发。 cluster.fork().on('disconnect', function() { // Worker has disconnected }); 事件: 'exit'事件: 'exit' • code {Number} 正常退出时的退出代码. • signal {String} 使得进程被终止的信号的名称(比如 SIGHUP )。 和 cluster.on('exit') 事件类似, 仅能在特定工作进程里触发。 var worker = cluster.fork(); worker.on('exit', function(code, signal) { if( signal ) { console.log("worker was killed by signal: "+signal); 第 6 章 集群 | 78 } else if( code !== 0 ) { console.log("worker exited with error code: "+code); } else { console.log("worker success!"); } }); 事件: 'error'事件: 'error' 和 child_process.fork() 事件类似。 工作进程里,你也可以用 process.on('error') 。 第 6 章 集群 | 79 77 控制台控制台 稳定性: 4 - 冻结 • {Object} 用于打印输出字符到 stdout 和 stderr。和多数浏览器提供的 console 对象函数一样,Node 也是输出到 stdout 和 stderr。 当输出目标是终端或文件的时候,console 函数是同步的(为了防止意外退出数据丢失),输出是管道的时候是 异步的(防止阻塞时间太长)。 下面的例子里,stdout 是非阻塞的,而 stderr 是阻塞的。 $ node script.js 2> error.log | tee info.log 平常使用过程中,不用考虑阻塞或非阻塞问题,除非有大批量的数据。 console.log([data][, ...])console.log([data][, ...]) 输出到 stdout 并新起一行。和 printf() 类似,stdout 可以传入多个参数。例如: var count = 5; console.log('count: %d', count); // prints 'count: 5' 如果第一个字符里没有找到格式化的元素, util.inspect 将会应用到各个参数,参见util.format() (https://nodej s.org/api/util.html#util_util_format_format) console.info([data][, ...])console.info([data][, ...]) 参见 console.log 。 console.error([data][, ...])console.error([data][, ...]) 参见 console.log ,不同的是打印到 stderr。 console.warn([data][, ...])console.warn([data][, ...]) 参见 console.error 。 第 7 章 控制台 | 81 console.dir(obj[, options])console.dir(obj[, options]) 在 obj 使用 util.inspect ,并打印结果到 stdout,而这个函数绕过 inspect() 。 options 参数可能传入以下几 种: • showHidden - 如果是 true ,将会展示对象的非枚举属性,默认是 false 。 • depth - inspect 对象递归的次数,对于复杂对象的扫描非常有用。默认是 2 。想要严格递归,传入 nul l 。 • colors - 如果是 true , 输出会格式化为 ANSI 颜色代码。默认是 false 。颜色可以定制,下面会介绍。 console.time(label)console.time(label) 标记一个时间点。 console.timeEnd(label)console.timeEnd(label) 计时器结束的时候,记录输出,例如: console.time('100-elements'); for (var i = 0; i < 100; i++) { ; } console.timeEnd('100-elements'); // prints 100-elements: 262ms console.trace(message[, ...])console.trace(message[, ...]) 输出当前位置的栈跟踪到 stderr 'Trace :' 。 console.assert(value[, message][, ...])console.assert(value[, message][, ...]) 和 assert.ok() (https://nodejs.org/api/assert.html#assert_assert_value_message_assert_ok_valu e_message) 类似, 但是错误的输出格式为: util.format(message...) 。 第 7 章 控制台 | 82 88 加密加密 稳定性: 2 - 不稳定; 正在讨论未来版本的 API 改进,会尽量减少重大变化。详见后文。 使用 require('crypto') 来访问这个模块。 加密模块提供了 HTTP 或 HTTPS 连接过程中封装安全凭证的方法。 它也提供了 OpenSSL 的哈希,hmac, 加密(cipher), 解密(decipher), 签名(sign) 和 验证(verify) 方 法的封装。 crypto.setEngine(engine[, flags])crypto.setEngine(engine[, flags]) 为某些/所有 OpenSSL 函数加载并设置引擎(根据参数 flags 来设置)。 engine 可能是 id,或者是指向引擎共享库的路径。 flags 是可选参数,默认值是 ENGINE_METHOD_ALL ,它可以是以下一个或多个参数的组合(在 constant s 里定义): • ENGINE_METHOD_RSA • ENGINE_METHOD_DSA • ENGINE_METHOD_DH • ENGINE_METHOD_RAND • ENGINE_METHOD_ECDH • ENGINE_METHOD_ECDSA • ENGINE_METHOD_CIPHERS • ENGINE_METHOD_DIGESTS • ENGINE_METHOD_STORE • ENGINE_METHOD_PKEY_METH • ENGINE_METHOD_PKEY_ASN1_METH • ENGINE_METHOD_ALL • ENGINE_METHOD_NONE crypto.getCiphers()crypto.getCiphers() 返回支持的加密算法名数组。 第 8 章 加密 | 84 例如: var ciphers = crypto.getCiphers(); console.log(ciphers); // ['AES-128-CBC', 'AES-128-CBC-HMAC-SHA1', ...] crypto.getHashes()crypto.getHashes() 返回支持的哈希算法名数组。 例如: var hashes = crypto.getHashes(); console.log(hashes); // ['sha', 'sha1', 'sha1WithRSAEncryption', ...] crypto.createCredentials(details)crypto.createCredentials(details) 稳定性: 0 - 抛弃. 用 [tls.createSecureContext][] 替换. 根据参数 details,创建一个加密凭证对象。参数为字典,key 包括: • pfx : 字符串或者buffer对象,表示经PFX或PKCS12编码产生的私钥、证书以及CA证书 • key : 进过 PEM 编码的私钥 • passphrase : 私钥或 pfx 的密码 • cert : PEM 编码的证书 • ca : 字符串或字符串数组,PEM 编码的可信任的 CA 证书。 • crl : 字符串或字符串数组,PEM 编码的 CRLs(证书吊销列表Certificate Revocation List)。 • ciphers : 字符串,使用或者排除的加密算法。参见http://www.openssl.org/docs/apps/ciphers.html#CI PHER_LIST_FORMAT。 如果没有指定 'ca',Node.js将会使用下面列表中的CAhttp://mxr.mozilla.org/mozilla/source/security/nss/lib/ ckfw/builtins/certdata.txt。 crypto.createHash(algorithm)crypto.createHash(algorithm) 创建并返回一个哈希对象,使用指定的算法来生成哈希摘要。 第 8 章 加密 | 85 参数 algorithm 取决于平台上 OpenSSL 版本所支持的算法。例如, 'sha1' , 'md5' , 'sha256' , 'sha512' 等 等。在最近的版本中, openssllist-message-digest-algorithms 会显示所有算法。 例如: 这个程序会计算文件的 sha1 的和。 var filename = process.argv[2]; var crypto = require('crypto'); var fs = require('fs'); var shasum = crypto.createHash('sha1'); var s = fs.ReadStream(filename); s.on('data', function(d) { shasum.update(d); }); s.on('end', function() { var d = shasum.digest('hex'); console.log(d + ' ' + filename); }); 类: Hash类: Hash 用来生成数据的哈希值。 它是可读写的流 stream () 。写入的数据来用计算哈希值。当写入流结束后,使用 read() 方法来获取计算后的 哈希值。也支持老的 update 和 digest 方法。 通过 crypto.createHash 返回。 hash.update(data[, input_encoding])hash.update(data[, input_encoding]) 根据 data 来更新哈希内容,编码方式根据 input_encoding 来定,有 'utf8' , 'ascii' 或 'binary' 。如果没有 传入值,默认编码方式是 'binary' 。如果 data 是 Buffer , input_encoding 将会被忽略。 因为它是流式数据,所以可以使用不同的数据调用很多次。 第 8 章 加密 | 86 hash.digest([encoding])hash.digest([encoding]) 计算传入的数据的哈希摘要。 encoding 可以是 'hex' , 'binary' 或 'base64' ,如果没有指定 encoding ,将 返回 buffer。 注意:调用 digest() 后不能再用 hash 对象。 crypto.createHmac(algorithm, key)crypto.createHmac(algorithm, key) 创建并返回一个 hmac 对象,用指定的算法和秘钥生成 hmac 图谱。 它是可读写的流 stream () 。写入的数据来用计算 hmac。当写入流结束后,使用 read() 方法来获取计算后的 值。也支持老的 update 和 digest 方法。 参数 algorithm 取决于平台上 OpenSSL 版本所支持的算法,参见前面的 createHash。 key 是 hmac 算法 中用的 key。 类: Hmac类: Hmac 用来创建 hmac 加密图谱。 通过 crypto.createHmac 返回。 hmac.update(data)hmac.update(data) 根据 data 更新 hmac 对象。因为它是流式数据,所以可以使用新数据调用多次。 hmac.digest([encoding])hmac.digest([encoding]) 计算传入数据的 hmac 值。 encoding 可以是 'hex' , 'binary' 或 'base64' ,如果没有指定 encoding ,将返 回 buffer。 注意:调用 digest() 后不能再用 hmac 对象。 crypto.createCipher(algorithm, password)crypto.createCipher(algorithm, password) 使用传入的算法和秘钥来生成并返回加密对象。 第 8 章 加密 | 87 algorithm 取决于 OpenSSL,例如 'aes192' 等。最近发布的版本中, openssl list-cipher-algorithms 将会 展示可用的加密算法。 password 用来派生 key 和 IV,它必须是一个 'binary' 编码的字符串或者一个buffer () 。 它是可读写的流 stream () 。写入的数据来用计算 hmac。当写入流结束后,使用 read() 方法来获取计算后的 值。也支持老的 update 和 digest 方法。 注意,OpenSSL 函数EVP_BytesToKey (https://www.openssl.org/docs/crypto/EVP_BytesToKey.htm l) 摘要算法如果是一次迭代(one iteration),无需盐值(no salt)的 MD5 时, createCipher 为它派生秘 钥。缺少盐值使得字典攻击,相同的密码总是生成相同的key,低迭代次数和非加密的哈希算法,使得密码测试非 常迅速。 OpenSSL推荐使用 pbkdf2 来替换 EVP_BytesToKey,推荐使用 crypto.pbkdf2 (页 0) 来派生 key 和 iv ,推荐使用 createCipheriv() (页 0) 来创建加密流。 crypto.createCipheriv(algorithm, key, iv)crypto.createCipheriv(algorithm, key, iv) 创建并返回一个加密对象,用指定的算法,key 和 iv。 algorithm 参数和 createCipher() 一致。 key 在算法中用到. iv 是一个initialization vector (http://en.wikip edia.org/wiki/Initialization_vector) . key 和 iv 必须是 'binary' 的编码字符串或buffers () . 类: Cipher类: Cipher 加密数据的类。. 通过 crypto.createCipher 和 crypto.createCipheriv 返回。 它是可读写的流 stream () 。写入的数据来用计算 hmac。当写入流结束后,使用 read() 方法来获取计算后的 值。也支持老的 update 和 digest 方法。 cipher.update(data[, input_encoding][, output_encoding])cipher.update(data[, input_encoding][, output_encoding]) 根据 data 来更新哈希内容,编码方式根据 input_encoding 来定,有 'utf8' , 'ascii' or 'binary' 。如果没有 传入值,默认编码方式是 'binary' 。如果 data 是 Buffer , input_encoding 将会被忽略。 第 8 章 加密 | 88 output_encoding 指定了输出的加密数据的编码格式,它可用是 'binary' , 'base64' 或 'hex' 。如果没有提供 编码,将返回 buffer 。 返回加密后的内容,因为它是流式数据,所以可以使用不同的数据调用很多次。 cipher.final([output_encoding])cipher.final([output_encoding]) 返回加密后的内容,编码方式是由 output_encoding 指定,可以是 'binary' , 'base64' 或 'hex' 。如果没有 传入值,将返回 buffer。 注意: cipher 对象不能在 final() 方法之后调用。 cipher.setAutoPadding(auto_padding=true)cipher.setAutoPadding(auto_padding=true) 你可以禁用输入数据自动填充到块大小的功能。如果 auto_padding 是false, 那么输入数据的长度必须是加密 器块大小的整倍数,否则 final 会失败。这对非标准的填充很有用,例如使用0x0而不是PKCS的填充。这个函 数必须在 cipher.final 之前调用。 cipher.getAuthTag()cipher.getAuthTag() 加密认证模式(目前支持:GCM),这个方法返回经过计算的认证标志 Buffer 。必须使用 final 方法完全加密 后调用。 cipher.setAAD(buffer)cipher.setAAD(buffer) 加密认证模式(目前支持:GCM),这个方法设置附加认证数据( AAD )。 crypto.createDecipher(algorithm, password)crypto.createDecipher(algorithm, password) 根据传入的算法和密钥,创建并返回一个解密对象。这是 createCipher() (页 0) 的镜像。 crypto.createDecipheriv(algorithm, key, iv)crypto.createDecipheriv(algorithm, key, iv) 根据传入的算法,密钥和 iv,创建并返回一个解密对象。这是 createCipheriv() (页 0) 的镜像。 第 8 章 加密 | 89 类: Decipher类: Decipher 解密数据类。 通过 crypto.createDecipher 和 crypto.createDecipheriv 返回。 解密对象是可读写的流 streams () 。用写入的加密数据生成可读的纯文本数据。也支持老的 update 和 digest 方法。 decipher.update(data[, input_encoding][, output_encoding])decipher.update(data[, input_encoding][, output_encoding]) 使用参数 data 更新需要解密的内容,其编码方式是 'binary' , 'base64' 或 'hex' 。如果没有指定编码方 式,则把 data 当成 buffer 对象。 如果 data 是 Buffer ,则忽略 input_encoding 参数。 参数 output_decoding 指定返回文本的格式,是 'binary' , 'ascii' 或 'utf8' 之一。如果没有提供编码格 式,则返回 buffer。 decipher.final([output_encoding])decipher.final([output_encoding]) 返回剩余的解密过的内容,参数 output_encoding 是 'binary' , 'ascii' 或 'utf8' ,如果没有指定编码方式,返 回 buffer。 注意, decipher 对象不能在 final() 方法之后使用。 decipher.setAutoPadding(auto_padding=true)decipher.setAutoPadding(auto_padding=true) 如果加密的数据是非标准块,可以禁止其自动填充,防止 decipher.final 检查并移除。仅在输入数据长度是加密 块长度的整数倍的时才有效。你必须在 decipher.update 前调用。 decipher.setAuthTag(buffer)decipher.setAuthTag(buffer) 对于加密认证模式(目前支持:GCM),必须用这个方法来传递接收到的认证标志。如果没有提供标志,或者密 文被篡改,将会抛出 final 标志,认证失败,密文会被抛弃, 第 8 章 加密 | 90 decipher.setAAD(buffer)decipher.setAAD(buffer) 对于加密认证模式(目前支持:GCM),用这个方法设置附加认证数据( AAD )。 crypto.createSign(algorithm)crypto.createSign(algorithm) 根据传入的算法创建并返回一个签名数据。 OpenSSL 的最近版本里, openssl list-public-key-algorithms 会 列出所有算法,比如 'RSA-SHA256' 。 类: Sign类: Sign 生成数字签名的类。 通过 crypto.createSign 返回。 签名对象是可读写的流 streams () 。可写数据用来生成签名。当所有的数据写完, sign 签名方法会返回签 名。也支持老的 update 和 digest 方法。 sign.update(data)sign.update(data) 用参数 data 来更新签名对象。因为是流式数据,它可以被多次调用。 sign.sign(private_key[, output_format])sign.sign(private_key[, output_format]) 根据传送给sign的数据来计算电子签名。 private_key 可以是一个对象或者字符串。如果是字符串,将会被当做没有密码的key。 private_key : • key : 包含 PEM 编码的私钥 • passphrase : 私钥的密码 返回值 output_format 包含数字签名, 格式是 'binary' , 'hex' 或 'base64' 之一。如果没有指定 encoding ,将返回 buffer。 注意: sign 对象不能在 sign() 方法之后调用。 第 8 章 加密 | 91 crypto.createVerify(algorithm)crypto.createVerify(algorithm) 根据传入的算法,创建并返回验证对象。是签名对象(signing object)的镜像。 类: Verify类: Verify 用来验证签名的类。 通过 crypto.createVerify 返回。 是可写流 streams () 。可写数据用来验证签名。一旦所有数据写完后,如签名正确 verify 方法会返回 true 。 也支持老的 update 方法。 verifier.update(data)verifier.update(data) 用参数 data 来更新验证对象。因为是流式数据,它可以被多次调用。 verifier.verify(object, signature[, signature_format])verifier.verify(object, signature[, signature_format]) 使用 object 和 signature 验证签名数据。参数 object 是包含了 PEM 编码对象的字符串,它可以是 RSA 公 钥, DSA 公钥, 或 X.509 证书。 signature 是之前计算出来的数字签名。 signature_format 可以是 'binary' , 'hex' 或 'base64' 之一,如果没有指定编码方式 ,则默认是buffer 对象。 根据数据和公钥验证签名有效性,来返回 true 或 false。 注意: verifier 对象不能在 verify() 方法之后调用。 crypto.createDiffieHellman(prime_length[, generator])crypto.createDiffieHellman(prime_length[, generator]) 创建一个 Diffie-Hellman 密钥交换(Diffie-Hellman key exchange)对象,并根据给定的位长度生成一个质 数。如果没有指定参数 generator ,默认为 2 。 第 8 章 加密 | 92 crypto.createDiffieHellman(prime[, prime_encoding][, generator][, generator_encocrypto.createDiffieHellman(prime[, prime_encoding][, generator][, generator_enco ding])ding]) 使用传入的 prime 和 generator 创建 Diffie-Hellman 秘钥交互对象。 generator 可以是数字,字符串或Buffer。 如果没有指定 generator ,使用 2 . prime_encoding 和 generator_encoding 可以是 'binary' , 'hex' , 或 'base64' 。 如果没有指定 prime_encoding , 则 Buffer 为 prime 。 如果没有指定 generator_encoding ,则 Buffer 为 generator 。 类: DiffieHellman类: DiffieHellman 创建 Diffie-Hellman 秘钥交换的类。 通过 crypto.createDiffieHellman 返回。 diffieHellman.verifyErrordiffieHellman.verifyError 在初始化的时候,如果有警告或错误,将会反应到这。它是以下值(定义在 constants 模块): • DH_CHECK_P_NOT_SAFE_PRIME • DH_CHECK_P_NOT_PRIME • DH_UNABLE_TO_CHECK_GENERATOR • DH_NOT_SUITABLE_GENERATOR diffieHellman.generateKeys([encoding])diffieHellman.generateKeys([encoding]) 生成秘钥和公钥,并返回指定格式的公钥。这个值必须传给其他部分。编码方式: 'binary' , 'hex' , 或 'base6 4' 。如果没有指定编码方式,将返回 buffer。 第 8 章 加密 | 93 diffieHellman.computeSecret(other_public_key[, input_encoding][, output_encoding])diffieHellman.computeSecret(other_public_key[, input_encoding][, output_encoding]) 使用 other_public_key 作为第三方公钥来计算并返回共享秘密(shared secret)。秘钥用 input_encoding 编码。编码方式为: 'binary' , 'hex' , 或 'base64' 。如果没有指定编码方式 ,默认为 buffer。 如果没有指定返回编码方式,将返回 buffer。 diffieHellman.getPrime([encoding])diffieHellman.getPrime([encoding]) 用参数 encoding 指明的编码方式返回 Diffie-Hellman 质数,编码方式为: 'binary' , 'hex' , 或 'base64' 。 如 果没有指定编码方式,将返回 buffer。 diffieHellman.getGenerator([encoding])diffieHellman.getGenerator([encoding]) 用参数 encoding 指明的编码方式返回 Diffie-Hellman 生成器,编码方式为: 'binary' , 'hex' , 或 'base64' . 如 果没有指定编码方式 ,将返回 buffer。 diffieHellman.getPublicKey([encoding])diffieHellman.getPublicKey([encoding]) 用参数 encoding 指明的编码方式返回 Diffie-Hellman 公钥,编码方式为: 'binary' , 'hex' , 或 'base64' . 如果 没有指定编码方式 ,将返回 buffer。 diffieHellman.getPrivateKey([encoding])diffieHellman.getPrivateKey([encoding]) 用参数 encoding 指明的编码方式返回 Diffie-Hellman 私钥,编码方式为: 'binary' , 'hex' , 或 'base64' . 如果 没有指定编码方式 ,将返回 buffer。 diffieHellman.setPublicKey(public_key[, encoding])diffieHellman.setPublicKey(public_key[, encoding]) 设置 Diffie-Hellman 的公钥,编码方式为: 'binary' , 'hex' , 或 'base64' ,如果没有指定编码方式 ,默认为 b uffer。 第 8 章 加密 | 94 diffieHellman.setPrivateKey(private_key[, encoding])diffieHellman.setPrivateKey(private_key[, encoding]) 设置 Diffie-Hellman 的私钥,编码方式为: 'binary' , 'hex' , 或 'base64' ,如果没有指定编码方式 ,默认为 b uffer。 crypto.getDiffieHellman(group_name)crypto.getDiffieHellman(group_name) 创建一个预定义的 Diffie-Hellman 秘钥交换对象。支持的组: 'modp1' , 'modp2' , 'modp5' (定义于RFC 24 12 (http://www.rfc-editor.org/rfc/rfc2412.txt) ) and 'modp14' , 'modp15' , 'modp16' , 'modp17' , 'modp1 8' (定义于RFC 3526 (http://www.rfc-editor.org/rfc/rfc3526.txt) ). 返回对象模仿了上述创建的crypto.creat eDiffieHellman() (页 0)对象,但是不允许修改秘钥交换(例如,diffieHellman.setPublicKey() (页 0))。使用 这套流程的好处是,双方不需要生成或交换组组余数,节省了计算和通讯时间。 例如 (获取一个共享秘密): var crypto = require('crypto'); var alice = crypto.getDiffieHellman('modp5'); var bob = crypto.getDiffieHellman('modp5'); alice.generateKeys(); bob.generateKeys(); var alice_secret = alice.computeSecret(bob.getPublicKey(), null, 'hex'); var bob_secret = bob.computeSecret(alice.getPublicKey(), null, 'hex'); /* alice_secret and bob_secret should be the same */ console.log(alice_secret == bob_secret); crypto.createECDH(curve_name)crypto.createECDH(curve_name) 使用传入的参数 curve_name ,创建一个 Elliptic Curve (EC) Diffie-Hellman 秘钥交换对象。 类: ECDH类: ECDH 这个类用来创建 EC Diffie-Hellman 秘钥交换。 通过 crypto.createECDH 返回。 第 8 章 加密 | 95 ECDH.generateKeys([encoding[, format]])ECDH.generateKeys([encoding[, format]]) 生成 EC Diffie-Hellman 的秘钥和公钥,并返回指定格式和编码的公钥,它会传递给第三方。 参数 format 是 'compressed' , 'uncompressed' , 或 'hybrid' . 如果没有指定,将返回 'uncompressed' 格 式. 参数 encoding 是 'binary' , 'hex' , 或 'base64' . 如果没有指定编码方式 ,将返回 buffer。 ECDH.computeSecret(other_public_key[, input_encoding][, output_encoding])ECDH.computeSecret(other_public_key[, input_encoding][, output_encoding]) 以 other_public_key 作为第三方公钥计算共享秘密,并返回。秘钥会以 input_encoding 来解读。编码是: 'bi nary' , 'hex' , 或 'base64' 。如果没有指定编码方式 ,默认为 buffer。 如果没有指定编码方式 ,将返回 buffer。 ECDH.getPublicKey([encoding[, format]])ECDH.getPublicKey([encoding[, format]]) 用参数 encoding 指明的编码方式返回 EC Diffie-Hellman 公钥,编码方式为: 'compressed' , 'uncompresse d' , 或 'hybrid' . 如果没有指定编码方式 ,将返回 'uncompressed' 。 编码是: 'binary' , 'hex' , 或 'base64' 。如果没有指定编码方式 ,默认为 buffer。 ECDH.getPrivateKey([encoding])ECDH.getPrivateKey([encoding]) 用参数 encoding 指明的编码方式返回 EC Diffie-Hellman 私钥,编码是: 'binary' , 'hex' , 或 'base64' 。如 果没有指定编码方式 ,默认为 buffer。 ECDH.setPublicKey(public_key[, encoding])ECDH.setPublicKey(public_key[, encoding]) 设置 EC Diffie-Hellman 的公钥,编码方式为: 'binary' , 'hex' , 或 'base64' ,如果没有指定编码方式 ,默认 为 buffer。 ECDH.setPrivateKey(private_key[, encoding])ECDH.setPrivateKey(private_key[, encoding]) 设置 EC Diffie-Hellman 的私钥,编码方式为: 'binary' , 'hex' , 或 'base64' ,如果没有指定编码方式 ,默认 为 buffer。 第 8 章 加密 | 96 例如 (包含一个共享秘密): var crypto = require('crypto'); var alice = crypto.createECDH('secp256k1'); var bob = crypto.createECDH('secp256k1'); alice.generateKeys(); bob.generateKeys(); var alice_secret = alice.computeSecret(bob.getPublicKey(), null, 'hex'); var bob_secret = bob.computeSecret(alice.getPublicKey(), null, 'hex'); /* alice_secret and bob_secret should be the same */ console.log(alice_secret == bob_secret); crypto.pbkdf2(password, salt, iterations, keylen[, digest], callback)crypto.pbkdf2(password, salt, iterations, keylen[, digest], callback) 异步 PBKDF2 提供了一个伪随机函数 HMAC-SHA1,根据给定密码的长度,salt 和 iterations 来得出一个密 钥。回调函数得到两个参数 (err, derivedKey)。 例如: crypto.pbkdf2('secret', 'salt', 4096, 512, 'sha256', function(err, key) { if (err) throw err; console.log(key.toString('hex')); // 'c5e478d...1469e50' }); 在 crypto.getHashes() (页 0) 里有支持的摘要函数列表。 crypto.pbkdf2Sync(password, salt, iterations, keylen[, digest])crypto.pbkdf2Sync(password, salt, iterations, keylen[, digest]) 异步 PBKDF2 函数. 返回 derivedKey 或抛出错误。 crypto.randomBytes(size[, callback])crypto.randomBytes(size[, callback]) 生成一个密码强度随机的数据: // async crypto.randomBytes(256, function(ex, buf) { 第 8 章 加密 | 97 if (ex) throw ex; console.log('Have %d bytes of random data: %s', buf.length, buf); }); // sync try { var buf = crypto.randomBytes(256); console.log('Have %d bytes of random data: %s', buf.length, buf); } catch (ex) { // handle error // most likely, entropy sources are drained } 注意:如果没有足够积累的熵来生成随机强度的密码,将会抛出错误,或调用回调函数返回错误。换句话说,没 有回调函数的 crypto.randomBytes 不会阻塞,即使耗尽所有的熵。 crypto.pseudoRandomBytes(size[, callback])crypto.pseudoRandomBytes(size[, callback]) 生成非密码学强度的伪随机数据。如果数据足够长会返回一个唯一数据,但是这个数可能是可以预期的。因 此,当不可预期很重要的时候,不要用这个函数。例如,在生成加密的秘钥时。 用法和 crypto.randomBytes 相同。 类: Certificate类: Certificate 这个类和签过名的公钥打交道。最重要的场景是处理 元素,http://www.openssl.org/docs/apps/s pkac.html。 通过 crypto.Certificate 返回. Certificate.verifySpkac(spkac)Certificate.verifySpkac(spkac) 根据 SPKAC 返回 true 或 false。 Certificate.exportChallenge(spkac)Certificate.exportChallenge(spkac) 根据提供的SPKAC,返回加密的公钥。 第 8 章 加密 | 98 Certificate.exportPublicKey(spkac)Certificate.exportPublicKey(spkac) 输出和 SPKAC 关联的编码 challenge。 crypto.publicEncrypt(public_key, buffer)crypto.publicEncrypt(public_key, buffer) 使用 public_key 加密 buffer 。目前仅支持 RSA。 public_key 可以是对象或字符串。如果 public_key 是一个字符串,将会当做没有密码的key,并会用 RS A_PKCS1_OAEP_PADDING 。 public_key : • key : 包含有 PEM 编码的私钥。 • padding : 填充值,如下 • constants.RSA_NO_PADDING • constants.RSA_PKCS1_PADDING • constants.RSA_PKCS1_OAEP_PADDING 注意: 所有的填充值 定义于 constants 模块. crypto.privateDecrypt(private_key, buffer)crypto.privateDecrypt(private_key, buffer) 使用 private_key 来解密 buffer . private_key : • key : 包含有 PEM 编码的私钥 • passphrase : 私钥的密码 • padding : 填充值,如下: • constants.RSA_NO_PADDING • constants.RSA_PKCS1_PADDING • constants.RSA_PKCS1_OAEP_PADDING 注意: 所有的填充值 定义于 constants 模块. 第 8 章 加密 | 99 crypto.DEFAULT_ENCODINGcrypto.DEFAULT_ENCODING 函数所用的编码方式可以是字符串或 buffer ,默认值是 'buffer'。这是为了加密模块兼容默认 'binary' 为编码方 式的遗留程序。 注意,新程序希望用 buffer 对象,所以这是暂时手段。 Recent API ChangesRecent API Changes 在统一的流 API 概念出现前,在引入 Buffer 对象来处理二进制数据之前,Crypto 模块就已经添加到 Node。 因此,流相关的类里没有其他的 Node 类里的典型方法,并且很多方法接收并返回二级制编码的字符串,而不是 Buffers。在最近的版本中,这些函数改成默认使用 Buffers。 对于一些场景来说这是重大变化。 例如,如果你使用默认参数给签名类,将结果返回给认证类,中间没有验证数据,程序会正常工作。之前你会得 到二进制编码的字符串,并传递给验证类,现在则是 Buffer。 如果你之前使用的字符串数据在 Buffers 对象不能正常工作(比如,连接数据,并存储在数据库里 )。或者你传 递了二进制字符串给加密函数,但是没有指定编码方式,现在就需要提供编码参数。如果想切换回原来的风 格,将 crypto.DEFAULT_ENCODING 设置为 'binary'。注意,新的程序希望是 buffers,所以之前的方法只能 作为临时的办法。 第 8 章 加密 | 100 99 调试器调试器 稳定性: 3 - 稳定 V8 提供了强大的调试工具,可以通过 TCP protocol (http://code.google.com/p/v8/wiki/DebuggerProtoco l) 从外部访问。Node 内置这个调试工具客户端。要使用这个调试器,以 debug 参数启动 Node,出现提示: % node debug myscript.js < debugger listening on port 5858 connecting... ok break in /home/indutny/Code/git/indutny/myscript.js:1 1 x = 5; 2 setTimeout(function () { 3 debugger; debug> Node 的调试器不支持所有的命令,但是简单的步进和检查还是可以的。在代码里嵌入 debugger; ,可以设置断 点。 例如, myscript.js 代码如下: // myscript.js x = 5; setTimeout(function () { debugger; console.log("world"); }, 1000); console.log("hello"); 如果启动 debugger,它会断在第四行: % node debug myscript.js < debugger listening on port 5858 connecting... ok break in /home/indutny/Code/git/indutny/myscript.js:1 1 x = 5; 2 setTimeout(function () { 3 debugger; debug> cont < hello break in /home/indutny/Code/git/indutny/myscript.js:3 1 x = 5; 2 setTimeout(function () { 3 debugger; 4 console.log("world"); 5 }, 1000); debug> next break in /home/indutny/Code/git/indutny/myscript.js:4 第 9 章 调试器 | 102 2 setTimeout(function () { 3 debugger; 4 console.log("world"); 5 }, 1000); 6 console.log("hello"); debug> repl Press Ctrl + C to leave debug repl > x 5 > 2+2 4 debug> next < world break in /home/indutny/Code/git/indutny/myscript.js:5 3 debugger; 4 console.log("world"); 5 }, 1000); 6 console.log("hello"); 7 debug> quit % repl 命令能执行远程代码; next 能步进到下一行。此外可以输入 help 查看哪些命令可用。 监视器-Watchers监视器-Watchers 调试的时候可以查看表达式和变量。每个断点处,监视器都会显示上下文。 输入 watch("my_expression") 开始监视表达式, watchers 显示活跃的监视器。输入 unwatch("my_express ion") 可以移除监视器。 命令参考-Commands reference命令参考-Commands reference 步进-Stepping步进-Stepping • cont , c - 继续执行 • next , n - Step next • step , s - Step in • out , o - Step out • pause - 暂停 (类似开发工具的暂停按钮) 第 9 章 调试器 | 103 断点Breakpoints断点Breakpoints • setBreakpoint() , sb() - 当前行设置断点 • setBreakpoint(line) , sb(line) - 在指定行设置断点 • setBreakpoint('fn()') , sb(...) - 在函数里的第一行设置断点 • setBreakpoint('script.js', 1) , sb(...) - 在 script.js 第一行设置断点。 • clearBreakpoint , cb(...) - 清除断点 也可以在尚未加载的文件里设置断点。 % ./node debug test/fixtures/break-in-module/main.js < debugger listening on port 5858 connecting to port 5858... ok break in test/fixtures/break-in-module/main.js:1 1 var mod = require('./mod.js'); 2 mod.hello(); 3 mod.hello(); debug> setBreakpoint('mod.js', 23) Warning: script 'mod.js' was not loaded yet. 1 var mod = require('./mod.js'); 2 mod.hello(); 3 mod.hello(); debug> c break in test/fixtures/break-in-module/mod.js:23 21 22 exports.hello = function() { 23 return 'hello from module'; 24 }; 25 debug> 信息Info信息Info • backtrace , bt - 打印当前执行框架的backtrace • list(5) - 显示脚本代码的 5 行上下文(之前 5 行和之后 5 行) • watch(expr) - 监视列表里添加表达式 • unwatch(expr) - 从监视列表里删除表达式 • watchers - 显示所有的监视器和它们的值(每个断点都会自动列出) 第 9 章 调试器 | 104 • repl - 在所调试的脚本的上下文中,打开调试器的 repl 执行控制Execution control执行控制Execution control • run - 运行脚本 (开始调试的时候自动运行) • restart - 重新运行脚本 • kill - 杀死脚本 杂项Various杂项Various • scripts - 列出所有已经加载的脚本 • version - 显示 v8 版本 高级应用Advanced Usage高级应用Advanced Usage V8 调试器可以用两种方法启用和访问, --debug 命令启动调试,或向已经启动 Node 发送 SIGUSR1 。 一旦一个进程进入调试模式,它可以被 node 调试器连接。调试器可以通过 pid 或 URI 来连接。 • node debug -p - 通过 pid 连接进程 • node debug - 通过 URI (比如localhost:5858) 连接进程w 第 9 章 调试器 | 105 1010 DNSDNS 稳定性: 3 - 稳定 调用 require('dns') 可以访问这个模块。 这个模块包含的函数属于2个不同的分类: 1)使用系统底层的特性,完成名字解析,这个过程不需要网络通讯,这个分类仅有一个函数: dns.lookup 。开开 发者在同一个系统里名字解析都是用发者在同一个系统里名字解析都是用 dns.lookupdns.lookup .. 下面的例子,解析 www.google.com . var dns = require('dns'); dns.lookup('www.google.com', function onLookup(err, addresses, family) { console.log('addresses:', addresses); }); 2)连接到 DNS 服务器进行名字解析,始终始终使用网络来进行域名查询。这个分类包含除了 dns.lookup 外的所 有函数。这些函数不会和 dns.lookup 使用同一套配置文件。如果你不想使用系统底层的特性来进行名字解 析,而想进行 DNS 查询的话,可以用这个分类的函数。 下面的例子,解析了 'www.google.com' ,并反向解析返回的 IP 地址。 var dns = require('dns'); dns.resolve4('www.google.com', function (err, addresses) { if (err) throw err; console.log('addresses: ' + JSON.stringify(addresses)); addresses.forEach(function (a) { dns.reverse(a, function (err, hostnames) { if (err) { throw err; } console.log('reverse for ' + a + ': ' + JSON.stringify(hostnames)); }); }); }); 更多细节参考Implementation considerations section (页 0)。 第 10 章 DNS | 107 dns.lookup(hostname[, options], callback)dns.lookup(hostname[, options], callback) 将域名(比如 'google.com' )解析为第一条找到的记录 A (IPV4)或 AAAA(IPV6)。参数 options 可以是一 个对象或整数。如果没有提供 options ,IP v4 和 v6 地址都可以。如果 options 是整数,则必须是 4 或 6 。 options 参数可能是包含 family 和 hints 两个属性的对象。这两个属性都是可选的。如果提供了 family ,则 必须是 4 或 6 ,否则,IP v4 和 v6 地址都可以。如果提供了 hints ,可以是一个或者多个 getaddrinfo 标 志,若不提供,没有标志会传给 getaddrinfo 。多个标志位可以通过或运算来整合。以下的例子展示如何使用 o ptions 。 { family: 4, hints: dns.ADDRCONFIG | dns.V4MAPPED } 参见 supported getaddrinfo flags (页 0) 查看更多的标志位。 回调函数包含参数 (err, address, family) 。 address 参数表示 IP v4 或 v6 地址。 family 参数是4 或 6,表示 address 家族(不一定是之前传入 lookup 的值)。 出错时,参数 err 是 Error 对象, err.code 是错误代码。请记住, err.code 等于 'ENOENT' ,不仅可能是 因为域名不存在,还有可能是是其他原因,比如没有可用文件描述符。 dns.lookup 不必和 DNS 协议有关系。它使用了操作系统的特性,能将名字和地址关联。 实现这些东西也许很简单,但是对于 Node.js 程序来说都重要,所以在使用前请花点时间阅读Implementation c onsiderations section (页 0)。 第 10 章 DNS | 108 第 10 章 dns.lookupService(address, port, callback)第 10 章 dns.lookupService(address, port, callback) 第 10 章 dns.lookupService(address, port, callback) | 109 使用 getnameinfo 解析传入的地址和端口为域名和服务。 这个回调函数的参数是 (err, hostname, service) 。 hostname 和 service 都是字符串 (比如 'localhost' 和 'http' )。 出错时,参数 err 是 Error 对象, err.code 是错误代码。 dns.resolve(hostname[, rrtype], callback)dns.resolve(hostname[, rrtype], callback) 将一个域名(如 'google.com')解析为一个 rrtype 指定记录类型的数组。 有效的 rrtypes 值为: • 'A' (IPV4 地址, 默认) • 'AAAA' (IPV6 地址) • 'MX' (邮件交换记录) • 'TXT' (text 记录) • 'SRV' (SRV 记录) • 'PTR' (用来反向 IP 查找) • 'NS' (域名服务器 记录) • 'CNAME' (别名 记录) • 'SOA' (授权记录的初始值) 回调参数为 (err, addresses) . 其中 addresses 中每一项的类型都取决于记录类型, 详见下文对应的查找方 法。 出错时,参数 err 是 Error 对象, err.code 是错误代码。 dns.resolve4(hostname, callback)dns.resolve4(hostname, callback) 和 dns.resolve() 类似, 仅能查询 IPv4 ( A 记录)。 addresses IPv4 地址数组 (比如, ['74.125.79.104', '7 4.125.79.105', '74.125.79.106'] )。 第 10 章 dns.lookupService(address, port, callback) | 110 dns.resolve6(hostname, callback)dns.resolve6(hostname, callback) 和 dns.resolve4() 类似, 仅能查询 IPv4( AAAA 查询)。 dns.resolveMx(hostname, callback)dns.resolveMx(hostname, callback) 和 dns.resolve() 类似, 仅能查询邮件交换( MX 记录)。 addresses 是 MX 记录数组, 每一个包含优先级和交换属性(比如, [{'priority': 10, 'exchange': 'mx.example.co m'},...] )。 dns.resolveTxt(hostname, callback)dns.resolveTxt(hostname, callback) 和 dns.resolve() 类似, 仅能进行文本查询 ( TXT 记录)。 addresses 是 2-d 文本记录数组。(比如, [ ['v=spf1 ip4:0.0.0.0 ', '~all' ] ] )。 每个子数组包含一条记录的 TXT 块。根据使用情况可以连接在一起,也可单 独使用。 dns.resolveSrv(hostname, callback)dns.resolveSrv(hostname, callback) 和 dns.resolve() 类似, 仅能进行服务记录查询 ( SRV 记录)。 addresses 是 hostname 可用的 SRV 记录 数组。 SRV 记录属性有优先级(priority),权重(weight), 端口(port), 和名字(name) (比如, [{'priorit y': 10, 'weight': 5, 'port': 21223, 'name': 'service.example.com'}, ...] )。 dns.resolveSoa(hostname, callback)dns.resolveSoa(hostname, callback) 和 dns.resolve() 类似, 仅能查询权威记录( SOA 记录)。 addresses 是包含以下结构的对象: { nsname: 'ns.example.com', hostmaster: 'root.example.com', serial: 2013101809, refresh: 10000, retry: 2400, expire: 604800, 第 10 章 dns.lookupService(address, port, callback) | 111 minttl: 3600 } dns.resolveNs(hostname, callback)dns.resolveNs(hostname, callback) 和 dns.resolve() 类似, 仅能进行域名服务器记录查询( NS 记录)。 addresses 是域名服务器记录数组( ho stname 可以使用) (比如, ['ns1.example.com', 'ns2.example.com'] )。 dns.resolveCname(hostname, callback)dns.resolveCname(hostname, callback) 和 dns.resolve() 类似, 仅能进行别名记录查询 ( CNAME 记录)。 addresses 是对 hostname 可用的别名记 录数组 (比如,, ['bar.example.com'] )。 dns.reverse(ip, callback)dns.reverse(ip, callback) 反向解析 IP 地址,返回指向该 IP 地址的域名数组。 回调函数参数 (err, hostnames) 。 出错时,参数 err 是 Error 对象, err.code 是错误代码。 dns.getServers()dns.getServers() 返回一个用于当前解析的 IP 地址的数组的字符串。 dns.setServers(servers)dns.setServers(servers) 指定一组 IP 地址作为解析服务器。 如果你给地址指定了端口,端口会被忽略,因为底层库不支持。 传入无效参数,会抛出以下错误: Error codesError codes 每个 DNS 查询都可能返回以下错误: 第 10 章 dns.lookupService(address, port, callback) | 112 • dns.NODATA : DNS 服务器返回无数据应答。 • dns.FORMERR : DNS 服务器声称查询格式错误。 • dns.SERVFAIL : DNS 服务器返回一般失败。 • dns.NOTFOUND : 没有找到域名。 • dns.NOTIMP : DNS 服务器未实现请求的操作。 • dns.REFUSED : DNS 服务器拒绝查询。 • dns.BADQUERY : DNS 查询格式错误。 • dns.BADNAME : 域名格式错误。 • dns.BADFAMILY : 地址协议不支持。 • dns.BADRESP : DNS 回复格式错误。 • dns.CONNREFUSED : 无法连接到DNS 服务器。 • dns.TIMEOUT : 连接DNS 服务器超时。 • dns.EOF : 文件末端。 • dns.FILE : 读文件错误。 • dns.NOMEM : 内存溢出。 • dns.DESTRUCTION : 通道被摧毁。 • dns.BADSTR : 字符串格式错误。 • dns.BADFLAGS : 非法标识符。 • dns.NONAME : 所给主机不是数字。 • dns.BADHINTS : 非法HINTS标识符。 • dns.NOTINITIALIZED : c c-ares 库尚未初始化。 • dns.LOADIPHLPAPI : 加载 iphlpapi.dll 出错。 • dns.ADDRGETNETWORKPARAMS : 无法找到 GetNetworkParams 函数。 • dns.CANCELLED : 取消 DNS 查询。 支持的 getaddrinfo 标志支持的 getaddrinfo 标志 以下内容可作为 hints 标志传给 dns.lookup 第 10 章 dns.lookupService(address, port, callback) | 113 • dns.ADDRCONFIG : 返回当前系统支持的地址类型。例如,如果当前系统至少配置了一个 IPv4 地址,则 返回 IPv4地址。 • dns.V4MAPPED : 如果指定了 IPv6 家族, 但是没有找到 IPv6 地址,将返回 IPv4 映射的 IPv6地址。 Implementation considerationsImplementation considerations 虽然 dns.lookup 和 dns.resolve*/dns.reverse 函数都能实现网络名和网络地址的关联,但是他们的行为不太 一样。这些不同点虽然很巧妙,但是会对 Node.js 程序产生显著的影响。 dns.lookupdns.lookup dns.lookup 和绝大多数程序一样使用了相同的系统特性。例如, dns.lookup 和 ping 命令用相同的方法解析 了一个指定的名字。多数类似 POSIX 的系统, dns.lookup 函数可以通过改变 nsswitch.conf(5) 和/或 resol v.conf(5) 的设置调整。如果改变这些文件将会影响系统里的其他应用。 虽然,JavaScript 调用是异步的,它的实现是同步的调用 libuv 线程池里的 getaddrinfo(3) 。因为 libuv 线程池 固定大小,所以如果调用 getaddrinfo(3) 的时间太长,会使的池里的其他操作(比如文件操作)性能降低。为 了降低这个风险,可以通过增加 'UV_THREADPOOL_SIZE' 的值,让它超过4,来调整libuv线程池大小,更多 信息参见[the official libuv documentation](http://docs.libuv.org/en/latest/threadpool.html)。 dns.resolve, functions starting with dns.resolve and dns.reversedns.resolve, functions starting with dns.resolve and dns.reverse 这些函数的实现和 dns.lookup 不大相同。他们不会用到 getaddrinfo(3) ,而是始终进行网络查询。这些操作都 是异步的,和libuv线程池无关。 因此,这些操作对于其他线程不会产生负面影响,这和 dns.lookup 不同。 它们不会用到 dns.lookup 的配置文件(例如 /etc/hosts _)。 第 10 章 dns.lookupService(address, port, callback) | 114 1111 域域 稳定性: 2 - 不稳定 域提供了一种方法,它能把多个不同的 IO 操作看成一个单独组。如果任何一个注册到域的事件或者回调触发 err or 事件,或者抛出一个异常,域就会接收到通知,而不是在 process.on('uncaughtException') 处理函数一样丢 失错误的上下文,也不会使程序立即退出。 警告:不要忽视错误!警告:不要忽视错误! 域错误处理程序并不是一个错误发生时关闭你的进程的替代品。 基于 JavaScript 中抛出异常的工作原理,基本上不可能在不泄露引用,或者不造成一些其他未定义的状态 下,完全重现现场。 响应抛出错误最安全的方法就是关闭进程。一个正常的服务器会可能有很多活跃的连接,因为某个错误就关闭所 有连接显然是不合理的。 比较好的方法是给触发错误的请求发送回应,让其他连接正常工作时,停止监听触发错误的人的新请求。 按这种方法, 域 和集群(cluster)模块可以协同工作,当某个进程遇到错误时,主进程可以复制一个新的进 程。对于 Node 程序,终端代理或者注册的服务,可以留意错误并做出反应。 举例来说,下面的代码就不是好办法: javascript // XXX WARNING! BAD IDEA! var d = require('domain').create(); d.on('error', function(er) { // The error won't crash the process, but what it does is worse! // Though we've prevented abrupt process restarting, we are leaking // resources like crazy if this ever happens. // This is no better than process.on('uncaughtException')! console.log('error, but oh well', er.message); }); d.run(function() { require('http').createServer(function(req, res) { handleRequest(req, res); }).listen(PORT); }); 通过使用域的上下文,并将程序切为多个工作进程,我们能够更合理的响应,处理错误更安全。 第 11 章 域 | 116 javascript // 好一些的做法! var cluster = require('cluster'); var PORT = +process.env.PORT || 1337; if (cluster.isMaster) { // In real life, you'd probably use more than just 2 workers, // and perhaps not put the master and worker in the same file. // // You can also of course get a bit fancier about logging, and // implement whatever custom logic you need to prevent DoS // attacks and other bad behavior. // // See the options in the cluster documentation. // // The important thing is that the master does very little, // increasing our resilience to unexpected errors. cluster.fork(); cluster.fork(); cluster.on('disconnect', function(worker) { console.error('disconnect!'); cluster.fork(); }); } else { // the worker // // This is where we put our bugs! var domain = require('domain'); // See the cluster documentation for more details about using // worker processes to serve requests. How it works, caveats, etc. var server = require('http').createServer(function(req, res) { var d = domain.create(); d.on('error', function(er) { console.error('error', er.stack); // Note: we're in dangerous territory! // By definition, something unexpected occurred, // which we probably didn't want. 第 11 章 域 | 117 // Anything can happen now! Be very careful! try { // make sure we close down within 30 seconds var killtimer = setTimeout(function() { process.exit(1); }, 30000); // But don't keep the process open just for that! killtimer.unref(); // stop taking new requests. server.close(); // Let the master know we're dead. This will trigger a // 'disconnect' in the cluster master, and then it will fork // a new worker. cluster.worker.disconnect(); // try to send an error to the request that triggered the problem res.statusCode = 500; res.setHeader('content-type', 'text/plain'); res.end('Oops, there was a problem!\n'); } catch (er2) { // oh well, not much we can do at this point. console.error('Error sending 500!', er2.stack); } }); // Because req and res were created before this domain existed, // we need to explicitly add them. // See the explanation of implicit vs explicit binding below. d.add(req); d.add(res); // Now run the handler function in the domain. d.run(function() { handleRequest(req, res); }); }); server.listen(PORT); } // This part isn't important. Just an example routing thing. // You'd put your fancy application logic here. function handleRequest(req, res) { 第 11 章 域 | 118 switch(req.url) { case '/error': // We do some async stuff, and then... setTimeout(function() { // Whoops! flerb.bark(); }); break; default: res.end('ok'); } } 错误对象的附加内容错误对象的附加内容 任何时候一个错误被路由传到一个域的时,会添加几个字段。 • error.domain 第一个处理错误的域 • error.domainEmitter 用这个错误对象触发 'error' 事件的事件分发器 • error.domainBound 绑定到 domain 的回调函数,第一个参数是 error。 • error.domainThrown boolean 值,表明是抛出错误,分发,或者传递给绑定的回到函数。 隐式绑定隐式绑定 如果域正在使用中,所有新分发的对象(包括 流对象,请求,响应等)将会隐式的绑定到这个域。 另外,传递给底层事件循环(比如 fs.open 或其他接收回调的方法)的回调函数将会自动的绑定到这个域。如果 他们抛出异常,域会捕捉到错误信息。 为了避免过度使用内存,域对象不会象隐式的添加为有效域的子对象。如果这样做的话,很容易影响到请求和响 应对象的垃圾回收。 如果你想将域对象作为子对象嵌入到父域里,就必须显式的添加它们。 隐式绑定路由抛出的错误和 'error' 事件,但是不会注册事件分发器到域,所以 domain.dispose() 不会关闭事 件分发器。隐式绑定仅需注意抛出的错误和 'error' 事件。 第 11 章 域 | 119 显式绑定显式绑定 有时候正在使用的域并不是某个事件分发器的域。或者说,事件分发器可能在某个域里创建,但是被绑定到另外 一个域里。 例如,HTTP 服务器使用正一个域对象,但我们希望可以每一个请求使用一个不同的域。 这可以通过显式绑定来实现。 例如: // create a top-level domain for the server var serverDomain = domain.create(); serverDomain.run(function() { // server is created in the scope of serverDomain http.createServer(function(req, res) { // req and res are also created in the scope of serverDomain // however, we'd prefer to have a separate domain for each request. // create it first thing, and add req and res to it. var reqd = domain.create(); reqd.add(req); reqd.add(res); reqd.on('error', function(er) { console.error('Error', er, req.url); try { res.writeHead(500); res.end('Error occurred, sorry.'); } catch (er) { console.error('Error sending 500', er, req.url); } }); }).listen(1337); }); domain.create()domain.create() • return: {Domain} 返回一个新的域对象。 第 11 章 域 | 120 Class: DomainClass: Domain 这个类封装了将错误和没有捕捉到的异常到有效对象功能。 域是 EventEmitter (events.html#events_class_events_eventemitter) 的子类. 监听它的 error 事件来处理 捕捉到的错误。 domain.run(fn)domain.run(fn) • fn {Function} 在域的上下文运行提供的函数,隐式的绑定了所有的事件分发器,计时器和底层请求。 这是使用域的基本方法。 例如: var d = domain.create(); d.on('error', function(er) { console.error('Caught error!', er); }); d.run(function() { process.nextTick(function() { setTimeout(function() { // simulating some various async stuff fs.open('non-existent file', 'r', function(er, fd) { if (er) throw er; // proceed... }); }, 100); }); }); 这个例子里程序不会崩溃,而会触发 d.on('error') 。 domain.membersdomain.members • {Array} 显式添加到域里的计时器和事件分发器数组。 第 11 章 域 | 121 domain.add(emitter)domain.add(emitter) • emitter {EventEmitter | Timer} 添加到域里的计时器和事件分发器 显式将一个分发器添加到域。如果分发器调用的事件处理函数抛出错误,或者分发器遇到 error 事件,将会导向 域的 error 事件,和隐式绑定一样。 对于 setInterval 和 setTimeout 返回的计时器同样适用。如果这些回调函数抛出错误,将会被域的 'error' 处 理器捕捉到。 如果计时器或分发器已经绑定到域,那它将会从上一个域移除,绑定到当前域。 domain.remove(emitter)domain.remove(emitter) • emitter {EventEmitter | Timer} 要移除的分发器或计时器 与 domain.add(emitter) 函数恰恰相反,这个函数将分发器移除出域。 domain.bind(callback)domain.bind(callback) • callback {Function} 回调函数 • return: {Function}被绑定的函数 返回的函数是一个对于所提供的回调函数的包装函数。当调用这个返回的函数被时,所有被抛出的错误都会被导 向到这个域的 error 事件。 ExampleExample var d = domain.create(); function readSomeFile(filename, cb) { fs.readFile(filename, 'utf8', d.bind(function(er, data) { // if this throws, it will also be passed to the domain return cb(er, data ? JSON.parse(data) : null); })); } d.on('error', function(er) { // an error occurred somewhere. // if we throw it now, it will crash the program // with the normal line number and stack message. }); 第 11 章 域 | 122 domain.intercept(callback)domain.intercept(callback) • callback {Function} 回调函数 • return: {Function} 被拦截的函数 和 domain.bind(callback) 类似。除了捕捉被抛出的错误外,它还会拦截 Error 对象作为参数传递到这个函数。 这种方式下,常见的 if (er) return callback(er); 模式,能被一个地方一个错误处理替换。 ExampleExample var d = domain.create(); function readSomeFile(filename, cb) { fs.readFile(filename, 'utf8', d.intercept(function(data) { // note, the first argument is never passed to the // callback since it is assumed to be the 'Error' argument // and thus intercepted by the domain. // if this throws, it will also be passed to the domain // so the error-handling logic can be moved to the 'error' // event on the domain instead of being repeated throughout // the program. return cb(null, JSON.parse(data)); })); } d.on('error', function(er) { // an error occurred somewhere. // if we throw it now, it will crash the program // with the normal line number and stack message. }); domain.enter()domain.enter() 这个函数就像 run , bind , 和 intercept 的管道系统,它设置有效域。它设定了域的 domain.active and pro cess.domain ,还隐式的将域推到域模块管理的域栈(关于域栈的细节详见 domain.exit() )。enter函数的调 用,分隔了异步调用链以及绑定到一个域的I/O操作的结束或中断。 调用 enter 仅改变活动的域,而不改变域本身。在一个单独的域里可以调用任意多次 Enter 和 exit 。 第 11 章 域 | 123 domain.exit()domain.exit() exit 函数退出当前域,并从域的栈里移除。每当程序的执行流程要切换到不同的异步调用链的时候,要保证退出 当前域。调用 exit 函数,分隔了异步调用链,和绑定到一个域的I/O操作的结束或中断。 如果有多个嵌套的域绑定到当前的上下文, exit 函数将会退出所有嵌套。 调用 exit 仅改变活跃域,不会改变自身域。在一个单独的域里可以调用任意多次 Enter 和 exit 。 如果在这个域名下 exit 已经被设置,exit 将不退出域返回。 domain.dispose()domain.dispose() 稳定性: 0 - 抛弃。通过域里设置的错误事件来显示的消除失败的 IO 操作。 调用 dispos 后,通过 run,bind 或 intercept 绑定到域的回调函数不再使用这个域,并且分发 dispose 事 件。 第 11 章 域 | 124 1212 事件事件 文档: 4 - API 冻结 Node 里很多对象会分发事件: 每次有连接的时候 net.Server 会分发事件,当文件打开的时候 fs.readStrea m 会分发事件。所有能分发事件的对象都是 events.EventEmitter 的实例。通过 require("events"); 能访问这 个模块。 一般来说,事件名都遵照驼峰规则,但这不是强制规定,任何形式的字符串都可以做为事件名。 为了处理事件,通常将函数关联到对象上。这些函数也叫监听者(listeners)。在这个函数里, this 指向 监听者 所关联的 EventEmitter 。 () 类: events.EventEmitter类: events.EventEmitter 通过 require('events').EventEmitter 获取 EventEmitter 类。 EventEmitter 实例遇到错误后,通常会触发一个错误事件。错误事件在 node 里是特殊例子。如果没有监听 者,默认的操作是打印一个堆栈信息并退出程序。 当添加新的监听者时, EventEmitters 会触发 'newListener' 事件,当移除时会触发 'removeListener' 。 emitter.addListener(event, listener)emitter.addListener(event, listener) emitter.on(event, listener)emitter.on(event, listener) 添加一个监听者到特定 event 的监听数组的尾部,触发器不会检查是否已经添加过这个监听者。 多次调用相同 的 event 和 listener 将会导致 listener 添加多次。 server.on('connection', function (stream) { console.log('someone connected!'); }); 返回 emitter。 emitter.once(event, listener)emitter.once(event, listener) 给事件添加一个一次性的 listener,这个 listener 只会被触发一次,之后就会被移除。 第 12 章 事件 | 126 server.once('connection', function (stream) { console.log('Ah, we have our first user!'); }); 返回emitter。 emitter.removeListener(event, listener)emitter.removeListener(event, listener) 从一个某个事件的 listener 数组中移除一个 listener。注意注意,这个操作会改变 listener 数组内容的次序。 var callback = function(stream) { console.log('someone connected!'); }; server.on('connection', callback); // ... server.removeListener('connection', callback); removeListener 最多会移除数组里的一个 listener。如果多次添加同一个 listener 到数组,那就需要多次调用 removeListener 来移除每一个实例。 返回emitter。 emitter.removeAllListeners([event])emitter.removeAllListeners([event]) 移除所有的 listener,或者某个事件的 listener。最好不要移除全部 listener,尤其是那些不是你传入的(比如 s ocket 或 文件流)。 返回emitter。 emitter.setMaxListeners(n)emitter.setMaxListeners(n) 默认情况下,给单个事件添加超过 10 个 listener ,事件分发器会打印警告。这样有利于检查内存泄露。不过不 是所有的分发器都应该限制在 10 个,这个函数允许改变 listener 数量,无论是 0 还是更多。 返回emitter。 EventEmitter.defaultMaxListenersEventEmitter.defaultMaxListeners emitter.setMaxListeners(n) 设置一个分发器的最大 listener 数,而这个函数会立即设置所有所有 EventEmitter 的 当前值和默认值。要小心使用。 第 12 章 事件 | 127 请注意, emitter.setMaxListeners(n) 的优先级高于 EventEmitter.defaultMaxListeners . emitter.listeners(event)emitter.listeners(event) 返回事件的 listener 数组。 server.on('connection', function (stream) { console.log('someone connected!'); }); console.log(util.inspect(server.listeners('connection'))); // [ [Function] ] emitter.emit(event[, arg1][, arg2][, ...])emitter.emit(event[, arg1][, arg2][, ...]) 使用指定的参数顺序的执行每一个 listener. 如果事件有 listener,返回 true , 否则 false 类方法: EventEmitter.listenerCount(emitter, event)类方法: EventEmitter.listenerCount(emitter, event) 返回指定事件的 listener 数量。 Event: 'newListener'Event: 'newListener' • event {String} 事件名 • listener {Function} 事件处理函数 添加 listener 的时候会触发这个事件。当这个事件触发的时候,listener 可能还没添加到 listener 数组。 Event: 'removeListener'Event: 'removeListener' • event {String} 事件名 • listener {Function} 事件处理函数 删除 listener 的时候会触发这个事件。当这个事件触发的时候,listener 可能还还没从 listener 数组移除。 第 12 章 事件 | 128 1313 文件系统文件系统 稳定性: 3 - 稳定 文件系统模块是一个封装了标准的 POSIX 文件 I/O 操作的集合。通过 require('fs') 使用这个模块。所有的方法 都有同步和异步两种模式。 异步方法最后一个参数都是回调函数,这个回调的参数取决于方法,不过第一个参数一般都是异常。如果操作成 功,那么第一个参数就是 null 或 undefined 。 当使用一个同步操作的时候,任意的异常都立即抛出,可以用 try/catch 来处理异常,使得程序正常运行。 这是异步操作的例子: var fs = require('fs'); fs.unlink('/tmp/hello', function (err) { if (err) throw err; console.log('successfully deleted /tmp/hello'); }); 这是同步操作的例子: var fs = require('fs'); fs.unlinkSync('/tmp/hello'); console.log('successfully deleted /tmp/hello'); 异步方法不能保证操作顺序,因此下面的例子很容易出错: fs.rename('/tmp/hello', '/tmp/world', function (err) { if (err) throw err; console.log('renamed complete'); }); fs.stat('/tmp/world', function (err, stats) { if (err) throw err; console.log('stats: ' + JSON.stringify(stats)); }); 可能先执行了 fs.stat 方法。正确的方法: fs.rename('/tmp/hello', '/tmp/world', function (err) { if (err) throw err; fs.stat('/tmp/world', function (err, stats) { if (err) throw err; console.log('stats: ' + JSON.stringify(stats)); }); }); 第 13 章 文件系统 | 130 在繁忙的进程里,强烈建议使用异步方法。同步方法会阻塞整个进程,直到方法完成。 可能会用到相对路径,路径是相对 process.cwd() 来说的。 大部分 fs 函数会忽略回调参数,如果忽略,将会用默认函数抛出异常。如果想得到原调用点的堆栈信息,需要设 置环境变量 NODE_DEBUG; $ cat script.js function bad() { require('fs').readFile('/'); } bad(); $ env NODE_DEBUG=fs node script.js fs.js:66 throw err; ^ Error: EISDIR, read at rethrow (fs.js:61:21) at maybeCallback (fs.js:79:42) at Object.fs.readFile (fs.js:153:18) at bad (/path/to/script.js:2:17) at Object. (/path/to/script.js:5:1) fs.rename(oldPath, newPath, callback)fs.rename(oldPath, newPath, callback) 异步函数 rename(2)。回调函数只有一个参数:可能出现的异常。 fs.renameSync(oldPath, newPath)fs.renameSync(oldPath, newPath) 同步函数 rename(2)。 返回 undefined 。 fs.ftruncate(fd, len, callback)fs.ftruncate(fd, len, callback) 异步函数 ftruncate(2)。 回调函数只有一个参数:可能出现的异常。 第 13 章 文件系统 | 131 fs.ftruncateSync(fd, len)fs.ftruncateSync(fd, len) 同步函数 ftruncate(2)。 返回 undefined 。 fs.truncate(path, len, callback)fs.truncate(path, len, callback) 异步函数 truncate(2)。 回调函数只有一个参数:可能出现的异常。 文件描述符也可以作为第一个参数,如果这 种情况,调用 fs.ftruncate() 。 fs.truncateSync(path, len)fs.truncateSync(path, len) 同步函数 truncate(2)。 返回 undefined 。 fs.chown(path, uid, gid, callback)fs.chown(path, uid, gid, callback) 异步函数 chown(2)。 回调函数只有一个参数:可能出现的异常。 fs.chownSync(path, uid, gid)fs.chownSync(path, uid, gid) 同步函数 chown(2)。 返回 undefined 。 fs.fchown(fd, uid, gid, callback)fs.fchown(fd, uid, gid, callback) 异步函数 fchown(2)。 回调函数只有一个参数:可能出现的异常。 fs.fchownSync(fd, uid, gid)fs.fchownSync(fd, uid, gid) 同步函数 fchown(2)。 返回 undefined 。 fs.lchown(path, uid, gid, callback)fs.lchown(path, uid, gid, callback) 异步函数 lchown(2)。 回调函数只有一个参数:可能出现的异常。 第 13 章 文件系统 | 132 fs.lchownSync(path, uid, gid)fs.lchownSync(path, uid, gid) 同步函数 lchown(2)。 返回 undefined 。 fs.chmod(path, mode, callback)fs.chmod(path, mode, callback) 异步函数 chmod(2)。回调函数只有一个参数:可能出现的异常。 fs.chmodSync(path, mode)fs.chmodSync(path, mode) 同步函数 chmod(2)。 返回 undefined 。 fs.fchmod(fd, mode, callback)fs.fchmod(fd, mode, callback) 异步函数 fchmod(2)。 回调函数只有一个参数:可能出现的异常。 fs.fchmodSync(fd, mode)fs.fchmodSync(fd, mode) 同步函数 fchmod(2)。 返回 undefined 。 fs.lchmod(path, mode, callback)fs.lchmod(path, mode, callback) 异步函数 lchmod(2)。 回调函数只有一个参数:可能出现的异常。 仅在 Mac OS X 可用。 fs.lchmodSync(path, mode)fs.lchmodSync(path, mode) 同步函数 lchmod(2)。 返回 undefined 。 第 13 章 文件系统 | 133 fs.stat(path, callback)fs.stat(path, callback) 异步函数 stat(2)。 回调函数有两个参数: (err, stats) ,其中 stats 是一个 fs.Stats 对象。 详情请参考 fs.St ats。 fs.lstat(path, callback)fs.lstat(path, callback) 异步函数 lstat(2)。 回调函数有两个参数: (err, stats) ,其中 stats 是一个 fs.Stats 对象。 lstat() 与 sta t() 基本相同, 区别在于,如果 path 是链接,读取的是链接本身,而不是它所链接到的文件。 fs.fstat(fd, callback)fs.fstat(fd, callback) 异步函数 fstat(2)。 回调函数有两个参数: (err, stats) ,其中 stats 是一个 fs.Stats 对象。 fs.statSync(path)fs.statSync(path) 同步函数 stat(2)。 返回 fs.Stats 实例。 fs.lstatSync(path)fs.lstatSync(path) 同步函数 lstat(2)。 返回 fs.Stats 实例。 fs.fstatSync(fd)fs.fstatSync(fd) 同步函数 fstat(2)。 返回 fs.Stats 实例。 fs.link(srcpath, dstpath, callback)fs.link(srcpath, dstpath, callback) 异步函数 link(2)。 回调函数只有一个参数:可能出现的异常。 第 13 章 文件系统 | 134 fs.linkSync(srcpath, dstpath)fs.linkSync(srcpath, dstpath) 同步函数 link(2)。 返回 undefined 。 fs.symlink(srcpath, dstpath[, type], callback)fs.symlink(srcpath, dstpath[, type], callback) 异步函数 symlink(2)。 回调函数只有一个参数:可能出现的异常。 type 可能是 'dir' , 'file' , 或 'junction' (默认 'file' ) ,仅在 Windows(不考虑其他系统)有效。注意, Wind ows junction 要求目的地址需要绝对的。当使用 'junction' 的时候, destination 参数将会自动转换为绝对路 径。 fs.symlinkSync(srcpath, dstpath[, type])fs.symlinkSync(srcpath, dstpath[, type]) 同步函数 symlink(2)。 返回 undefined 。 fs.readlink(path, callback)fs.readlink(path, callback) 异步函数 readlink(2)。 回调函数有2个参数 (err, linkString) . fs.readlinkSync(path)fs.readlinkSync(path) 同步函数 readlink(2)。 返回符号链接的字符串值。 fs.realpath(path[, cache], callback)fs.realpath(path[, cache], callback) 异步函数 realpath(2)。 回调函数有2个参数 (err,resolvedPath) 。可以使用 process.cwd 来解决相对路径问 题。 例如: var cache = {'/etc':'/private/etc'}; fs.realpath('/etc/passwd', cache, function (err, resolvedPath) { if (err) throw err; 第 13 章 文件系统 | 135 console.log(resolvedPath); }); fs.realpathSync(path[, cache])fs.realpathSync(path[, cache]) 同步函数 realpath(2)。 返回解析出的路径。 fs.unlink(path, callback)fs.unlink(path, callback) 异步函数 unlink(2)。 回调函数只有一个参数:可能出现的异常. fs.unlinkSync(path)fs.unlinkSync(path) 同步函数 unlink(2)。 返回 undefined 。 fs.rmdir(path, callback)fs.rmdir(path, callback) 异步函数 rmdir(2)。 回调函数只有一个参数:可能出现的异常. fs.rmdirSync(path)fs.rmdirSync(path) 同步函数 rmdir(2)。 返回 undefined 。 fs.mkdir(path[, mode], callback)fs.mkdir(path[, mode], callback) 异步函数 mkdir(2)。 回调函数只有一个参数:可能出现的异常. mode 默认s to 0777 . fs.mkdirSync(path[, mode])fs.mkdirSync(path[, mode]) 同步函数 mkdir(2)。 返回 undefined 。 第 13 章 文件系统 | 136 fs.readdir(path, callback)fs.readdir(path, callback) 异步函数 readdir(3)。 读取文件夹的内容。回调有2个参数 (err, files) files 是文件夹里除了名字 为 , '.' 和 '..'`之外的所有文件名。 fs.readdirSync(path)fs.readdirSync(path) 同步函数 readdir(3)。 返回除了文件名为 '.' 和 '..' 之外的所有文件. fs.close(fd, callback)fs.close(fd, callback) 异步函数 close(2)。 回调函数只有一个参数:可能出现的异常. fs.closeSync(fd)fs.closeSync(fd) 同步函数 close(2)。 返回 undefined 。 fs.open(path, flags[, mode], callback)fs.open(path, flags[, mode], callback) 异步函数 file open. 参见 open(2)。 flags 是: • 'r' - 以只读模式打开.如果文件不存在,抛出异常。 • 'r+' -以读写模式打开.如果文件不存在,抛出异常。 • 'rs' - 同步的,以只读模式打开. 指令绕过操作系统直接使用本地文件系统缓存。 这个功能主要用来打开 N FS 挂载的文件,因为它能让你跳过可能过时的本地缓存。如果对 I/O 性能很在乎,就不要使用这个标志位。 这里不是调用 fs.open() 变成同步阻塞请求,如果你想要这样,可以调用 fs.openSync() 。 • 'rs+' - 同步模式下以读写方式打开文件。注意事项参见 'rs' . • 'w' - 以只写模式打开。文件会被创建 (如果文件不存在) 或者覆盖 (如果存在)。 • 'wx' - 和 'w' 类似,如果文件存储操作失败 • 'w+' - 以可读写方式打开。文件会被创建 (如果文件不存在) 或者覆盖 (如果存在) 第 13 章 文件系统 | 137 • 'wx+' - 和 'w+' 类似,如果文件存储操作失败。 • 'a' - 以附加的形式打开。如果文件不存在则创建一个。 • 'ax' - 和 'a' 类似,如果文件存储操作失败。 • 'a+' - 以只读和附加的形式打开文件.若文件不存在,则会建立该文件 • 'ax+' - 和 'a+' 类似,如果文件存储操作失败. 如果文件存在,参数 mode 设置文件模式 (permission 和 sticky bits)。 默认是 0666 , 可读写. 回调有2个参数 (err, fd) . 排除标记 'x' (对应 open(2)的 O_EXCL 标记) 保证 path 是新创建的。在 POSIX 系统里,即使文件不存 在,也会被认定为文件存在。 排除标记不能确定在网络文件系统中是否有效。 Linux系统里,无法对以追加模式打开的文件进行指定位置写。系统核心忽略了位置参数,每次把数据写到文件的 最后。 fs.openSync(path, flags[, mode])fs.openSync(path, flags[, mode]) fs.open() 的同步版本. 返回整数形式的文件描述符。. fs.utimes(path, atime, mtime, callback)fs.utimes(path, atime, mtime, callback) 改变指定路径文件的时间戳。 fs.utimesSync(path, atime, mtime)fs.utimesSync(path, atime, mtime) fs.utimes() 的同步版本. 返回 undefined 。 fs.futimes(fd, atime, mtime, callback)fs.futimes(fd, atime, mtime, callback) 改变传入的文件描述符指向文件的时间戳。 第 13 章 文件系统 | 138 fs.futimesSync(fd, atime, mtime)fs.futimesSync(fd, atime, mtime) fs.futimes() 的同步版本. 返回 undefined 。 fs.fsync(fd, callback)fs.fsync(fd, callback) 异步函数 fsync(2)。 回调函数只有一个参数:可能出现的异常. fs.fsyncSync(fd)fs.fsyncSync(fd) 同步 fsync(2)。 返回 undefined 。 fs.write(fd, buffer, offset, length[, position], callback)fs.write(fd, buffer, offset, length[, position], callback) 将 buffer 写到 fd 指定的文件里。 参数 offset 和 length 确定写哪个部分的缓存。 参数 position 是要写入的文件位置。如果 typeof position !== 'number' ,将会在当前位置写入。参见 pwrit e(2)。 回调函数有三个参数 (err, written, buffer) , written 指定 buffer 的多少字节用来写。 注意,如果 fs.write 的回调还没执行,就多次调用 fs.write ,这样很不安全。因此,推荐使用 fs.createWrite Stream 。 Linux系统里,无法对以追加模式打开的文件进行指定位置写。系统核心忽略了位置参数,每次把数据写到文件的 最后。 fs.write(fd, data[, position[, encoding]], callback)fs.write(fd, data[, position[, encoding]], callback) 将 buffer 写到 fd 指定的文件里。如果 data 不是 buffer,那么它就会被强制转换为字符串。 参数 position 是要写入的文件位置。如果 typeof position !== 'number' ,将会在当前位置写入。参见 pwrit e(2)。 第 13 章 文件系统 | 139 参数 encoding :字符串的编码方式. 回调函数有三个参数 (err, written, buffer) , written 指定 buffer 的多少字节用来写。注意写入的字节(byte s)和字符(string characters)不同。参见Buffer.byteLength (页 0)。 和写入 buffer 不同,必须写入整个字符串,不能截取字符串。这是因为返回的字节的位移跟字符串的位移是不 一样的。 注意,如果 fs.write 的回调还没执行,就多次调用 fs.write ,这样很不安全。因此,推荐使用 fs.createWrite Stream Linux系统里,无法对以追加模式打开的文件进行指定位置写。系统核心忽略了位置参数,每次把数据写到文件的 最后。 fs.writeSync(fd, buffer, offset, length[, position])fs.writeSync(fd, buffer, offset, length[, position]) fs.writeSync(fd, data[, position[, encoding]])fs.writeSync(fd, data[, position[, encoding]]) fs.write() 的同步版本. 返回要写的bytes数. fs.read(fd, buffer, offset, length, position, callback)fs.read(fd, buffer, offset, length, position, callback) 读取 fd 指定文件的数据。 buffer 是缓冲区,数据将会写入到这里. offset 写入的偏移量 length 需要读的文件长度 position 读取的文件起始位置,如果是 position 是 null , 将会从当前位置读。 回调函数有3个参数, (err, bytesRead, buffer) . fs.readSync(fd, buffer, offset, length, position)fs.readSync(fd, buffer, offset, length, position) fs.read 的同步版本. 返回 bytesRead 的数量. 第 13 章 文件系统 | 140 fs.readFile(filename[, options], callback)fs.readFile(filename[, options], callback) • filename {String} • options {Object} • encoding {String | Null} 默认 = null • flag {String} 默认 = 'r' • callback {Function} 异步读取整个文件的内容。例如: fs.readFile('/etc/passwd', function (err, data) { if (err) throw err; console.log(data); }); 回调函数有2个参数 (err, data) , 参数 data 是文件的内容。 如果没有指定参数 encoding , 返回原生 buffer fs.readFileSync(filename[, options])fs.readFileSync(filename[, options]) fs.readFile 的同步版本. 返回整个文件的内容. 如果没有指定参数 encoding , 返回buffer。 fs.writeFile(filename, data[, options], callback)fs.writeFile(filename, data[, options], callback) • filename {String} • data {String | Buffer} • options {Object} • encoding {String | Null} 默认 = 'utf8' • mode {Number} 默认 = 438 (aka 0666 in Octal) • flag {String} 默认 = 'w' • callback {Function} 第 13 章 文件系统 | 141 异步写文件,如果文件已经存在则替换。 data 可以是缓存或者字符串。 如果参数 data 是 buffer,会忽略参数 encoding 。默认值是 'utf8' 。 列如: fs.writeFile('message.txt', 'Hello Node', function (err) { if (err) throw err; console.log('It\'s saved!'); }); fs.writeFileSync(filename, data[, options])fs.writeFileSync(filename, data[, options]) fs.writeFile 的同步版本. 返回 undefined 。 fs.appendFile(filename, data[, options], callback)fs.appendFile(filename, data[, options], callback) • filename {String} • data {String | Buffer} • options {Object} • encoding {String | Null} 默认 = 'utf8' • mode {Number} 默认 = 438 (aka 0666 in Octal) • flag {String} 默认 = 'a' • callback {Function} 异步的给文件添加数据,如果文件不存在,就创建一个。 data 可以是缓存或者字符串。 例如: fs.appendFile('message.txt', 'data to append', function (err) { if (err) throw err; console.log('The "data to append" was appended to file!'); }); fs.appendFileSync(filename, data[, options])fs.appendFileSync(filename, data[, options]) fs.appendFile 的同步版本. 返回 undefined 。 第 13 章 文件系统 | 142 fs.watchFile(filename[, options], listener)fs.watchFile(filename[, options], listener) 稳定性: 2 - 不稳定。 尽可能的用 fs.watch 来替换。 监视 filename 文件的变化。每当文件被访问的时候都会调用 listener 。 第二个参数可选。如果有,它必须包含两个 boolean 参数( persistent 和 interval )的对象。 persistent 指 定文件被监视时进程是否继续运行。 interval 指定了查询文件的间隔,以毫秒为单位。缺省值为{ persistent: tr ue, interval: 5007 }。 listener 有两个参数,第一个为文件现在的状态,第二个为文件的前一个状态: fs.watchFile('message.text', function (curr, prev) { console.log('the current mtime is: ' + curr.mtime); console.log('the previous mtime was: ' + prev.mtime); }); listener中的文件状态对象类型为 fs.Stat。 如果想修改文件时被通知,而不是访问的时候就通知,可以比较 curr.mtime 和 prev.mtime 。 fs.unwatchFile(filename[, listener])fs.unwatchFile(filename[, listener]) 稳定性: 2 - 不稳定. 尽可能的用 fs.watch 来替换。 停止监视 filename 文件的变化。如果指定了 listener ,那只会移除这个 listener 。否则,移除所有的 listene r,并会停止监视 filename 。 调用 fs.unwatchFile() 停止监视一个没被监视的文件,不会触发错误,而会发生一个no-op。 fs.watch(filename[, options][, listener])fs.watch(filename[, options][, listener]) 稳定性: 2 - 不稳定. 观察 filename 指定的文件或文件夹的改变。返回对象是 fs.FSWatcher (页 0)。 第二个参数可选。如果有,它必须是包含两个 boolean 参数( persistent 和 recursive )的对象。 persisten t 指定文件被监视时进程是否继续运行。 recursive 表明是监视所有的子文件夹还是当前文件夹,这个参数只有 监视对象是文件夹时才有效,而且仅在支持的系统里有效(参见下面注意事项)。 第 13 章 文件系统 | 143 默认值 { persistent: true, recursive: false } . 回调函数有2个参数 (event, filename) 。 event 是 rename 或 change 。 filename 是触发事件的文件名。 注意事项注意事项 fs.watch API 不是 100% 的跨平台兼容,可能在某些情况下不可用。 recursive 参数仅在 OS X 上可用。仅 FSEvents 支持这个类型文件的监视,所以未来也不太可能有新的平台 加入。 可用性可用性 这些特性依赖于底层系统提供文件系统变动的通知。 • Linux 系统,使用 inotify . • BSD 系统,使用 kqueue . • OS X,文件使用 kqueue ,文件夹使用 FSEvents . • SunOS 系统(包括 Solaris 和 SmartOS),使用 event ports . • Windows 系统, 依赖与 ReadDirectoryChangesW . 如果底层系统函数不可用,那么 fs.watch 就无法工作。例如,监视网络文件系统(NFS, SMB, 等)经常不能 用。你仍然可以用 fs.watchFile 查询,但是会比较慢,且不可靠。 文件名参数文件名参数 回调函数中提供文件名参数,不是每个平台都能用(Linux 和 Windows 就不行)。即使在可用的平台,也不能 保证都能提供。所以不要假设回调函数中 filename 参数有效,要在代码里添加一些为空的逻辑判断。 fs.watch('somedir', function (event, filename) { console.log('event is: ' + event); if (filename) { console.log('filename provided: ' + filename); } else { console.log('filename not provided'); } }); fs.exists(path, callback)fs.exists(path, callback) 判断文件是否存在,回调函数参数是 bool 值。例如: 第 13 章 文件系统 | 144 fs.exists('/etc/passwd', function (exists) { util.debug(exists ? "it's there" : "no passwd!"); }); fs.exists() 是老版本的函数,因此在代码里不要用。 另外,打开文件前判断是否存在有漏洞,在 fs.exists() 和 fs.open() 调用中间,另外一个进程有可能已经移除 了文件。最好用 fs.open() 来打开文件,根据回调函数来判断是否有错误。 fs.exists() 未来会被移除。 fs.existsSync(path)fs.existsSync(path) fs.exists() 的同步版本. 如果文件存在返回 true , 否则返回 false 。 fs.existsSync() 未来会被移除。 fs.access(path[, mode], callback)fs.access(path[, mode], callback) 测试由参数 path 指向的文件的用户权限。可选参数 mode 为整数,它表示需要检查的权限。下面列出了所有 值。 mode 可以是单个值,或者可以通过或运算,掩码运算实现多个权限检查。 • fs.F_OK - 文件是对于进程可见,可以用来检查文件是否存在。参数 mode 的默认值。 • fs.R_OK - 文件对于进程是否可读。 • fs.W_OK - 文件对于进程是否可写。 • fs.X_OK - 文件对于进程是否可执行。(Windows系统不可用,执行效果等同 fs.F_OK ) 第三个参数是回调函数。如果检查失败,回调函数的参数就是响应的错误。下面的例子检查文件 /etc/passwd 是 否能被当前的进程读写。 fs.access('/etc/passwd', fs.R_OK | fs.W_OK, function(err) { util.debug(err ? 'no access!' : 'can read/write'); }); fs.accessSync(path[, mode])fs.accessSync(path[, mode]) fs.access 的同步版本. 如果发生错误抛出异常,否则不做任何事情。 第 13 章 文件系统 | 145 类: fs.Stats类: fs.Stats fs.stat() , fs.lstat() 和 fs.fstat() 以及同步版本的返回对象。 • stats.isFile() • stats.isDirectory() • stats.isBlockDevice() • stats.isCharacterDevice() • stats.isSymbolicLink() (only valid with fs.lstat() ) • stats.isFIFO() • stats.isSocket() 对普通文件使用 util.inspect(stats) ,返回的字符串和下面类似: { dev: 2114, ino: 48064969, mode: 33188, nlink: 1, uid: 85, gid: 100, rdev: 0, size: 527, blksize: 4096, blocks: 8, atime: Mon, 10 Oct 2011 23:24:11 GMT, mtime: Mon, 10 Oct 2011 23:24:11 GMT, ctime: Mon, 10 Oct 2011 23:24:11 GMT, birthtime: Mon, 10 Oct 2011 23:24:11 GMT } atime , mtime , birthtime , 和 ctime 都是 Date (https://developer.mozilla.org/en/JavaScript/Referenc e/Global_Objects/Date) 的实例,需要使用合适的方法来比较这些值。通常使用 getTime() (https://develope r.mozilla.org/en/JavaScript/Reference/Global_Objects/Date/getTime) 来获取时间戳(毫秒,从 1 Januar y 1970 00:00:00 UTC 开始算),这个整数基本能满足任何比较条件。也有一些其他方法来显示额外信息。更多 参见MDN JavaScript Reference (https://developer.mozilla.org/en/JavaScript/Reference/Global_Objec ts/Date) 第 13 章 文件系统 | 146 Stat Time ValuesStat Time Values 状态对象(stat object)有以下语义: • atime 访问时间 - 文件的最后访问时间. mknod(2) , utimes(2) , 和 read(2) 等系统调用可以改变. • mtime 修改时间 - 文件的最后修改时间. mknod(2) , utimes(2) , 和 write(2) 等系统调用可以改变. • ctime 改变时间 - 文件状态(inode)的最后修改时间. chmod(2) , chown(2) , link(2) , mknod(2) , rena me(2) , unlink(2) , utimes(2) , read(2) , 和 write(2) 等系统调用可以改变. • birthtime "Birth Time" - 文件创建时间,文件创建时生成。 在一些不提供文件 birthtime 的文件系统中, 这 个字段会使用 ctime 或 1970-01-01T00:00Z (ie, unix epoch timestamp 0)来填充。 在 Darwin 和其他 FreeBSD 系统变体中, 也将 atime 显式地设置成比它现在的 birthtime 更早的一个时间值,这个过程使用了 utimes(2) 系统调用。 在 Node v0.12 版本之前, Windows 系统里 ctime 有 birthtime 值. 注意在v.0.12版本中, ctime 不再是"creatio n time", 而且在Unix系统中,他一直都不是。 fs.createReadStream(path[, options])fs.createReadStream(path[, options]) 返回可读流对象 (见 Readable Stream )。 options 默认值如下: { flags: 'r', encoding: null, fd: null, mode: 0666, autoClose: true } 参数 options 提供 start 和 end 位置来读取文件的特定范围内容,而不是整个文件。 start 和 end 都在文 件范围里,并从 0 开始, encoding 是 'utf8' , 'ascii' , 或 'base64' 。 如果给了 fd 值, ReadStream 将会忽略 path 参数,而使用文件描述,这样不会触发任何 open 事件。 如果 autoClose 为 false,即使发生错误文件也不会关闭,需要你来负责关闭,避免文件描述符泄露。如果 au toClose 是 true(默认值),遇到 error 或 end ,文件描述符将会自动关闭。 例如,从100个字节的文件里,读取最少10个字节: 第 13 章 文件系统 | 147 fs.createReadStream('sample.txt', {start: 90, end: 99}); Class: fs.ReadStreamClass: fs.ReadStream ReadStream 是 Readable Stream (页 0)。 Event: 'open'Event: 'open' • fd {Integer} ReadStream 所使用的文件描述符。 当创建文件的ReadStream时触发。 fs.createWriteStream(path[, options])fs.createWriteStream(path[, options]) 返回一个新的写对象 (参见 Writable Stream )。 options 是一个对象,默认值: { flags: 'w', encoding: null, fd: null, mode: 0666 } options 也可以包含一个 start 选项,在指定文件中写入数据开始位置。 修改而不替换文件需要 flags 的模式指 定为 r+ 而不是默值的 w. 和之前的 ReadStream 类似,如果 fd 不为空, WriteStream 将会忽略 path 参数,转而使用文件描述,这 样不会触发任何 open 事件。 类: fs.WriteStream类: fs.WriteStream WriteStream 是 Writable Stream (页 0)。 Event: 'open'Event: 'open' • fd {Integer} WriteStream 所用的文件描述符 打开 WriteStream file 时触发。 第 13 章 文件系统 | 148 file.bytesWrittenfile.bytesWritten 目前写入的字节数,不含等待写入的数据。 Class: fs.FSWatcherClass: fs.FSWatcher fs.watch() 返回的对象就是这个类. watcher.close()watcher.close() 停止观察 fs.FSWatcher 对象中的更改。 Event: 'change'Event: 'change' • event {String} fs 改变的类型 • filename {String} 改变的文件名 (if relevant/available) 当监听的文件或文件夹改变的时候触发,参见fs.watch (页 0)。 Event: 'error'Event: 'error' • error {Error object} 错误发生时触发。 第 13 章 文件系统 | 149 1414 全局对象全局对象 这些对象在所有模块里都可用。有些对象不是在全局作用域而是在模块作用域里,这些情况下面文档都会标注出 来。 globalglobal • {Object} 全局命名空间对象。 浏览器里,全局作用域就是顶级域。如果在全局域内定义变量 var something 将会是全局变量。 Node 里不 同,顶级域并不是全局域;在模块里定义变量 var something 只是模块内可用。 processprocess • {Object} 进程对象。 参见 process object (process.html#process_process) 章节. consoleconsole • {Object} 用来打印 stdout 和 stderr. 参见console (console.html) 章节. Class: BufferClass: Buffer • {Function} 用来处理二进制数据。 参见buffer 章节 (buffer.html) 。 require()require() • {Function} 引入模块。 参见Modules (modules.html#modules_modules) 章节. require 实际上并非全局的,而是各个 本地模块有效。 第 14 章 全局对象 | 151 require.resolve()require.resolve() 使用内部 require() 机制来查找 module 位置,但是不加载模块,只是返回解析过的文件名。 require.cacherequire.cache • {Object} 引入模块时会缓存到这个对象。通过删除该对象键值,下次调用 require 将会重载该模块。 require.extensionsrequire.extensions 稳定性: 0 - 抛弃 • {Object} 指导 require 如何处理特定的文件扩展名。 将 .sjs 文件当 .js 文件处理: require.extensions['.sjs'] = require.extensions['.js']; 抛弃抛弃 以前这个列表用来加载按需编译的非 JavaScript 模块到 node。实际上,有更好的办法来解决这个问 题,比如通过其他 node 程序来加载模块,或者提前编译成 JavaScript。 由于模块系统已经锁定,该功能可能永远不会去掉。改动它可能会产生 bug,所以最好不要动它。 __filename__filename • {String} 被执行的代码的文件名是相对路径。对于主程序来说,这和命令行里未必用同一个文件名。模块里的值是模块文 件的路径。 列如,运行 /Users/mjr 里的 node example.js : console.log(__filename); // /Users/mjr/example.js __filename 不是全局的,而是模块本地的。 第 14 章 全局对象 | 152 __dirname__dirname • {String} 执行的 script 代码所在的文件夹的名字。 列如,运行 /Users/mjr 里的 node example.js : console.log(__dirname); // /Users/mjr __dirname 不是全局的,而是模块本地的。 modulemodule • {Object} 当前模块的引用。通过 require() , module.exports 定义了哪个模块输出可用。 module 不是全局的,而是模块本地的。 更多信息参见module system documentation (modules.html) 。 exportsexports module.exports 的引用。何时用 exports 和 module.exports 可参加module system documentation (m odules.html) 。 module 不是全局的,而是模块本地的。 更多信息参见 module system documentation (modules.html) 。 更多信息参见module 章节 (modules.html) 。 setTimeout(cb, ms)setTimeout(cb, ms) 最少 ms 毫秒后调回调函数。实际的延迟依赖于外部因素,比如操作系统的粒度和负载。 timeout 值有效范围 1-2,147,483,647。如果超过范围,将会变为 1 毫秒。通常,定时器不应该超过 24.8 天。 第 14 章 全局对象 | 153 返回一个代表定时器的句柄值。 clearTimeout(t)clearTimeout(t) 停止一个之前通过 setTimeout() 创建的定时器。不会再被执行回调。 setInterval(cb, ms)setInterval(cb, ms) 每隔 ms 毫秒调用回调函数 cb 。实际的间隔依赖于外部因素,比如操作系统的粒度和系统负载。通常会大于 ms 。 间隔值有效范围 1-2,147,483,647。如果超过范围,将会变为 1 毫秒。通常,定时器不应该超过 24.8 天。 返回一个代表该定时器的句柄值。 clearInterval(t)clearInterval(t) 停止一个之前通过 setInterval() 创建的定时器。不会再被执行回调。 timer 函数是全局变量。 参见timers (timers.html) 章节。 第 14 章 全局对象 | 154 1515 HTTPHTTP 稳定性: 3 - 稳定 使用 HTTP 服务器或客户端功能必须调用 require('http') 。 Node 里的 HTTP 接口支持协议里原本比较难用的特性。特别是很大的或块编码的消息。这些接口不会完全缓存 整个请求或响应,这样用户可以在请求或响应中使用数据流。 HTTP消息头对象和下面的例子类似: { 'content-length': '123', 'content-type': 'text/plain', 'connection': 'keep-alive', 'host': 'mysite.com', 'accept': '*/*' } Keys 都是小写,值不能修改。 为了能支持尽可能多的 HTTP 应用程序,Node 提供的 HTTP API 接口都是底层的。仅能处理流和消息。它把 消息解析成报文头和报文体,但是不解析实际的报文头和报文体内容。 定义报文头的时候,多个值间可用 , 分隔。除了 set-cookie 和 cookie 头,因为它们表示值的数组。诸如 c ontent-length 的只有一个值的报文头,直接解析,并且只有单值可以表示成已经解析好的对象。 接收到的原始头信息以数组( [key, value, key2, value2, ...] )的形式保存在 rawHeaders 里。例如,之前提 到的消息对象会有如下的 rawHeaders : [ 'ConTent-Length', '123456', 'content-LENGTH', '123', 'content-type', 'text/plain', 'CONNECTION', 'keep-alive', 'Host', 'mysite.com', 'accepT', '*/*' ] http.METHODShttp.METHODS • {Array} 解析器支持的 HTTP 方法列表。 http.STATUS_CODEShttp.STATUS_CODES • {Object} 第 15 章 HTTP | 156 全部标准 HTTP 响应状态码的和描述的集合。例如, http.STATUS_CODES[404] === 'Not Found' 。 http.createServer([requestListener])http.createServer([requestListener]) 返回 http.Server (页 0) 的新实例。 requestListener 函数自动加到 'request' 事件里。 http.createClient([port][, host])http.createClient([port][, host]) 这个函数已经被抛弃,用 http.request() (页 164) 替换。创建一个新的 HTTP 客户端,连接到服务器的 port 和 host 。 类: http.Server类: http.Server 这是事件分发器 EventEmitter (events.html#events_class_events_eventemitter) ,有以下事件: 事件: 'request'事件: 'request' function (request, response) { } 每当有请求的时候触发。注意:每个连接可以有多个请求(在 keep-alive 连接中)。 request 是 http.Incomi ngMessage (页 0) 实例, response 是 http.ServerResponse (页 0) 的实例。 事件: 'connection'事件: 'connection' function (socket) { } 当建立新的 TCP 流的时候。 socket 是一个 net.Socket 对象。通常用户不会访问这个事件。协议解析器绑定 套接字时采用的方式使套接字不会出发 readable 事件。也能通过 request.connection 访问 socket 。 事件: 'close'事件: 'close' function () { } 服务器关闭的时候触发。 第 15 章 HTTP | 157 事件: 'checkContinue'事件: 'checkContinue' function (request, response) { } 当 http 收到 100-continue 的 http 请求时会触发。如果没有监听这个事件,服务器将会自动发送 100 Continu e 的响应。 如果客户端需要继续发送请求主题,或者生成合适的 HTTP 响应(如,400 请求无效),可以通过调用 respon se.writeContinue() (页 0) 来处理。 注意:触发并处理这个事件的时候,不会再触发 request 事件。 事件: 'connect'事件: 'connect' function (request, socket, head) { } 当客户端请求 http 连接时触发。如果没有监听这个事件,客户端请求连接的时候会被关闭。 • request 是 http 请求的参数,与 request 事件参数相同。 • socket 是服务器和客户端间的 socket。 • head 是 buffer 的实例。网络隧道的第一个包,可能为空。 这个事件触发后,请求的 socket 不会有 data 事件监听器,也就是说你需要绑定一个监听器到 data 上,来处 理在发送到服务器上的 socket 数据。 事件: 'upgrade'事件: 'upgrade' function (request, socket, head) { } 当客户端请求 http upgrage 时候会触发。如果没有监听这个事件,客户端请求一个连接的时候会被关闭。 • request 是 http 请求的参数,与 request 事件参数相同。 • socket 是服务器和客户端间的 socket。 • head 是 buffer 的实例。网络隧道的第一个包,可能为空。 这个事件触发后,请求的 socket 不会有 data 事件监听器,也就是说你需要绑定一个监听器到 data 上,来处 理在发送到服务器上的 socket 数据。 第 15 章 HTTP | 158 事件: 'clientError'事件: 'clientError' function (exception, socket) { } 如果一个客户端连接触发了一个 'error' 事件, 它就会转发到这里. socket 是导致错误的 net.Socket 对象。 () server.listen(port[, hostname][, backlog][, callback])server.listen(port[, hostname][, backlog][, callback]) 在指定的的端口和主机名上开始接收连接。 如果忽略主机名,服务器将会接收指向任意 IPv4 的地址( INADD R_ANY )。 监听一个 unix socket,需要提供一个文件名而不是主机名和端口。 积压量 backlog 为等待连接队列的最大长度。实际的长度由你的操作系统的 sysctl 设置决定(比如 linux 上的 t cp_max_syn_backlog and somaxconn )。默认参数值为 511 (不是 512) 这是异步函数。最后一个参数 callback 会作为事件监听器添加到 listening 事件。参见net.Server.listen(por t) (net.html#net_server_listen_port_host_backlog_callback) 。 server.listen(path[, callback])server.listen(path[, callback]) 启动一个 UNIX socket 服务器所给路径 path 这是异步函数。最后一个参数 callback 会作为事件监听器添加到 listening 事件。参见net.Server.listen(port) (net.html#net_server_listen_port_host_backlog_callback) 。 server.listen(handle[, callback])server.listen(handle[, callback]) • handle {Object} • callback {Function} handle 对象可以是 server 或 socket(任意以下划线 _handle 开头的成员),或者 {fd: } 对象。 这会导致 server 用参数 handle 接收连接,前提条件是文件描述符或句柄已经连接到端口或域 socket。 第 15 章 HTTP | 159 Windows 不能监听文件句柄。 这是异步函数。最后一个参数 callback 会作为事件监听器添加到 listening 事件。参见net.Server.listen(por t) (net.html#net_server_listen_port_host_backlog_callback) 。 () server.close([callback])server.close([callback]) 禁止 server 接收连接。参见 net.Server.close() (net.html#net_server_close_callback) . server.maxHeadersCountserver.maxHeadersCount 最大请求头的数量限制,默认 1000。如果设置为 0,则不做任何限制。 () server.setTimeout(msecs, callback)server.setTimeout(msecs, callback) • msecs {Number} • callback {Function} 为 socket 设置超时时间,单位为毫秒,如果发生超时,在 server 对象上触发 'timeout' 事件,参数为 socket 。 如果在 Server 对象上有一个 'timeout' 事件监听器,超时的时候,将会调用它,参数为 socket 。 默认情况下,Server 的超时为 2 分钟,如果超时将会销毁 socket。如果你给 Server 的超时事件设置了回调函 数,那你就得负责处理 socket 超时。 () server.timeoutserver.timeout • {Number} Default = 120000 (2 minutes) 超时的时长,单位为毫秒。 注意,socket 的超时逻辑在连接时设定,所以有新的连接时才能改变这个值。 第 15 章 HTTP | 160 设为 0 时,建立连接的自动超时将失效。 类: http.ServerResponse类: http.ServerResponse 这是一个由 HTTP 服务器(而不是用户)内部创建的对象。作为第二个参数传递给 'request' 事件。 该响应实现了 Writable Stream (stream.html#stream_class_stream_writable) 接口。这是一个包含下列事 件的 EventEmitter (events.html#events_class_events_eventemitter) : 事件: 'close'事件: 'close' function () { } 在调用 response.end() (页 0),或准备 flush 前,底层连接结束。 事件: 'finish'事件: 'finish' function () { } 发送完响应触发。响应头和响应体最后一段数据被剥离给操作系统后,通过网络来传输时被触发。这并不代表客 户端已经收到数据。 这个事件之后,响应对象不会再触发任何事件。 response.writeContinue()response.writeContinue() 发送 HTTP/1.1 100 Continue 消息给客户端,表示请求体可以发送。可以在服务器上查看'checkContinue' (页 0) 事件。 response.writeHead(statusCode[, statusMessage][, headers])response.writeHead(statusCode[, statusMessage][, headers]) 发送一个响应头给请求。状态码是 3 位数字,如 404 。最后一个参数 headers 是响应头。建议第二个参数设 置为可以看的懂的消息。 例如: var body = 'hello world'; response.writeHead(200, { 第 15 章 HTTP | 161 'Content-Length': body.length, 'Content-Type': 'text/plain' }); 这个方法仅能在消息中调用一次,而且必须在 response.end() (页 0) 前调用。 如果你在这之前调用 response.write() (页 0) 或 response.end() (页 0),将会计算出不稳定的头。 Content-Length 是字节数,而不是字符数。上面的例子 'hello world' 仅包含一个字节字符。如果 body 包含 高级编码的字符, Buffer.byteLength() 就必须确定指定编码的字符数。Node 不会检查Content-Length 和 b ody 的长度是否相同。 response.setTimeout(msecs, callback)response.setTimeout(msecs, callback) • msecs {Number} • callback {Function} 设置 socket 超时时间,单位为毫秒。如果提供了回调函数,将会在 response 对象的 'timeout' 事件上添加监 听器。 如果没有给请求、响应、服务器添加 'timeout' 监视器,超时的时候将会销毁 socket。如果你给请求、响应、服 务器加了处理函数,就需要负责处理 socket 超时。 response.statusCoderesponse.statusCode 使用默认的 headers 时(没有显式的调用 response.writeHead() (页 0) ),这个属性表示将要发送给客户端 状态码。 例如: response.statusCode = 404; 响应头发送给客户端的后,这个属性表示状态码已经发送。 response.statusMessageresponse.statusMessage 使用默认 headers 时(没有显式的调用 response.writeHead() (页 0) ), 这个属性表示将要发送给客户端状态 信息。 如果这个没有定义,将会使用状态码的标准消息。 例如: response.statusMessage = 'Not found'; 第 15 章 HTTP | 162 当响应头发送给客户端的时候,这个属性表示状态消息已经发送。 response.setHeader(name, value)response.setHeader(name, value) 设置默认头某个字段内容。如果这个头即将被发送,内容会被替换。如果你想设置更多的头, 就使用一个相同名 字的字符串数组。 例如: response.setHeader("Content-Type", "text/html"); 或 response.setHeader("Set-Cookie", ["type=ninja", "language=javascript"]); response.headersSentresponse.headersSent Boolean (只读)。如果headers发送完毕,则为 true,反之为 false。 response.sendDateresponse.sendDate 默认值为 true。若为 true,当 headers 里没有 Date 值时,自动生成 Date 并发送。 只有在测试环境才能禁用; 因为 HTTP 要求响应包含 Date 头. response.getHeader(name)response.getHeader(name) 读取一个在队列中但是还没有被发送至客户端的header。名字是大小写敏感。仅能再头被flushed前调用。 例如: var contentType = response.getHeader('content-type'); response.removeHeader(name)response.removeHeader(name) 从即将发送的队列里移除头。 例如: response.removeHeader("Content-Encoding"); 第 15 章 HTTP | 163 response.write(chunk[, encoding][, callback])response.write(chunk[, encoding][, callback]) 如果调用了这个方法,且还没有调用 response.writeHead() (页 0),将会切换到默认的 header,并更新这个h eader。 这个方法将发送响应体数据块。可能会多次调用这个方法,以提供 body 成功的部分内容。 chunk 可以是字符串或 buffer。如果 chunk 是字符串,第二个参数表明如何将它编码成字节 流。 encoding 的默认值是 'utf8' 。最后一个参数在刷新这个数据块时调用。 注意:这个是原始的 HTTP body,和高级的multi-part body 编码无关。 第一次调用 response.write() 的时候,将会发送缓存的头信息和第一个 body 给客户端。第二次,将会调用 re sponse.write() 。Node 认为你将会独立发送流数据。这意味着,响应缓存在第一个数据块中。 如果成功的刷新全部数据到内核缓冲区,返回 true 。如果部分或全部数据在用户内存中还处于排队状况,返回 false 。当缓存再次释放的时候,将会触发 'drain' 。 response.addTrailers(headers)response.addTrailers(headers) 这个方法给响应添加 HTTP 的尾部 header(消息末尾的 header)。 只有只有数据块编码用于响应体时,才会触发 Trailers;如果不是(例如,请求是HTTP/1.0),它们将会被自动丢 弃。 如果你想触发 trailers, HTTP 会要求发送 Trailer 头,它包含一些信息,比如: response.writeHead(200, { 'Content-Type': 'text/plain', 'Trailer': 'Content-MD5' }); response.write(fileData); response.addTrailers({'Content-MD5': "7895bf4b8828b55ceaf47747b4bca667"}); response.end(); response.end([data][, encoding][, callback])response.end([data][, encoding][, callback]) 这个方法告诉服务器,所有的响应头和响应体已经发送;服务器可以认为消息结束。 response.end() 方法必须 在每个响应中调用。 如果指定了参数 data ,将会在响应流结束的时候调用。 () 第 15 章 HTTP | 164 http.request(options[, callback])http.request(options[, callback]) Node 维护每个服务器的连接来生成 HTTP 请求。这个函数让你可以发布请求。 参数 options 是对象或字符串。如果 options 是字符串,会通过 url.parse() (url.html#url_url_parse_urlst r_parsequerystring_slashesdenotehost) 自动解析。 options 值: • host : 请求的服务器域名或 IP 地址,默认: 'localhost' • hostname : 用于支持 url.parse() 。 hostname 优于 host • port : 远程服务器端口。 默认: 80. • localAddress : 用于绑定网络连接的本地接口 • socketPath : Unix域 socket(使用host:port或socketPath • method : 指定 HTTP 请求方法。 默认: 'GET' . • path : 请求路径。 默认: '/' 。如果有查询字符串,则需要包含。例如'/index.html?page=12'。请求路径 包含非法字符时抛出异常。目前,只有空格不行,不过在未来可能改变。 • headers : 包含请求头的对象 • auth : 用于计算认证头的基本认证,即 user:password • agent : 控制Agent的行为。当使用了一个Agent的时候,请求将默认为 Connection: keep-alive 。可能的 值为: • undefined (default): 在这个主机和端口上使用 global Agent (页 0) • Agent object: 在 Agent 中显式使用 passed . • false : 选择性停用连接池,默认请求为: Connection: close . • keepAlive : {Boolean} 持资源池周围的socket,用于未来其它请求。默认值为 false 。 • keepAliveMsecs : {Integer} 使用HTTP KeepAlive 的时候,通过正在保持活动的sockets发送TCP KeepAlive包的频繁程度。默认值为1000。仅当keepAlive为true时才相关。 可选参数 callback 将会作为一次性的监视器,添加给 'response' (页 0) 事件。 http.request() 返回一个 http.ClientRequest类的实例。ClientRequest实例是一个可写流对象。如果需要用 P OST 请求上传一个文件的话,就将其写入到ClientRequest对象。 第 15 章 HTTP | 165 例如: var postData = querystring.stringify({ 'msg' : 'Hello World!' }); var options = { hostname: 'www.google.com', port: 80, path: '/upload', method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Content-Length': postData.length } }; var req = http.request(options, function(res) { console.log('STATUS: ' + res.statusCode); console.log('HEADERS: ' + JSON.stringify(res.headers)); res.setEncoding('utf8'); res.on('data', function (chunk) { console.log('BODY: ' + chunk); }); }); req.on('error', function(e) { console.log('problem with request: ' + e.message); }); // write data to request body req.write(postData); req.end(); 注意,例子里调用了 req.end() 。 http.request() 必须调用 req.end() 来表明请求已经完成,即使没有数据写 入到请求 body 里。 如果在请求的时候遇到错误(DNS 解析、TCP 级别的错误或实际 HTTP 解析错误),在返回的请求对象时会触 发一个 'error' 事件。 有一些特殊的头需要注意: • 发送 Connection: keep-alive 告诉服务器保持连接,直到下一个请求到来。 • 发送 Content-length 头将会禁用chunked编码。 第 15 章 HTTP | 166 • 发送一个 Expect 头,会立即发送请求头,一般来说,发送 Expect: 100-continue ,你必须设置超时,并 监听 continue 事件。更多细节参见 RFC2616 Section 8.2.3 。 • 发送一个授权头,将会使用 auth 参数重写,来计算基本的授权。 http.get(options[, callback])http.get(options[, callback]) 因为多数请求是没有报文体的 GET 请求, Node 提供了这个简便的方法。和 http.request() 唯一不同点在 于,这个方法自动设置 GET,并自动调用 req.end() 。 例如: http.get("http://www.google.com/index.html", function(res) { console.log("Got response: " + res.statusCode); }).on('error', function(e) { console.log("Got error: " + e.message); }); () 类: http.Agent类: http.Agent HTTP Agent 用于 socket 池,用于 HTTP 客户端请求。 HTTP Agent 也把客户端请求默认为使用 Connection:keep-alive 。如果没有 HTTP 请求正在等着成为空闲 s ocket 的话,那么 socket 将关闭。这意味着,Node 的资源池在负载的情况下对 keep-alive 有利,但是仍然不 需要开发人员使用 KeepAlive 来手动关闭 HTTP 客户端。 如果你选择使用 HTTP KeepAlive, 可以创建一个 Agent 对象,将 flag 设置为 true . (参见下面的 construct or options (页 0) ) ,这样 Agent 会把没用到的 socket 放到池里,以便将来使用。他们会被显式的标志,让 No de 不运行。但是,当不再使用它的时候,需要显式的调用 destroy() (页 0),这样 socket 将会被关闭。 当 socket 事件触发 close 事件或特殊的 agentRemove 事件时,socket 将会从 agent 池里移除。如果你要 保持 HTTP 请求保持长时间打开,并且不希望他们在池里,可以参考以下代码: http.get(options, function(res) { // Do stuff }).on("socket", function (socket) { socket.emit("agentRemove"); }); 第 15 章 HTTP | 167 另外,你可以使用 agent:false 让资源池停用: http.get({ hostname: 'localhost', port: 80, path: '/', agent: false // create a new agent just for this one request }, function (res) { // Do stuff with response }) new Agent([options])new Agent([options]) • options {Object} agent 上的设置选项集合,有以下字段内容: • keepAlive {Boolean} 持资源池周围的 socket,用于未来其它请求。默认值为 false 。 • keepAliveMsecs {Integer} 使用 HTTP KeepAlive 的时候,通过正在保持活动的 sockets 发送 TCP KeepAlive 包的频繁程度。默认值为 1000。仅当 keepAlive 为 true 时才相关。.】 • maxSockets {Number} 在空闲状态下,还依然开启的 socket 的最大值。仅当 keepAlive 设置为 tru e 的时候有效。默认值为 256。 被 http.request 使用的默认的 http.globalAgent ,会设置全部的值为默认。 必须在创建你自己的 Agent 对象后,才能配置这些值。 var http = require('http'); var keepAliveAgent = new http.Agent({ keepAlive: true }); options.agent = keepAliveAgent; http.request(options, onResponseCallback); agent.maxSocketsagent.maxSockets 默认值为 Infinity。决定了每台主机上的 agent 可以拥有的并发 socket 的打开数量,主机可以是 host:port 或 host:port:localAddress 。 agent.maxFreeSocketsagent.maxFreeSockets 默认值 256. 对于支持 HTTP KeepAlive 的 Agent 而言,这个方法设置了空闲状态下仍然打开的套接字数的最 大值。 第 15 章 HTTP | 168 agent.socketsagent.sockets 这个对象包含了当前 Agent 使用中的 socket 数组。不要修改它。 agent.freeSocketsagent.freeSockets 使用 HTTP KeepAlive 的时候,这个对象包含等待当前 Agent 使用的 socket 数组。不要修改它。 agent.requestsagent.requests 这个对象包含了还没分配给 socket 的请求数组。不要修改它。 agent.destroy()agent.destroy() 销毁任意一个被 agent 使用 的socket。 通常情况下不要这么做。如果你正在使用一个允许 KeepAlive 的 agent,当你知道不在使用它的时候,最好关闭 agent。否则,socket 会一直保存打开状态,直到服务器关闭。 agent.getName(options)agent.getName(options) 获取一组请求选项的唯一名,来确定某个连接是否可重用。在 http agent 里,它会返回 host:port:localAddres s 。在 http agent 里, name 包括 CA,cert, ciphers, 和其他 HTTPS/TLS 特殊选项来决定 socket 是否可以 重用。 http.globalAgenthttp.globalAgent Agent 的全局实例,是 http 客户端的默认请求。 类: http.ClientRequest类: http.ClientRequest 该对象在内部创建并从 http.request() 返回。他是 正在 处理的请求,其头部已经在队列中。使用 setHeader(n ame, value) , getHeader(name) , removeHeader(name) API 可以改变header。当关闭连接的时候,head er将会和第一个数据块一起发送。 第 15 章 HTTP | 169 为了获取响应,可以给请求对象的 'response' 添加监听器。当接收到响应头的时候将会从请求对象里触发 'resp onse' 。 'response' 事件执行时有一个参数,该参数为 http.IncomingMessage (页 0) 的实例。 在 'response' 事件期间,可以给响应对象添加监视器,监听 'data' 事件。 如果没有添加 'response' 处理函数,响应将被完全忽略。如果你添加了 'response' 事件处理函数,那你 必须必须 消费掉从响应对象获取的数据,可以在 'readable' 事件里调用 response.read() ,或者添加一个 'data' 处理 函数,或者调用 .resume() 方法。如果未读取数据,它将会消耗内存,最终产生 process out of memory 错 误。 Node 不会检查 Content-Length 和 body 的长度是否相同。 该请求实现了 Writable Stream (stream.html#stream_class_stream_writable) 接口。这是一个包含下列事 件的 EventEmitter (events.html#events_class_events_eventemitter) 。 事件: 'response'事件: 'response' function (response) { } 当接收到请求的时候会触发,仅会触发一次。 response 的参数是 http.IncomingMessage (页 0) 的实例。 Options: • host : 要请求的服务器域名或 IP 地址 • port : 远程服务器的端口 • socketPath : Unix 域 Socket (使用 host:port 或 socketPath 之一) 事件: 'socket'事件: 'socket' function (socket) { } Socket 附加到这个请求的时候触发。 事件: 'connect'事件: 'connect' function (response, socket, head) { } 每次服务器使用 CONNECT 方法响应一个请求时触发。如果这个这个事件未被监听,接收 CONNECT 方法的 客户端将关闭他们的连接。 第 15 章 HTTP | 170 下面的例子展示了一对匹配的客户端/服务器如何监听 connect 事件。 var http = require('http'); var net = re quire('net'); var url = require('url'); // Create an HTTP tunneling proxy var proxy = http.createServer(function (req, res) { res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('okay'); }); proxy.on('connect', function(req, cltSocket, head) { // connect to an origin server var srvUrl = url.parse('http://' + req.url); var srvSocket = net.connect(srvUrl.port, srvUrl.hostname, function() { cltSocket.write('HTTP/1.1 200 Connection Established\r\n' + 'Proxy-agent: Node-Proxy\r\n' + '\r\n'); srvSocket.write(head); srvSocket.pipe(cltSocket); cltSocket.pipe(srvSocket); }); }); // now that proxy is running proxy.listen(1337, '127.0.0.1', function() { // make a request to a tunneling proxy var options = { port: 1337, hostname: '127.0.0.1', method: 'CONNECT', path: 'www.google.com:80' }; var req = http.request(options); req.end(); req.on('connect', function(res, socket, head) { console.log('got connected!'); // make a request over an HTTP tunnel socket.write('GET / HTTP/1.1\r\n' + 'Host: www.google.com:80\r\n' + 'Connection: close\r\n' + '\r\n'); socket.on('data', function(chunk) { console.log(chunk.toString()); 第 15 章 HTTP | 171 }); socket.on('end', function() { proxy.close(); }); }); }); 事件: 'upgrade'事件: 'upgrade' function (response, socket, head) { } 每当服务器响应 upgrade 请求时触发。如果没有监听这个事件,客户端会收到 upgrade 头后关闭连接。 下面的例子展示了一对匹配的客户端/服务器如何监听 upgrade 事件。 var http = require('http'); // Create an HTTP server var srv = http.createServer(function (req, res) { res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('okay'); }); srv.on('upgrade', function(req, socket, head) { socket.write('HTTP/1.1 101 Web Socket Protocol Handshake\r\n' + 'Upgrade: WebSocket\r\n' + 'Connection: Upgrade\r\n' + '\r\n'); socket.pipe(socket); // echo back }); // now that server is running srv.listen(1337, '127.0.0.1', function() { // make a request var options = { port: 1337, hostname: '127.0.0.1', headers: { 'Connection': 'Upgrade', 'Upgrade': 'websocket' } }; var req = http.request(options); 第 15 章 HTTP | 172 req.end(); req.on('upgrade', function(res, socket, upgradeHead) { console.log('got upgraded!'); socket.end(); process.exit(0); }); }); 事件: 'continue'事件: 'continue' function () { } 当服务器发送 '100 Continue' HTTP 响应的时候触发,通常因为请求包含 'Expect: 100-continue'。该指令表 示客户端应发送请求体。 request.flushHeaders()request.flushHeaders() 刷新请求的头。 考虑效率因素,Node.js 通常会缓存请求的头直到你调用 request.end() ,或写入请求的第一个数据块。然 后,包装请求的头和数据到一个独立的 TCP 包里。 request.write(chunk[, encoding][, callback])request.write(chunk[, encoding][, callback]) 发送一个请求体的数据块。通过多次调用这个函数,用户能流式的发送请求给服务器,这种情况下,建议使用 ['T ransfer-Encoding', 'chunked'] 头。 chunk 参数必须是 Buffer (buffer.html#buffer_buffer) 或字符串。 回调参数可选,当这个数据块被刷新的时候会被调用。 request.end([data][, encoding][, callback])request.end([data][, encoding][, callback]) 发送请求完毕。如果 body 的数据没被发送,将会将他们刷新到流里。如果请求是分块的,该方法会发送终结符 0\r\n\r\n 。 如果指定了 data ,等同于先调用 request.write(data, encoding) ,再调用 request.end(callback) 。 如果有 callback ,将会在请求流结束的时候调用。 第 15 章 HTTP | 173 request.abort()request.abort() 终止一个请求. (v0.3.8 开始新加入)。 request.setTimeout(timeout[, callback])request.setTimeout(timeout[, callback]) 如果 socket 被分配给这个请求,并完成连接,将会调用 socket.setTimeout() (net.html#net_socket_settim eout_timeout_callback) 。 request.setNoDelay([noDelay])request.setNoDelay([noDelay]) 如果 socket 被分配给这个请求,并完成连接,将会调用 socket.setNoDelay() (net.html#net_socket_setno delay_nodelay) 。 request.setSocketKeepAlive([enable][, initialDelay])request.setSocketKeepAlive([enable][, initialDelay]) 如果 socket 被分配给这个请求,并完成连接,将会调用 socket.setKeepAlive() (net.html#net_socket_setk eepalive_enable_initialdelay) 。 http.IncomingMessagehttp.IncomingMessage http.Server (页 0) 或 http.ClientRequest (页 0) 创建了 IncomingMessage 对象,作为第一个参数传递给 'r esponse' 。它可以用来访问应答的状态,头文件和数据。 它实现了 Readable Stream (stream.html#stream_class_stream_readable) 接口,以及以下额外的事 件,方法和属性。 事件: 'close'事件: 'close' function () { } 表示底层连接已经关闭。 和 'end' 类似,这个事件每个应答只会发送一次。 第 15 章 HTTP | 174 message.httpVersionmessage.httpVersion 客户端向服务器发送请求时,客户端发送的 HTTP 版本;或者服务器想客户端返回应答时,服务器的 HTTP 版 本。通常是 '1.1' 或 '1.0' 。 另外, response.httpVersionMajor 是第一个整数, response.httpVersionMinor 是第二个整数。 message.headersmessage.headers 请求/响应头对象。 只读的头名称和值的映射。头的名字是小写,比如: // Prints something like: // // { 'user-agent': 'curl/7.22.0', // host: '127.0.0.1:8000', // accept: '*/*' } console.log(request.headers); message.rawHeadersmessage.rawHeaders 接收到的请求/响应头字段列表。 注意,键和值在同一个列表中。它并非一个元组列表。所以,偶数偏移量为键,奇数偏移量为对应的值。 头名字不是小写敏感,也没用合并重复的头。 // Prints something like: // // [ 'user-agent', // 'this is invalid be cause there can be only one', // 'User-Agent', // 'curl/7.22.0', // 'Host', // '127.0.0.1:8000', // 'ACCEPT', // '/' ] console.log(request.rawHeaders); message.trailersmessage.trailers 请求/响应 的尾部对象。只在 'end' 事件中存在。 message.rawTrailersmessage.rawTrailers 接收到的原始的请求/响应尾部键和值。仅在 'end' 事件中存在。 第 15 章 HTTP | 175 message.setTimeout(msecs, callback)message.setTimeout(msecs, callback) • msecs {Number} • callback {Function} 调用 message.connection.setTimeout(msecs, callback) . message.methodmessage.method 仅对从仅对从 http.Serverhttp.Server (页 0)(页 0) 获得的请求有效。获得的请求有效。 请求方法如果一个只读的字符串。 例如: 'GET' , 'DELETE' . message.urlmessage.url 仅对从仅对从 http.Serverhttp.Server (页 0)(页 0) 获得的请求有效。获得的请求有效。 请求的 URL 字符串。它仅包含实际的 HTTP 请求中所提供的 URL,比如请求如下: GET /status?name=ryan HTTP/1.1\r\n Accept: text/plain\r\n \r\n request.url 就是: '/status?name=ryan' 如果你想将 URL 分解,可以用 require('url').parse(request.url) ,例如: node> require('url').parse('/status?name=ryan') { href: '/status?name=ryan', search: '?name=ryan', query: 'name=ryan', pathname: '/status' } 如果想从查询字符串中解析出参数,可以用 require('querystring').parse 函数,或者将 true 作为第二个参数传 递给 require('url').parse 。 例如: node> require('url').parse('/status?name=ryan', true) { href: '/status?name=ryan', search: '?name=ryan', 第 15 章 HTTP | 176 query: { name: 'ryan' }, pathname: '/status' } message.statusCodemessage.statusCode 仅对从仅对从 http.ClientRequesthttp.ClientRequest 获取的响应有效。获取的响应有效。 3位数的 HTTP 响应状态码 404 。 message.statusMessagemessage.statusMessage 仅对从仅对从 http.ClientRequesthttp.ClientRequest 获取的响应有效。获取的响应有效。 HTTP 的响应消息。比如, OK 或 Internal Server Error . message.socketmessage.socket 和连接相关联的 net.Socket 对象。 通过 HTTPS 支持,使用 request.connection.verifyPeer() 和 request.connection.getPeerCertificate() 获取 客户端的身份信息。 第 15 章 HTTP | 177 1616 HTTPSHTTPS 稳定性: 3 - 稳定 HTTPS 是基于 TLS/SSL 的 HTTP 协议。在 Node 里作为单独的模块来实现。 类: https.Server类: https.Server 这是 tls.Server 的子类,并且和 http.Server 一样触发事件。更多信息参见 http.Server 。 server.setTimeout(msecs, callback)server.setTimeout(msecs, callback) 详情参见 http.Server#setTimeout() (http.md#http_server_settimeout_msecs_callback) . server.timeoutserver.timeout 详情参见 http.Server#timeout (http.md#http_server_timeout) . https.createServer(options[, requestListener])https.createServer(options[, requestListener]) 返回一个新的 HTTPS 服务器对象。其中 options 类似于 [tls.createServer()][tls.md#tls_tls_createserve r_options_secureconnectionlistener]。 requestListener 函数自动加到 'request' 事件里。 例如: // curl -k https://localhost:8000/ var https = require('https'); var fs = require('fs'); var options = { key: fs.readFileSync('test/fixtures/keys/agent2-key.pem'), cert: fs.readFileSync('test/fixtures/keys/agent2-cert.pem') }; https.createServer(options, function (req, res) { res.writeHead(200); res.end("hello world\n"); }).listen(8000); 或 第 16 章 HTTPS | 179 var https = require('https'); var fs = require('fs'); var options = { pfx: fs.readFileSync('server.pfx') }; https.createServer(options, function (req, res) { res.writeHead(200); res.end("hello world\n"); }).listen(8000); server.listen(port[, host][, backlog][, callback])server.listen(port[, host][, backlog][, callback]) server.listen(path[, callback])server.listen(path[, callback]) server.listen(handle[, callback])server.listen(handle[, callback]) 详情参见 http.listen() (http.md#http_server_listen_port_hostname_backlog_callback) 。 server.close([callback])server.close([callback]) 详情参见 http.close() (http.md#http_server_close_callback) 。 () https.request(options, callback)https.request(options, callback) 发送请求到安全 web 服务器。 options 可以是一个对象或字符串。如果 options 是字符串。会被 url.parse() (url.md#url.parse) 解析。 所有来自 http.request() (http.md#http_http_request_options_callback) 选项都是经过验证的。 例如: var https = require('https'); var options = { hostname: 'encrypted.google.com', port: 443, path: '/', 第 16 章 HTTPS | 180 method: 'GET' }; var req = https.request(options, function(res) { console.log("statusCode: ", res.statusCode); console.log("headers: ", res.headers); res.on('data', function(d) { process.stdout.write(d); }); }); req.end(); req.on('error', function(e) { console.error(e); }); option 参数有以下的值: • host : 请求的服务器域名或 IP 地址,默认: 'localhost' • hostname : 用于支持 url.parse() 。 hostname 优于 host • port : 远程服务器端口。 默认: 443. • method : 指定 HTTP 请求方法。 默认: 'GET' . • path : 请求路径。 默认: '/' 。如果有查询字符串,则需要包含。比如 '/index.html?page=12' • headers : 包含请求头的对象 • auth : 用于计算认证头的基本认证,即 user:password • agent : 控制Agent的行为。当使用了一个Agent的时候,请求将默认为 Connection: keep-alive 。可能的 值为: • undefined (default): 在这个主机和端口上使用 [global Agent][] • Agent object: 在 Agent 中显式使用 passed. • false : 选择性停用连接池,默认请求为: Connection: close tls.connect() (tls.md#tls_tls_connect_options_callback) 的参数也能指定。但是,globalAgent (https.m d#https_https_globalagent) 会忽略他们。 • pfx : SSL 使用的证书,私钥,和证书Certificate, 默认 null . • key : SSL 使用的私钥. 默认 null . • passphrase : 私钥或 pfx 的口令字符串. 默认 null . 第 16 章 HTTPS | 181 • cert : 所用公有 x509 证书. 默认 null . • ca : 用于检查远程主机的证书颁发机构或包含一系列证书颁发机构的数组。 • ciphers : 描述要使用或排除的密码的字符串,格式请参阅 http://www.openssl.org/docs/apps/ciphers.h tml#CIPHER_LIST_FORMAT • rejectUnauthorized : 如为 true 则服务器证书会使用所给 CA 列表验证。如果验证失败则会触发 error 事件。验证过程发生于连接层,在 HTTP 请求发送之前。缺省为 true . • secureProtocol : 所用的 SSL 方法,比如 TLSv1_method 强制使用 TLS version 1。可取值取决于您安 装的 OpenSSL, 和定义于 SSL_METHODS (http://www.openssl.org/docs/ssl/ssl.html#DEALIN G_WITH_PROTOCOL_METHODS) 的常量。 要指定这些选项,使用一个自定义 Agent . 例如: var options = { hostname: 'encrypted.google.com', port: 443, path: '/', method: 'GET', key: fs.readFileSync('test/fixtures/keys/agent2-key.pem'), cert: fs.readFileSync('test/fixtures/keys/agent2-cert.pem') }; options.agent = new https.Agent(options); var req = https.request(options, function(res) { ... } 或者不使用 Agent . 例如: var options = { hostname: 'encrypted.google.com', port: 443, path: '/', method: 'GET', key: fs.readFileSync('test/fixtures/keys/agent2-key.pem'), cert: fs.readFileSync('test/fixtures/keys/agent2-cert.pem'), agent: false }; 第 16 章 HTTPS | 182 var req = https.request(options, function(res) { ... } https.get(options, callback)https.get(options, callback) 和 http.get() 类似,不过是 HTTPS 版本的. options 可以是字符串对象. 如果 options 是字符串, 会自动使用 url.parse() (页 324) 解析。 例如: var https = require('https'); https.get('https://encrypted.google.com/', function(res) { console.log("statusCode: ", res.statusCode); console.log("headers: ", res.headers); res.on('data', function(d) { process.stdout.write(d); }); }).on('error', function(e) { console.error(e); }); () 类: https.Agent类: https.Agent HTTPS 的 Agent 对象,和 http.Agent (http.md#http_class_http_agent) 类似。 详情参见 https.request() (https.md#https_https_request_options_callback) 。 () https.globalAgenthttps.globalAgent 所有 HTTPS 客户端请求的 https.Agent (https.md#https_class_https_agent) 全局实例。 第 16 章 HTTPS | 183 1717 模块模块 稳定性: 5 - 锁定 Node 有简单的模块加载系统。在 Node 里,文件和模块是一一对应的。下面例子里, foo.js 加载同一个文件夹 里的 circle.js 模块。 foo.js 内容: var circle = require('./circle.js'); console.log( 'The area of a circle of radius 4 is ' + circle.area(4)); circle.js 内容: var PI = Math.PI; exports.area = function (r) { return PI * r * r; }; exports.circumference = function (r) { return 2 * PI * r; }; circle.js 模块输出了 area() 和 circumference() 函数。想要给根模块添加函数和对象,你可以将他们添加到 特定的 exports 对象。 加载到模块里的变量是私有的,仿佛模块是包含在一个函数里。在这个例子里, PI 是 circle.js 的私有变量。 如果你想模块里的根像一个函数一样的输出(比如 构造函数),或者你想输出一个完整对象,那就分派给 modul e.exports ,而不是 exports 。 bar.js 使用 square 模块, 它输出了构造函数: var square = require('./square.js'); var mySquare = square(2); console.log('The area of my square is ' + mySquare.area()); square 定义在 square.js 文件里: // assigning to exports will not modify module, must use module.exports module.exports = function(width) { return { area: function() { return width * width; } 第 17 章 模块 | 185 }; } 模块系统在 require("module") 模块里实现。 CyclesCycles 环形调用 require() ,当返回时模块可能都没执行结束。 考虑以下场景: a.js : console.log('a starting'); exports.done = false; var b = require('./b.js'); console.log('in a, b.done = %j', b.done); exports.done = true; console.log('a done'); b.js : console.log('b starting'); exports.done = false; var a = require('./a.js'); console.log('in b, a.done = %j', a.done); exports.done = true; console.log('b done'); main.js : console.log('main starting'); var a = require('./a.js'); var b = require('./b.js'); console.log('in main, a.done=%j, b.done=%j', a.done, b.done); 当 main.js 加载 a.js , a.js 加载 b.js 。此时, b.js 试着加载 a.js 。为了阻止循环调用, a.js 输出对象的 不完全拷贝返回给 b.js 模块。 b.js 会结束加载,并且它的 exports 对象提供给 a.js 模块。 main.js 加载完两个模块时,它们都会结束。这个程序的输出如下: $ node main.js main starting a starting b starting 第 17 章 模块 | 186 in b, a.done = false b done in a, b.done = true a done in main, a.done=true, b.done=true 如果你的程序有环形模块依赖,需要保证是线性的。 核心模块核心模块 Node 有很多模块编译成二进制。这些模块在本文档的其他地方有更详细的描述。 核心模块定义在 Node 的源代码 lib/ 目录里。 require() 总是会优先加载核心模块。例如, require('http') 总是返回编译好的 HTTP 模块,而不管这个文件的 名字。 文件模块文件模块 如果按照文件名没有找到模块,那么 Node 会试着加载添加了后缀 .js , .json 的文件,如果还没好到,再试着 加载添加了后缀 .node 的文件。 .js 会解析为 JavaScript 的文本文件, .json 会解析为 JSON 文本文件, .node 会解析为编译过的插件模 块,由 dlopen 负责加载。 模块的前缀 '/' 表示绝对路径。例如 require('/home/marco/foo.js') 将会加载 /home/marco/foo.js 文件。 模块的前缀 './' 表示相对于调用 require() 的路径。就是说, circle.js 必须和 foo.js 在 同一个目录里, requir e('./circle') 才能找到。 文件前没有 / 或 ./ 前缀,表示模块可能是 core module ,或者已经从 node_modules 文件夹里加载过了。 如果指定的路径不存在, require() 将会抛出一个 code 属性为 'MODULE_NOT_FOUND' 的异常。 从从 node_modulesnode_modules 目录里加载目录里加载 如传递给 require() 的模块不是一个本地模块,并且不以 '/' , '../' , 或 './' 开头,那么 Node 会从当前模块的父 目录开始,尝试在它的 node_modules 文件夹里加载模块。 如果没有找到,那么会到父目录,直到到文件系统的根目录里找。 第 17 章 模块 | 187 例如,如果 '/home/ry/projects/foo.js' 里的文件加载 require('bar.js') ,那么 Node 将会按照下面的顺序查找: • /home/ry/projects/node_modules/bar.js • /home/ry/node_modules/bar.js • /home/node_modules/bar.js • /node_modules/bar.js 这样允许程序独立,不会产生冲突。 可以请求指定的文件或分布子目录里的模块,在模块名后添加路径后缀。例如, require('example-module/path/ to/file') 会解决 path/to/file 相对于 example-module 的加载位置。路径后缀使用相同语法。 文件夹作为模块文件夹作为模块 可以把程序和库放到独立的文件夹里,并提供单一的入口指向他们。有三种方法可以将文件夹作为参数传给 req uire() 。 第一个方法是,在文件夹的根创建一个 package.json 文件,它指定了 main 模块。 package.json 的例子如 下: { "name" : "some-library", "main" : "./lib/some-library.js" } 如果这是在 ./some-library 里的文件夹, require('./some-library') 将会试着加载 ./some-library/lib/some-li brary.js 。 如果文件夹里没有 package.json 文件,Node 会试着加载 index.js 或 index.node 文件。例如,如果上面 的例子里没有 package.json 文件。那么 require('./some-library') 将会试着加载: • ./some-library/index.js • ./some-library/index.node 缓存缓存 模块第一次加载后会被被缓存。这就是说,每次调用 require('foo') 都会返回同一个对象,当然,必须每次都要 解析到同一个文件。 第 17 章 模块 | 188 多次调用 require('foo') 也许不会导致模块代码多次执行。这是很重要的特性,这样就可以返回 "partially done" 对象,允许加载过渡性的依赖关系,即使可能会引起环形调用。 如果你希望多次调用一个模块,那么就输出一个函数,然后调用这个函数。 模块换成预警模块换成预警 模块的缓存依赖于解析后的文件名。因此随着调用位置的不同,模块可能解析到不同的文件(例如,从 node_m odules 文件夹加载)。如果解析为不同的文件, require('foo') 可能会返回不同的对象。 modulemodule 对象对象 • {Object} 在每个模块中,变量 module 是一个代表当前模块的对象的引用。为了方便, module.exports 可以通过 exp orts 全局模块访问。 module 不是事实上的全局对象,而是每个模块内部的。 module.exportsmodule.exports • {Object} 模块系统创建 module.exports 对象。很多人希望自己的模块是某个类的实例。因此,把将要导出的对象赋值给 module.exports 。注意,将想要的对象赋值给 exports ,只是简单的将它绑定到本地 exports 变量,这可能 并不是你想要的。 例如,假设我们有一个模块叫 a.js 。 var EventEmitter = require('events').EventEmitter; module.exports = new EventEmitter(); // Do some work, and after some time emit // the 'ready' event from the module itself. setTimeout(function() { module.exports.emit('ready'); }, 1000); 另一个文件可以这么写: 第 17 章 模块 | 189 var a = require('./a'); a.on('ready', function() { console.log('module a is ready'); }); 注意,赋给 module.exports 必须马上执行,并且不能在回调中执行。 x.js: setTimeout(function() { module.exports = { a: "hello" }; }, 0); y.js: var x = require('./x'); console.log(x.a); exports aliasexports alias exports 变量在引用到 module.exports 的模块里可用。和其他变量一样,如果你给他赋一个新的值,它不再 指向老的值。 为了展示这个特性,假设实现: require() : function require(...) { // ... function (module, exports) { // Your module code here exports = some_func; // re-assigns exports, exports is no longer // a shortcut, and nothing is exported. module.exports = some_func; // makes your module export 0 } (module, module.exports); return module; } 如果你对 exports 和 module.exports 间的关系感到迷糊,那就只用 module.exports 就好。 module.require(id)module.require(id) • id {String} • 返回: {Object} 已经解析模块的 module.exports module.require 方法提供了一种像 require() 一样从最初的模块加载一个模块的方法。 第 17 章 模块 | 190 为了能这样做,你必须获得 module 对象的引用。 require() 返回 module.exports ,并且 module 是一个典 型的只能在特定模块作用域内有效的变量,如果要使用它,就必须明确的导出。 module.idmodule.id • {String} 模块的标识符。通常是完全解析的文件名。 module.filenamemodule.filename • {String} 模块完全解析的文件名。 module.loadedmodule.loaded • {Boolean} 模块是已经加载完毕,还是在加载中。 module.parentmodule.parent • {Module Object} 引入这个模块的模块。 module.childrenmodule.children • {Array} 由这个模块引入的模块。 其他...其他... 为了获取即将用 require() 加载的准确文件名,可以使用 require.resolve() 函数。 综上所述,下面用伪代码的高级算法形式演示了 require.resolve 的工作流程: 第 17 章 模块 | 191 require(X) from module at path Y 1. If X is a core module, a. return the core module b. STOP 2. If X begins with './' or '/' or '../' a. LOAD_AS_FILE(Y + X) b. LOAD_AS_DIRECTORY(Y + X) 3. LOAD_NODE_MODULES(X, dirname(Y)) 4. THROW "not found" LOAD_AS_FILE(X) 1. If X is a file, load X as JavaScript text. STOP 2. If X.js is a file, load X.js as JavaScript text. STOP 3. If X.json is a file, parse X.json to a JavaScript Object. STOP 4. If X.node is a file, load X.node as binary addon. STOP LOAD_AS_DIRECTORY(X) 1. If X/package.json is a file, a. Parse X/package.json, and look for "main" field. b. let M = X + (json main field) c. LOAD_AS_FILE(M) 2. If X/index.js is a file, load X/index.js as JavaScript text. STOP 3. If X/index.json is a file, parse X/index.json to a JavaScript object. STOP 4. If X/index.node is a file, load X/index.node as binary addon. STOP LOAD_NODE_MODULES(X, START) 1. let DIRS=NODE_MODULES_PATHS(START) 2. for each DIR in DIRS: a. LOAD_AS_FILE(DIR/X) b. LOAD_AS_DIRECTORY(DIR/X) NODE_MODULES_PATHS(START) 1. let PARTS = path split(START) 2. let I = count of PARTS - 1 3. let DIRS = [] 4. while I >= 0, a. if PARTS[I] = "node_modules" CONTINUE c. DIR = path join(PARTS[0 .. I] + "node_modules") b. DIRS = DIRS + DIR c. let I = I - 1 5. return DIRS 第 17 章 模块 | 192 从全局文件夹加载从全局文件夹加载 如果环境变量 NODE_PATH 设置为冒号分割的绝对路径列表,并且在模块在其他地方没有找到,Node 将会搜 索这些路径。(注意,Windows 里, NODE_PATH 用分号分割 )。 另外, Node 将会搜索这些路径。 • 1: $HOME/.node_modules • 2: $HOME/.node_libraries • 3: $PREFIX/lib/node $HOME 是用户的 home 文件夹, $PREFIX 是 Node 里配置的 node_prefix 。 这大多是历史原因照成的。强烈建议将所以来的模块放到 node_modules 文件夹里。这样加载会更快。 访问主模块访问主模块 当 Node 运行一个文件时, require.main 就会设置为它的 module 。也就是说你可以通过测试判断文件是否 被直接运行。 require.main === module 对于 foo.js 文件。 如果直接运行 node foo.js ,返回 true , 如果通过 require('./foo') 是间接运行。 因为 module 提供了 filename 属性(通常等于 __filename ),程序的入口点可以通过检查 require.main.fil ename 来获得。 附录: 包管理技巧附录: 包管理技巧 Node 的 require() 函数语义定义的足够通用,它能支持各种常规目录结构。诸如 dpkg , rpm , 和 npm 包管 理程序,不用修改就可以从 Node 模块构建本地包。 下面我们介绍一个可行的目录结构: 假设我们有一个文件夹 /usr/lib/node// ,包含指定版本的包内容。 一个包可以依赖于其他包。为了安装包 foo,可能需要安装特定版本的 bar 包。 bar 包可能有自己的包依 赖,某些条件下,依赖关系可能会发生冲突或形成循环。 第 17 章 模块 | 193 因为 Node 会查找他所加载的模块的 realpath (也就是说会解析符号链接),然后按照上文描述的方式在 nod e_modules 目录中寻找依赖关系,这种情形跟以下体系结构非常相像: • /usr/lib/node/foo/1.2.3/ - foo 包, version 1.2.3. • /usr/lib/node/bar/4.3.2/ - foo 依赖的 bar 包内容 • /usr/lib/node/foo/1.2.3/node_modules/bar - 指向 /usr/lib/node/bar/4.3.2/ 的符号链接 • /usr/lib/node/bar/4.3.2/node_modules/* - 指向 bar 包所依赖的包的符号链接 因此,即使存在循环依赖或依赖冲突,每个模块还可以获得他所依赖的包得可用版本。 当 foo 包里的代码调用 foo ,将会获得符号链接 /usr/lib/node/foo/1.2.3/node_modules/bar 指向的版本。然 后,当 bar 包中的代码调用 require('queue') ,将会获得符号链接 /usr/lib/node/bar/4.3.2/node_modules/quu x 指向的版本。 另外,为了让模块搜索更快些,不要将包直接放在 /usr/lib/node 目录中,而是将它们放在 /usr/lib/node_modul es// 目录中。 这样在依赖的包找不到的情况下,就不会一直寻找 / usr/node_modules 目录 或 /node_modules 目录了。基于调用 require() 的文件所在真实路径,因此包本身可以放在任何位置。 为了让 Node 模块对 Node REPL 可用,可能需要将 /usr/lib/node_modules 文件夹路径添加到环境变量 $N ODE_PATH 。由于模块查找 $NODE_PATH 文件夹都是相对路径,因此包可以放到任何位置。 第 17 章 模块 | 194 1818 网络网络 稳定性: 3 - 稳定 net 模块提供了异步网络封装,它包含了创建服务器/客户端的方法(调用 streams)。可以通过调用 requir e('net') 包含这个模块。 net.createServer([options][, connectionListener])net.createServer([options][, connectionListener]) 创建一个 TCP 服务器。参数 connectionListener 自动给 'connection' (页 202) 事件创建监听器。 options 包含有以下默认值: { allowHalfOpen: false, pauseOnConnect: false } 如果 allowHalfOpen = true , 当另一端 socket 发送 FIN 包时 socket 不会自动发送 FIN 包。socket 变为不 可读,但仍可写。你需要显式的调用 end() 方法。更多信息参见 'end' (页 204) 事件。 如果 pauseOnConnect = true ,当连接到来的时候相关联的 socket 将会暂停。它允许在初始进程不读取数据 情况下,让连接在进程间传递。调用 resume() 从暂停的 socket 里读取数据。 下面是一个监听 8124 端口连接的应答服务器的例子: var net = require('net'); var server = net.createServer(function(c) { //'connection' listener console.log('client connected'); c.on('end', function() { console.log('client disconnected'); }); c.write('hello\r\n'); c.pipe(c); }); server.listen(8124, function() { //'listening' listener console.log('server bound'); }); 使用 telnet 来测试: telnet localhost 8124 要监听 socket t /tmp/echo.sock ,仅需要改倒数第三行代码: server.listen('/tmp/echo.sock', function() { //'listening' listener 第 18 章 网络 | 196 使用 nc 连接到一个 UNIX domain socket 服务器: nc -U /tmp/echo.sock必须 net.connect(options[, connectionListener])net.connect(options[, connectionListener]) net.createConnection(options[, connectionListener])net.createConnection(options[, connectionListener]) 工厂方法,返回一个新的 'net.Socket' (页 0),并连接到指定的地址和端口。 当 socket 建立的时候,将会触发 'connect' (页 0) 事件。 和'net.Socket' (页 0)有相同的方法。 对于 TCP sockets,参数 options 因为下列参数的对象: • port : 客户端连接到 Port 的端口(必须)。 • host : 客户端要连接到得主机。默认 'localhost' . • localAddress : 网络连接绑定的本地接口。 • localPort : 网络连接绑定的本地端口。 • family : IP 栈版本。默认 4 . 对于本地域socket,参数 options 因为下列参数的对象: • path : 客户端连接到得路径(必须). 通用选项: • 如果 allowHalfOpen = true , 当另一端 socket 发送 FIN 包时 socket 不会自动发送 FIN 包。socket 变 为不可读,但仍可写。你需要显式的调用 end() 方法。更多信息参见 'end' (页 204) 事件。 connectListener 参数将会作为监听器添加到'connect' (页 0)事件上。 下面是一个用上述方法应答服务器的客户端例子: var net = require('net'); var client = net.connect({port: 8124}, function() { //'connect' listener console.log('connected to server!'); client.write('world!\r\n'); 第 18 章 网络 | 197 }); client.on('data', function(data) { console.log(data.toString()); client.end(); }); client.on('end', function() { console.log('disconnected from server'); }); 要连接到 socket /tmp/echo.sock ,仅需将第二行代码改为: var client = net.connect({path: '/tmp/echo.sock'}); net.connect(port[, host][, connectListener])net.connect(port[, host][, connectListener]) net.createConnection(port[, host][, connectListener])net.createConnection(port[, host][, connectListener]) 创建一个到端口 port 和 主机 host 的 TCP 连接。如果忽略主机 host ,则假定为 'localhost' 。参数 connec tListener 将会作为监听器添加到 'connect' (页 0) 事件。 这是工厂方法,返回一个新的 'net.Socket' (页 0)。 net.connect(path[, connectListener])net.connect(path[, connectListener]) net.createConnection(path[, connectListener])net.createConnection(path[, connectListener]) 创建到 path 的 unix socket 连接。 参数 connectListener 将会作为监听器添加到 'connect' (页 0) 事件上。 这是工厂方法,返回一个新的 'net.Socket' (页 0)。 Class: net.ServerClass: net.Server 这个类用来创建一个 TCP 或本地服务器。 server.listen(port[, host][, backlog][, callback])server.listen(port[, host][, backlog][, callback]) 开始接受指定端口 port 和 主机 host 的连接。如果忽略主机 host , 服务器将会接受任何 IPv4 地址( INAD DR_ANY )的直接连接。端口为 0,则会分配一个随机端口。 第 18 章 网络 | 198 积压量(Backlog)为连接等待队列的最大长度。实际长度由您的操作系统通过 sysctl 设定,比如 linux 上的 tc p_max_syn_backlog 和 somaxconn 。这个参数默认值是 511 (不是 512)。 这是异步函数。当服务器被绑定时会触发 'listening' (页 0) 事件。最后一个参数 callback 将会作为'listening' (页 0) 事件的监听器。 有些用户会遇到 EADDRINUSE 错误,它表示另外一个服务器已经运行在所请求的端口上。处理这个情况的办 法是等一段事件再重试: server.on('error', function (e) { if (e.code == 'EADDRINUSE') { console.log('Address in use, retrying...'); setTimeout(function () { server.close(); server.listen(PORT, HOST); }, 1000); } }); (注意: Node 中的所有 socket 已设置了 SO_REUSEADDR ) server.listen(path[, callback])server.listen(path[, callback]) • path {String} • callback {Function} 启动一个本地 socket 服务器,监听指定 path 的连接。 这是异步函数。绑定服务器后,会触发 'listening' (页 0) 事件。最后一个参数 callback 将会作为'listening' (页 0) 事件的监听器。 UNIX 上,本地域通常默认为 UNIX 域。参数 path 是文件系统路径,就和创建文件时一样,它也遵从命名规则 和权限检查,并且在文件系统里可见,并持续到关闭关联。 Windows上,本地域通过命名管道实现。路径必须是以 \\?\pipe\ 或 \\.\pipe\ 入口。任意字符串都可以,不过 之后进行相同的管道命名处理,比如解决 .. 序列。管道命名空间是平的。管道不会一直持久,当最后一个引用 关闭的时候,管道将会移除。不要忘记 javascript 字符字符串转义要求路径使用双反斜杠,比如: net.createServer().listen( path.join('\\\\?\\pipe', process.cwd(), 'myctl')) 第 18 章 网络 | 199 server.listen(handle[, callback])server.listen(handle[, callback]) • handle {Object} • callback {Function} handle 对象可以设置成 server 或 socket(任意以下划线 _handle 开头的成员),或者是 {fd: } 对象。 这将是服务器用指定的句柄接收连接,前提是文件描述符或句柄已经绑定到端口或域 socket。 Windows 不支持监听文件句柄。 这是异步函数。当服务器已经被绑定,将会触发'listening' (页 0) 事件。最后一个参数 callback 将会作为 'listen ing' (页 0) 事件的监听器。 server.listen(options[, callback])server.listen(options[, callback]) • options {Object} - 必须. 支持以下属性: • port {Number} - 可选. • host {String} - 可选. • backlog {Number} - 可选. • path {String} - 可选. • exclusive {Boolean} - 可选. • callback {Function} - 可选. options 的属性:端口 port , 主机 host , 和 backlog , 以及可选参数 callback 函数, 他们在一起调用server.l isten(port, [host], [backlog], [callback]) (页 0)。还有,参数 path 可以用来指定 UNIX socket。 如果参数 exclusive 是 false (默认值),集群进程将会使用同一个句柄,允许连接共享。当参数 exclusive 是 true 时,句柄不会共享,如果共享端口会返回错误。监听独家端口例子如下: server.listen({ host: 'localhost', port: 80, exclusive: true }); 第 18 章 网络 | 200 server.close([callback])server.close([callback]) 服务器停止接收新的连接,保持现有连接。这是异步函数,当所有连接结束的时候服务器会关闭,并会触发 'clos e' 事件。你可以传一个回调函数来监听 'close' 事件。如果存在,将会调用回调函数,错误(如果有)作为唯一 参数。 server.address()server.address() 操作系统返回绑定的地址,协议族名和服务器端口。查找哪个端口已经被系统绑定时,非常有用。返回的对象有3 个属性,比如: { port: 12346, family: 'IPv4', address: '127.0.0.1' } 例如: var server = net.createServer(function (socket) { socket.end("goodbye\n"); }); // grab a random port. server.listen(function() { address = server.address(); console.log("opened server on %j", address); }); 在 'listening' 事件触发前,不要调用 server.address() 。 server.unref()server.unref() 如果这是事件系统中唯一一个活动的服务器,调用 unref 将允许程序退出。如果服务器已被 unref,则再次调用 unref 并不会产生影响。 server.ref()server.ref() 与 unref 相反,如果这是唯一的服务器,在之前被 unref 了的服务器上调用 ref 将不会让程序退出(默认行 为)。如果服务器已经被 ref ,则再次调用 ref 并不会产生影响。 server.maxConnectionsserver.maxConnections 设置这个选项后,当服务器连接数超过数量时拒绝新连接。 第 18 章 网络 | 201 一旦已经用 child_process.fork() 方法将 socket 发送给子进程, 就不推荐使用这个选项。 server.connectionsserver.connections 已经抛弃这个函数。请用 server.getConnections() (页 0) 代替。服务器上当前连接的数量。 当调用 child_process.fork() 发送一个 socket 给子进程时,它将变为 null 。 要轮询子进程来获取当前活动连 接的数量,请用 server.getConnections 代替. server.getConnections(callback)server.getConnections(callback) 异步获取服务器当前活跃连接的数量。当 socket 发送给子进程后才有效; 回调函数有 2 个参数 err 和 count 。 net.Server 是事件分发器 EventEmitter (events.html#events_class_events_eventemitter) , 有以下事 件: 事件: 'listening'事件: 'listening' 当服务器调用 server.listen 绑定后会触发。 () 事件:'connection'事件:'connection' • {Socket object} 连接对象 当新连接创建后会被触发。 socket 是 net.Socket 实例。 事件: 'close'事件: 'close' 服务器关闭时会触发。注意,如果存在连接,这个事件不会被触发直到所有的连接关闭。 事件: 'error'事件: 'error' • {Error Object} 第 18 章 网络 | 202 发生错误时触发。'close' 事件将被下列事件直接调用。请查看 server.listen 例子。 Class: net.SocketClass: net.Socket 这个对象是 TCP 或 UNIX Socket 的抽象。 net.Socket 实例实现了一个双工流接口。 他们可以在用户创建客 户端(使用 connect())时使用, 或者由 Node 创建它们,并通过 connection 服务器事件传递给用户。 new net.Socket([options])new net.Socket([options]) 构造一个新的 socket 对象。 options 对象有以下默认值: { fd: null allowHalfOpen: false, readable: false, writable: false } 参数 fd 允许你指定一个存在的文件描述符。将 readable 和(或) writable 设为 true ,允许在这个 sock et 上读和(或)写(注意,仅在参数 fd 有效时)。关于 allowHalfOpen ,参见 createServer() 和 'end' 事 件。 socket.connect(port[, host][, connectListener])socket.connect(port[, host][, connectListener]) socket.connect(path[, connectListener])socket.connect(path[, connectListener]) 使用传入的 socket 打开一个连接。如果指定了端口 port 和 主机 host ,TCP socket 将打开 socket 。如果 忽略参数 host ,则默认为 localhost 。如果指定了 path ,socket 将会被指定路径的 unix socket 打开。 通常情况不需要使用这个函数,比如使用 net.createConnection 打开 socket。只有你实现了自己的 socket 时 才会用到。 这是异步函数。当 'connect' (页 0) 事件被触发时, socket 已经建立。如果这是问题连接, 'connect' 事件不 会被触发, 将会抛出 'error' 事件。 参数 connectListener 将会作为监听器添加到 'connect' (页 0) 事件。 第 18 章 网络 | 203 socket.bufferSizesocket.bufferSize 是 net.Socket 的一个属性,用于 socket.write() 。帮助用户获取更快的运行速度。计算机不能一直处于写入 大量数据状态--网络连接可能太慢。Node 在内部会将排队数据写入到socket,并在网络可用时发送。(内部实 现:轮询 socket 的文件描述符直到变为可写)。 这种内部缓冲的缺点是会增加内存使用量。这个属性表示当前准备写的缓冲字符数。(字符的数量等于准备写入 的字节的数量,但是缓冲区可能包含字符串,这些字符串是惰性编码的,所以准确的字节数还无法知道)。 遇到很大增长很快的 bufferSize 时,用户可用尝试用 pause() 和 resume() 来控制字符流。 socket.setEncoding([encoding])socket.setEncoding([encoding]) 设置 socket 的编码为可读流。更多信息参见stream.setEncoding() (stream.html#stream_stream_setenc oding_encoding) socket.write(data[, encoding][, callback])socket.write(data[, encoding][, callback]) 在 socket 上发送数据。第二个参数指定了字符串的编码,默认是 UTF8 编码。 如果所有数据成功刷新到内核缓冲区,返回 true 。如果数据全部或部分在用户内存里,返回 false 。当缓冲区 为空的时候会触发 'drain' 。 当数据最终被完整写入的的时候,可选的 callback 参数会被执行,但不一定会马上执行。 () socket.end([data][, encoding])socket.end([data][, encoding]) 半关闭 socket。例如,它发送一个 FIN 包。可能服务器仍在发送数据。 如果参数 data 不为空,等同于调用 socket.write(data, encoding) 后再调用 socket.end() 。 socket.destroy()socket.destroy() 确保没有 I/O 活动在这个套接字上。只有在错误发生情况下才需要。(处理错误等等)。 第 18 章 网络 | 204 socket.pause()socket.pause() 暂停读取数据。就是说,不会再触发 data 事件。对于控制上传非常有用。 socket.resume()socket.resume() 调用 pause() 后想恢复读取数据。 socket.setTimeout(timeout[, callback])socket.setTimeout(timeout[, callback]) socket 闲置时间超过 timeout 毫秒后 ,将 socket 设置为超时。 触发空闲超时事件时,socket 将会收到 'timeout' 事件,但是连接不会被断开。用户必须手动调用 end() 或 d estroy() 这个socket。 如果 timeout = 0, 那么现有的闲置超时会被禁用 可选的 callback 参数将会被添加成为 'timeout' 事件的一次性监听器。 socket.setNoDelay([noDelay])socket.setNoDelay([noDelay]) 禁用纳格(Nagle)算法。默认情况下 TCP 连接使用纳格算法,在发送前他们会缓冲数据。将 noDelay 设置 为 true 将会在调用 socket.write() 时立即发送数据。 noDelay 默认值为 true 。 socket.setKeepAlive([enable][, initialDelay])socket.setKeepAlive([enable][, initialDelay]) 禁用/启用长连接功能,并在发送第一个在闲置 socket 上的长连接 probe 之前,可选地设定初始延时。默认为 fa lse。 设定 initialDelay (毫秒),来设定收到的最后一个数据包和第一个长连接probe之间的延时。将 initialDelay 设为0,将会保留默认(或者之前)的值。默认值为0. socket.address()socket.address() 操作系统返回绑定的地址,协议族名和服务器端口。返回的对象有 3 个属性,比如 { port: 12346, family: 'IPv4', a ddress: '127.0.0.1' } 。 第 18 章 网络 | 205 socket.unref()socket.unref() 如果这是事件系统中唯一一个活动的服务器,调用 unref 将允许程序退出。如果服务器已被 unref,则再次调用 unref 并不会产生影响。 socket.ref()socket.ref() 与 unref 相反,如果这是唯一的服务器,在之前被 unref 了的服务器上调用 ref 将不会让程序退出(默认行 为)。如果服务器已经被 ref ,则再次调用 ref 并不会产生影响。 socket.remoteAddresssocket.remoteAddress 远程的 IP 地址字符串,例如: '74.125.127.100' or '2001:4860:a005::68' . socket.remoteFamilysocket.remoteFamily 远程IP协议族字符串,比如 'IPv4' or 'IPv6' . socket.remotePortsocket.remotePort 远程端口,数字表示,例如: 80 or 21 . socket.localAddresssocket.localAddress 网络连接绑定的本地接口 远程客户端正在连接的本地 IP 地址,字符串表示。例如,如果你在监听'0.0.0.0'而客户 端连接在'192.168.1.1',这个值就会是 '192.168.1.1'。 socket.localPortsocket.localPort 本地端口地址,数字表示。例如: 80 or 21 . socket.bytesReadsocket.bytesRead 接收到得字节数。 第 18 章 网络 | 206 socket.bytesWrittensocket.bytesWritten 发送的字节数。 net.Socket 是事件分发器 EventEmitter (events.html#events_class_events_eventemitter) 的实例, 有 以下事件: 事件: 'lookup'事件: 'lookup' 在解析域名后,但在连接前,触发这个事件。对 UNIX sokcet 不适用。 • err {Error | Null} 错误对象。 参见 dns.lookup() (dns.html#dns_dns_lookup_domain_family_callba ck) . • address {String} IP 地址。 • family {String | Null} 地址类型。 参见 dns.lookup() (dns.html#dns_dns_lookup_domain_family_cal lback) . 事件: 'connect'事件: 'connect' 当成功建立 socket 连接时触发。 参见 connect() . 事件: 'data'事件: 'data' • {Buffer object} 当接收到数据时触发。参数 data 可以是 Buffer 或 String 。使用 socket.setEncoding() 设定数据编码。(更 多信息参见 Readable Stream (stream.html#stream_class_stream_readable) )。 当 Socket 触发一个 'data' 事件时,如果没有监听器,数据将会丢失。 事件: 'end'事件: 'end' 当 socket 另一端发送 FIN 包时,触发该事件。 默认情况下( allowHalfOpen == false ),一旦 socket 将队列里的数据写完毕,socket 将会销毁它的文件描述 符。如果 allowHalfOpen == true ,socket 不会从它这边自动调用 end(),使的用户可以随意写入数据,而让用 户端自己调用 end()。 第 18 章 网络 | 207 事件: 'timeout'事件: 'timeout' 当 socket 空闲超时时触发,仅是表明 socket 已经空闲。用户必须手动关闭连接。 参见 : socket.setTimeout() 事件: 'drain'事件: 'drain' 当写缓存为空得时候触发。可用来控制上传。 参见 : socket.write() 的返回值。 事件: 'error'事件: 'error' • {Error object} 错误发生时触发。以下事件将会直接触发 'close' 事件。 事件: 'close'事件: 'close' • had_error {Boolean} 如果 socket 传输错误,为 true 当 socket 完全关闭时触发。参数 had_error 是 boolean,它表示是否因为传输错误导致 socket 关闭。 net.isIP(input)net.isIP(input) 测试是否输入的为 IP 地址。字符串无效时返回 0。 IPV4 情况下返回 4, IPV6情况下返回 6. net.isIPv4(input)net.isIPv4(input) 如果输入的地址为 IPV4, 返回 true,否则返回 false。 net.isIPv6(input)net.isIPv6(input) 如果输入的地址为 IPV6, 返回 true,否则返回 false。 第 18 章 网络 | 208 1919 系统系统 稳定性: 4 - API 冻结 提供一些基本的操作系统相关函数。 使用 require('os') 访问这个模块。 os.tmpdir()os.tmpdir() 返回操作系统的默认临时文件夹 os.endianness()os.endianness() 返回 CPU 的字节序,可能的是 "BE" 或 "LE"。 os.hostname()os.hostname() 返回操作系统的主机名。 os.type()os.type() 返回操作系统名。 os.platform()os.platform() 返回操作系统名 os.arch()os.arch() 返回操作系统 CPU 架构,可能的值有 "x64"、"arm" 和 "ia32"。 os.release()os.release() 返回操作系统的发行版本 第 19 章 系统 | 210 os.uptime()os.uptime() 返回操作系统运行的时间,以秒为单位。 os.loadavg()os.loadavg() 显示原文其他翻译纠错 返回一个包含 1、5、15 分钟平均负载的数组。 平均负载是系统的一个指标,操作系统计算,用一个很小的数字表示。理论上来说,平均负载最好比系统里的 C PU 低。 平均负载是一个非常 UNIX-y 的概念,windows 系统没有相同的概念。所以 windows 总是返回 [0, 0, 0] 。 os.totalmem()os.totalmem() 返回系统内存总量,单位为字节。 os.freemem()os.freemem() 返回操作系统空闲内存量,单位是字节。 os.cpus()os.cpus() 返回一个对象数组,包含所安装的每个 CPU/内核的信息:型号、速度(单位 MHz)、时间(一个包含 user、n ice、sys、idle 和 irq 所使用 CPU/内核毫秒数的对象)。 os.cpus 的例子: [ { model: 'Intel(R) Core(TM) i7 CPU 860 @ 2.80GHz', speed: 2926, times: { user: 252020, nice: 0, sys: 30340, idle: 1070356870, irq: 0 } }, { model: 'Intel(R) Core(TM) i7 CPU 860 @ 2.80GHz', 第 19 章 系统 | 211 speed: 2926, times: { user: 306960, nice: 0, sys: 26980, idle: 1071569080, irq: 0 } }, { model: 'Intel(R) Core(TM) i7 CPU 860 @ 2.80GHz', speed: 2926, times: { user: 248450, nice: 0, sys: 21750, idle: 1070919370, irq: 0 } }, { model: 'Intel(R) Core(TM) i7 CPU 860 @ 2.80GHz', speed: 2926, times: { user: 256880, nice: 0, sys: 19430, idle: 1070905480, irq: 20 } }, { model: 'Intel(R) Core(TM) i7 CPU 860 @ 2.80GHz', speed: 2926, times: { user: 511580, nice: 20, sys: 40900, idle: 1070842510, irq: 0 } }, { model: 'Intel(R) Core(TM) i7 CPU 860 @ 2.80GHz', speed: 2926, times: { user: 291660, nice: 0, sys: 34360, idle: 1070888000, irq: 10 } }, { model: 'Intel(R) Core(TM) i7 CPU 860 @ 2.80GHz', speed: 2926, times: { user: 308260, nice: 0, sys: 55410, 第 19 章 系统 | 212 idle: 1071129970, irq: 880 } }, { model: 'Intel(R) Core(TM) i7 CPU 860 @ 2.80GHz', speed: 2926, times: { user: 266450, nice: 1480, sys: 34920, idle: 1072572010, irq: 30 } } ] os.networkInterfaces()os.networkInterfaces() 获得网络接口列表: { lo: [ { address: '127.0.0.1', netmask: '255.0.0.0', family: 'IPv4', mac: '00:00:00:00:00:00', internal: true }, { address: '::1', netmask: 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff', family: 'IPv6', mac: '00:00:00:00:00:00', internal: true } ], eth0: [ { address: '192.168.1.108', netmask: '255.255.255.0', family: 'IPv4', mac: '01:02:03:0a:0b:0c', internal: false }, { address: 'fe80::a00:27ff:fe4e:66a1', netmask: 'ffff:ffff:ffff:ffff::', family: 'IPv6', mac: '01:02:03:0a:0b:0c', internal: false } ] } os.EOLos.EOL 定义了操作系统的 End-of-line 的常量。 第 19 章 系统 | 213 2020 路径路径 稳定性: 3 - 稳定 本模块包含一系列处理和转换文件路径的工具集。基本所有的反复都仅对字符串转换。文件系统不会检查路径是 否有效。 通过 require('path') 来访问这个模块。提供了以下方法: path.normalize(p)path.normalize(p) 规范化路径,注意 '..' 和 '.' 。 发现多个斜杠时,会替换成一个斜杠。当路径末尾包含一个斜杠时,保留。Windows 系统使用反斜杠。 例如: path.normalize('/foo/bar//baz/asdf/quux/..') // returns '/foo/bar/baz/asdf' path.join([path1][, path2][, ...])path.join([path1][, path2][, ...]) 连接所有的参数,并规范化输出路径。 参数必须是字符串。在 v0.8 版本,非字符参数会被忽略。v0.10之后的版本后抛出异常。 例如: path.join('/foo', 'bar', 'baz/asdf', 'quux', '..') // returns '/foo/bar/baz/asdf' path.join('foo', {}, 'bar') // throws exception TypeError: Arguments to path.join must be strings path.resolve([from ...], to)path.resolve([from ...], to) 将 to 参数解析为绝对路径。 第 20 章 路径 | 215 如果参数 to 不是一个相对于参数 from 的绝对路径, to 会添加到 from 右侧,直到找到一个绝对路径为 止。如果使用所有 from 参数后,还是没有找到绝对路径,将会使用当前工作目录。返回的路径已经规范化 过,并且去掉了尾部的斜杠(除非是根目录)。非字符串的参数会被忽略。 另一种思路就是在shell里执行一系列的 cd 命令。 path.resolve('foo/bar', '/tmp/file/', '..', 'a/../subfile') 类似于: cd foo/bar cd /tmp/file/ cd .. cd a/../subfile pwd 不同点是,不同的路径不需要存在的,也可能是文件。 例如: path.resolve('/foo/bar', './baz') // returns '/foo/bar/baz' path.resolve('/foo/bar', '/tmp/file/') // returns '/tmp/file' path.resolve('wwwroot', 'static_files/png/', '../gif/image.gif') // if currently in /home/myself/node, it returns '/home/myself/node/wwwroot/static_files/gif/image.gif' path.isAbsolute(path)path.isAbsolute(path) 判断参数 path 是否是绝对路径。一个绝对路径解析后都会指向相同的位置,无论当前的工作目录在哪里。 Posix 例子: path.isAbsolute('/foo/bar') // true path.isAbsolute('/baz/..') // true path.isAbsolute('qux/') // false path.isAbsolute('.') // false Windows 例子: 第 20 章 路径 | 216 path.isAbsolute('//server') // true path.isAbsolute('C:/foo/..') // true path.isAbsolute('bar\\baz') // false path.isAbsolute('.') // false path.relative(from, to)path.relative(from, to) 解决从 from 到 to 的相对路径。 有时我们会有2个绝对路径,需要从中找到相对目录。这是 path.resolve 的逆实现: path.resolve(from, path.relative(from, to)) == path.resolve(to) 例如: path.relative('C:\\orandea\\test\\aaa', 'C:\\orandea\\impl\\bbb') // returns '..\\..\\impl\\bbb' path.relative('/data/orandea/test/aaa', '/data/orandea/impl/bbb') // returns '../../impl/bbb' path.dirname(p)path.dirname(p) 返回路径 p 所在的目录。和 Unix dirname 命令类似。 例如: path.dirname('/foo/bar/baz/asdf/quux') // returns '/foo/bar/baz/asdf' path.basename(p[, ext])path.basename(p[, ext]) 返回路径的最后一个部分。和 Unix basename 命令类似。 例如: path.basename('/foo/bar/baz/asdf/quux.html') // returns 'quux.html' 第 20 章 路径 | 217 path.basename('/foo/bar/baz/asdf/quux.html', '.html') // returns 'quux' path.extname(p)path.extname(p) 返回路径 p 的扩展名,从最后一个 '.' 到字符串的末尾。如果最后一个部分没有 '.' ,或者路径是以 '.' 开头,则返 回空字符串。例如。 path.extname('index.html') // returns '.html' path.extname('index.coffee.md') // returns '.md' path.extname('index.') // returns '.' path.extname('index') // returns '' path.seppath.sep 特定平台的文件分隔符, '\\' 或 '/' 。 *nix 上的例子: 'foo/bar/baz'.split(path.sep) // returns ['foo', 'bar', 'baz'] Windows 的例子: 'foo\\bar\\baz'.split(path.sep) // returns ['foo', 'bar', 'baz'] 第 20 章 路径 | 218 path.delimiterpath.delimiter 特定平台的分隔符, ; or ':' . *nix 上的例子: console.log(process.env.PATH) // '/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin' process.env.PATH.split(path.delimiter) // returns ['/usr/bin', '/bin', '/usr/sbin', '/sbin', '/usr/local/bin'] Windows 例子: console.log(process.env.PATH) // 'C:\Windows\system32;C:\Windows;C:\Program Files\nodejs\' process.env.PATH.split(path.delimiter) // returns ['C:\\Windows\\system32', 'C:\\Windows', 'C:\\Program Files\\nodejs\\'] path.parse(pathString)path.parse(pathString) 返回路径字符串的对象。 *nix 上的例子: path.parse('/home/user/dir/file.txt') // returns { root : "/", dir : "/home/user/dir", base : "file.txt", ext : ".txt", name : "file" } Windows 例子: path.parse('C:\\path\\dir\\index.html') // returns { 第 20 章 路径 | 219 root : "C:\\", dir : "C:\\path\\dir", base : "index.html", ext : ".html", name : "index" } path.format(pathObject)path.format(pathObject) 从对象中返回路径字符串,和 path.parse 相反。 path.format({ root : "/", dir : "/home/user/dir", base : "file.txt", ext : ".txt", name : "file" }) // returns '/home/user/dir/file.txt' path.posixpath.posix 提供上述 path 路径访问,不过总是以 posix 兼容的方式交互。 path.win32path.win32 提供上述 path 路径访问,不过总是以 win32 兼容的方式交互。 第 20 章 路径 | 220 2121 进程进程 process 是全局对象,能够在任意位置访问,是 EventEmitter (events.html#events_class_events_event emitter) 的实例。 退出状态码退出状态码 当没有新的异步的操作等待处理时,Node 正常情况下退出时会返回状态码 0 。下面的状态码表示其他状态: • 1 未捕获的致命异常-Uncaught Fatal Exception未捕获的致命异常-Uncaught Fatal Exception - 有未捕获异常,并且没有被域或 uncaughtExceptio n 处理函数处理。 • 2 - Unused (保留) • 3 JavaScript解析错误-Internal JavaScript Parse ErrorJavaScript解析错误-Internal JavaScript Parse Error - JavaScript的源码启动 Node 进程时引起解 析错误。非常罕见,仅会在开发 Node 时才会有。 • 4 JavaScript评估失败-Internal JavaScript Evaluation FailureJavaScript评估失败-Internal JavaScript Evaluation Failure - JavaScript的源码启动 Node 进 程,评估时返回函数失败。非常罕见,仅会在开发 Node 时才会有。 • 5 致命错误-Fatal Error致命错误-Fatal Error - V8 里致命的不可恢复的错误。通常会打印到 stderr ,内容为: FATAL ERR OR • 6 Non-function 异常处理-Non-function Internal Exception HandlerNon-function 异常处理-Non-function Internal Exception Handler - 未捕获异常,内部异常处理函 数不知为何设置为on-function,并且不能被调用。 • 7 异常处理函数运行时失败-Internal Exception Handler Run-Time Failure异常处理函数运行时失败-Internal Exception Handler Run-Time Failure - 未捕获的异常, 并且异 常处理函数处理时自己抛出了异常。例如,如果 process.on('uncaughtException') 或 domain.on('error') 抛出了异常。 • 8 - Unused保留. 之前版本的 Node, 8 有时表示未捕获异常。 • 9 - 参数非法-Invalid Argument参数非法-Invalid Argument - 可能是给了未知的参数,或者给的参数没有值。 • 10 运行时失败-Internal JavaScript Run-Time Failure运行时失败-Internal JavaScript Run-Time Failure - JavaScript的源码启动 Node 进程时抛出错 误,非常罕见,仅会在开发 Node 时才会有。 • 12 无效的 Debug 参数-Invalid Debug Argument无效的 Debug 参数-Invalid Debug Argument - 设置了参数 --debug 和/或 --debug-brk ,但是 选择了错误端口。 • >128 信号退出-Signal Exits信号退出-Signal Exits - 如果 Node 接收到致命信号,比如 SIGKILL 或 SIGHUP ,那么退出代 码就是 128 加信号代码。这是标准的 Unix 做法,退出信号代码放在高位。 第 21 章 进程 | 222 事件: 'exit'事件: 'exit' 当进程准备退出时触发。此时已经没有办法阻止从事件循环中推出。因此,你必须在处理函数中执行同步操 作。这是一个在固定事件检查模块状态(比如单元测试)的好时机。回调函数有一个参数,它是进程的退出代 码。 监听 exit 事件的例子: process.on('exit', function(code) { // do *NOT* do this setTimeout(function() { console.log('This will not run'); }, 0); console.log('About to exit with code:', code); }); 事件: 'beforeExit'事件: 'beforeExit' 当 node 清空事件循环,并且没有其他安排时触发这个事件。通常来说,当没有进程安排时 node 退出,但是 'b eforeExit' 的监听器可以异步调用,这样 node 就会继续执行。 'beforeExit' 并不是明确退出的条件, process.exit() 或异常捕获才是,所以不要把它当做'exit' 事件。除非你想 安排更多的工作。 事件: 'uncaughtException'事件: 'uncaughtException' 当一个异常冒泡回到事件循环,触发这个事件。如果给异常添加了监视器,默认的操作(打印堆栈跟踪信息并退 出)就不会发生。 监听 uncaughtException 的例子: process.on('uncaughtException', function(err) { console.log('Caught exception: ' + err); }); setTimeout(function() { console.log('This will still run.'); }, 500); 第 21 章 进程 | 223 // Intentionally cause an exception, but don't catch it. nonexistentFunc(); console.log('This will not run.'); 注意, uncaughtException 是非常简略的异常处理机制。 尽量不要使用它,而应该用 domains () 。如果你用了,每次未处理异常后,重启你的程序。 不要使用 node.js 里诸如 On Error Resume Next 这样操作。每个未处理的异常意味着你的程序,和你的 nod e.js 扩展程序,一个未知状态。盲目的恢复意味着 任何事情 都可能发生 你在升级的系统时拉掉了电源线,然后恢复了。可能10次里有9次没有问题,但是第10次,你的系统可能就会挂 掉。 Signal 事件Signal 事件 当进程接收到信号时就触发。信号列表详见标准的 POSIX 信号名,如 SIGINT、SIGUSR1 等 监听 SIGINT 的例子: // Start reading from stdin so we don't exit. process.stdin.resume(); process.on('SIGINT', function() { console.log('Got SIGINT. Press Control-D to exit.'); }); 在大多数终端程序里,发送 SIGINT 信号的简单方法是按下 信号Control-C 。 注意: • SIGUSR1 node.js 接收这个信号开启调试模式。可以安装一个监听器,但开始时不会中断调试。 • SIGTERM 和 SIGINT 在非 Windows 系统里,有默认的处理函数,退出(伴随退出代码 128 + 信号 码 )前,重置退出模式。如果这些信号有监视器,默认的行为将会被移除。 • SIGPIPE 默认情况下忽略,可以加监听器。 • SIGHUP 当 Windowns 控制台关闭的时候生成,其他平台的类似条件,参见signal(7)。可以添加监听 者,Windows 平台上 10 秒后会无条件退出。在非 Windows 平台上, SIGHUP 的默认操作是终止 nod e,但是一旦添加了监听器,默认动作将会被移除。 SIGHUP is to terminate node, but once a listener has been installed its • SIGTERM Windows 不支持, 可以被监听。 第 21 章 进程 | 224 • SIGINT 所有的终端都支持,通常由 CTRL+C 生成(可能需要配置)。当终端原始模式启用后不会再生 成。 • SIGBREAK Windows 里,按下 CTRL+BREAK 会发送。非 Windows 平台,可以被监听,但是不能发 送或生成。 • SIGWINCH - 当控制台被重设大小时发送。Windows 系统里,仅会在控制台上输入内容时,光标移 动,或者可读的 tty在原始模式上使用。 • SIGKILL 不能有监视器,在所有平台上无条件关闭 node。 • SIGSTOP 不能有监视器。 Windows 不支持发送信号,但是 node 提供了很多 process.kill() 和 child_process.kill() 的模拟: - 发送 Se nding 信号 0 可以查找运行中得进程 - 发送 SIGINT , SIGTERM , 和 SIGKILL 会引起目标进程无条件退 出。 process.stdoutprocess.stdout 一个 Writable Stream 执向 stdout (on fd 1 ). 例如: console.log 的定义: console.log = function(d) { process.stdout.write(d + '\n'); }; process.stderr 和 process.stdout 和 node 里的其他流不同,他们不会被关闭( end() 将会被抛出),它们 不会触发 finish 事件,并且写是阻塞的。 • 引用指向常规文件或 TTY 文件描述符时,是阻塞的。 • 引用指向 pipe 管道时: • 在 Linux/Unix 里阻塞. • 在 Windows 像其他流一样,不被阻塞 检查 Node 是否运行在 TTY 上下文中,从 process.stderr , process.stdout , 或 process.stdin 里读取 isTT Y 属性。 $ node -p "Boolean(process.stdin.isTTY)" true $ echo "foo" | node -p "Boolean(process.stdin.isTTY)" 第 21 章 进程 | 225 false $ node -p "Boolean(process.stdout.isTTY)" true $ node -p "Boolean(process.stdout.isTTY)" | cat false 更多信息参见 the tty docs (页 0)。 process.stderrprocess.stderr 一个指向 stderr (on fd 2 )的可写流。 process.stderr 和 process.stdout 和 node 里的其他流不同,他们不会被关闭( end() 将会被抛出),它们 不会触发 finish 事件,并且写是阻塞的。 • 引用指向常规文件或 TTY 文件描述符时,是阻塞的。 • 引用指向 pipe 管道时: • 在 Linux/Unix 里阻塞. • 在 Windows 像其他流一样,不被阻塞 process.stdinprocess.stdin 一个指向 stdin (on fd 0 )的可读流。 以下例子:打开标准输入流,并监听两个事件: process.stdin.setEncoding('utf8'); process.stdin.on('readable', function() { var chunk = process.stdin.read(); if (chunk !== null) { process.stdout.write('data: ' + chunk); } }); process.stdin.on('end', function() { process.stdout.write('end'); }); process.stdin 可以工作在老模式里,和 v0.10 之前版本的 node 代码兼容。 第 21 章 进程 | 226 更多信息参见Stream compatibility (页 0). 在老的流模式里,stdin流默认暂停,必须调用 process.stdin.resume() 读取。可以调用 process.stdin.resum e() 切换到老的模式。 如果开始一个新的工程,最好选择新的流,而不是用老的流。 process.argvprocess.argv 包含命令行参数的数组。第一个元素是'node',第二个参数是 JavaScript 文件的名字,第三个参数是任意的命令 行参数。 // print process.argv process.argv.forEach(function(val, index, array) { console.log(index + ': ' + val); }); 将会生成: $ node process-2.js one two=three four 0: node 1: /Users/mjr/work/node/process-2.js 2: one 3: two=three 4: four process.execPathprocess.execPath 开启当前进程的执行文件的绝对路径。 例子: /usr/local/bin/node process.execArgvprocess.execArgv 启动进程所需的 node 命令行参数。这些参数不会在 process.argv 里出现,并且不包含 node 执行文件的名 字,或者任何在名字之后的参数。这些用来生成子进程,使之拥有和父进程有相同的参数。 例子: 第 21 章 进程 | 227 $ node --harmony script.js --version process.execArgv 的参数: ['--harmony'] process.argv 的参数: ['/usr/local/bin/node', 'script.js', '--version'] process.abort()process.abort() 这将导致 node 触发 abort 事件。会让 node 退出并生成一个核心文件。 process.chdir(directory)process.chdir(directory) 改变当前工作进程的目录,如果操作失败抛出异常。 console.log('Starting directory: ' + process.cwd()); try { process.chdir('/tmp'); console.log('New directory: ' + process.cwd()); } catch (err) { console.log('chdir: ' + err); } process.cwd()process.cwd() 返回当前进程的工作目录 console.log('Current directory: ' + process.cwd()); process.envprocess.env 包含用户环境的对象,参见 environ(7). 这个对象的例子: 第 21 章 进程 | 228 { TERM: 'xterm-256color', SHELL: '/usr/local/bin/bash', USER: 'maciej', PATH: '~/.bin/:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin', PWD: '/Users/maciej', EDITOR: 'vim', SHLVL: '1', HOME: '/Users/maciej', LOGNAME: 'maciej', _: '/usr/local/bin/node' } 你可以写入这个对象,但是不会改变当前运行的进程。以下的命令不会成功: node -e 'process.env.foo = "bar"' && echo $foo 这个会成功: process.env.foo = 'bar'; console.log(process.env.foo); process.exit([code])process.exit([code]) 使用指定的 code 结束进程。如果忽略,将会使用 code 0 使用失败的代码退出: process.exit(1); Shell 将会看到退出代码为1. process.exitCodeprocess.exitCode 进程退出时的代码,如果进程优雅的退出,或者通过 process.exit() 退出,不需要指定退出码。 设定 process.exit(code) 将会重写之前设置的 process.exitCode 。 process.getgid()process.getgid() 注意:这个函数仅在 POSIX 平台上可用(例如,非Windows 和 Android)。 获取进程的群组标识(参见 getgid(2))。获取到得时群组的数字 id,而不是名字。 第 21 章 进程 | 229 if (process.getgid) { console.log('Current gid: ' + process.getgid()); } process.setgid(id)process.setgid(id) 注意:这个函数仅在 POSIX 平台上可用(例如,非Windows 和 Android)。 设置进程的群组标识(参见 setgid(2))。可以接收数字 ID 或者群组名。如果指定了群组名,会阻塞等待解析为 数字 ID 。 if (process.getgid && process.setgid) { console.log('Current gid: ' + process.getgid()); try { process.setgid(501); console.log('New gid: ' + process.getgid()); } catch (err) { console.log('Failed to set gid: ' + err); } } process.getuid()process.getuid() 注意:这个函数仅在 POSIX 平台上可用(例如,非Windows 和 Android)。 获取进程的用户标识(参见 getuid(2))。这是数字的用户 id,不是用户名 if (process.getuid) { console.log('Current uid: ' + process.getuid()); } process.setuid(id)process.setuid(id) 注意:这个函数仅在 POSIX 平台上可用(例如,非Windows 和 Android)。 设置进程的用户标识(参见setuid(2))。接收数字 ID或字符串名字。果指定了群组名,会阻塞等待解析为数字 I D 。 if (process.getuid && process.setuid) { console.log('Current uid: ' + process.getuid()); 第 21 章 进程 | 230 try { process.setuid(501); console.log('New uid: ' + process.getuid()); } catch (err) { console.log('Failed to set uid: ' + err); } } process.getgroups()process.getgroups() 注意:这个函数仅在 POSIX 平台上可用(例如,非Windows 和 Android)。 返回进程的群组 iD 数组。POSIX 系统没有保证一定有,但是 node.js 保证有。 process.setgroups(groups)process.setgroups(groups) 注意:这个函数仅在 POSIX 平台上可用(例如,非Windows 和 Android)。 设置进程的群组 ID。这是授权操作,所有你需要有 root 权限,或者有 CAP_SETGID 能力。 列表可以包含群组 IDs,群组名,或者两者都有。 process.initgroups(user, extra_group)process.initgroups(user, extra_group) 注意:这个函数仅在 POSIX 平台上可用(例如,非Windows 和 Android)。 读取 /etc/group ,并初始化群组访问列表,使用成员所在的所有群组。这是授权操作,所有你需要有 root 权 限,或者有 CAP_SETGID 能力。 user 是用户名或者用户 ID, extra_group 是群组名或群组 ID。 当你在注销权限 (dropping privileges) 的时候需要注意. 例子: console.log(process.getgroups()); // [ 0 ] process.initgroups('bnoordhuis', 1000); // switch user console.log(process.getgroups()); // [ 27, 30, 46, 1000, 0 ] process.setgid(1000); // drop root gid console.log(process.getgroups()); // [ 27, 30, 46, 1000 ] 第 21 章 进程 | 231 process.versionprocess.version 一个编译属性,包含 NODE_VERSION . console.log('Version: ' + process.version); process.versionsprocess.versions 一个属性,包含了 node 的版本和依赖. console.log(process.versions); 打印出来: { http_parser: '1.0', node: '0.10.4', v8: '3.14.5.8', ares: '1.9.0-DEV', uv: '0.10.3', zlib: '1.2.3', modules: '11', openssl: '1.0.1e' } process.configprocess.config 一个包含用来编译当前 node 执行文件的 javascript 配置选项的对象。它与运行 ./configure 脚本生成的 "confi g.gypi" 文件相同。 一个可能的输出: { target_defaults: { cflags: [], default_configuration: 'Release', defines: [], include_dirs: [], libraries: [] }, variables: { host_arch: 'x64', node_install_npm: 'true', node_prefix: '', node_shared_cares: 'false', 第 21 章 进程 | 232 node_shared_http_parser: 'false', node_shared_libuv: 'false', node_shared_v8: 'false', node_shared_zlib: 'false', node_use_dtrace: 'false', node_use_openssl: 'true', node_shared_openssl: 'false', strict_aliasing: 'true', target_arch: 'x64', v8_use_snapshot: 'true' } } process.kill(pid[, signal])process.kill(pid[, signal]) 发送信号给进程. pid 是进程id,并且 signal 是发送的信号的字符串描述。信号名是字符串,比如 'SIGINT' 或 'SIGHUP'。如果忽略,信号会是 'SIGTERM'.更多信息参见 Signal 事件 (页 0) 和 kill(2) . 如果进程没有退出,会抛出错误。信号 0 可以用来测试进程是否存在。 注意,虽然这个这个函数名叫 process.kill ,它真的仅是信号发射器,就像 kill 系统调用。信号发射可以做其他 事情,不仅是杀死目标进程。 例子, 给自己发信号: process.on('SIGHUP', function() { console.log('Got SIGHUP signal.'); }); setTimeout(function() { console.log('Exiting.'); process.exit(0); }, 100); process.kill(process.pid, 'SIGHUP'); 注意: 当 Node.js 接收到 SIGUSR1 信号,它会开启 debugger 调试模式, 参见 Signal Events (页 0). process.pidprocess.pid 当前进程的 PID console.log('This process is pid ' + process.pid); 第 21 章 进程 | 233 process.titleprocess.title 获取/设置(Getter/setter) 'ps' 中显示的进程名。 使用 setter 时,字符串的长度由系统指定,可能会很短。 在 Linux 和 OS X 上,它受限于名称的长度加上命令行参数的长度,因为它会覆盖参数内存(argv memory)。 v0.8 版本允许更长的进程标题字符串,也支持覆盖环境内存,但是存在潜在的不安全和混乱(很难说清楚)。 process.archprocess.arch 当前 CPU 的架构:'arm'、'ia32' 或者 'x64'. console.log('This processor architecture is ' + process.arch); process.platformprocess.platform 运行程序所在的平台系统 'darwin' , 'freebsd' , 'linux' , 'sunos' or 'win32' console.log('This platform is ' + process.platform); process.memoryUsage()process.memoryUsage() 返回一个对象,描述了 Node 进程所用的内存状况,单位为字节。 var util = require('util'); console.log(util.inspect(process.memoryUsage())); 将会生成: { rss: 4935680, heapTotal: 1826816, heapUsed: 650472 } heapTotal and heapUsed refer to V8's memory usage. 第 21 章 进程 | 234 process.nextTick(callback)process.nextTick(callback) • callback {Function} 一旦当前事件循环结束,调用回到函数。 这不是 setTimeout(fn, 0) 的简单别名,这个效率更高。在任何附加 I/O 事件在子队列事件循环中触发前,它就 会运行。 console.log('start'); process.nextTick(function() { console.log('nextTick callback'); }); console.log('scheduled'); // Output: // start // scheduled // nextTick callback 在对象构造后,在 I/O 事件发生前,你又想改变附加事件处理函数时,这个非常有用。 function MyThing(options) { this.setupOptions(options); process.nextTick(function() { this.startDoingStuff(); }.bind(this)); } var thing = new MyThing(); thing.getReadyForStuff(); // thing.startDoingStuff() gets called now, not before. 要保证你的函数一定是 100% 同步执行,或者 100% 异步执行。例子: // WARNING! DO NOT USE! BAD UNSAFE HAZARD! function maybeSync(arg, cb) { if (arg) { cb(); return; } 第 21 章 进程 | 235 fs.stat('file', cb); } 这个 API 非常危险. 如果你这么做: maybeSync(true, function() { foo(); }); bar(); 不清楚 foo() 或 bar() 哪个先执行。 更好的方法: function definitelyAsync(arg, cb) { if (arg) { process.nextTick(cb); return; } fs.stat('file', cb); } 注意:nextTick 队列会在完全执行完毕之后才调用 I/O 操作。因此,递归设置 nextTick 的回调就像一个 whil e(true); 循环一样,将会阻止任何 I/O 操作的发生。 process.umask([mask])process.umask([mask]) 设置或读取进程文件的掩码。子进程从父进程继承掩码。如果 mask 参数有效,返回旧的掩码。否则,返回当前 掩码。 var oldmask, newmask = 0022; oldmask = process.umask(newmask); console.log('Changed umask from: ' + oldmask.toString(8) + ' to ' + newmask.toString(8)); process.uptime()process.uptime() 返回 Node 已经运行的秒数。 第 21 章 进程 | 236 process.hrtime()process.hrtime() 返回当前进程的高分辨时间,形式为 [seconds, nanoseconds] 数组。它是相对于过去的任意事件。该值与日期 无关,因此不受时钟漂移的影响。主要用途是可以通过精确的时间间隔,来衡量程序的性能。 你可以将之前的结果传递给当前的 process.hrtime() ,会返回两者间的时间差,用来基准和测量时间间隔。 var time = process.hrtime(); // [ 1800216, 25 ] setTimeout(function() { var diff = process.hrtime(time); // [ 1, 552 ] console.log('benchmark took %d nanoseconds', diff[0] * 1e9 + diff[1]); // benchmark took 1000000527 nanoseconds }, 1000); process.mainModuleprocess.mainModule require.main (页 0) 的备选方法。不同点,如果主模块在运行时改变, require.main 可能会继续返回老的模 块。可以认为,这两者引用了同一个模块。 Alternate way to retrieve require.main (页 0). The difference is that if the main module changes at run time, require.main might still refer to the original main module in modules that were required before th e change occurred. Generally it's safe to assume that the two refer to the same module. 和 require.main 一样, 如果没有入口脚本,将会返回 undefined 。 第 21 章 进程 | 237 2222 PunycodePunycode 稳定性: 2 - 不稳定 Punycode.js (http://mths.be/punycode) 从 Node.js v0.6.2+ 开始内置. 使用 require('punycode') 来访问。 (要在其他 Node.js 版本中访问,先用 npm 来 punycode 安装)。 punycode.decode(string)punycode.decode(string) 将一个纯 ASCII 的 Punycode 字符串转换为 Unicode 字符串。 // decode domain name parts punycode.decode('maana-pta'); // 'mañana' punycode.decode('--dqo34k'); // '☃-⌘' punycode.encode(string)punycode.encode(string) 将一个纯 Unicode Punycode 字符串转换为 纯 ASCII 字符串。 // encode domain name parts punycode.encode('mañana'); // 'maana-pta' punycode.encode('☃-⌘'); // '--dqo34k' punycode.toUnicode(domain)punycode.toUnicode(domain) 将一个表示域名的 Punycode 字符串转换为 Unicode。只有域名中的 Punycode 部分会被转换,也就是说你也 可以在一个已经转换为 Unicode 的字符串上调用它。 // decode domain names punycode.toUnicode('xn--maana-pta.com'); // 'mañana.com' punycode.toUnicode('xn----dqo34k.com'); // '☃-⌘.com' punycode.toASCII(domain)punycode.toASCII(domain) 将一个表示域名的 Unicode 字符串转换为 Punycode。只有域名中的非 ASCII 部分会被转换,也就是说你也可 以在一个已经转换为 ASCII 的字符串上调用它。 // encode domain names punycode.toASCII('mañana.com'); // 'xn--maana-pta.com' punycode.toASCII('☃-⌘.com'); // 'xn----dqo34k.com' 第 22 章 Punycode | 239 punycode.ucs2punycode.ucs2 punycode.ucs2.decode(string)punycode.ucs2.decode(string) 创建一个包含字符串中每个 Unicode 符号的数字编码点的数组,。由于 JavaScript uses UCS-2 internally (h ttp://mathiasbynens.be/notes/javascript-encoding) 在内部使用 UCS-2, 该函数会按照 UTF-16 将一对 代半数(UCS-2 暴露的单独的字符)转换为单独一个编码点。 punycode.ucs2.decode('abc'); // [0x61, 0x62, 0x63] // surrogate pair for U+1D306 tetragram for centre: punycode.ucs2.decode('\uD834\uDF06'); // [0x1D306] punycode.ucs2.encode(codePoints)punycode.ucs2.encode(codePoints) 创建以一组数字编码点为基础一个字符串。 punycode.ucs2.encode([0x61, 0x62, 0x63]); // 'abc' punycode.ucs2.encode([0x1D306]); // '\uD834\uDF06' punycode.versionpunycode.version 表示当前 Punycode.js 版本数字的字符串。 第 22 章 Punycode | 240 2323 Query StringQuery String 稳定性: 3 - 稳定 这个模块提供了一些处理 query strings 的工具,包括以下方法: querystring.stringify(obj[, sep][, eq][, options])querystring.stringify(obj[, sep][, eq][, options]) 将一个对象序列化化为一个 query string 。 可以选择重写默认的分隔符( '&' ) 和分配符 ( '=' )。 Options 对象可能包含 encodeURIComponent 属性 (默认: querystring.escape ),如果需要,它可以用 no n-utf8 编码字符串。 例子: querystring.stringify({ foo: 'bar', baz: ['qux', 'quux'], corge: '' }) // returns 'foo=bar&baz=qux&baz=quux&corge=' querystring.stringify({foo: 'bar', baz: 'qux'}, ';', ':') // returns 'foo:bar;baz:qux' // Suppose gbkEncodeURIComponent function already exists, // it can encode string with `gbk` encoding querystring.stringify({ w: '中文', foo: 'bar' }, null, null, { encodeURIComponent: gbkEncodeURIComponent }) // returns 'w=%D6%D0%CE%C4&foo=bar' querystring.parse(str[, sep][, eq][, options])querystring.parse(str[, sep][, eq][, options]) 将 query string 反序列化为对象。 可以选择重写默认的分隔符( '&' ) 和分配符 ( '=' )。 Options 对象可能包含 maxKeys 属性(默认:1000),用来限制处理过的健值(keys)。设置为 0 的话,可 以去掉键值的数量限制。 Options 对象可能包含 decodeURIComponent 属性(默认: querystring.unescape ),如果需要,可以用 来解码 non-utf8 编码的字符串。 第 23 章 Query String | 242 例子: querystring.parse('foo=bar&baz=qux&baz=quux&corge') // returns { foo: 'bar', baz: ['qux', 'quux'], corge: '' } // Suppose gbkDecodeURIComponent function already exists, // it can decode `gbk` encoding string querystring.parse('w=%D6%D0%CE%C4&foo=bar', null, null, { decodeURIComponent: gbkDecodeURIComponent }) // returns { w: '中文', foo: 'bar' } querystring.escapequerystring.escape escape 函数供 querystring.stringify 使用,必要时,可以重写。 querystring.unescapequerystring.unescape unescape函数供 querystring.parse 使用。必要时,可以重写。 首先会尝试用 decodeURIComponent ,如果失败,会回退,不会抛出格式不正确的 URLs。 第 23 章 Query String | 243 2424 逐行读取逐行读取 稳定性: 2 - 不稳定 使用 require('readline') ,可以使用这个模块。逐行读取(Readline)可以逐行读取流(比如 process.stdin ) 一旦你开启了这个模块,node 程序将不会终止,直到你关闭接口。以下的代码展示了如何优雅的退出程序: var readline = require('readline'); var rl = readline.createInterface({ input: process.stdin, output: process.stdout }); rl.question("What do you think of node.js? ", function(answer) { // TODO: Log the answer in a database console.log("Thank you for your valuable feedback:", answer); rl.close(); }); readline.createInterface(options)readline.createInterface(options) 创建一个逐行读取(Readline) Interface 实例. 参数 "options" 对象有以下值: • input - 监听的可读流 (必填). • output - 逐行读取(Readline)数据要写入的可写流(可选). • completer - 用于 Tab 自动补全的可选函数。参见下面的例子。 • terminal - 如果希望和 TTY 一样,对待 input 和 output 流,设置为 true。 并且由 ANSI/VT100 转 码。默认情况下,检查 isTTY 是否在 output 流上实例化。 completer 给出当前行的入口,应该返回包含2条记录的数组 1. 一个匹配当前输入补全的字符串数组 2. 用来匹配的子字符串 最终像这样: [[substr1, substr2, ...], originalsubstring] . 例子: function completer(line) { var completions = '.help .error .exit .quit .q'.split(' ') 第 24 章 逐行读取 | 245 var hits = completions.filter(function(c) { return c.indexOf(line) == 0 }) // show all completions if none found return [hits.length ? hits : completions, line] } 同时, completer 可以异步运行,此时接收到2个参数: function completer(linePartial, callback) { callback(null, [['123'], linePartial]); } 为了接受用户输入, createInterface 通常和 process.stdin , process.stdout 一起使用: var readline = require('readline'); var rl = readline.createInterface({ input: process.stdin, output: process.stdout }); 如果你有逐行读取(Readline)实例, 通常会监听 "line" 事件. 如果这个实例参数 terminal = true ,而且定义了 output.columns 属性,那么 output 流将会最佳兼容 性,并且,当 columns 变化时(当它是 TTY 时, process.stdout 会自动这么做),会在 output 流上触发 "resize" 事件。 Class: InterfaceClass: Interface 代表一个包含输入/输出流的逐行读取(Readline)接口的类, rl.setPrompt(prompt)rl.setPrompt(prompt) 设置提示符,比如当你再命令行里运行 node 时,可以看到 node 的提示符 > 。 rl.prompt([preserveCursor])rl.prompt([preserveCursor]) 为用户输入准备好逐行读取(Readline),将当前 setPrompt 选项方法哦新的行中,让用户有新的地方输 入。设置 preserveCursor 为 true ,防止当前的游标重置为 0 。 如果暂停,使用 createInterface 也可以重置 input 输入流。 调用 createInterface 时,如果 output 设置为 null 或 undefined ,不会重新写提示符。 第 24 章 逐行读取 | 246 rl.question(query, callback)rl.question(query, callback) 预先提示 query ,用户应答后触发 callback 。给用户显示 query 后,用户应答被输入后,调用 callback 。 如果暂停,使用 createInterface 也可以重置 input 输入流。 调用 createInterface 时,如果 output 设置为 null 或 undefined ,不会重新写提示符。 例子: interface.question('What is your favorite food?', function(answer) { console.log('Oh, so your favorite food is ' + answer); }); rl.pause()rl.pause() 暂停逐行读取(Readline)的 input 输入流, 如果需要可以重新启动。 注意,这不会立即暂停流。调用 pause 后还会有很多事件触发,包含 line 。 rl.resume()rl.resume() 恢复 逐行读取(Readline) input 输入流. rl.close()rl.close() 关闭 Interface 实例, 放弃控制输入输出流。会触发"close" 事件。 rl.write(data[, key])rl.write(data[, key]) 调用 createInterface 后,将数据 data 写到 output 输出流,除非 output 为 null ,或未定义 undefined 。 key 是一个代表键序列的对象;当终端是一个 TTY 时可用。 暂停 input 输入流后,这个方法可以恢复。 例子: rl.write('Delete me!'); // Simulate ctrl+u to delete the line written previously rl.write(null, {ctrl: true, name: 'u'}); 第 24 章 逐行读取 | 247 EventsEvents 事件: 'line'事件: 'line' function (line) {} input 输入流收到 \n 后触发,通常因为用户敲回车或返回键。这是监听用户输入的好办法。 监听 line 的例子: rl.on('line', function (cmd) { console.log('You just typed: '+cmd); }); 事件: 'pause'事件: 'pause' function () {} 暂停 input 输入流后,会触发这个方法。 当输入流未被暂停,但收到 SIGCONT 也会触发。 (详见 SIGTSTP 和 SIGCONT 事件) 监听 pause 的例子: rl.on('pause', function() { console.log('Readline paused.'); }); 事件: 'resume'事件: 'resume' function () {} 恢复 input 输入流后,会触发这个方法。 监听 resume 的例子: rl.on('resume', function() { console.log('Readline resumed.'); }); 第 24 章 逐行读取 | 248 事件: 'close'事件: 'close' function () {} 调用 close() 方法时会触发。 当 input 输入流收到 "end" 事件时会触发。一旦触发,可以认为 Interface 实例结束。例如当 input 输入流收 到 ^D ,被当做 EOT 。 如果没有 SIGINT 事件监听器,当 input 输入流接收到 ^C (被当做 SIGINT ),也会触发这个事件。 事件: 'SIGINT'事件: 'SIGINT' function () {} 当 input 输入流收到 ^C 时会触发, 被当做 SIGINT 。如果没有 SIGINT 事件监听器,当 input 输入流接收 到 SIGINT (被当做 SIGINT ),会触发 pause 事件。 监听 SIGINT 的例子: rl.on('SIGINT', function() { rl.question('Are you sure you want to exit?', function(answer) { if (answer.match(/^y(es)?$/i)) rl.pause(); }); }); 事件: 'SIGTSTP'事件: 'SIGTSTP' function () {} Windows 里不可用Windows 里不可用 当 input 输入流收到 ^Z 时会触发,被当做 SIGTSTP 。如果没有 SIGINT 事件监听器,当 input 输入流接 收到 SIGTSTP ,程序将会切换到后台。 当程序通过 fg 恢复,将会触发 pause 和 SIGCONT 事件。你可以使用两者中任一事件来恢复流。 程切换到后台前,如果暂停了流, pause 和 SIGCONT 事件不会被触发。 监听 SIGTSTP 的例子: 第 24 章 逐行读取 | 249 rl.on('SIGTSTP', function() { // This will override SIGTSTP and prevent the program from going to the // background. console.log('Caught SIGTSTP.'); }); 事件: 'SIGCONT'事件: 'SIGCONT' function () {} Windows 里不可用Windows 里不可用 一旦 input 流中含有 ^Z并被切换到后台就会触发。被当做 SIGTSTP ,然后继续执行 fg(1) 。程切换到后台 前,如果流没被暂停,这个事件可以被触发。 监听 SIGCONT 的例子: rl.on('SIGCONT', function() { // `prompt` will automatically resume the stream rl.prompt(); }); 例子: Tiny CLI例子: Tiny CLI 以下的例子,展示了如何所有这些方法的命令行接口: var readline = require('readline'), rl = readline.createInterface(process.stdin, process.stdout); rl.setPrompt('OHAI> '); rl.prompt(); rl.on('line', function(line) { switch(line.trim()) { case 'hello': console.log('world!'); break; default: console.log('Say what? I might have heard `' + line.trim() + '`'); break; } rl.prompt(); }).on('close', function() { 第 24 章 逐行读取 | 250 console.log('Have a great day!'); process.exit(0); }); readline.cursorTo(stream, x, y)readline.cursorTo(stream, x, y) 在TTY 流里,移动光标到指定位置。 readline.moveCursor(stream, dx, dy)readline.moveCursor(stream, dx, dy) 在TTY 流里,移动光标到当前位置的相对位置。 readline.clearLine(stream, dir)readline.clearLine(stream, dir) 清空 TTY 流里指定方向的行。 dir 是以下值: • -1 - 从光标到左边 • 1 - 从光标到右边 • 0 - 整行 readline.clearScreenDown(stream)readline.clearScreenDown(stream) 清空屏幕上从当前光标位置起的内容。 第 24 章 逐行读取 | 251 2525 REPLREPL 稳定性: 3 - 稳定 Read-Eval-Print-Loop (REPL 读取-执行-输出循环)即可作为独立程序,也可以集成到其他程序中。REPL 提供了一种交互的执行 JavaScript 并查看输出结果的方法。可以用来调试,测试,或仅是用来试试。 在命令行中不带任何参数的执行 node ,就是 REPL 模式。它提供了简单的 emacs 行编辑。 mjr:~$ node Type '.help' for options. > a = [ 1, 2, 3]; [ 1, 2, 3 ] > a.forEach(function (v) { ... console.log(v); ... }); 1 2 3 若想使用高级的编辑模式,使用环境变量 NODE_NO_READLINE=1 打开 node。这样会开启 REPL 模式,允 许你使用 rlwrap 。 例如,你可以添加以下代码到你的 bashrc 文件里。 alias node="env NODE_NO_READLINE=1 rlwrap node" repl.start(options)repl.start(options) 启动并返回一个 REPLServer 实例。它继承自[Readline Interface][]。接收的参数 "options" 有以下值: • prompt - 所有输入输出的提示符和流。默认是 > . • input - 需要监听的可读流,默认 process.stdin . • output - 用来输出数据的可写流,默认为 process.stdout . • terminal - 如果 stream 被当成 TTY,并且有 ANSI/VT100 转义, 传 true 。默认在实例的输出流上检 查 isTTY 。 • eval - 用来对每一行进行求值的函数。默认为 eval() 的异步封装。参见后面的自定义 eval 例子。 • useColors - 写函数输出是否有颜色。如果设定了不同的 writer 函数则无效。默认为 repl 的 terminal 值。 第 25 章 REPL | 253 • useGlobal - 如果为 true ,则 repl 将会使用全局对象,而不是在独立的上下文中运行scripts。默认为 f alse 。 • ignoreUndefined - 如果为 true ,repl 不会输出未定义命令的返回值。默认为 false 。 • writer - 每个命令行被求值时都会调用这个函数,它会返回格式化显示内容(包括颜色)。默认是 util.insp ect 。 如果有以下特性,可以使用自己的 eval 函数: function eval(cmd, context, filename, callback) { callback(null, result); } 在同一个 node 的运行实例上,可以打开多个 REPLs。每个都会共享一个全局对象,但会有独立的 I/O。 以下的例子,在stdin, Unix socket, 和 TCP socket 上开启 REPL : var net = require("net"), repl = require("repl"); connections = 0; repl.start({ prompt: "node via stdin> ", input: process.stdin, output: process.stdout }); net.createServer(function (socket) { connections += 1; repl.start({ prompt: "node via Unix socket> ", input: socket, output: socket }).on('exit', function() { socket.end(); }) }).listen("/tmp/node-repl-sock"); net.createServer(function (socket) { connections += 1; repl.start({ prompt: "node via TCP socket> ", input: socket, 第 25 章 REPL | 254 output: socket }).on('exit', function() { socket.end(); }); }).listen(5001); 从命令行运行这个程序,将会在 stdin 上启动 REPL。其他的 REPL 客户端可能通过 Unix socket 或 TCP soc ket 连接。 telnet 常用于连接 TCP socket, socat 用于连接Unix 和 TCP sockets 从Unix socket-based 服务器启动 REPL(而非stdin),你可以建立长连接,不用重启它们。 通过 net.Server 和 net.Socket 实例运行"full-featured" ( terminal ) REPL的例子,参见: https://gist.githu b.com/2209310 通过 curl(1) 实例运行 REPL的例子,参见: https://gist.github.com/2053342 Event: 'exit'Event: 'exit' function () {} 当用户通过预定义的方式退出 REPL 将会触发这个事件。预定义的方式包括,在 repl 里输入 .exit ,按 Ctrl+C 两次来发送 SIGINT 信号,或者在 input 流上按 Ctrl+D 来发送"end"。 监听 exit 的例子: r.on('exit', function () { console.log('Got "exit" event from repl!'); process.exit(); }); Event: 'reset'Event: 'reset' function (context) {} 重置 REPL 的上下文的时候触发。当你输入 .clear 会重置。如果你用 { useGlobal: true } 启动 repl,那这个事 件永远不会被触发。 监听 reset 的例子: // Extend the initial repl context. r = repl.start({ options ... }); someExtension.extend(r.context); 第 25 章 REPL | 255 // When a new context is created extend it as well. r.on('reset', function (context) { console.log('repl has a new context'); someExtension.extend(context); }); REPL 特性REPL 特性 在 REPL 里, Control+D 会退出。可以输入多行表达式。支持全局变量和局部变量的 TAB 自动补全。 特殊变量 _ (下划线)包含上一个表达式的结果。 > [ "a", "b", "c" ] [ 'a', 'b', 'c' ] > _.length 3 > _ += 1 4 REPL支持在全局域里访问任何变量。将变量赋值个和 REPLServer 关联的上下文对象,你可以显示的讲变量暴 露给 REPL,例如: // repl_test.js var repl = require("repl"), msg = "message"; repl.start("> ").context.m = msg; context 对象里的东西,会以局部变量的形式出现: mjr:~$ node repl_test.js > m 'message' 有一些特殊的REPL命令: • .break - 当你输入多行表达式时,也许你走神了或者不想完成了, .break 可以重新开始。 • .clear - 重置 context 对象为空对象,并且清空多行表达式。 • .exit - 关闭输入/输出流,会让 REPL 退出。 • .help - 打印这些特殊命令。 • .save - 保存当前 REPL 会话到文件。 >.save ./file/to/save.js • .load - 加载一个文件到当前REPL 会话 >.load ./file/to/load.js 第 25 章 REPL | 256 下面的组合键在 REPL 中有以下效果: • C - 和 .break 键类似. 在一个空行连按两次会强制退出。 • D - 和 .exit 键类似。 第 25 章 REPL | 257 2626 SmallocSmalloc 稳定性: 1 - 试验 类: smalloc类: smalloc 由简单内存分配器(处理扩展原始内存的分配)支持的缓存。Smalloc 有以下函数: smalloc.alloc(length[, receiver][, type])smalloc.alloc(length[, receiver][, type]) • length {Number} <= smalloc.kMaxLength • receiver {Object} 默认: new Object • type {Enum} 默认: Uint8 返回 receiver 对象,包含分配的外部数组数据。如果没有传入 receiver ,将会创建并返回一个新的对象。 这可用于创建你自己的类似 buffer 的类。不会设置其他属性,因此使用者需要跟踪其他所需信息(比如分配的长 度)。 function SimpleData(n) { this.length = n; smalloc.alloc(this.length, this); } SimpleData.prototype = { /* ... */ }; 仅检查 receiver 是否是非数组的对象。因此,可以分配扩展数据数据,不仅是普通对象。 function allocMe() { } smalloc.alloc(3, allocMe); // { [Function allocMe] '0': 0, '1': 0, '2': 0 } v8 不支持给数组分配扩展数组对象,如果这么做,将会抛出。 你可以指定外部数组数据的类型。所有可用类型在 smalloc.Types 列出,例如: var doubleArr = smalloc.alloc(3, smalloc.Types.Double); for (var i = 0; i < 3; i++) doubleArr = i / 10; // { '0': 0, '1': 0.1, '2': 0.2 } 第 26 章 Smalloc | 259 使用 Object.freeze , Object.seal 和 Object.preventExtensions 不能冻结,封锁,阻止对象的使用扩展数据 扩展。 smalloc.copyOnto(source, sourceStart, dest, destStart, copyLength);smalloc.copyOnto(source, sourceStart, dest, destStart, copyLength); • source {Object} 分配了外部数组的对象 • sourceStart {Number} 负责的起始位置 • dest {Object} 分配了外部数组的对象 • destStart {Number} 拷贝到目标的起始位置 • copyLength {Number} 需要拷贝的长度 从一个外部数组拷贝内存到另外一个,所有的参数都必填,否则会抛出异常。 var a = smalloc.alloc(4); var b = smalloc.alloc(4); for (var i = 0; i < 4; i++) { a[i] = i; b[i] = i * 2; } // { '0': 0, '1': 1, '2': 2, '3': 3 } // { '0': 0, '1': 2, '2': 4, '3': 6 } smalloc.copyOnto(b, 2, a, 0, 2); // { '0': 4, '1': 6, '2': 2, '3': 3 } copyOnto 会在内部自动检测分配的长度,因此不必设置任何附加参数。 smalloc.dispose(obj)smalloc.dispose(obj) • obj Object 释放通过 smalloc.alloc 给对象分配的内存。 var a = {}; smalloc.alloc(3, a); // { '0': 0, '1': 0, '2': 0 } 第 26 章 Smalloc | 260 smalloc.dispose(a); // {} 有利于减轻垃圾回收器的负担,但是开发时候还是要小心。程序里可能会出现难以跟踪的错误。 var a = smalloc.alloc(4); var b = smalloc.alloc(4); // perform this somewhere along the line smalloc.dispose(b); // now trying to copy some data out smalloc.copyOnto(b, 2, a, 0, 2); // now results in: // RangeError: copy_length > source_length 调用 dispose() ,对象依旧拥有外部数据,例如 smalloc.hasExternalData() 会返回 true 。 dispose() 不支 持缓存,如果传入将会抛出。 smalloc.hasExternalData(obj)smalloc.hasExternalData(obj) • obj {Object} 如果 obj 拥有外部分配的内存,返回 true 。 smalloc.kMaxLengthsmalloc.kMaxLength 可分配的最大数量。同样适用于缓存创建。 smalloc.Typessmalloc.Types 外部数组的类型,包含: • Int8 • Uint8 • Int16 • Uint16 第 26 章 Smalloc | 261 • Int32 • Uint32 • Float • Double • Uint8Clamped 第 26 章 Smalloc | 262 2727 流流 稳定性: 2 - 不稳定 流是一个抽象接口,在 Node 里被不同的对象实现。例如request to an HTTP server (页 0) 是流,stdout (pr ocess.html#process_process_stdout) 是流。流是可读,可写,或者可读写。所有的流是 EventEmitter (ev ents.html#events_class_events_eventemitter) 的实例。 你可以通过 require('stream') 加载 Stream 基类。其中包括了 Readable 流、 Writable 流、 Duplex 流和 Transform 流的基类。 这个文档分为 3 个章节。第一个章节解释了在你的程序中使用流时候需要了解的部分。如果你不用实现流式 AP I,可以只看这个章节。 如果你想实现你自己的流,第二个章节解释了这部分 API。这些 API 让你的实现更加简单。 第三个部分深入的解释了流是如何工作的,包括一些内部机制和函数,这些内容不要改动,除非你明确知道你要 做什么。 面向流消费者的 API面向流消费者的 API 流可以是可读(Readable),可写(Writable),或者兼具两者(Duplex,双工)的。 所有的流都是事件分发器(EventEmitters),但是也有自己的方法和属性,这取决于他它们是可读(Readabl e),可写(Writable),或者兼具两者(Duplex,双工)的。 如果流式可读写的,则它实现了下面的所有方法和事件。因此,这个章节 API 完全阐述了Duplex (页 0) 或 Tran sform (页 0) 流,即便他们的实现有所不同。 没有必要为了消费流而在你的程序里实现流的接口。如果你正在你的程序里实现流接口,请同时参考下面的API f or Stream Implementors (页 0)。 基本所有的 Node 程序,无论多简单,都会使用到流。这有一个使用流的例子。 javascript var http = require('http'); var server = http.createServer(function (req, res) { // req is an http.IncomingMessage, which is 可读流(Readable stream) // res is an http.ServerResponse, which is a Writable Stream var body = ''; // we want to get the data as utf8 strings // If you don't set an encoding, then you'll get Buffer objects req.setEncoding('utf8'); 第 27 章 流 | 264 // 可读流(Readable stream) emit 'data' 事件 once a 监听器(listener) is added req.on('data', function (chunk) { body += chunk; }); // the end 事件 tells you that you have entire body req.on('end', function () { try { var data = JSON.parse(body); } catch (er) { // uh oh! bad json! res.statusCode = 400; return res.end('error: ' + er.message); } // write back something interesting to the user: res.write(typeof data); res.end(); }); }); server.listen(1337); // $ curl localhost:1337 -d '{}' // object // $ curl localhost:1337 -d '"foo"' // string // $ curl localhost:1337 -d 'not json' // error: Unexpected token o 类: stream.Readable类: stream.Readable 可读流(Readable stream)接口是对你正在读取的数据的来源的抽象。换句话说,数据来来自 可读流(Readable stream)不会分发数据,直到你表明准备就绪。 可读流(Readable stream) 有2种模式: 流动模式(flowing mode)流动模式(flowing mode) 和 暂停模式(paused mode)暂停模式(paused mode). 流动模 式(flowing mode)时,尽快的从底层系统读取数据并提供给你的程序。 暂停模式(paused mode)时, 你必须 明确的调用 stream.read() 来读取数据。 暂停模式(paused mode) 是默认模式。 注意注意: 如果没有绑定数据处理函数,并且没有 [ pipe() ][] 目标,流会切换到流动模式(flowing mode),并且数 据会丢失。 第 27 章 流 | 265 可以通过下面几个方法,将流切换到流动模式(flowing mode)。 • 添加一个 [ 'data' 事件][] 事件处理器来监听数据. • 调用 [ resume() ][] 方法来明确的开启数据流。 • 调用 [ pipe() ][] 方法来发送数据给Writable (页 0). 可以通过以下方法来切换到暂停模式(paused mode): • 如果没有 导流(pipe) 目标,调用 [ pause() ][]方法. • 如果有 导流(pipe) 目标, 移除所有的 [ 'data' 事件][]处理函数, 调用 [ unpipe() ][] 方法移除所有的 导 流(pipe) 目标。 注意, 为了向后兼容考虑, 移除 'data' 事件监听器并不会自动暂停流。同样的,当有导流目标时,调用 pause() 并不能保证流在那些目标排空后,请求更多数据时保持暂停状态。 可读流(Readable stream)例子包括: • http responses, on the client (https://nodejs.org/api/http.html#http_http_incomingmessage) • http requests, on the server (https://nodejs.org/api/http.html#http_http_incomingmessage) • fs read streams (https://nodejs.org/api/fs.html#fs_class_fs_readstream) • zlib streams (zlib.html) • crypto streams (crypto.html) • tcp sockets (net.html#net_class_net_socket) • child process stdout and stderr (child_process.html#child_process_child_stdout) • process.stdin (process.html#process_process_stdin) 事件: 'readable'事件: 'readable' 当一个数据块可以从流中读出,将会触发 'readable' 事件.` 某些情况下, 如果没有准备好,监听一个 'readable' 事件将会导致一些数据从底层系统读取到内部缓存。 javascript var readble = getReadableStreamSomehow(); readable.on('readable', function() { // there is some data to read now }); 一旦内部缓存排空,一旦有更多数据将会再次触发 readable 事件。 第 27 章 流 | 266 事件: 'data'事件: 'data' • chunk {Buffer | String} 数据块 绑定一个 data 事件的监听器(listener)到一个未明确暂停的流,会将流切换到流动模式。数据会尽额能的传 递。 如果你像尽快的从流中获取数据,这是最快的方法。 javascript var readable = getReadableStreamSomehow(); readable.on('data', function(chunk) { console.log('got %d bytes of data', chunk.length); }); 事件: 'end'事件: 'end' 如果没有更多的可读数据,将会触发这个事件。 注意,除非数据已经被完全消费, the end 事件才会触发才会触发。 可以通过切换到流动模式(flowing mode)来实 现,或者通过调用重复调用 read() 获取数据,直到结束。 javascript var readable = getReadableStreamSomehow(); readable.on('data', function(chunk) { console.log('got %d bytes of data', chunk.length); }); readable.on('end', function() { console.log('there will be no more data.'); }); 事件: 'close'事件: 'close' 当底层资源(例如源头的文件描述符)关闭时触发。并不是所有流都会触发这个事件。 事件: 'error'事件: 'error' • {Error Object} 当接收数据时发生错误触发。 readable.read([size])readable.read([size]) • size {Number} 可选参数, 需要读入的数据量 • 返回 {String | Buffer | null} read() 方法从内部缓存中拉取数据。如果没有可用数据,将会返回 null 如果传了 size 参数,将会返回相当字节的数据。如果 size 不可用,将会返回 null 第 27 章 流 | 267 如果你没有指定 size 参数。将会返回内部缓存的所有数据。 这个方法仅能再暂停模式(paused mode)里调用. 流动模式(flowing mode)下这个方法会被自动调用直到 内存缓存排空。 javascript var readable = getReadableStreamSomehow(); readable.on('readable', function() { var chunk; while (null !== (chunk = readable.read())) { console.log('got %d bytes of data', chunk.length); } }); 如果这个方法返回一个数据块, 它同时也会触发[ 'data' 事件][]. readable.setEncoding(encoding)readable.setEncoding(encoding) • encoding {String} 要使用的编码. • 返回: this 调用此函数会使得流返回指定编码的字符串,而不是 Buffer 对象。例如,如果你调用 readable.setEncoding('utf 8') ,输出数据将会是UTF-8 编码,并且返回字符串。如果你调用 readable.setEncoding('hex') ,将会返回2进 制编码的数据。 该方法能正确处理多字节字符。如果不想这么做,仅简单的直接拉取缓存并调 buf.toString(encoding) ,可能会 导致字节错位。因此,如果你想以字符串读取数据,请使用这个方法。 javascript var readable = getReadableStreamSomehow(); readable.setEncoding('utf8'); readable.on('data', function(chunk) { assert.equal(typeof chunk, 'string'); console.log('got %d characters of string data', chunk.length); }); readable.resume()readable.resume() • 返回: this 这个方法让可读流(Readable stream)继续触发 data 事件. 这个方法会将流切换到流动模式(flowing mode). 如果你不想从流中消费数据,而想得到 end 事件,可以调 用 [ readable.resume() ][] 来打开数据流。 第 27 章 流 | 268 javascript var readable = getReadableStreamSomehow(); readable.resume(); readable.on('end', function(chunk) { console.log('got to the end, but did not read anything'); }); readable.pause()readable.pause() • 返回: this 这个方法会使得流动模式(flowing mode)的流停止触发 data 事件, 切换到流动模式(flowing mode). 并让 后续可用数据留在内部缓冲区中。 javascript var readable = getReadableStreamSomehow(); readable.on('data', function(chunk) { console.log('got %d bytes of data', chunk.length); readable.pause(); console.log('there will be no more data for 1 second'); setTimeout(function() { console.log('now data will start flowing again'); readable.resume(); }, 1000); }); readable.isPaused()readable.isPaused() • 返回: Boolean 这个方法返回 readable 是否被客户端代码 明确明确的暂停(调用 readable.pause() )。 ``javascript var readable = new stream.Readable readable.isPaused() // === false readable.pause() readable.isPaused() // === true readable.resume() readable.isPaused() // === false #### readable.pipe(destination[, options]) * `destination` {[Writable][] Stream} 写入数据的目标 * `options` {Object} 导流(pipe) 选项 * `end` {Boolean} 读取到结束符时,结束写入者。默认 = `true` 这个方法从可读流(Readable stream)拉取所有数据, 并将数据写入到提供的目标中。自动管理流量,这样目标不会快速的可读流(Readable stream)淹没。 可以导流到多个目标。 第 27 章 流 | 269 javascript var readable = getReadableStreamSomehow(); var writable = fs.createWriteStream('file.tx t'); // All the data from readable goes into 'file.txt' readable.pipe(writable); 这个函数返回目标流, 因此你可以建立导流链: javascript var r = fs.createReadStream('file.txt'); var z = zlib.createGzip(); var w = fs.createWriteStrea m('file.txt.gz'); r.pipe(z).pipe(w); 例如, 模拟 Unix 的 `cat` 命令: javascript process.stdin.pipe(process.stdout); 默认情况下,当源数据流触发 `end`的时候调用[`end()`][],所以 `destination` 不可再写。传 `{ end:false }`作为`options`,可以保持目标流打开状态。 这会让 `writer`保持打开状态,可以在最后写入"Goodbye" 。 javascript reader.pipe(writer, { end: false }); reader.on('end', function() { writer.end('Goodbye\n'); }); 注意 `process.stderr` 和 `process.stdout` 直到进程结束才会关闭,无论是否指定 #### readable.unpipe([destination]) * `destination` {[Writable][] Stream} 可选,指定解除导流的流 这个方法会解除之前调用 `pipe()` 设置的钩子( `pipe()` )。 如果没有指定 `destination`,所有的 导流(pipe) 都会被移除。 如果指定了 `destination`,但是没有建立如果没有指定 `destination`,则什么事情都不会发生。 javascript var readable = getReadableStreamSomehow(); var writable = fs.createWriteStream('file.tx t'); // All the data from readable goes into 'file.txt', // but only for the first second readable.pipe(writabl e); setTimeout(function() { console.log('stop writing to file.txt'); readable.unpipe(writable); console.lo g('manually close the file stream'); writable.end(); }, 1000); #### readable.unshift(chunk) * `chunk` {Buffer | String} 数据块插入到读队列中 第 27 章 流 | 270 这个方法很有用,当一个流正被一个解析器消费,解析器可能需要将某些刚拉取出的数据“逆消费”,返回到原来的源,以便流能将它传递给其它消费者。 如果你在程序中必须经常调用 `stream.unshift(chunk)` ,那你可以考虑实现[Transform][]来替换(参见下文API for Stream Implementors)。 javascript // Pull off a header delimited by \n\n // use unshift() if we get too much // Call the callback wit h (error, header, stream) var StringDecoder = require('string_decoder').StringDecoder; function pars eHeader(stream, callback) { stream.on('error', callback); stream.on('readable', onReadable); var deco der = new StringDecoder('utf8'); var header = ''; function onReadable() { var chunk; while (null !== (ch unk = stream.read())) { var str = decoder.write(chunk); if (str.match(/\n\n/)) { // found the header boun dary var split = str.split(/\n\n/); header += split.shift(); var remaining = split.join('\n\n'); var buf = new Bu ffer(remaining, 'utf8'); if (buf.length) stream.unshift(buf); stream.removeListener('error', callback); stre am.removeListener('readable', onReadable); // now the body of the message can be read from the str eam. callback(null, header, stream); } else { // still reading the header. header += str; } } } } #### readable.wrap(stream) * `stream` {Stream} 一个旧式的可读流(Readable stream) v0.10 版本之前的 Node 流并未实现现在所有流的API(更多信息详见下文“兼容性”章节)。 如果你使用的是旧的 Node 库,它触发 `'data'` 事件,并拥有仅做查询用的[`pause()`][] 方法,那么你能使用`wrap()` 方法来创建一个[Readable][] 流来使用旧版本的流,作为数据源。 你应该很少需要用到这个函数,但它会留下方便和旧版本的 Node 程序和库交互。 例如: javascript var OldReader = require('./old-api-module.js').OldReader; var oreader = new OldReader; v ar Readable = require('stream').Readable; var myReader = new Readable().wrap(oreader); myReader.on('readable', function() { myReader.read(); // etc. }); ### 类: stream.Writable 可写流(Writable stream )接口是你正把数据写到一个目标的抽象。 可写流(Writable stream )的例子包括: * [http requests, on the client](#http_class_http_clientrequest) * [http responses, on the server](#http_class_http_serverresponse) 第 27 章 流 | 271 * [fs write streams](#fs_class_fs_writestream) * [zlib streams][] * [crypto streams][] * [tcp sockets][] * [child process stdin](#child_process_child_stdin) * [process.stdout][], [process.stderr][] #### writable.write(chunk[, encoding][, callback]) * `chunk` {String | Buffer} 准备写的数据 * `encoding` {String} 编码方式(如果`chunk` 是字符串) * `callback` {Function} 数据块写入后的回调 * 返回: {Boolean} 如果数据已被全部处理返回true 这个方法向底层系统写入数据,并在数据处理完毕后调用所给的回调。 返回值表示你是否应该继续立即写入。如果数据要缓存在内部,将会返回`false`。否则返回 `true`。 返回值仅供参考。即使返回 `false`,你也可能继续写。但是写会缓存在内存里,所以不要做的太过分。最好的办法是等待`drain` 事件后,再写入数据。 #### 事件: 'drain' 如果调用 [`writable.write(chunk)`][] 返回 false, `drain` 事件会告诉你什么时候将更多的数据写入到流中。 javascript // Write the data to the supplied 可写流(Writable stream ) 1MM times. // Be attentive to ba ck-pressure. function writeOneMillionTimes(writer, data, encoding, callback) { var i = 1000000; writ e(); function write() { var ok = true; do { i -= 1; if (i === 0) { // last time! writer.write(data, encoding, callb ack); } else { // see if we should continue, or wait // don't pass the callback, because we're not done ye t. ok = writer.write(data, encoding); } } while (i > 0 && ok); if (i > 0) { // had to stop early! // write some m ore once it drains writer.once('drain', write); } } } #### writable.cork() 强制缓存所有写入。 调用 `.uncork()` 或 `.end()`后,会把缓存数据写入。 #### writable.uncork() 写入所有 .cork() 调用之后缓存的数据。 #### writable.setDefaultEncoding(encoding) 第 27 章 流 | 272 * `encoding` {String} 新的默认编码 * 返回: `Boolean` 给写数据流设置默认编码方式,如编码有效,返回 `true` ,否则返回 `false`。 #### writable.end([chunk][, encoding][, callback]) * `chunk` {String | Buffer} 可选,要写入的数据 * `encoding` {String} 编码方式(如果 `chunk` 是字符串) * `callback` {Function} 可选, stream 结束时的回调函数 当没有更多的数据写入的时候调用这个方法。如果给出,回调会被用作 finish 事件的监听器。 调用 [`end()`][] 后调用 [`write()`][] 会产生错误。 javascript // write 'hello, ' and then end with 'world!' var file = fs.createWriteStream('example.txt'); file.w rite('hello, '); file.end('world!'); // writing more now is not allowed! #### 事件: 'finish' 调用[`end()`][] 方法后,并且所有的数据已经写入到底层系统,将会触发这个事件。 javascript var writer = getWritableStreamSomehow(); for (var i = 0; i < 100; i ++) { writer.write('hello, #' + i + '!\n'); } writer.end('this is the end\n'); writer.on('finish', function() { console.error('all writes are now complete.'); }); #### 事件: 'pipe' * `src` {[Readable][] Stream} 是导流(pipe)到可写流的源流 无论何时在可写流(Writable stream )上调用`pipe()` 方法,都会触发 'pipe' 事件,添加这个流到目标。 javascript var writer = getWritableStreamSomehow(); var reader = getReadableStreamSomehow(); w riter.on('pipe', function(src) { console.error('something is piping into the writer'); assert.equal(src, read er); }); reader.pipe(writer); #### 事件: 'unpipe' * `src` {[Readable][] Stream} The source stream that [unpiped][] this writable 无论何时在可写流(Writable stream )上调用`unpipe()` 方法,都会触发 'unpipe' 事件,将这个流从目标上移除。 第 27 章 流 | 273 javascript var writer = getWritableStreamSomehow(); var reader = getReadableStreamSomehow(); w riter.on('unpipe', function(src) { console.error('something has stopped piping into the writer'); assert.e qual(src, reader); }); reader.pipe(writer); reader.unpipe(writer); #### 事件: 'error' * {Error object} 写或导流(pipe)数据时,如果有错误会触发。 ### 类: stream.Duplex 双工流(Duplex streams)是同时实现了 [Readable][] and [Writable][] 接口。用法详见下文。 双工流(Duplex streams) 的例子包括: * [tcp sockets][] * [zlib streams][] * [crypto streams][] ### 类: stream.Transform 转换流(Transform streams) 是双工 [Duplex][] 流,它的输出是从输入计算得来。 它实现了[Readable][] 和 [Writable][] 接口. 用法详见下文. 转换流(Transform streams) 的例子包括: * [zlib streams][] * [crypto streams][] ## API for Stream Implementors 无论实现什么形式的流,模式都是一样的: 1. 在你的子类中扩展适合的父类. ([`util.inherits`][] 方法很有帮助) 2. 在你的构造函数中调用父类的构造函数,以确保内部的机制初始化正确。 2. 实现一个或多个方法,如下所列 所扩展的类和要实现的方法取决于你要编写的流类。 第 27 章 流 | 274

Use-case

Class

方法(s) to implement

Reading only

[Readable](#stream_class_stream_readable_1)

[_read][]

Writing only

[Writable](#stream_class_stream_writable_1)

[_write][]

Reading and writing

[Duplex](#stream_class_stream_duplex_1)

第 27 章 流 | 275

[_read][], [_write][]

Operate on written data, then read the result

[Transform](#stream_class_stream_transform_1)

_transform, _flush

在你的代码里,千万不要调用 [API for Stream Consumers][] 里的方法。否则可能会引起消费流的程序副作用。 ### 类: stream.Readable `stream.Readable` 是一个可被扩充的、实现了底层 `_read(size)` 方法的抽象类。 参照之前的[API for Stream Consumers][]查看如何在你的程序里消费流。底下内容解释了在你的程序里如何实现可读流(Readable stream)。 #### Example: 计数流 这是可读流(Readable stream)的基础例子. 它将从 1 至 1,000,000 递增地触发数字,然后结束。 javascript var Readable = require('stream').Readable; var util = require('util'); util.inherits(Counter, Rea dable); function Counter(opt) { Readable.call(this, opt); this._max = 1000000; this._index = 1; } Counter.prototype._read = function() { var i = this._index++; if (i > this._max) this.push(null); else { var str = '' + i; var buf = new Buffer(str, 'ascii'); this.push(buf); } }; #### Example: 简单协议 v1 (初始版) 和之前描述的 `parseHeader` 函数类似, 但它被实现为自定义流。注意这个实现不会将输入数据转换为字符串。 第 27 章 流 | 276 实际上,更好的办法是将他实现为 [Transform][] 流。下面的实现方法更好。 javascript // A parser for a simple data protocol. // "header" is a JSON object, followed by 2 \n characte rs, and // then a message body. // // 注意: This can be done more simply as a Transform stream! // Usi ng Readable directly for this is sub-optimal. See the // alternative example below under Transform se ction. var Readable = require('stream').Readable; var util = require('util'); util.inherits(SimpleProtocol, Readable); function SimpleProtocol(source, options) { if (!(this instanceof SimpleProtocol)) return new SimpleProt ocol(source, options); Readable.call(this, options; this._inBody = false; this._sawFirstCr = false; // source is 可读流(Readable stream), such as a socket or file this._source = source; var self = this; source.on('end', function() { self.push(null); }); // give it a kick whenever the source is readable // read(0) will not consume any bytes source.on('read able', function() { self.read(0); }); this._rawHeader = []; this.header = null; } SimpleProtocol.prototype._read = function(n) { if (!this._inBody) { var chunk = this._source.read(); // if the source doesn't have data, we don't have data yet. if (chunk === null) return this.push(''); // check if the chunk has a \n\n var split = -1; for (var i = 0; i < chunk.length; i++) { if (chunk[i] === 10) { // '\n' if (this._sawFirstCr) { split = i; break; } else { this._sawFirstCr = true; } } else { 第 27 章 流 | 277 this._sawFirstCr = false; } } if (split === -1) { // still waiting for the \n\n // stash the chunk, and try again. this._rawHeader.push(chunk); this.push(''); } else { this._inBody = true; var h = chunk.slice(0, split); this._rawHeader.push(h); var header = Buffer.concat(this._rawHeader).toString(); try { this.header = JSON.parse(header); } catch (er) { this.emit('error', new Error('invalid simple protocol data')); return; } // now, because we got some extra data, unshift the rest // back into the 读取队列 so that our consumer will see it. var b = chunk.slice(split); this.unshift(b); // and let them know that we are done parsing the header. this.emit('header', this.header); } } else { // from there on, just provide the data to our consumer. // careful not to push(null), since that w ould indicate EOF. var chunk = this._source.read(); if (chunk) this.push(chunk); } }; // Usage: // var parser = new SimpleProtocol(source); // Now parser is 可读流(Readable stream) tha t will emit 'header' // with the parsed header data. #### new stream.Readable([options]) * `options` {Object} * `highWaterMark` {Number} 停止从底层资源读取数据前,存储在内部缓存的最大字节数。默认=16kb, `objectMode` 流是16. * `encoding` {String} 若指定,则 Buffer 会被解码成所给编码的字符串。缺省为 null * `objectMode` {Boolean} 该流是否为对象的流。意思是说 stream.read(n) 返回一个单独的值,而不是大小为 n 的 Buffer。 Readable 的扩展类中,确保调用了 Readable 的构造函数,这样才能正确初始化。 第 27 章 流 | 278 #### readable.\_read(size) * `size` {Number} 异步读取的字节数 注意: **实现这个函数, 但不要直接调用.** 这个函数不要直接调用. 在子类里实现,仅能被内部的 Readable 类调用。 所有可读流(Readable stream) 的实现必须停供一个 `_read` 方法,从底层资源里获取数据。 这个方法以下划线开头,是因为对于定义它的类是内部的,不会被用户程序直接调用。 你可以在自己的扩展类中实现。 当数据可用时,通过调用`readable.push(chunk)` 将之放到读取队列中。再次调用 `_read` ,需要继续推出更多数据。 `size` 参数仅供参考. 调用 “read” 可以知道知道应当抓取多少数据;其余与之无关的实现,比如 TCP 或 TLS,则可忽略这个参数,并在可用时返回数据。例如,没有必要“等到” size 个字节可用时才调用stream.push(chunk)。 #### readable.push(chunk[, encoding]) * `chunk` {Buffer | null | String} 推入到读取队列的数据块 * `encoding` {String} 字符串块的编码。必须是有效的 Buffer 编码,比如 utf8 或 ascii。 * 返回 {Boolean} 是否应该继续推入 注意: **这个函数必须被 Readable 实现者调用, 而不是可读流(Readable stream)的消费者.** `_read()` 函数直到调用`push(chunk)` 后才能被再次调用。 `Readable` 类将数据放到读取队列,当 `'readable'` 事件触发后,被 `read()` 方法取出。`push()` 方法会插入数据到读取队列中。如果调用了 `null` ,会触发 数据结束信号 (EOF)。 这个 API 被设计成尽可能地灵活。比如说,你可以包装一个低级别的,具备某种暂停/恢复机制,和数据回调的数据源。这种情况下,你可以通过这种方式包装低级别来源对象: javascript // source is an object with readStop() and readStart() 方法s, // and an ondata member that gets called when it has data, and // an onend member that gets called when the data is over. util.inherits(SourceWrapper, Readable); function SourceWrapper(options) { Readable.call(this, options); this._source = getLowlevelSourceObject(); var self = this; // Every time there's data, we push it into the internal buffer. this._source.ondata = function(chunk) { // if push() 返回 false, then we need to stop reading from source if (!self.push(chunk)) self._source.read Stop(); }; 第 27 章 流 | 279 // When the source ends, we push the EOF-signaling null chunk this._source.onend = function() { s elf.push(null); }; } // _read will be called when the stream wants to pull more data in // the advisory size 参数 is ignored in this case. SourceWrapper.prototype._read = function(size) { this._source.readStart(); }; ### 类: stream.Writable `stream.Writable` 是个抽象类,它扩展了一个底层的实现[`_write(chunk, encoding, callback)`][] 方法. 参考上面的[API for Stream Consumers][],来了解在你的程序里如何消费可写流。下面内容介绍了如何在你的程序里实现可写流。 #### new stream.Writable([options]) * `options` {Object} * `highWaterMark` {Number} 当 [`write()`][] 返回 false 时的缓存级别. 默认=16kb,`objectMode` 流是 16. * `decodeStrings` {Boolean} 传给 [`_write()`][] 前是否解码为字符串。 默认=true * `objectMode` {Boolean} `write(anyObj)` 是否是有效操作.如果为 true,可以写任意数据,而不仅仅是`Buffer` / `String`. 默认=false 请确保 Writable 类的扩展类中,调用构造函数以便缓冲设定能被正确初始化。 #### writable.\_write(chunk, encoding, callback) * `chunk` {Buffer | String} 要写入的数据块。总是 buffer, 除非 `decodeStrings` 选项为 `false`。 * `encoding` {String} 如果数据块是字符串,这个参数就是编码方式。如果是缓存,则忽略。注意,除非`decodeStrings` 被设置为 `false` ,否则这个数据块一直是buffer。 * `callback` {函数} 当你处理完数据后调用这个函数 (错误参数为可选参数)。 所以可写流(Writable stream ) 实现必须提供一个 [`_write()`][]方法,来发送数据给底层资源。 注意: **这个函数不能直接调用** ,由子类实现, 仅内部可写方法可以调用。 使用标准的 `callback(error)` 方法调用回调函数,来表明写入完成或遇到错误。 如果构造函数选项中设定了 `decodeStrings` 标识,则 `chunk` 可能会是字符串而不是 Buffer, `encoding` 表明了字符串的格式。这种设计是为了支持对某些字符串数据编码提供优化处理的实现。如果你没有明确的设置`decodeStrings` 为 `false`,这样你就可以安不管 `encoding` 参数,并假定 `chunk` 一直是一个缓存。 该方法以下划线开头,是因为对于定义它的类来说,这个方法是内部的,并且不应该被用户程序直接调用。你应当在你的扩充类中重写这个方法。 ### writable.\_writev(chunks, callback) * `chunks` {Array} 准备写入的数据块,每个块格式如下: `{ chunk: ..., encoding: ... }`. 第 27 章 流 | 280 * `callback` {函数} 当你处理完数据后调用这个函数 (错误参数为可选参数)。 注意: **这个函数不能直接调用。** 由子类实现,仅内部可写方法可以调用. 这个函数的实现是可选的。多数情况下,没有必要实现。如果实现,将会在所有数据块缓存到写队列后调用。 ### 类: stream.Duplex 双工流(duplex stream)同时兼具可读和可写特性,比如一个 TCP socket 连接。 注意 `stream.Duplex` 可以像 Readable 或 Writable 一样被扩充,实现了底层 _read(sise) 和 _write(chunk, encoding, callback) 方法的抽象类。 由于 JavaScript 并没有多重继承能力,因此这个类继承自 Readable,寄生自 Writable.从而让用户在双工扩展类中同时实现低级别的`_read(n)` 方法和低级别的[`_write(chunk, encoding, callback)`][]方法。 #### new stream.Duplex(options) * `options` {Object} 传递 Writable and Readable 构造函数,有以下的内容: * `allowHalfOpen` {Boolean} 默认=true. 如果设置为 `false`, 当写端结束的时候,流会自动的结束读端,反之亦然。 * `readableObjectMode` {Boolean} 默认=false. 将 `objectMode` 设为读端的流,如果为 `true`,将没有效果。 * `writableObjectMode` {Boolean} 默认=false. 将 `objectMode`设为写端的流,如果为 `true`,将没有效果。 扩展自 Duplex 的类,确保调用了父亲的构造函数,保证缓存设置能正确初始化。 ### 类: stream.Transform 转换流(transform class) 是双工流(duplex stream),输入输出端有因果关系,比如[zlib][] 流或 [crypto][] 流。 输入输出没有要求大小相同,块数量相同,到达时间相同。例如,一个 Hash 流只会在输入结束时产生一个数据块的输出;一个 zlib 流会产生比输入小得多或大得多的输出。 转换流(transform class) 必须实现`_transform()` 方法,而不是[`_read()`][] 和 [`_write()`][] 方法,也可以实现`_flush()` 方法(参见如下)。 #### new stream.Transform([options]) * `options` {Object} 传递给 Writable and Readable 构造函数。 扩展自 转换流(transform class) 的类,确保调用了父亲的构造函数,保证缓存设置能正确初始化。 #### transform.\_transform(chunk, encoding, callback) * `chunk` {Buffer | String} 准备转换的数据块。是buffer,除非 `decodeStrings` 选项设置为 `false`。 * `encoding` {String} 如果数据块是字符串, 这个参数就是编码方式,否则就忽略这个参数 * `callback` {函数} 当你处理完数据后调用这个函数 (错误参数为可选参数)。 第 27 章 流 | 281 注意: **这个函数不能直接调用。** 由子类实现,仅内部可写方法可以调用. 所有的转换流(transform class) 实现必须提供 `_transform`方法来接收输入,并生产输出。 `_transform` 可以做转换流(transform class)里的任何事,处理写入的字节,传给接口的写端,异步 I/O,处理事情等等。 调用 `transform.push(outputChunk)`0或多次,从这个输入块里产生输出,依赖于你想要多少数据作为输出。 仅在当前数据块完全消费后调用这个回调。注意,输入块可能有,也可能没有对应的输出块。如果你提供了第二个参数,将会传给push 方法。如底下的例子 javascript transform.prototype._transform = function (data, encoding, callback) { this.push(data); callb ack(); } transform.prototype._transform = function (data, encoding, callback) { callback(null, data); } 该方法以下划线开头,是因为对于定义它的类来说,这个方法是内部的,并且不应该被用户程序直接调用。你应当在你的扩充类中重写这个方法。 #### transform.\_flush(callback) * `callback` {函数} 当你处理完数据后调用这个函数 (错误参数为可选参数) 注意: **这个函数不能直接调用。** 由子类实现,仅内部可写方法可以调用. 某些情况下,转换操作可能需要分发一点流最后的数据。例如, `Zlib`流会存储一些内部状态,以便优化压缩输出。 有些时候,你可以实现 `_flush` 方法,它可以在最后面调用,当所有的写入数据被消费后,分发`end`告诉读端。和 `_transform` 一样,当刷新操作完毕, `transform.push(chunk)` 为0或更多次数,。 该方法以下划线开头,是因为对于定义它的类来说,这个方法是内部的,并且不应该被用户程序直接调用。你应当在你的扩充类中重写这个方法。 #### 事件: 'finish' and 'end' [`finish`][] 和 [`end`][] 事件 分别来自 Writable 和 Readable 类。`.end()`事件结束后调用 `finish` 事件,所有的数据已经被`_transform`处理完毕,调用 `_flush` 后,所有的数据输出完毕,触发`end`。 #### Example: `SimpleProtocol` parser v2 上面的简单协议分析例子列子可以通过使用高级别的[Transform][] 流来实现,和 `parseHeader` , `SimpleProtocol v1`列子类似。 在这个示例中,输入会被导流到解析器中,而不是作为参数提供。这种做法更符合 Node 流的惯例。 javascript var util = require('util'); var Transform = require('stream').Transform; util.inherits(SimpleProt ocol, Transform); 第 27 章 流 | 282 function SimpleProtocol(options) { if (!(this instanceof SimpleProtocol)) return new SimpleProtocol(opt ions); Transform.call(this, options); this._inBody = false; this._sawFirstCr = false; this._rawHeader = []; thi s.header = null; } SimpleProtocol.prototype._transform = function(chunk, encoding, done) { if (!this._inBody) { // check if the chunk has a \n\n var split = -1; for (var i = 0; i < chunk.length; i++) { if (chunk[i] === 10) { // '\n' if (thi s._sawFirstCr) { split = i; break; } else { this._sawFirstCr = true; } } else { this._sawFirstCr = false; } } if (split === -1) { // still waiting for the \n\n // stash the chunk, and try again. this._rawHeader.push(chunk); } else { this._inBody = true; var h = chunk.slice(0, split); this._rawHeader.push(h); var header = Buffer.concat(this._rawHeader).toString(); try { this.header = JSON.parse(header); } catch (er) { this.emit('error', new Error('invalid simple protocol data')); return; } // and let them know that we are done parsing the header. this.emit('header', this.header); // now, because we got some extra data, emit this first. this.push(chunk.slice(split)); } } else { // from there on, just provide the data to our consumer as-is. this.push(chunk); } done(); }; // Usage: // var parser = new SimpleProtocol(); // source.pipe(parser) // Now parser is 可读流(Readab le stream) that will emit 'header' // with the parsed header data. ### 类: stream.PassThrough 这是[Transform][] 流的简单实现,将输入的字节简单的传递给输出。它的主要用途是测试和演示。偶尔要构建某种特殊流时也会用到。 第 27 章 流 | 283 ## 流: 内部细节 ### 缓冲 可写流(Writable streams ) 和 可读流(Readable stream)都会缓存数据到内部对象上,叫做 `_writableState.buffer` 或 `_readableState.buffer`。 缓存的数据量,取决于构造函数是传入的 `highWaterMark` 参数。 调用 [`stream.push(chunk)`][] 时,缓存数据到可读流(Readable stream)。在数据消费者调用 `stream.read()` 前,数据会一直缓存在内部队列中。 调用 [`stream.write(chunk)`][] 时,缓存数据到可写流(Writable stream)。即使 `write()` 返回 `false`。 流(尤其是`pipe()` 方法)得目的是限制数据的缓存量到一个可接受的水平,使得不同速度的源和目的不会淹没可用内存。 ### `stream.read(0)` 某些时候,你可能想不消费数据的情况下,触发底层可读流(Readable stream)机制的刷新。这种情况下可以调用 stream.read(0),它总会返回 null。 如果内部读取缓冲低于 `highWaterMark`,并且流当前不在读取状态,那么调用 `read(0)` 会触发一个低级 `_read` 调用。 虽然基本上没有必要这么做。但你在 Node 内部的某些地方看到它确实这么做了,尤其是在 Readable 流类的内部。 ### `stream.push('')` 推一个0字节的字符串或缓存 (不在[Object mode][]时)会发送有趣的副作用. 因为它是一个对 [`stream.push()`][] 的调用, 它将会结束 `reading` 进程. 然而,它没有添加任何数据到可读缓冲区中,所以没有东西可供用户消费。 少数情况下,你当时没有提供数据,但你的流的消费者(或你的代码的其它部分)会通过调用 `stream.read(0)` 得知何时再次检查。在这种情况下,你可以调用 `stream.push('')`。 到目前为止,这个功能唯一一个使用情景是在 [tls.CryptoStream][] 类中,但它将在 Node v0.12 中被废弃。如果你发现你不得不使用 `stream.push('')`,请考虑另一种方式。 ### 和老版本的兼容性 v0.10 版本前,可读流(Readable stream)接口比较简单,因此功能和用处也小。 * `'data'`事件会立即开始触发,而不会等待你调用 `read()` 方法。如果你需要进行某些 I/O 来决定如何处理数据,那么你只能将数据块储存到某种缓冲区中以防它们流失。 * [`pause()`][] 方法仅供参考,而不保证生效。这意味着,即便流处于暂停状态时,你仍然需要准备接收 'data' 事件。 第 27 章 流 | 284 在 Node v0.10中, 加入了下文所述的 Readable 类。为了考虑向后兼容,添加了 'data' 事件监听器或 resume() 方法被调用时,可读流(Readable stream)会切换到 "流动模式(flowing mode)"。其作用是,即便你不使用新的 `read()` 方法和`'readable'`事件,你也不必担心丢失`'data'` 数据块。 大多数程序会维持正常功能。然而,下列条件下也会引入边界情况: * 没有添加 [`'data'` 事件][] 处理器 * 从来没有调用 [`resume()`][] 方法 * 流从来没有被倒流(pipe)到任何可写目标上、 例如: javascript // WARNING! BROKEN! net.createServer(function(socket) { // we add an 'end' 方法, but never consume the data socket.on('end', function() { // It will never get her e. socket.end('I got your message (but didnt read it)\n'); }); }).listen(1337); v0.10 版本前的 Node, 流入的消息数据会被简单的抛弃。之后的版本,socket 会一直保持暂停。 这种情形下,调用`resume()` 方法来开始工作: javascript // Workaround net.createServer(function(socket) { socket.on('end', function() { socket.end('I got your message (but didnt read it)\n'); }); // start the flow of data, discarding it. socket.resume(); }).listen(1337); 可读流(Readable stream)切换到流动模式(flowing mode),v0.10 版本前,可以使用`wrap()` 方法将风格流包含在一个可读类里。 ### Object Mode 通常情况下,流仅操作字符串和缓存。 处于 **object mode** 的流,除了 缓存和字符串,还可以可以读出普通 JavaScript值。 在对象模式里,可读流(Readable stream) 调用 `stream.read(size)`总会返回单个项目,无论是什么参数。 第 27 章 流 | 285 在对象模式里, 可写流(Writable stream ) 总会忽略传给`stream.write(data, encoding)`的 `encoding`参数。 特殊值 `null` 在对象模式里,依旧保持它的特殊性。也就说,对于对象模式的可读流(Readable stream),`stream.read()` 返回 null 意味着没有更多数据,同时[`stream.push(null)`][] 会告知流数据结束(EOF)。 Node 核心不存在对象模式的流,这种设计只被某些用户态流式库所使用。 应该在你的子类构造函数里,设置`objectMode` 。在过程中设置不安全。 对于双工流(Duplex streams),`objectMode`可以用`readableObjectMode` 和 `writableObjectMode` 分别为读写端分别设置。这些选项,被转换流(Transform streams)用来实现解析和序列化。 javascript var util = require('util'); var StringDecoder = require('string_decoder').StringDecoder; var Tr ansform = require('stream').Transform; util.inherits(JSONParseStream, Transform); // Gets \n-delimited JSON string data, and emits the parsed objects function JSONParseStream() { if (!(this instanceof JSONParseStream)) return new JSONParseStream(); Transform.call(this, { readableObjectMode : true }); this._buffer = ''; this._decoder = new StringDecoder('utf8'); } JSONParseStream.prototype._transform = function(chunk, encoding, cb) { this._buffer += this._deco der.write(chunk); // split on newlines var lines = this._buffer.split(/\r?\n/); // keep the last partial line buff ered this._buffer = lines.pop(); for (var l = 0; l < lines.length; l++) { var line = lines[l]; try { var obj = JSO N.parse(line); } catch (er) { this.emit('error', er); return; } // push the parsed object out to the readable consumer this.push(obj); } cb(); }; JSONParseStream.prototype._flush = function(cb) { // Just handle any leftover var rem = this._buffe r.trim(); if (rem) { try { var obj = JSON.parse(rem); } catch (er) { this.emit('error', er); return; } // push th e parsed object out to the readable consumer this.push(obj); } cb(); }; ``` 第 27 章 流 | 286 2828 字符串解码器字符串解码器 稳定性: 3 - 稳定 通过 require('string_decoder') ,可以使用这个模块。字符串解码器(StringDecoder)将缓存(buffer)解码 为字符串。这是 buffer.toString() 的简单接口,提供了 utf8 支持。 var StringDecoder = require('string_decoder').StringDecoder; var decoder = new StringDecoder('utf8'); var cent = new Buffer([0xC2, 0xA2]); console.log(decoder.write(cent)); var euro = new Buffer([0xE2, 0x82, 0xAC]); console.log(decoder.write(euro)); Class: StringDecoderClass: StringDecoder 接受一个参数 encoding ,默认值 utf8 。 decoder.write(buffer)decoder.write(buffer) 返回解码后的字符串。 decoder.end()decoder.end() 返回 buffer 里剩下的末尾字节。 第 28 章 字符串解码器 | 288 2929 定时器定时器 稳定性: 5 - 锁定 所有的定时器函数都是全局的。不需要通过 require() 就可以访问。 setTimeout(callback, delay[, arg][, ...])setTimeout(callback, delay[, arg][, ...]) delay 毫秒之后执行 callback 。返回 timeoutObject 对象,可能会用来 clearTimeout() 。你也可以给回调 函数传参数。 需要注意,你的回调函数可能不会非常准确的在 delay 毫秒后执行,Node.js 不保证回调函数的精确时间和执 行顺序。回调函数会尽量的靠近指定的时间。 clearTimeout(timeoutObject)clearTimeout(timeoutObject) 阻止一个 timeout 被触发。 setInterval(callback, delay[, arg][, ...])setInterval(callback, delay[, arg][, ...]) 每隔 delay 毫秒就重复执行 callback 。返回 timeoutObject 对象,可能会用来 clearTimeout() 。你也可以 给回调函数传参数。 clearInterval(intervalObject)clearInterval(intervalObject) 阻止一个 interval 被触发。 unref()unref() setTimeout 和 setInterval 所返回的值,拥有 timer.unref() 方法,它能让你创建一个活动的定时器,但是它 所在的事件循环中如果仅剩它一个计时器,将不会保持程序运行。如果计时器已经调用了 unref ,再次调用将无 效。 在 setTimeout 场景中,当你使用 unref 并创建了一个独立定时器它将会唤醒事件循环。创建太多的这样的东 西会影响事件循环性能,所以谨慎使用。 第 29 章 定时器 | 290 ref()ref() 如果你之前已经使用 unref() 一个定时器,就可以使用 ref() 来明确的请求定时器保持程序打开状态。果计时器 已经调用了 ref() ,再次调用将无效。 setImmediate(callback[, arg][, ...])setImmediate(callback[, arg][, ...]) 在 setTimeout 和 setInterval 事件前,在输入/输出事件后,安排一个 callback "immediate" 立即执行。 immediates 的回调以它们创建的顺序加入队列。整个回调队列会在事件循环迭代中执行。如果你将immediates 加入到一个正在执行回调中,那么将不会触发immediate,直到下次事件循环迭代。 clearImmediate(immediateObject)clearImmediate(immediateObject) 停止一个 immediate 的触发。 第 29 章 定时器 | 291 3030 TLS/SSLTLS/SSL Stability: 3 - Stable 可以使用 require('tls') 来访问这个模块。 tls 模块 使用 OpenSSL 来提供传输层(Transport Layer)安全性和(或)安全套接层(Secure Socket La yer):加密过的流通讯。 TLS/SSL 是一种公钥/私钥基础架构。每个客户端和服务端都需要一个私钥。私钥可以用以下方法创建的: openssl genrsa -out ryans-key.pem 2048 所有服务器和某些客户端需要证书。证书由认证中心(Certificate Authority)签名,或者自签名。获得证书第一 步是创建一个证书签名请求 "Certificate Signing Request" (CSR)文件。证书可以用以下方法创建的: openssl req -new -sha256 -key ryans-key.pem -out ryans-csr.pem 使用CSR创建一个自签名的证书: openssl x509 -req -in ryans-csr.pem -signkey ryans-key.pem -out ryans-cert.pem 或者你可以发送 CSR 给认证中心(Certificate Authority)来签名。 (TODO: 创建 CA 的文档, 感兴趣的读者可以在 Node 源码 test/fixtures/keys/Makefile 里查看) 创建 .pfx 或 .p12,可以这么做: openssl pkcs12 -export -in agent5-cert.pem -inkey agent5-key.pem \ -certfile ca-cert.pem -out agent5.pfx • in : certificate • inkey : private key • certfile : all CA certs concatenated in one file like cat ca1-cert.pem ca2-cert.pem > ca-cert.pem 协议支持协议支持 Node.js 默认遵循 SSLv2 和 SSLv3 协议,不过这些协议被禁用。因为他们不太可靠,很容易受到威胁,参见 CVE-2014-3566 (https://access.redhat.com/articles/1232123) 。某些情况下,旧版本客户端/服务器(比 如 IE6)可能会产生问题。如果你想启用 SSLv2 或 SSLv3 ,使用参数 --enable-ssl2 或 --enable-ssl3 运 行 Node。Node.js 的未来版本中不会再默认编译 SSLv2 和 SSLv3。 有一个办法可以强制 node 进入仅使用 SSLv3 或 SSLv2 模式,分别指定 secureProtocol 为 'SSLv3_metho d' 或 'SSLv2_method' 。 第 30 章 TLS/SSL | 293 Node.js 使用的默认协议方法准确名字是 AutoNegotiate_method , 这个方法会尝试并协商客户端支持的从高 到底协议。为了提供默认的安全级别,Node.js(v0.10.33 版本之后)通过将 secureOptions 设为 SSL_OP_N O_SSLv3|SSL_OP_NO_SSLv2 ,明确的禁用了 SSLv3 和 SSLv2(除非你给 secureProtocol 传值 --ena ble-ssl3 , 或 --enable-ssl2 , 或 SSLv3_method )。 如果你设置了 secureOptions ,我们不会重新这个参数。 改变这个行为的后果: • 如果你的应用被当做为安全服务器, SSLv3 客户端不能协商建立连接,会被拒绝。这种情况下,你的服务 器会触发 clientError 事件。错误消息会包含错误版本数字( 'wrong version number' ). • 如果你的应用被当做安全客户端,和一个不支持比 SSLv3 更高安全性的方法的服务器通讯,你的连接不会 协商成功。这种情况下,你的客户端会触发 clientError 事件。错误消息会包含错误版本数字( 'wrong versi on number' ). Client-initiated renegotiation attack mitigationClient-initiated renegotiation attack mitigation TLS 协议让客户端协商 TLS 会话的某些方法内容。但是,会话协商需要服务器端响应的资源,这回让它成为阻 断服务攻击(denial-of-service attacks)的潜在媒介。 为了降低这种情况的发生,重新协商被限制为每10分钟3次。当超出这个界限时,在 tls.TLSSocket (页 0) 实例 上会触发错误。这个限制可设置: • tls.CLIENT_RENEG_LIMIT : 重新协商 limit, 默认是 3. • tls.CLIENT_RENEG_WINDOW : 重新协商窗口的时间,单位秒, 默认是 10 分钟. 除非你明确知道自己在干什么,否则不要改变默认值。 要测试你的服务器的话,使用 openssl s_client -connect address:port 连接服务器,并敲 R (字母 R 键加回车)几次。 NPN and SNINPN and SNI NPN (Next Protocol Negotiation 下次协议协商) 和 SNI (Server Name Indication 域名指示) 都是 TLS 握手 扩展,运行你: • NPN - 同一个TLS服务器使用多种协议 (HTTP, SPDY) 第 30 章 TLS/SSL | 294 • SNI - 同一个TLS服务器使用多个主机名(不同的 SSL 证书)。 certificates. 完全正向保密完全正向保密 "Forward Secrecy (http://en.wikipedia.org/wiki/Perfect_forward_secrecy) " 或 "Perfect Forward Secre cy-完全正向保密" 协议描述了秘钥协商(比如秘钥交换)方法的特点。实际上这意味着及时你的服务器的秘钥有 危险,通讯仅有可能被一类人窃听,他们必须设法获的每次会话都会生成的秘钥对。 完全正向保密是通过每次握手时为秘钥协商随机生成密钥对来完成(和所有会话一个 key 相反)。实现这个技 术(提供完全正向保密-Perfect Forward Secrecy)的方法被称为 "ephemeral"。 通常目前有2个方法用于完成完全正向保密(Perfect Forward Secrecy): • DHE (https://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange) - 一个迪菲-赫尔曼 密钥交换密钥协议(Diffie Hellman key-agreement protocol)短暂(ephemeral)版本。 • ECDHE (https://en.wikipedia.org/wiki/Elliptic_curve_Diffie%E2%80%93Hellman) - 一个椭圆曲线密 钥交换密钥协议( Elliptic Curve Diffie Hellman key-agreement protocol)短暂(ephemeral)版本。 短暂(ephemeral)方法有性能缺点,因为生成 key 非常耗费资源。 tls.getCiphers()tls.getCiphers() 返回支持的 SSL 密码名数组。 例子: var ciphers = tls.getCiphers(); console.log(ciphers); // ['AES128-SHA', 'AES256-SHA', ...] () tls.createServer(options[, secureConnectionListener])tls.createServer(options[, secureConnectionListener]) 创建一个新的 tls.Server (页 0)。参数 connectionListener 会自动设置为 secureConnection (页 0) 事件的监 听器。参数 options 对象有以下可能性: • pfx : 包含私钥,证书和服务器的 CA 证书(PFX 或 PKCS12 格式)字符串或缓存 Buffer 。( key , cer t 和 ca 互斥)。 第 30 章 TLS/SSL | 295 • key : 包含服务器私钥(PEM 格式)字符串或缓存 Buffer 。(可以是keys的数组)(必传)。 • passphrase : 私钥或 pfx 的密码字符串 • cert : 包含服务器证书key(PEM 格式)字符串或缓存 Buffer 。(可以是certs的数组)(必传)。 • ca : 信任的证书(PEM 格式)的字符串/缓存数组。如果忽略这个参数,将会使用"root" CAs ,比如 VeriS ign。用来授权连接。 • crl : 不是 PEM 编码 CRLs (证书撤销列表 Certificate Revocation List)的字符串就是字符串列表. • ciphers : 要使用或排除的密码(cipher)字符串 为了减轻BEAST attacks (http://blog.ivanristic.com/2011/10/mitigating-the-beast-attack-on-tls.ht ml) ,推荐使用这个参数和之后会提到的 honorCipherOrder 参数来优化non-CBC 密码(cipher) 默认: ECDHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA256:AES128-GCM-SHA256:RC4:HI GH:!MD5:!aNULL . 格式上更多细节参见 OpenSSL cipher list format documentation (http://www.ope nssl.org/docs/apps/ciphers.html#CIPHER_LIST_FORMAT) ECDHE-RSA-AES128-SHA256 , DHE-RSA-AES128-SHA256 和 AES128-GCM-SHA256 都是 T LS v1.2 密码(cipher),当 node.js 连接 OpenSSL 1.0.1 或更早版本(比如)时使用。注意, honorCi pherOrder 设置为 enabled 后,现在任然可以和 TLS v1.2 客户端协商弱密码(cipher), RC4 可作为客户端和老版本 TLS 协议通讯的备用方法。 RC4 这些年受到怀疑,任何对信任敏感的对象 都会考虑其威胁性。国家级别(state-level)的参与者拥有中断它的能力。 注意注意: 早些版本的修订建议, AES256-SHA 作为可以接受的密码(cipher).Unfortunately, AES256-S HA 是一个 CBC 密码(cipher),容易受到 BEAST attacks (http://blog.ivanristic.com/2011/10/mitiga ting-the-beast-attack-on-tls.html) 攻击。 不要 使用它。 • ecdhCurve : 包含用来 ECDH 秘钥交换弧形(curve)名字符串,或者 false 禁用 ECDH。 默认 prime256v1 . 更多细节参考 RFC 4492 (http://www.rfc-editor.org/rfc/rfc4492.txt) 。 • dhparam : DH 参数文件,用于 DHE 秘钥协商。使用 openssl dhparam 命令来创建。如果加载文件失 败,会悄悄的抛弃它。 • handshakeTimeout : 如果 SSL/TLS 握手事件超过这个参数,会放弃里连接。 默认是 120 秒. 握手超时后, tls.Server 对象会触发 'clientError ' 事件。 • honorCipherOrder : 当选择一个密码(cipher) 时, 使用服务器配置,而不是客户端的。 第 30 章 TLS/SSL | 296 虽然这个参数默认不可用,还是推荐你用这个参数,和 ciphers 参数连接使用,减轻 BEAST 攻击。 注意,如果使用了 SSLv2,服务器会发送自己的配置列表给客户端,客户端会挑选密码(cipher)。默认不 支持 SSLv2,除非 node.js 配置了 ./configure --with-sslv2 。 • requestCert : 如果设为 true ,服务器会要求连接的客户端发送证书,并尝试验证证书。默认: false 。 • rejectUnauthorized : 如果为 true ,服务器将会拒绝任何不被 CAs 列表授权的连接。仅 requestCert 参数为 true 时这个参数才有效。默认: false 。 • checkServerIdentity(servername, cert) : 提供一个重写的方法来检查证书对应的主机名。如果验证失 败,返回 error。如果验证通过,返回 undefined 。 • NPNProtocols : NPN 协议的 Buffer 数组(协议需按优先级排序)。 • SNICallback(servername, cb) : 如果客户端支持 SNI TLS 扩展会调用这个函数。会传入2个参数: serve rname 和 cb 。 SNICallback 必须调用 cb(null, ctx) ,其中 ctx 是 SecureContext 实例。(你可以 用 tls.createSecureContext(...) 来获取相应的 SecureContext 上下文)。如果 SNICallback 没有提 供,将会使用高级的 API(参见下文). • sessionTimeout : 整数,设定了服务器创建TLS 会话标示符(TLS session identifiers)和 TLS 会话票 据(TLS session tickets)后的超时时间(单位:秒)。更多细节参见:SSL_CTX_set_timeout (htt p://www.openssl.org/docs/ssl/SSL_CTX_set_timeout.html) 。 • ticketKeys : 一个 48 字节的 Buffer 实例,由 16 字节的前缀,16 字节的 hmac key,16 字节的 AES ke y 组成。可用用它来接受 tls 服务器实例上的 tls会话票据(tls session tickets)。 注意: 自动在集群模块( cluster module)工作进程间共享。 • sessionIdContext : 会话恢复(session resumption)的标示符字符串。如果 requestCert 为 tru e 。默认值为命令行生成的 MD5 哈希值。否则不提供默认值。 • secureProtocol : SSL 使用的方法,例如, SSLv3_method 强制 SSL 版本为3。可能的值定义于你所安 装的 OpenSSL 中的常量SSL_METHODS (http://www.openssl.org/docs/ssl/ssl.html#DEALING_WI TH_PROTOCOL_METHODS) 。 • secureOptions : 设置服务器配置。例如设置 SSL_OP_NO_SSLv3 可用禁用 SSLv3 协议。所有可用的 参数见SSL_CTX_set_options (https://www.openssl.org/docs/ssl/SSL_CTX_set_options.html) 响应服务器的简单例子: 第 30 章 TLS/SSL | 297 var tls = require('tls'); var fs = require('fs'); var options = { key: fs.readFileSync('server-key.pem'), cert: fs.readFileSync('server-cert.pem'), // This is necessary only if using the client certificate authentication. requestCert: true, // This is necessary only if the client uses the self-signed certificate. ca: [ fs.readFileSync('client-cert.pem') ] }; var server = tls.createServer(options, function(socket) { console.log('server connected', socket.authorized ? 'authorized' : 'unauthorized'); socket.write("welcome!\n"); socket.setEncoding('utf8'); socket.pipe(socket); }); server.listen(8000, function() { console.log('server bound'); }); 或 var tls = require('tls'); var fs = require('fs'); var options = { pfx: fs.readFileSync('server.pfx'), // This is necessary only if using the client certificate authentication. requestCert: true, }; var server = tls.createServer(options, function(socket) { console.log('server connected', socket.authorized ? 'authorized' : 'unauthorized'); socket.write("welcome!\n"); socket.setEncoding('utf8'); socket.pipe(socket); }); server.listen(8000, function() { 第 30 章 TLS/SSL | 298 console.log('server bound'); }); 你可以通过 openssl s_client 连接服务器来测试: openssl s_client -connect 127.0.0.1:8000 () tls.connect(options[, callback])tls.connect(options[, callback]) tls.connect(port[, host][, options][, callback])tls.connect(port[, host][, options][, callback]) 创建一个新的客户端连接到指定的端口和主机( port and host )(老版本 API),或者 options.port 和 opti ons.host (如果忽略 host ,默认为 localhost )。 options 是一个包含以下值得对象: • host : 客户端需要连接到的主机 • port : 客户端需要连接到的端口 • socket : 在指定的 socket (而非新建)上建立安全连接。如果这个参数有值,将忽略 host 和 port 参 数。 • path : 创建 到参数 path 的 unix socket 连接。如果这个参数有值,将忽略 host 和 port 参数。 • pfx : 包含私钥,证书和客户端( PFX 或 PKCS12 格式)的 CA 证书的字符串或 Buffer 缓存。 • key : 包含客户端( PEM 格式)的 私钥的字符串或 Buffer 缓存。可以是 keys 数组。 • passphrase : 私钥或 pfx 的密码字符串。 • cert : 包含客户端证书key(PEM 格式)字符串或缓存 Buffer 。(可以是certs的数组)。 • ca : 信任的证书(PEM 格式)的字符串/缓存数组。如果忽略这个参数,将会使用"root" CAs ,比如 VeriS ign。用来授权连接。 • rejectUnauthorized : 如果为 true ,服务器证书根据 CAs 列表授权列表验证。如果验证失败,触发 'erro r' 事件; err.code 包含 OpenSSL 错误代码。默认: true 。 • NPNProtocols : NPN 协议的字符串或 Buffer 数组。 Buffer 必须有以下格式 0x05hello0x05world ,第 一个字节是下一个协议名字的长度。(传的数组通常非常简单,比如: ['hello', 'world'] )。 • servername : SNI(域名指示 Server Name Indication) TLS 扩展的服务器名。 第 30 章 TLS/SSL | 299 • secureProtocol : SSL 使用的方法,例如, SSLv3_method 强制 SSL 版本为3。可能的值定义于你所安 装的 OpenSSL 中的常量SSL_METHODS (http://www.openssl.org/docs/ssl/ssl.html#DEALING_WI TH_PROTOCOL_METHODS) 。 • session : 一个 Buffer 实例, 包含 TLS 会话. 参数 callback 添加到 'secureConnect' (页 0) 事件上,其效果如同监听器。 tls.connect() 返回一个 tls.TLSSocket (页 0) 对象。 这是一个简单的客户端应答服务器例子: var tls = require('tls'); var fs = require('fs'); var options = { // These are necessary only if using the client certificate authentication key: fs.readFileSync('client-key.pem'), cert: fs.readFileSync('client-cert.pem'), // This is necessary only if the server uses the self-signed certificate ca: [ fs.readFileSync('server-cert.pem') ] }; var socket = tls.connect(8000, options, function() { console.log('client connected', socket.authorized ? 'authorized' : 'unauthorized'); process.stdin.pipe(socket); process.stdin.resume(); }); socket.setEncoding('utf8'); socket.on('data', function(data) { console.log(data); }); socket.on('end', function() { server.close(); }); 或 var tls = require('tls'); var fs = require('fs'); var options = { pfx: fs.readFileSync('client.pfx') 第 30 章 TLS/SSL | 300 }; var socket = tls.connect(8000, options, function() { console.log('client connected', socket.authorized ? 'authorized' : 'unauthorized'); process.stdin.pipe(socket); process.stdin.resume(); }); socket.setEncoding('utf8'); socket.on('data', function(data) { console.log(data); }); socket.on('end', function() { server.close(); }); 类: tls.TLSSocket类: tls.TLSSocket net.Socket (net.html#net_class_net_socket) 实例的封装,取代内部 socket 读写程序,执行透明的输入/输 出数据的加密/解密。 new tls.TLSSocket(socket, options)new tls.TLSSocket(socket, options) 从现有的 TCP socket 里构造一个新的 TLSSocket 对象。 socket 一个 net.Socket (net.html#net_class_net_socket) 的实例 options 一个包含以下属性的对象: • secureContext : 来自 tls.createSecureContext( ... ) 的可选 TLS 上下文对象。 • isServer : 如果为 true, TLS socket 将会在服务器模式(server-mode)初始化。 • server : 一个可选的 net.Server (net.html#net_class_net_server) 实例 • requestCert : 可选, 参见 tls.createSecurePair (页 0) • rejectUnauthorized : 可选, 参见 tls.createSecurePair (页 0) • NPNProtocols : 可选, 参见 tls.createServer (页 295) • SNICallback : 可选, 参见 tls.createServer (页 295) 第 30 章 TLS/SSL | 301 • session : 可选, 一个 Buffer 实例, 包含 TLS 会话 • requestOCSP : 可选, 如果为 true - OCSP 状态请求扩展将会被添加到客户端 hello,并且 OCSPResp onse 事件将会在 socket 上建立安全通讯前触发。 tls.createSecureContext(details)tls.createSecureContext(details) 创建一个凭证(credentials)对象,包含字典有以下的key: • pfx : 包含 PFX 或 PKCS12 加密的私钥,证书和服务器的 CA 证书(PFX 或 PKCS12 格式)字符串或缓 存 Buffer 。( key , cert 和 ca 互斥)。 • key : 包含 PEM 加密过的私钥的字符串。 • passphrase : 私钥或 pfx 的密码字符串。 • cert : 包含 PEM 加密过的证书的字符串。 • ca : 信任的 PEM 加密过的可信任的证书(PEM 格式)字符串/缓存数组。 • crl :PEM 加密过的 CRLs(证书撤销列表) • ciphers : 要使用或排除的密码(cipher)字符串。更多格式上的细节参见 http://www.openssl.org/docs/ apps/ciphers.html#CIPHER_LIST_FORMAT • honorCipherOrder : 当选择一个密码(cipher) 时, 使用服务器配置,而不是客户端的。 更多细节参见 tl s 模块文档。 如果没给 'ca' 细节,node.js 将会使用默认的公开信任的 CAs 列表(参见http://mxr.mozilla.org/mozilla/sourc e/security/nss/lib/ckfw/builtins/certdata.txt)。 tls.createSecurePair([context][, isServer][, requestCert][, rejectUnauthorized])tls.createSecurePair([context][, isServer][, requestCert][, rejectUnauthorized]) 创建一个新的安全对(secure pair)对象,包含2个流,其中一个读/写加密过的数据,另外一个读/写明文数 据。通常加密端数据来自是从输入的加密数据流,另一端被当做初始加密流。 • credentials : 来自 tls.createSecureContext( ... ) 的安全上下文对象。 • isServer : 是否以服务器/客户端模式打开这个 tls 连接。 • requestCert : 是否服务器需要连接的客户端发送证书。仅适用于服务端连接。 第 30 章 TLS/SSL | 302 • rejectUnauthorized :非法证书时,是否服务器需要自动拒绝客户端。启用 requestCert 后,才适用于服务 器。 tls.createSecurePair() 返回一个安全对(SecurePair)对象,包含明文 cleartext 和 密文 encrypted 流 。 ?注意: cleartext 和 tls.TLSSocket (页 0) 拥有相同的 API。 类: SecurePair类: SecurePair 通过 tls.createSecurePair 返回。 事件: 'secure'事件: 'secure' 一旦安全对(SecurePair)成功建立一个安全连接,安全对(SecurePair)将会触发这个事件。 和检查服务器 'secureConnection' 事件一样,pair.cleartext.authorized 必须检查确认是否适用的证书是授权 过的。 类: tls.Server类: tls.Server 这是 net.Server 的子类,拥有相同的方法。这个类接受适用 TLS 或 SSL 的加密连接,而不是接受原始 TCP 连接。 事件: 'secureConnection'事件: 'secureConnection' function (tlsSocket) {} 新的连接握手成功后回触发这个事件。参数是 tls.TLSSocket (页 0) 实例。它拥有常用的流方法和事件。 socket.authorized 是否客户端被证书(服务器提供)授权。如果 socket.authorized 为 false, socket.auth orizationError 是如何授权失败。值得一提的是,依赖于 TLS 服务器的设置,你的未授权连接可能也会被接受。 socket.authorizationError 如何授权失败。值得一提的是,依赖于 TLS 服务器的设置,你的未授权连接可能也 会被接受。 socket.npnProtocol 包含选择的 NPN 协议的字符串. socket.servername 包含 SNI 请求的服务 器名的字符串。 第 30 章 TLS/SSL | 303 事件: 'clientError'事件: 'clientError' function (exception, tlsSocket) { } 在安全连接建立前,客户端连接触发 'error' 事件会转发到这里来。 tlsSocket 是 tls.TLSSocket (页 0),错误是从这里触发的。 事件: 'newSession'事件: 'newSession' function (sessionId, sessionData, callback) { } 创建 TLS 会话的时候会触发。可能用来在外部存储器里存储会话。 callback 必须最后调用,否则没法从安全连 接发送/接收数据。 注意: 添加这个事件监听器仅会在连接连接时有效果。 事件: 'resumeSession'事件: 'resumeSession' function (sessionId, callback) { } 当客户端想恢复之前的 TLS 会话时会触发。事件监听器可能会使用 sessionId 到外部存储器里查找,一旦结束 会触发 callback(null, sessionData) 。如果会话不能恢复(比如不存在这个会话),可能会调用 callback(null, null) 。调用 callback(err) 将会终止连接,并销毁 socket。 注意: 添加这个事件监听器仅会在连接连接时有效果。 事件: 'OCSPRequest'事件: 'OCSPRequest' function (certificate, issuer, callback) { } 当客户端发送证书状态请求时会触发。你可以解析服务器当前的证书,来获取 OCSP 网址和证书 id,获取 OCS P 响应调用 callback(null, resp) ,其中 resp 是 Buffer 实例。 证书( certificate )和发行者( issuer ) 都 是初级表达式缓存( Buffer DER-representations of the primary)和证书的发行者。它可以用来获取 OCS P 证书和 OCSP 终点网址。 可以调用 callback(null, null) ,表示没有 OCSP 响应。 调用 callback(err) 可能会导致调用 socket.destroy(err) 。 第 30 章 TLS/SSL | 304 典型流程: 1. 客户端连接服务器,并发送 OCSPRequest (通过 ClientHello 里的状态信息扩展)。 2. 服务器收到请求,调用 OCSPRequest 事件监听器。 3. 服务器从 certificate 或 issuer 获取 OCSP 网址,并执行 OCSP request (http://en.wikipedia.org/wik i/OCSP_stapling) 到 CA 4. 服务器 CA 收到 OCSPResponse , 并通过 callback 参数送回到客户端 5. 客户端验证响应,并销毁 socket 或 执行握手 注意: 如果证书是自签名的,或者如果发行者不再根证书列表里(你可以通过参数提供一个发行者)。 issuer 就 可能为 null。 注意: 添加这个事件监听器仅会在连接连接时有效果。 注意: 你可以能想要使用 npm 模块(比如 asn1.js (http://npmjs.org/package/asn1.js) )来解析证书。 server.listen(port[, host][, callback])server.listen(port[, host][, callback]) 在指定的端口和主机上开始接收连接。如果 host 参数没传,服务接受通过 IPv4 地址( INADDR_ANY )的直 连。 这是异步函数。当服务器已经绑定后回调用最后一个参数 callback 。 更多信息参见 net.Server 。 server.close()server.close() 停止服务器,不再接收新连接。这是异步函数,当服务器触发 'close' 事件后回最终关闭。 server.address()server.address() 返回绑定的地址,地址家族名和服务器端口。更多信息参见 net.Server.address() (net.html#net_server_add ress) 第 30 章 TLS/SSL | 305 server.addContext(hostname, context)server.addContext(hostname, context) 如果客户端请求 SNI 主机名和传入的 hostname 相匹配,将会用到安全上下文(secure context)。 contex t 可以包含 key , cert , ca 和/或 tls.createSecureContext options 参数的其他任何属性。 server.maxConnectionsserver.maxConnections 设置这个属性可以在服务器的连接数达到最大值时拒绝连接。 server.connectionsserver.connections 当前服务器连接数。 类: CryptoStream类: CryptoStream 稳定性: 0 - 抛弃. 使用 tls.TLSSocket 替代. 这是一个加密的流 cryptoStream.bytesWrittencryptoStream.bytesWritten 底层 socket 写字节访问器(bytesWritten accessor)的代理,将会返回写到 socket 的全部字节数。包括 TL S 的开销。 类: tls.TLSSocket类: tls.TLSSocket net.Socket (net.html#net_class_net_socket) 实例的封装,透明的加密写数据和所有必须的 TLS 协商。 这个接口实现了一个双工流接口。它包含所有常用的流方法和事件。 事件: 'secureConnect'事件: 'secureConnect' 新的连接成功握手后回触发这个事件。无论服务器证书是否授权,都会调用监听器。用于用户测试 tlsSocket.aut horized 看看如果服务器证书已经被指定的 CAs 签名。如果 tlsSocket.authorized === false ,可以在 tlsSoc 第 30 章 TLS/SSL | 306 ket.authorizationError 里找到错误。如果使用了 NPN,你可以检 tlsSocket.npnProtocol 获取协商协议(neg otiated protocol)。 事件: 'OCSPResponse'事件: 'OCSPResponse' function (response) { } 如果启用 requestOCSP 参赛会触发这个事件。 response 是缓存对象,包含服务器的 OCSP 响应。 一般来说, response 是服务器 CA 签名的对象,它包含服务器撤销证书状态的信息。 tlsSocket.encryptedtlsSocket.encrypted 静态 boolean 变量,一直是 true 。可以用来区别 TLS socket 和 常规对象。 tlsSocket.authorizedtlsSocket.authorized boolean 变量,如果 对等实体证书( peer's certificate)被指定的某个 CAs 签名,返回 true ,否则 false 。 tlsSocket.authorizationErrortlsSocket.authorizationError 对等实体证书( peer's certificate)没有验证通过的原因。当 tlsSocket.authorized === false 时,这个属性才 可用。 tlsSocket.getPeerCertificate([ detailed ])tlsSocket.getPeerCertificate([ detailed ]) 返回一个代表对等实体证书( peer's certificate)的对象。这个返回对象有一些属性和证书内容相对应。如果参 数 detailed 是 true ,将会返回包含发行者 issuer 完整链。如果 false ,仅有顶级证书没有发行者 issuer 属 性。 例子: { subject: { C: 'UK', ST: 'Acknack Ltd', L: 'Rhys Jones', O: 'node.js', OU: 'Test TLS Certificate', CN: 'localhost' }, 第 30 章 TLS/SSL | 307 issuerInfo: { C: 'UK', ST: 'Acknack Ltd', L: 'Rhys Jones', O: 'node.js', OU: 'Test TLS Certificate', CN: 'localhost' }, issuer: { ... another certificate ... }, raw: < RAW DER buffer >, valid_from: 'Nov 11 09:52:22 2009 GMT', valid_to: 'Nov 6 09:52:22 2029 GMT', fingerprint: '2A:7A:C2:DD:E5:F9:CC:53:72:35:99:7A:02:5A:71:38:52:EC:8A:DF', serialNumber: 'B9B0D332A1AA5635' } 如果 peer 没有提供证书,返回 null 或空对象。 tlsSocket.getCipher()tlsSocket.getCipher() 返回一个对象,它代表了密码名和当前连接的 SSL/TLS 协议的版本。 例子: { name: 'AES256-SHA', version: 'TLSv1/SSLv3' } 更多信息参见http://www.openssl.org/docs/ssl/ssl.html#DEALING_WITH_CIPHERS 里的 SSL_CIPHE R_get_name() 和 SSL_CIPHER_get_version() 。 tlsSocket.renegotiate(options, callback)tlsSocket.renegotiate(options, callback) 初始化 TLS 重新协商进程。参数 options 可能包含以下内容: rejectUnauthorized , requestCert (细节参见 tls.createServer (页 295))。一旦重新协商成(renegotiation)功完成,将会执行 callback(err) ,其中 err 为 null 。 注意: 当安全连接建立后,可以用这来请求对等实体证书( peer's certificate)。 注意: 作为服务器运行时, handshakeTimeout 超时后,socket 将会被销毁。 tlsSocket.setMaxSendFragment(size)tlsSocket.setMaxSendFragment(size) 设置最大的 TLS 碎片大小(默认最大值为: 16384 ,最小值为: 512 )。成功的话,返回 true ,否则返回 f alse 。 第 30 章 TLS/SSL | 308 小的碎片包会减少客户端的缓存延迟:大的碎片直到接收完毕后才能被 TLS 层完全缓存,并且验证过完整性;大 的碎片可能会有多次往返,并且可能会因为丢包或重新排序导致延迟。而小的碎片会增加额外的 TLS 帧字节和 C PU 负载,这会减少 CPU 的吞吐量。 tlsSocket.getSession()tlsSocket.getSession() 返回 ASN.1 编码的 TLS 会话,如果没有协商,会返回。连接到服务器时,可以用来加速握手的建立。 tlsSocket.getTLSTicket()tlsSocket.getTLSTicket() 注意: 仅和客户端 TLS socket 打交道。仅在调试时有用,会话重用是,提供 session 参数给 tls.connect 。 返回 TLS 会话票据(ticket),或如果没有协商(negotiated),返回 undefined 。 tlsSocket.address()tlsSocket.address() 返回绑定的地址,地址家族名和服务器端口。更多信息参见 net.Server.address() (net.html#net_server_add ress) 。返回三个属性, 比如: { port: 12346, family: 'IPv4', address: '127.0.0.1' } tlsSocket.remoteAddresstlsSocket.remoteAddress 表示远程 IP 地址(字符串表示),例如: '74.125.127.100' 或 '2001:4860:a005::68' . tlsSocket.remoteFamilytlsSocket.remoteFamily 表示远程 IP 家族, 'IPv4' 或 'IPv6' . tlsSocket.remotePorttlsSocket.remotePort 远程端口(数字表示),列如, 443 . tlsSocket.localAddresstlsSocket.localAddress 本地 IP 地址(字符串表示)。 第 30 章 TLS/SSL | 309 tlsSocket.localPorttlsSocket.localPort 本地端口号(数字表示)。 第 30 章 TLS/SSL | 310 3131 TTYTTY 稳定性: 2 - 不稳定 tty 模块包含 tty.ReadStream 和 tty.WriteStream 类。多数情况下,你不必直接使用这个模块。 当 node 检测到自己正运行于 TTY 上下文时, process.stdin 将会是一个 tty.ReadStream 实例,并且 proc ess.stdout 将会是 tty.WriteStream 实例。检测 node 是否运行在 TTY 上下文的好方法是检测 process.stdo ut.isTTY : $ node -p -e "Boolean(process.stdout.isTTY)" true $ node -p -e "Boolean(process.stdout.isTTY)" | cat false tty.isatty(fd)tty.isatty(fd) 如果 fd 和终端相关联返回 true ,否则返回 false 。 tty.setRawMode(mode)tty.setRawMode(mode) 已经抛弃。使用 tty.ReadStream#setRawMode() (比如 process.stdin.setRawMode() ) 替换。 Class: ReadStreamClass: ReadStream net.Socket 的子类,表示 tty 的可读部分。通常情况,在任何 node 程序里(仅当 isatty(0) 为 true 时), p rocess.stdin 是 tty.ReadStream 的唯一实例。 rs.isRawrs.isRaw Boolean 值,默认为 false 。它代表当前 tty.ReadStream 实例的 "raw" 状态。 rs.setRawMode(mode)rs.setRawMode(mode) mode 需是 true 或 false 。它设定 tty.ReadStream 属性为原始设备或默认。 isRaw 将会设置为结果模 式。 第 31 章 TTY | 312 Class: WriteStreamClass: WriteStream net.Socket 的子类,代表 tty 的可写部分。通常情况下, process.stdout 是 tty.WriteStream 唯一实例(仅 当 isatty(1) 为 true 时)。 ws.columnsws.columns TTY 当前 拥有的列数。触发 "resize" 事件时会更新这个值。 ws.rowsws.rows TTY 当前 拥有的行数。触发 "resize" 事件时会更新这个值。 Event: 'resize'Event: 'resize' function () {} 行或列变化时会触发 refreshSize() 事件。 process.stdout.on('resize', function() { console.log('screen size has changed!'); console.log(process.stdout.columns + 'x' + process.stdout.rows); }); 第 31 章 TTY | 313 3232 UDP/Datagram SocketsUDP/Datagram Sockets 稳定性: 3 - 稳定 调用 require('dgram') ,可以使用数据报文 sockets(Datagram sockets)。 重要提醒: dgram.Socket#bind() 的行为在 v0.10 做了改动 ,它总是异步的。如果你的代码像下面的一样: var s = dgram.createSocket('udp4'); s.bind(1234); s.addMembership('224.0.0.114'); 现在需要改为: var s = dgram.createSocket('udp4'); s.bind(1234, function() { s.addMembership('224.0.0.114'); }); dgram.createSocket(type[, callback])dgram.createSocket(type[, callback]) • type 字符串. 'udp4' 或 'udp6' • callback 函数. 附加到 message 事件的监听器。可选参数。 • 返回: Socket 对象 创建指定类型的数据报文(datagram) Socket。有效类型是 udp4 和 udp6 接受一个可选的回调,会被添加为 message 的监听事件。 如果你想接收数据报文(datagram)可以调用 socket.bind() 。 socket.bind() 将会绑定到所有接口("all interfa ces")的随机端口上( udp4 和 udp6 sockets 都适用)。你可以通过 socket.address().address 和 sock et.address().port 获取地址和端口。 dgram.createSocket(options[, callback])dgram.createSocket(options[, callback]) • options 对象 • callback 函数. 给 message 事件添加事件监听器. • 返回: Socket 对象 参数 options 必须包含 type 值( udp4 或 udp6 ),或可选的 boolean 值 reuseAddr 。 第 32 章 UDP/Datagram Sockets | 315 当 reuseAddr 为 true 时, socket.bind() 将会重用地址,即使另一个进程已经绑定 socket。 reuseAddr 默认为 false 。 回调函数为可选参数,作为 message 事件的监听器。 如果你想接受数据报文(datagram),可以调用 socket.bind() 。 socket.bind() 将会绑定到所有接口( "all inte rfaces" )地址的随机端口上( udp4 和 udp6 sockets 都适用)。你可以通过 socket.address().address 和 socket.address().port 获取地址和端口。 Class: dgram.SocketClass: dgram.Socket 报文数据 Socket 类封装了数据报文(datagram) 函数。必须通过 dgram.createSocket(...) 函数创建。 Event: 'message'Event: 'message' • msg 缓存对象. 消息。 • rinfo 对象. 远程地址信息。 当 socket 上新的数据报文(datagram)可用的时候,会触发这个事件。 msg 是一个缓存, rinfo 是一个包含发 送者地址信息的对象 socket.on('message', function(msg, rinfo) { console.log('Received %d bytes from %s:%d\n', msg.length, rinfo.address, rinfo.port); }); Event: 'listening'Event: 'listening' 当 socket 开始监听数据报文(datagram)时触发。在 UDP socket 创建时触发。 Event: 'close'Event: 'close' 当 socket 使用 close() 关闭时触发。在这个 socket 上不会触发新的消息事件。 Event: 'error'Event: 'error' • exception Error 对象 第 32 章 UDP/Datagram Sockets | 316 当发生错误时触发。 socket.send(buf, offset, length, port, address[, callback])socket.send(buf, offset, length, port, address[, callback]) • buf 缓存对象 或 字符串. 要发送的消息。 • offset 整数. 消息在缓存中得偏移量。 • length 整数. 消息的比特数。 • port 整数. 端口的描述。 • address 字符串. 目标的主机名或 IP 地址。 • callback 函数. 当消息发送完毕的时候调用。可选。 对于 UDP socket,必须指定目标端口和地址。 address 参数可能是字符串,它会被 DNS 解析。 如果忽略地址或者地址是空字符串,将使用 '0.0.0.0' 或 '::0' 替代。依赖于网络配置,这些默认值有可能行也可 能不行。 如果 socket 之前没被调用 bind 绑定,则它会被分配一个随机端口并绑定到所有接口( "all interfaces" )地 址( udp4 sockets 的 '0.0.0.0' , udp6 sockets 的 '::0' ) 回调函数可能用来检测 DNS 错误,或用来确定什么时候重用 buf 对象。注意,DNS 查询会导致发送tick延 迟。通过回调函数能确认数据报文(datagram)是否已经发送的 考虑到多字节字符串情况,偏移量和长度是字节长度byte length (页 0),而不是字符串长度。 下面的例子是在 localhost 上发送一个 UDP 包给随机端口: var dgram = require('dgram'); var message = new Buffer("Some bytes"); var client = dgram.createSocket("udp4"); client.send(message, 0, message.length, 41234, "localhost", function(err) { client.close(); }); 关于 UDP 数据报文(datagram) 尺寸关于 UDP 数据报文(datagram) 尺寸 IPv4/v6 数据报文(datagram)的最大长度依赖于 MTU (Maximum Transmission Unit)和 Payload Length 的长度。 第 32 章 UDP/Datagram Sockets | 317 • Payload Length 内容为 16 位宽,它意味着 Payload 的最大字节说不超过 64k,其中包括了头信息和数 据(65,507 字节 = 65,535 − 8 字节 UDP 头 − 20 字节 IP 头);对于环回接口(loopback interfaces)这 是真的,但对于多数主机和网络来说不太现实。 • MTU 能支持数据报文(datagram)的最大值(以目前链路层技术来说)。对于任何连接, IPv4 允许的最 小值为 68 的 MTU ,推荐值为 576 (通常推荐作拨号应用的 MTU ),无论他们是完整接收还是碎片接 收。 对于 IPv6 , MTU 的最小值为 1280 字节,最小碎片缓存大小为 1500 字节。16 字节实在是太小,所 以目前链路层一般最小 MTU 大小为 1500 。 我们不可能知道一个包可能进过的每个连接的MTU。通常发送一个超过接收端 MTU 大小的数据报文(datagra m)会失效。(数据包会被悄悄的抛弃,不会通知发送端数据包没有到达接收端)。 socket.bind(port[, address][, callback])socket.bind(port[, address][, callback]) • port 整数 • address 字符串, 可选 • callback 没有参数的函数, 可选。绑定时会调用回调。 对于 UDP socket,在一个端口和可选地址上监听数据报文(datagram)。如果没有指定地点,系统将会参数监听 所有的地址。绑定完毕后,会触发 "listening" 事件,并会调用传入的回调函数。指定监听事件和回调函数非常有 用。 一个绑定了的数据报文 socket 会保持 node 进程运行来接收数据。 如果绑定失败,会产生错误事件。极少数情况(比如绑定一个关闭的 socket)。这个方法会抛出一个错误。 以下是 UDP 服务器监听端口 41234 的例子: var dgram = require("dgram"); var server = dgram.createSocket("udp4"); server.on("error", function (err) { console.log("server error:\n" + err.stack); server.close(); }); server.on("message", function (msg, rinfo) { console.log("server got: " + msg + " from " + 第 32 章 UDP/Datagram Sockets | 318 rinfo.address + ":" + rinfo.port); }); server.on("listening", function () { var address = server.address(); console.log("server listening " + address.address + ":" + address.port); }); server.bind(41234); // server listening 0.0.0.0:41234 socket.bind(options[, callback])socket.bind(options[, callback]) • options {对象} - 必需. 有以下的属性: • port {Number} - 必需. • address {字符串} - 可选. • exclusive {Boolean} - 可选. • callback {函数} - 可选. options 的可选参数 port 和 address ,以及可选参数 callback ,好像在调用 socket.bind(port, [address], [callback]) (页 0)。 如果 exclusive 是 false (默认),集群进程将会使用相同的底层句柄,允许连接处理共享的任务。当 exclusi ve 为 true 时,句柄不会共享,尝试共享端口也会失败。监听 exclusive 端口的例子如下: socket.bind({ address: 'localhost', port: 8000, exclusive: true }); socket.close()socket.close() 关闭底层 socket 并且停止监听数据。 socket.address()socket.address() 返回一个包含套接字地址信息的对象。对于 UDP socket,这个对象会包含 address , family 和 port 。 第 32 章 UDP/Datagram Sockets | 319 socket.setBroadcast(flag)socket.setBroadcast(flag) • flag Boolean 设置或清除 SO_BROADCAST socket 选项。设置这个选项后,UDP 包可能会发送给一个本地的接口广播地 址。 socket.setTTL(ttl)socket.setTTL(ttl) • ttl 整数 设置 IP_TTL socket 选项。 TTL 表示生存时间(Time to Live),但是在这个上下文中它指的是报文允许通 过的 IP 跃点数。各个转发报文的路由器或者网关都会递减 TTL。如果 TTL 被路由器递减为0,则它将不会被转 发。改变 TTL 的值通常用于网络探测器或多播。 setTTL() 的参数为 1 到 255 的跃点数。多数系统默认值为 64. socket.setMulticastTTL(ttl)socket.setMulticastTTL(ttl) • ttl 整数 设置 IP_MULTICAST_TTL socket 选项. TTL 表示生存时间(Time to Live),但是在这个上下文中它指的是 报文允许通过的 IP 跃点数。 各个转发报文的路由器或者网关都会递减 TTL。如果 TTL 被路由器递减为0,则它 将不会被转发。改变 TTL 的值通常用于网络探测器或多播。 setMulticastTTL() 的参数为 1 到 255 的跃点数。多数系统默认值为 1. socket.setMulticastLoopback(flag)socket.setMulticastLoopback(flag) • flag Boolean 设置或清空 IP_MULTICAST_LOOP socket 选项。设置完这个选项后,当该选项被设置时,组播报文也会被 本地接口收到。 socket.addMembership(multicastAddress[, multicastInterface])socket.addMembership(multicastAddress[, multicastInterface]) • multicastAddress 字符串 第 32 章 UDP/Datagram Sockets | 320 • multicastInterface 字符串, 可选 告诉内核加入广播组,选项为 IP_ADD_MEMBERSHIP socket 如果没有指定 multicastInterface ,操作系统会给所有可用的接口添加关系。 socket.dropMembership(multicastAddress[, multicastInterface])socket.dropMembership(multicastAddress[, multicastInterface]) • multicastAddress 字符串 • multicastInterface 字符串, 可选 和 addMembership 相反 - 用 IP_DROP_MEMBERSHIP 选项告诉内核离开广播组 。 如果没有指定 multic astInterface ,操作系统会移除所有可用的接口关系。 socket.unref()socket.unref() 在 socket 上调用 unref 允许程序退出,如果这是在事件系统中唯一的活动 socket。如果 socket 已经 unre f ,再次调用 unref 将会无效。 socket.ref()socket.ref() 和 unref 相反,如果这是唯一的 socket,在一个之前被 unref 了的 socket 上调用 ref 将不会让程序退出(缺 省行为)。如果一个 socket 已经被 ref,则再次调用 ref 将会无效。 第 32 章 UDP/Datagram Sockets | 321 3333 URLURL 稳定性: 3 - 稳定 这个模块包含分析和解析 URL 的工具。调用 require('url') 来访问模块。 解析 URL 对象有以下内容,依赖于他们是否在 URL 字符串里存在。任何不在 URL 字符串里的部分,都不会出 现在解析对象里。例子如下: 'http://user:pass@host.com:8080/p/a/t/h?query=string#hash' • href : 准备解析的完整的 URL,包含协议和主机(小写)。 例子: 'http://user:pass@host.com:8080/p/a/t/h?query=string#hash' • protocol : 请求协议, 小写. 例子: 'http:' • slashes : 协议要求的斜杠(冒号后) 例子: true 或 false • host : 完整的 URL 小写 主机部分,包含端口信息。 例子: 'host.com:8080' • auth : url 中的验证信息。 例子: 'user:pass' • hostname : 域名中的小写主机名 例子: 'host.com' • port : 主机的端口号 例子: '8080' • pathname : URL 中的路径部分,在主机名后,查询字符前,包含第一个斜杠。 例子: '/p/a/t/h' • search : URL 中得查询字符串,包含开头的问号 例子: '?query=string' • path : pathname 和 search 连在一起 第 33 章 URL | 323 例子: '/p/a/t/h?query=string' • query : 查询字符串中得参数部分,或者使用 querystring.parse() 解析后返回的对象。 例子: 'query=string' or {'query':'string'} • hash : URL 的 “#” 后面部分(包括 # 符号) 例子: '#hash' URL 模块提供了以下方法: () url.parse(urlStr[, parseQueryString][, slashesDenoteHost])url.parse(urlStr[, parseQueryString][, slashesDenoteHost]) 输入 URL 字符串,返回一个对象。 第二个参数为 true 时,使用 querystring 来解析查询字符串。如果为 true , query 属性将会一直赋值为对 象,并且 search 属性将会一直是字符串(可能为空)。默认为 false 。 第三个参数为 true ,把 //foo/bar 当做 { host: 'foo', pathname: '/bar' } ,而不是 { pathname: '//foo/bar' } 。默认为 false 。 url.format(urlObj)url.format(urlObj) 输入一个解析过的 URL 对象,返回格式化过的字符串。 格式化的工作流程: • href 会被忽略 • protocol 无论是否有末尾的 : (冒号),会同样的处理 • http , https , ftp , gopher , file 协议会被添加后缀 :// • mailto , xmpp , aim , sftp , foo , 等协议添加后缀 : • slashes 如果协议需要 :// ,设置为 true • 仅需对之前列出的没有斜杠的协议,比如议 mongodb://localhost:8000/ • auth 如果出现将会使用. 第 33 章 URL | 324 • hostname 仅在缺少 host 时使用 • port 仅在缺少 host 时使用 • host 用来替换 hostname 和 port • pathname 无论结尾是否有 / 将会同样处理 • search 将会替代 query属性 • 无论前面是否有 / 将会同样处理 • query (对象; 参见 querystring ) 如果没有 search,将会使用 • hash 无论前面是否有#,都会同样处理 url.resolve(from, to)url.resolve(from, to) 给一个基础 URL, href URL,如同浏览器一样的解析它们可以带上锚点,例如: url.resolve('/one/two/three', 'four') // '/one/two/four' url.resolve('http://example.com/', '/one') // 'http://example.com/one' url.resolve('http://example.com/one', '/two') // 'http://example.com/two' 第 33 章 URL | 325 3434 实用工具实用工具 稳定性: 4 - 锁定 这些函数都在 'util' 模块里。使用 require('util') 来访问他们。 util 模块原先设计的初衷是用来支持 node 的内部 API 的。这里的很多的函数对你的程序来说都非常有用。如果 你觉得这些函数不能满足你的要求,那你可以写自己的工具函数。我们不希望 'util' 模块里添加对于 node 内部 函数无用的扩展。 util.debuglog(section)util.debuglog(section) • section {字符串} 被调试的程序节点部分 • Returns: {Function} 日志函数 用来创建一个有条件的写到 stderr 的函数(基于 NODE_DEBUG 环境变量)。如果 section 出现在环境变 量里,返回函数将会和 console.error() 类似。否则,返回一个空函数。 例如: javascript var debuglog = util.debuglog('foo'); var bar = 123; debuglog('hello from foo [%d]', bar); 如果这个程序以 NODE_DEBUG=foo 的环境运行,将会输出: FOO 3245: hello from foo [123] 3245 是进程 ID。如果没有运行在这个环境变量里,将不会打印任何东西。 可以用逗号切割多个 NODE_DEBUG 环境变量。例如: NODE_DEBUG=fs,net,tls 。 util.format(format[, ...])util.format(format[, ...]) 使用第一个参数返回一个格式化的字符串,类似 printf 。 第一个参数是字符串,它包含 0 或更多的占位符。每个占位符被替换成想要参数转换的值。支持的占位符包括: • %s - 字符串. • %d - 数字 (整数和浮点数). • %j - JSON. 如果参数包含循环引用,将会用字符串替换R 第 34 章 实用工具 | 327 • %% - 单独一个百分号 ( '%' )。 不会消耗一个参数。 如果占位符没有包含一个相应的参数,占位符不会被替换。 util.format('%s:%s', 'foo'); // 'foo:%s' 如果参数超过占位符,多余的参数将会用 util.inspect() 转换成字符串,并拼接在一起,用空格隔开。 util.format('%s:%s', 'foo', 'bar', 'baz'); // 'foo:bar baz' 如果第一个参数不是格式化字符串,那么 util.format() 会返回所有参数拼接成的字符串(空格分割)。每个参数 都会用 util.inspect() 转换成字符串。 util.format(1, 2, 3); // '1 2 3' util.log(string)util.log(string) 在 stdout 输出并带有时间戳. require('util').log('Timestamped message.'); util.inspect(object[, options])util.inspect(object[, options]) 返回一个对象的字符串表现形式,在代码调试的时候非常有用。 通过加入一些可选选项,来改变对象的格式化输出形式: • showHidden - 如果为 true ,将会显示对象的不可枚举属性。默认为 false 。 • depth - 告诉 inspect 格式化对象时递归多少次。这在格式化大且复杂对象时非常有用。默认为 2 。如 果想无穷递归的话,传 null 。 • colors - 如果为 true , 输出内容将会格式化为有颜色的代码。默认为 false , 颜色可以自定义,参见下 文。 • customInspect - 如果为 false , 那么定义在被检查对象上的inspect(depth, opts) 方法将不会被调用。 默认为true。 检查 util 对象上所有属性的例子: 第 34 章 实用工具 | 328 var util = require('util'); console.log(util.inspect(util, { showHidden: true, depth: null })); 当被调用的时候,参数值可以提供自己的自定义inspect(depth, opts)方法。该方法会接收当前的递归检查深 度,以及传入util.inspect()的其他参数。 自定义自定义 util.inspectutil.inspect 颜色颜色 util.inspect 通过 util.inspect.styles 和 util.inspect.colors 对象,自定义全局的输出颜色, util.inspect.styles 和 util.inspect.colors 组成风格颜色的一对映射。 高亮风格和他们的默认值: * 数字 (黄色) * boolean (黄色) * 字符串 (绿色) * date (洋红) * regexp (红色) * null (粗体) * undefined (斜体) * special - (青绿色) * name (内部用,不是风格) 预定义的颜色为: white , 斜体 , black , blue , cyan , 绿色 , 洋红 , 红色 和 黄色 . 以及 粗体 , 斜体 , 下 划线 和 反选 风格. 对象上德自定义对象上德自定义 inspect()inspect() 函数函数 对象也能自定义 inspect(depth) 函数, 当使用util.inspect()检查该对象的时候,将会执行对象自定义的检查方 法: var util = require('util'); var obj = { name: 'nate' }; obj.inspect = function(depth) { return '{' + this.name + '}'; }; util.inspect(obj); // "{nate}" 你可以返回另外一个对象,返回的字符串会根据返回的对象格式化。这和 JSON.stringify() 的工作流程类似。 Y ou may also return another Object entirely, and the returned 字符串 will be formatted according to the returned Object. This is similar to how JSON.stringify() works: var obj = { foo: 'this will not show up in the inspect() output' }; obj.inspect = function(depth) { return { bar: 'baz' }; }; 第 34 章 实用工具 | 329 util.inspect(obj); // "{ bar: 'baz' }" util.isArray(object)util.isArray(object) Array.isArray 的内部别名。 如果参数 "object" 是数组,返回 true ,否则返回 false 。 var util = require('util'); util.isArray([]) // true util.isArray(new Array) // true util.isArray({}) // false util.isRegExp(object)util.isRegExp(object) 如果参数 "object" 是 RegExp 返回 true ,否则返回 false 。 var util = require('util'); util.isRegExp(/some regexp/) // true util.isRegExp(new RegExp('another regexp')) // true util.isRegExp({}) // false util.isDate(object)util.isDate(object) 如果参数 "object" 是 Date 返回 true ,否则返回 false 。 var util = require('util'); util.isDate(new Date()) // true util.isDate(Date()) 第 34 章 实用工具 | 330 // false (without 'new' returns a String) util.isDate({}) // false util.isError(object)util.isError(object) 如果参数 "object" 是 Error 返回 true ,否则返回 false 。 var util = require('util'); util.isError(new Error()) // true util.isError(new TypeError()) // true util.isError({ name: 'Error', message: 'an error occurred' }) // false util.inherits(constructor, superConstructor)util.inherits(constructor, superConstructor) 从一个构造函数constructor (https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Obj ect/constructor) 继承原型方法到另一个。构造函数的原型将被设置为一个新的从超类( superConstructo r )创建的对象。 通过 constructor.super_ 属性可以访问 superConstructor 。 var util = require("util"); var events = require("events"); function MyStream() { events.EventEmitter.call(this); } util.inherits(MyStream, events.EventEmitter); MyStream.prototype.write = function(data) { this.emit("data", data); } var stream = new MyStream(); console.log(stream instanceof events.EventEmitter); // true console.log(MyStream.super_ === events.EventEmitter); // true 第 34 章 实用工具 | 331 stream.on("data", function(data) { console.log('Received data: "' + data + '"'); }) stream.write("It works!"); // Received data: "It works!" util.deprecate(function, string)util.deprecate(function, string) 标明该方法不要再使用。 exports.puts = exports.deprecate(function() { for (var i = 0, len = arguments.length; i < len; ++i) { process.stdout.write(arguments[i] + '\n'); } }, 'util.puts: Use console.log instead') 返回一个修改过的函数,默认情况下仅警告一次。如果设置了 --no-deprecation 该函数不做任何事。如果设 置了 --throw-deprecation ,如果使用了该 API 应用将会抛出异常 util.debug(string)util.debug(string) 稳定性: 0 - 抛弃: 使用 console.error() 替换。 console.error 的前身。 util.error([...])util.error([...]) 稳定性: 0 - 抛弃: 使用 console.error() 替换。 console.error 的前身。 util.puts([...])util.puts([...]) 稳定性: 0 - 抛弃:使用 console.log() 替换。 console.log 的前身。 第 34 章 实用工具 | 332 util.print([...])util.print([...]) 稳定性: 0 - 抛弃: 使用 console.log() 替换。 console.log 的前身。 util.pump(readableStream, writableStream[, callback])util.pump(readableStream, writableStream[, callback]) 稳定性: 0 - 抛弃: Use readableStream.pipe(writableStream) stream.pipe 的前身。 第 34 章 实用工具 | 333 3535 虚拟机虚拟机 稳定性: 3 - 稳定 可以通过以下方法访问该模块: var vm = require('vm'); JavaScript 可以立即编译立即执行,也可以编译,保存,之后再运行。 vm.runInThisContext(code[, options])vm.runInThisContext(code[, options]) vm.runInThisContext() 对 参数 code 编译,运行并返回结果。 运行的代码没有权限访问本地作用域(local s cope),但是可以访问全局对象。 使用 vm.runInThisContext 和 eval 方法运行同样代码的例子: var localVar = 'initial value'; var vmResult = vm.runInThisContext('localVar = "vm";'); console.log('vmResult: ', vmResult); console.log('localVar: ', localVar); var evalResult = eval('localVar = "eval";'); console.log('evalResult: ', evalResult); console.log('localVar: ', localVar); // vmResult: 'vm', localVar: 'initial value' // evalResult: 'eval', localVar: 'eval' vm.runInThisContext 没有访问本地作用域,所以没有改变 localVar 。 eval 范围了本地作用域,所以改变了 localVar 。 vm.runInThisContext 用起来很像间接调用 eval ,比如 (0,eval)('code') 。但是, vm.runInThisContext 也 包含以下选项: • filename : 允许更改显示在站追踪(stack traces)的文件名。 • displayErrors : 是否在 stderr 上打印错误,抛出异常的代码行高亮显示。会捕获编译时的语法错误,和执 行时抛出的错误。默认为 true 。 • timeout : 中断前代码执行的毫秒数。如果执行终止,将会抛出错误。 第 35 章 虚拟机 | 335 vm.createContext([sandbox])vm.createContext([sandbox]) 如果参数 sandbox 不为空,调用 vm.runInContext 或 script.runInContext 时可以调用沙箱的上下文。以此 方式运行的脚本, sandbox 是全局对象,它保留自己的属性同时拥有标准全局对象(global object (http://es 5.github.io/#x15.1) )拥有的内置对象和函数。 如果参数 sandbox 对象为空,返回一个可用的新且空的上下文相关的沙盒对象。 这个函数对于创建一个可运行多脚本的沙盒非常有用。比如,在模拟浏览器的时候可以使用该函数创建一个用于 表示 window 全局对象的沙箱,并将所有