本文的主要内容是对nodejs提供的一些重要模块,结合官方API进行介绍,遇到精彩的文章,我会附在文中并标明了出处。主要包括如下8个模块
- buffer 模块
- dns 模块
- process 模块
- child_process 模块
- domain 模块
- cluster 模块
- event 模块
- util 模块
转载请注明出处,多谢支持~
buffer
关于中文乱码
在node.js中,一个字符串的长度与根据该字符串所创建的缓存区的长度并不相同,因为在计算字符串的长度时,是以文字作为一个单位,而在计算缓存区的长度时,是以字节作为一个单位。
比如针对 ”我喜爱编程”这个字符串,该字符串对象的length属性值与根据该字符串创建的buffer对象的length属性值并不相同。因为字符串对象的length属性值获取的是文字个数,而buffer对象的length属性值获取的是缓存区的长度,即缓存区中的字节。
var str = '勇士队加油';
console.log(str.length);//5
var buf = new Buffer(str);
console.log(buf.length);//15
另外,可以使用0开始的序号来取出字符串对象或缓存区中的数据。但是,在获取数据时,字符串对象是以文字作为一个单位,而缓存区对象是以字节作为一个单位。比如,针对一个引用了字符串对象的str变量来说,str2获取的是第三个文字,而针对一个引用了缓存区对象的buf对象来说,buf2获取的是缓存区中的第三个字节数据转换为整数后的数值。如下:
console.log(str[2]);//队
console.log(buf[2]);//135
正确读取文件内容的方式
从上文中可以看出,如果读取文件内容是,恰好不是一个完整文字时,可能会输出错误信息
var fs = require('fs');
var rs = fs.createReadStream('testdata.md', {bufferSize: 11});
var data = '';
rs.on("data", function (trunk){
data += trunk;
});
rs.on("end", function () {
console.log(data);
});
可能会输出如下的内容
事件循���和请求���象构成了Node.js���异步I/O模型的���个基本���素,这也是典���的消费���生产者场景。
造成这个问题的根源在于data += trunk
语句里隐藏的错误,在默认的情况下,trunk是一个Buffer对象。这句话的实质是隐藏了toString的变换的:
data = data.toString() + trunk.toString();
由于汉字不是用一个字节来存储的,导致有被截破的汉字的存在,于是出现乱码。解决这个问题有一个简单的方案,是设置编码集:
var rs = fs.createReadStream('testdata.md', {encoding: 'utf-8', bufferSize: 11});
下面展示一个正确读取文件,并连接buffer对象的方法
var buffers = [];
var nread = 0;
readStream.on('data', function (chunk) {
buffers.push(chunk);
nread += chunk.length;
});
readStream.on('end', function () {
var buffer = null;
switch(buffers.length) {
case 0: buffer = new Buffer(0);
break;
case 1: buffer = buffers[0];
break;
default:
buffer = new Buffer(nread);
for (var i = 0, pos = 0, l = buffers.length; i < l; i++) {
var chunk = buffers[i];
// 把chunk复制到buffer对象从pos位置开始的地方
chunk.copy(buffer, pos);
pos += chunk.length;
}
break;
}
});
buf.copy(targetBuffer,[targetStart],[sourceStart],[sourceEnd]);
在Buffer对象的copy方法中,使用四个参数,第一个参数为必须指定的参数,其余三个参数均为可选参数。第一个参数用于指定复制的目标Buffer对象。第二个参数用于指定目标Buffer对象中从第几个字节开始写入数据,参数值为一个小于目标的Buffer对象长度的整数值,默认值为0(从开始处写入数据)。第三个参数用于指定从复制源Buffer对象中获取数据时的开始位置,默认值为0,即从复制源Buffer对象中的第一个字节开始获取数据,第四个参数用于指定从复制源Buffer对象中获取数据时的结束位置,默认值为复制源Buffer对象的长度,即一直获取完毕复制源Buffer对象中的所有剩余数据。
推荐文章
粉丝日志 - nodejs buffer对象
浅析 nodejs buffer 对象
dns
dns.lookup()
根据域名解析ip地址,设置参数可以返回一个域名对应的多个IP
var dns = require('dns');
dns.lookup('www.baid.com', {all: true} function(err, address, family){
console.log(address)
})
[ { address: '122.10.91.48', family: 4 } ]
注意:lookup函数会受本地host的影响。如,在host文件中配置了
127.0.0.1 www.baidu.com
使用dns.lookup()查询www.baidu.com这个域名时,会返回 127.0.0.1。此时可以考虑使用dns.resolve4()方法来替代解析域名。
dns.lookupService(address, port, callback)
使用dns.lookupService(address, port, callback)方法,该方法依赖getnameinfo底层函数。
callback函数有三个参数(err, hostname, service),service是protocol,为http或https,使用如下所示:
dns.lookupService('127.0.0.1',80,(err,hostname,service)=>{
if(err) console.log(err);
console.log('该IP对应的主机为:'+hostname+' 协议为:'+service);
});
// 该IP对应的主机为:localhost 协议为:http
推荐文章
process 模块
简介
process是一个全局内置对象,可以在代码中的任何位置访问此对象,这个对象代表我们的node.js代码宿主的操作系统进程对象。使用process对象可以截获进程的异常、退出等事件,也可以获取进程的当前目录、环境变量、内存占用等信息,还可以执行进程退出、工作目录切换等操作。
Process模块提供了访问正在运行的进程。child_process模块可以创建子进程,并与他们通信。cluster模块提供了实现共享相同端口的集群服务能力,允许多个请求同时处理。
process实现了EventEmitter接口,exit方法会在当进程退出的时候执行。因为进程退出之后将不再执行事件循环,所有只有那些没有回调函数的代码才会被执行。在下面例子中,setTimeout里面的语句是没有办法执行到的。
process.on('exit', function () {
setTimeout(function () {
console.log('This will not run');
}, 100);
console.log('Bye.');
});
属性
process.pid:当前进程的进程号。
process.version:Node的版本,比如v0.10.18。
process.platform:当前系统平台,比如Linux。
process.title:默认值为“node”,可以自定义该值。
-
process.argv:当前进程的命令行参数数组。
console.log("argv: ",process.argv.slice(2)); //node test.js a b c //argv: [ 'a', 'b', 'c' ]
process.env:指向当前shell的环境变量,比如process.env.HOME。
process.execPath:运行当前进程的可执行文件的绝对路径。
process.memoryUsage():node进程内存的使用情况,rss代表ram的使用情况,vsize代表总内存的使用大小,包括ram和swap;
process.heapTotal,process.heapUsed:分别代表v8引擎内存分配和正在使用的大小。
process.stdout:指向标准输出。
process.stdin:指向标准输入。
process.stderr:指向标准错误。
方法
process.exit():退出当前进程。
process.cwd():返回运行当前脚本的工作目录的路径。_
process.chdir():改变工作目录。
-
process.nextTick():将一个回调函数放在下次事件循环的顶部。
process.nextTick()的例子,指定下次事件循环首先运行的任务。process.nextTick(function () { console.log('Next event loop!'); });
上面代码可以用setTimeout改写,但是nextTick回调的优先级更高,会被放在事件队列的最前面,而settimeout是放在最后面.
setTimeout(function () { console.log('Next event loop!'); }, 0)
事件
-
exit事件
当前进程退出时,会触发exit事件,可以对该事件指定回调函数。这一个用来定时检查模块的状态的好钩子(hook)(例如单元测试),当主事件循环在执行完’exit’的回调函数后将不再执行,所以在exit事件中定义的定时器可能不会被加入事件列表.
process.on('exit', function () { fs.writeFileSync('/tmp/myfile', 'This MUST be saved on exit.'); });
uncaughtException事件
在你接触node之后,你就会发现那些影响了主事件循环的异常会把整个node进程宕掉的。这会是相当严重的问题,所以process提供了另外一个有用的事件uncaughtException
来解决这个问题,当前进程抛出一个没有被捕捉的意外时,会触发uncaughtException事件。
process.on('uncaughtException', function (err) {
console.log('Caught exception: ' + err);
});
setTimeout(function () {
console.log('This will still run.');
}, 2000);
// Intentionally cause an exception, but don't catch it.
nonexistentFunc();
console.log('This will not run.');
我们来看上面的例子,我们注册了uncaughtException事件来捕捉系统异常。执行到nonexistentFunc()时,因为该函数没有定义所以会抛出异常。
Caught exception: ReferenceError: nonexistentFunc is not defined
This will still run.
再看一个例子
var http = require('http');
var server = http.createServer(function(req,res) {
res.writeHead(200, {});
res.end('response');
badLoggingCall('sent response');
console.log('sent response');
});
process.on('uncaughtException', function(e) {
console.log(e);
});
server.listen(8080);
在这里例子中我们创建了一个web服务器,当处理完请求之后,我们会执行badLoggingCall()方法。因为这个方法不存在,所以会有异常抛出。但是我们注册的uncaughtException事件会对异常做出处理,这样服务器不会受到影响得以继续运行。我们会在服务器端记录错误日志
[ReferenceError: badLoggingCall is not defined]
但常规不建议使用该粗略的异常捕获处理,建议使用 domains
child process
child_process是Node.js的一个十分重要的模块,通过它可以实现创建多进程,以利用单机的多核计算资源。虽然,Nodejs天生是单线程单进程的,但是有了child_process模块,可以在程序中直接创建子进程,并使用主进程和子进程之间实现通信,等到子进程运行结束以后,主进程再用回调函数读取子进程的运行结果。
推荐文章
domain
nodejs的尴尬
try catch
无法捕获异步中的异常。所以我们能做的只能是
app.get('/index', function (req, res) {
// 业务逻辑
});
process.on('uncaughtException', function (err) {
logger.error(err);
});
这个时候,虽然我们可以记录下这个错误的日志,且进程也不会异常退出,但是我们是没有办法对发现错误的请求友好返回的,只能够让它超时返回。
这个时候 domain模块就出现了,它可以捕捉异步错误。而我们为了让 domain 模块来接管所有的http请求中的异常,所以把它写成一个中间件是非常方便的。
app.use(function (req, res, next) {
var reqDomain = domain.create();
reqDomain.on('error', function (err) { // 下面抛出的异常在这里被捕获,触发此事件
console.log('捕获到错误');
res.send(500, err.stack); // 成功给用户返回了 500
});
reqDomain.run(next);
});
app.use(function(req,res,next){ .....})
这是一个中间件,用来接收所有http请求,这里你可以捕获request
和 response
对象用来做一些过滤,逻辑判断等等,最后通过 next 来放行本次请求,那么这个中间件就完成了他的一次使命.
然后我们在 process 上将未处理的异常捕捉一下,做到万无一失.
process.on('uncaughtException', function (err) {
console.error("uncaughtException ERROR");
if (typeof err === 'object') {
if (err.message) {
console.error('ERROR: ' + err.message)
}
if (err.stack) {
console.error(err.stack);
}
} else {
console.error('argument is not an object');
}
});
然后抛出错误实践一下,是否能被捕捉
app.get('/err', function (req, res) {
//throw new Error('exception');
setTimeout(function () {
throw new Error('exception'); // 抛出一个异步异常
}, 1000);
})
每次 domian 捕获到错误后,我都在控制台输出了一行提示信息 "捕获到错误" 当前进程并没有因为异常而挂掉,这就是我们要的效果.
我们之所以想到用 setTimeout 就是想模拟一个异步的回调,如果你直接 throw new Error('exception');这样就不是异步了,直接会被 process 上的 uncaughtException 来接管.
进阶文章
cluster
nodejs最大的特点就是单进程、无阻塞运行,并且是异步事件驱动的。Nodejs的这些特性能够很好的解决一些问题,例如在服务器开发中,并发的请求处理是个大问题,阻塞式的函数会导致资源浪费和时间延迟。通过事件注册、异步函数,开发人员可以提高资源的利用率,性能也会改善。既 然Node.js采用单进程、单线程模式,那么在如今多核硬件流行的环境中,单核性能出色的Nodejs如何利用多核CPU呢?
cluster是一个nodejs内置的模块,用于nodejs多核处理。cluster模块,可以帮助我们简化多进程并行化程序的开发难度,轻松构建一个用于负载均衡的集群。
推荐文章
fork其实就是创建子进程的方法,新创建的进程被认为是子进程,而调用fork的进程则是父进程。 子进程和父进程本来是在独立的内存空间中的。但当你使用了fork之后,两者就处在同一个作用域内了。 但是,内存的读写,文件的map,都不会影响对方。也就是说,你创建的进程其实可以相互通信,并且被master进程 管理。
进程间使用消息通知来共享数据
多进程使用同一端口不冲突的原因
util 模块
简介
var util = require("util");
util.inherits(constructor, superConstructor)
util.inherits(constructor, superConstructor)
是一个实现对象间原型继承的方法。JavaScript 的面向对象特性是基于原型的继承,与常见的基于类的不同,JavaScript 没有提供对象继承的语言级别特性,而是通过原型链复制来实现的。inherits方法可以将父类原型链上的方法复制到子类中,实现原型式继承。
var events = require("events");
//MyStream构造函数,在构造函数将this指向本对象
function MyStream() {
events.EventEmitter.call(this);
}
//复制父对象上所有的方法
util.inherits(MyStream, events.EventEmitter);
//对MyStream类添加原型方法
MyStream.prototype.write = function(data) {
this.emit("data", data);
}
var stream = new MyStream();
//由于MyStream继承自EventEmitter,所以其实例stream是MyStream类的实例也是EventEmitter类的实例
console.log(stream instanceof events.EventEmitter); // true
console.log(MyStream.super_ === events.EventEmitter); // true
//父类中的方法调用
stream.on("data", function(data) {
console.log('Received data: "' + data + '"');
})
//子类中的方法调用
stream.write("It works!"); // Received data: "It works!"
event 模块
此模块是一个核心模块,直接引用
var events = require('events');
简介
events模块只提供了一个对象,events.EventEmitter,核心是 事件发射 和 事件监听 功能。每个事件由一个事件名(用于标识事件),和多个参数组成。事件名:字符串,通常表达一定的语义;事件被发射时,监听该事件的函数被依次调用。
监听
var events = require("events");
var emitter = new events.EventEmitter();
emitter.on("/click", function () {
console.log("first event");
})
emitter.on("/click", function () {
console.log("second event");
})
emitter.emit("/click");
注意
- /click是事件名(用于标识事件)
- 可以多个监听,用于监听同一个事件,然后依次执行;
- 需要先监听,后发射;
- 监听是on,发射是emit
- 把emit发射的事件赋值给变量。如果有监听该事件的,则变量值为true,如果无监听该事件,则返回值为false。注意,该变量赋值后不会改变,即
var nn = emitter.emit("/click1");
emitter.on("/click1", function () {
console.log("first event");
})
console.log(nn);// false
只监听一次:
EventEmitter.once(事件名, 回调函数)
,即把上面的on替换为once即可,然后这个只监听一次就失效;
移除事件
EventEmitter.removeListener(事件名, 回调函数名)
var events = require("events");
var emitter = new events.EventEmitter();
var first = function () {
console.log("first event");
}
var second = function () {
console.log("second event");
}
emitter.on("/click", first)
emitter.on("/click", second)
emitter.emit("/click");
emitter.removeListener("/click", first);
console.log("————移除完成————");
emitter.emit("/click");
输出
// first event
// second event
// --移除完成--
// second event
全部移除
var events = require("events");
var emitter = new events.EventEmitter();
var first = function () {
console.log("first event");
}
var second = function () {
console.log("second event");
}
emitter.on("/click", first)
emitter.on("/click", second)
emitter.emit("/click");
emitter.removeAllListeners("/click");
console.log("————移除完成————");
emitter.emit("/click");
输出
// first event
// second event
// --移除完成--
error事件
当遇见异常时会发射error事件,EventEmitter规定,如果没有监听其的监听器,Node.js会把其当成异常,退出程序并打印调用栈。因此需要设置监听其的监听器,避免遇见错误后整个程序崩溃。
var events = require("events");
var emitter = new events.EventEmitter();
var first = function () {
console.log("first event");
}
var error = function (error) {
console.log(error);
}
emitter.on("/click", first)
emitter.on("error", error) //如果没有这一行代码,下面在发射error时会出错然后退出程序
emitter.emit("/click");
emitter.emit("error", error)
console.log("————移除完成————");
emitter.emit("/click");
输出
// first event
// [Function]
// --移除完成--
// first event
实例
任何类型如果继承了该类就是一个事件触发体,继承该类的任何类型都是事件的一个实例(给事件绑定一个函数后,一旦触发事件,马上执行事件绑定函数.
下面例子演示通过继承给对象绑定一个事件,来自一介布衣_events模块的精彩示例
var util = require('util');
var events = require('events');
var Anythin = function (name) {
this.name = name;
}
util.inherits(Anythin, events.EventEmitter);
//创建一只猫
var cat = new Anythin('黑猫');
//绑定事件
cat.on("activity", function (activity) {
console.log(this.name + activity);
});
//创建一只老鼠
var mouse = new Anythin('老鼠');
//绑定事件
mouse.on("activity", function (activity) {
console.log(this.name + activity);
});
//创建屋子的主人
var people = new Anythin('主人');
//绑定事件
people.on("activity", function (activity) {
console.log(this.name + activity);
});
//创建主人的孩子
var child = new Anythin('婴儿');
//绑定事件
child.on("activity", function (activity) {
console.log(this.name + activity);
});
console.log('静静的夜晚,主人一家正在酣睡......');
console.log('黑猫紧盯着黑暗的角落.....');
setTimeout(function(){
console.log('黑猫再也坚持不住了......');
cat.emit("activity",'睡着了');
mouse.emit("activity",'爬出洞口');
people.emit("activity",'闻声而起');
child.emit("activity",'开始哭哭啼啼');
},3000);
上面的例子就是一个万能的造物主,可以创建宇宙中的万事万物.上面创建的一个事件引发体 "黑猫" 由于晚上'上班' 太辛苦,而偷偷去睡觉,这一事件诱因直接导致嚣张的'老鼠'从洞里出来觅食,由于老鼠觅食动作不当而吵醒做梦的'主人'及正在酣睡的'婴儿'
造物主制造的各种角色时已天生有个绑定事件的活动 activity ,这个功劳要归功于:
util.inherits(Anythin, events.EventEmitter);
util 模块也是node.js 中的核心模块,他构建了一些常用的代码, inherits 就是其中之一,让前面的类型继承后面的的类型.所以上面的代码是 Anythin 类型继承了 EventEmitter 类型,所以EventEmitter 类型实现的方法属性可以直接使用(当然包括绑定事件触发函数的方法,注册事件的方法 等)