NodeJs中的非阻塞方法介绍


Posted in NodeJs onJune 05, 2012

首先我们利用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
NodeJs 相关文章推荐
用nodejs写的一个简单项目打包工具
May 11 NodeJs
NodeJS url验证(url-valid)的使用方法
Nov 18 NodeJs
Nodejs使用mysql模块之获得更新和删除影响的行数的方法
Mar 18 NodeJs
NodeJS与HTML5相结合实现拖拽多个文件上传到服务器的实现方法
Jul 26 NodeJs
初识NodeJS服务端开发入门(Express+MySQL)
Apr 07 NodeJs
nodejs开发微信小程序实现密码加密
Jul 11 NodeJs
NodeJS安装图文教程
Apr 19 NodeJs
nodejs中方法和模块用法示例
Dec 24 NodeJs
深入理解nodejs搭建静态服务器(实现命令行)
Feb 05 NodeJs
NodeJs入门教程之定时器和队列
Mar 08 NodeJs
Nodejs 识别图片类型的方法
Aug 15 NodeJs
nodejs语言实现验证码生成功能的示例代码
Oct 13 NodeJs
nodejs win7下安装方法
May 24 #NodeJs
NodeJS的模块写法入门(实例代码)
Mar 07 #NodeJs
nodejs入门详解(多篇文章结合)
Mar 07 #NodeJs
NodeJS 模块开发及发布详解分享
Mar 07 #NodeJs
用nodejs访问ActiveX对象,以操作Access数据库为例。
Dec 15 #NodeJs
NodeJS框架Express的模板视图机制分析
Jul 19 #NodeJs
nodejs 后缀名判断限制代码
Mar 31 #NodeJs
You might like
PHP导出EXCEL快速开发指南--PHPEXCEL的使用详解
2013/06/03 PHP
php中实现记住密码下次自动登录的例子
2014/11/06 PHP
CodeIgniter基于Email类发邮件的方法
2016/03/29 PHP
Yii2主题(Theme)用法详解
2016/07/23 PHP
Zend Framework入门教程之Zend_Session会话操作详解
2016/12/08 PHP
php命名空间设计思想、用法与缺点分析
2019/07/17 PHP
跟着JQuery API学Jquery 之二 属性
2010/04/09 Javascript
jQuery温习篇 强大的JQuery选择器
2010/04/24 Javascript
面向对象的Javascript之二(接口实现介绍)
2012/01/27 Javascript
使用javascript创建快捷方式的简单实例
2013/08/09 Javascript
Js实现网页键盘控制翻页的方法
2014/10/30 Javascript
jquery小火箭返回顶部代码分享
2015/08/19 Javascript
JS截取与分割字符串常用技巧总结
2015/11/10 Javascript
jquery插件bootstrapValidator数据验证详解
2016/11/09 Javascript
Vue实例中生命周期created和mounted的区别详解
2017/08/25 Javascript
vue实现商城购物车功能
2017/11/27 Javascript
微信小程序异步API为Promise简化异步编程的操作方法
2018/08/14 Javascript
uni-app微信小程序登录授权的实现
2020/05/22 Javascript
Python使用matplotlib简单绘图示例
2018/02/01 Python
通过Py2exe将自己的python程序打包成.exe/.app的方法
2018/05/26 Python
浅谈django的render函数的参数问题
2018/10/16 Python
Python线性拟合实现函数与用法示例
2018/12/13 Python
Python多线程处理实例详解【单进程/多进程】
2019/01/30 Python
python提取log文件内容并画出图表
2019/07/08 Python
python实现数据分析与建模
2019/07/11 Python
Python制作微信好友背景墙教程(附完整代码)
2019/07/17 Python
opencv+pyQt5实现图片阈值编辑器/寻色块阈值利器
2020/11/13 Python
python 通过exifread读取照片信息
2020/12/24 Python
详解matplotlib绘图样式(style)初探
2021/02/03 Python
python网络爬虫实现发送短信验证码的方法
2021/02/25 Python
印度领先的眼镜电子商务网站:Lenskart
2019/12/16 全球购物
abstract class和interface有什么区别?
2012/01/03 面试题
司机检讨书
2014/02/13 职场文书
银行主办会计岗位职责
2014/08/13 职场文书
法学专业毕业实习自我鉴定2014
2014/09/27 职场文书
2014年物资管理工作总结
2014/12/02 职场文书