NodeJs中的非阻塞方法介绍

  首先我们利用NodeJs先构建一个基本的服务器。

  index.js

  

复制代码 代码如下:

  var requestHandler = require("./requestHandler");

  var server = require("./server");

  var route = {

  "/hello": requestHandler.hello,

  "/upload": requestHandler.upload

  };

  server.start(route);

  server.js

  

复制代码 代码如下:

  server.js

  

复制代码 代码如下:

  var http = require("http");

  var url = require("url");

  exports.start = function(route) {

  var server = http.createServer(function(req, res) {

  var pathName = url.parse(req.url).pathname;

  var handler = route[pathName];

  if (handler) {

  console.log("Through path:" + pathName + ":" + new Date().getTime());

  handler(res);

  } else {

  res.writeHead(404, {"Content-Type": "text/plain"});

  res.end();

  }

  });

  server.listen(8088);

  };

  requestHandler.js

  

复制代码 代码如下:

  exports.hello = function(res) {

  res.writeHead(200, {"Content-Type": "text/plain"});

  res.write("say hello.");

  res.end();

  };

  exports.upload = function(res) {

  res.writeHead(200, {"Content-Type": "text/plain"});

  res.write("upload");

  res.end();

  };

  在cmd中,键入node index.js即可启动。

  但是,上面的代码是阻塞的。如果在createServer的回调函数中,有花费长时间的计算。那么会阻塞node.js的事件轮询。

  NodeJS中,他的高效,关键在于快速的返回事件循环。

  我们将requestHandler.js改造如下,在这个例子中,由于事件循环一直被sleep函数阻塞着,导致createServer的callback无法及时返回。

  

复制代码 代码如下:

  function sleep(milliSecond) {

  var startTime = new Date().getTime();

  console.log(startTime);

  while(new Date().getTime() <= milliSecond + startTime) {

  }

  console.log(new Date().getTime());

  }

  exports.hello = function(res) {

  sleep(20000);

  res.writeHead(200, {"Content-Type": "text/plain"});

  res.write("say hello.");

  res.end();

  };

  exports.upload = function(res) {

  res.writeHead(200, {"Content-Type": "text/plain"});

  res.write("upload");

  res.end();

  };

  那么先键入http://localhost:8088/hello,后键入http://localhost:8088/upload。你会发现,upload虽然不需要花费太多时间,但是却要等到hello完成。

  我们试图找寻异步调用的方法。比如formidable中的上传,经测试是非阻塞的。查看formidable的源码,发现最关键的是下面的代码:

  

复制代码 代码如下:

  IncomingForm.prototype.parse = function(req, cb) {

  this.pause = function() {

  try {

  req.pause();

  } catch (err) {

  // the stream was destroyed

  if (!this.ended) {

  // before it was completed, crash & burn

  this._error(err);

  }

  return false;

  }

  return true;

  };

  this.resume = function() {

  try {

  req.resume();

  } catch (err) {

  // the stream was destroyed

  if (!this.ended) {

  // before it was completed, crash & burn

  this._error(err);

  }

  return false;

  }

  return true;

  };

  this.writeHeaders(req.headers);

  var self = this;

  req

  .on('error', function(err) {

  self._error(err);

  })

  .on('aborted', function() {

  self.emit('aborted');

  })

  .on('data', function(buffer) {

  self.write(buffer);

  })

  .on('end', function() {

  if (self.error) {

  return;

  }

  var err = self._parser.end();

  if (err) {

  self._error(err);

  }

  });

  if (cb) {

  var fields = {}, files = {};

  this

  .on('field', function(name, value) {

  fields[name] = value;

  })

  .on('file', function(name, file) {

  files[name] = file;

  })

  .on('error', function(err) {

  cb(err, fields, files);

  })

  .on('end', function() {

  cb(null, fields, files);

  });

  }

  return this;

  };

  在parse中,将head信息解析出来这段是阻塞的。但是真正上传文件却是在req.on(data)中,是利用了事件驱动,是非阻塞的。也就是说,他的非阻塞模型依赖整个nodeJS事件分派架构。

  那么像sleep那样消耗大量计算,但是又不能依赖nodeJS分派架构的时候怎么办?

  现在介绍一种,类似于html5 WebWorker的方法。

  将requestHandler.js改造如下:

  

复制代码 代码如下:

  var childProcess = require("child_process");

  exports.hello = function(res) {

  var n = childProcess.fork(__dirname + "/subProcess.js");

  n.on('message', function() {

  res.writeHead(200, {"Content-Type": "text/plain"});

  res.write("say hello.");

  res.end();

  });

  n.send({});

  };

  exports.upload = function(res) {

  res.writeHead(200, {"Content-Type": "text/plain"});

  res.write("upload");

  res.end();

  };

  并加入subProcess.js

  

复制代码 代码如下:

  function sleep(milliSecond) {

  var startTime = new Date().getTime();

  console.log(startTime);

  while(new Date().getTime() <= milliSecond + startTime) {

  }

  console.log(new Date().getTime());

  }

  process.on('message', function() {

  sleep(20000);

  process.send({});

  });

  测试,当hello还在等待时,upload已经返回。

  结语:

  大概在最近,我看了博客园上的很多NodeJs文章,大家都认为NodeJS是异步的。但是是何种程度的异步,这个概念就没有几篇文章讲对了。

  其实NodeJS,他是一个双层的架构。C++,和javascript。并且是单线程的。这点尤其重要。Node其实是C++利用v8调用js命令,为了实现调用顺序维护了一个Event序列。因此,在一个js function内部,他的调用绝对会对其他的function产生阻塞。所以,网上所说的process.nextTick和setTimeout等,都不能够产生新的线程,以保证不被阻塞。他所实现的,不过是Event序列的元素顺序问题。 相对于setTimeout,process.nextTick的实现要简单的多,直接加入Event序列的最顶层(有个啥啥事件)。而setTimeout是增加了一个c++线程,在指定的时间将callback加入Event序列

  以Node的file io为例。他的readFile等函数,第二个参数是一个callback。那么node中第一件事就是记录下callback,然后调用底层c++,调用c++开始的过程,你可以看成是异步的。因为那已经到了c++,而不是js这块。所以,exec到callback为止是异步的,http.createServer到触发callback为止是异步的。还有很多,比如mysql的调用方法,不知道大家有没有看过源码,他就是socket发送命令,相信这个过程速度非常快。然后等待回调的过程Node用c++隐藏了,他也是异步的。

  而我这篇文章想说明的是,如果再js端有花费大量时间的运算怎么办。就用我上面所说的方法,用js打开c++的线程,这个subprocess.js,不在node的event序列内部维护。是新的线程,因此不会阻塞其他的js function