首先我们利用NodeJs先构建一个基本的服务器。
index.js
var requestHandler = require("./requestHandler"); var server = require("./server"); var route = { "/hello": requestHandler.hello, "/upload": requestHandler.upload }; server.start(route);
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已经返回。