Nodejs API 学习系列(二)

本文的主要内容是对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请求,这里你可以捕获requestresponse 对象用来做一些过滤,逻辑判断等等,最后通过 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进程 管理。

201603170937192.png-9kB
201603170937192.png-9kB

进程间使用消息通知来共享数据

多进程使用同一端口不冲突的原因

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 类型实现的方法属性可以直接使用(当然包括绑定事件触发函数的方法,注册事件的方法 等)

其他系列文章

Nodejs模块学习笔记
极客学院 nodejs官方文档

未完待续~

推荐阅读更多精彩内容

  • https://nodejs.org/api/documentation.html 工具模块 Assert 测试 ...
    KeKeMars阅读 4,363评论 0 6
  • Node基本 node的最大特性莫过于基于事件驱动的非阻塞I/O模型。 node通过事件驱动的方式处理请求,无须为...
    AkaTBS阅读 1,279评论 0 11
  • Node.js是目前非常火热的技术,但是它的诞生经历却很奇特。 众所周知,在Netscape设计出JavaScri...
    w_zhuan阅读 2,756评论 2 40
  • 内容来自《Node.js开发指南》 核心模块是 Node.js 的心脏,它由一些精简而高效的库组成,为 Node....
    angelwgh阅读 506评论 0 1
  • # 模块机制 node采用模块化结构,按照CommonJS规范定义和使用模块,模块与文件是一一对应关系,即加载一个...
    RichRand阅读 1,071评论 0 3