var http = require('http');
var url = require('url');
http.createServer(function (request, response) {
response.writeHead(200, {'Content-Type': 'text/plain'});
if( url.parse(request.url).pathname == '/wait' ){
var startTime = new Date().getTime();
while (new Date().getTime() < startTime + 15000);
response.write('Thanks for waiting!');
} else{
response.write('Hello!');
}
response.end();
}).listen(8080);
console.log('Server started');
把文件保存为”blocking.js”,并在终端中执行命令”node blocking.js”。
现在我们运行了一个阻塞脚本,当用户访问’/wait’的时候,将运行一个循环,15秒之后打出一句话”Thanks for waiting!”。先打开浏览器,在地址栏输入http://localhost:8080/wait,然后再直接访问http://localhost:8080(记住时间尽量短,不要超过15秒哦)。这时候你应该发现即使是切换到了第二个url,但是牛B的”Hello”还是要等那15秒执行完毕后才打出来,这是肿么了?
Node.js被设计成单线程执行模型,这让它的表现异于其它Web开发技术,比如PHP会为每个连接创建一个新的线程。而在Node.js里,如果前一个请求需要耗时5秒钟,则后一个请求必须等待5秒后才能执行。我们把这个叫“阻塞”,前一个线程“阻塞”了后一个线程。
这通常在多数Web场景中是不可接受的,那怎么来避免“阻塞”呢?首先我们要让自己的代码变成“非阻塞”的,为了实现这个功能我们要用到回调函数。
什么是回调?
如果你以前有过使用JavaScript的经验,你应该对回调函数已经有所了解。让我们来想像一个基本场景,我们要干某件耗费大量时间的事,比如试 图读取一个大文件,我们不想让Node.js服务必须读完这个文件才能响应别的请求,为了完成这样的功能,我们最好是告诉Node.js在后台读取这个文 件,当读取完之后通过某种方式通知我们,而在这个过程中, 服务器同时还能处理别的请求,也就是使我们的代码变成“非阻塞”,就像我们刚刚举的例子一样,以适用于同时时间处理多个请求的情况,我们也把这个叫做异步 调用。
为了更清楚说明”非阻塞”,”异步”是怎么工作的,我们来看一下下面这个例子:
你在一个狭窄的道路上开车,前面有一个SB在停着打电话,很忙的样子(阻塞代码)使你不能到达目的地,这样你必须等这个SB打完电话把车启动起来才 能继续(有人可能想,用板砖干他丫的,但从程序角度来说,把他丫干死,前面少了一个司机,你要等警察来拖走或者自己先开走他的后再开自己的车,外加法律责 任,代价是很大滴,这叫破坏模型,比阻塞模型的代价还大)。
想像一下如果这条路上有紧急停车带,前面那SB司机可以变得不SB,先把车停在紧急停车带打电话。把路让出来让你先继续你的旅程。当那个不再SB的 司机打完电话之后也可以回到主干上来继续前行,还避免了可能碰到的板砖型程序员而导致血光之灾,皆大欢喜。这跟异步调用很像,在同一时间同一主干上跑多辆 车。
好了,现在用我们新学到的知识来写”非阻塞”代码吧,还是实现刚才那个功能。
首先放一段阻塞代码在一个新文件里:
var startTime = new Date().getTime();
while (new Date().getTime() < startTime + 10000);
保存为”block.js”。
接下来创建服务:
var http = require('http');
var url = require('url');
var cp = require('child_process');
function onRequest(request, response) {
var pathname = url.parse(request.url).pathname;
if( pathname == '/wait' ){
cp.exec('node block.js', myCallback);
}
else{
response.writeHead(200, {'Content-Type': 'text/plain'});
response.write('Hello!n');
response.end();
}
console.log('New connection');
function myCallback(){
response.writeHead(200, {'Content-Type': 'text/plain'});
response.write('Thanks for waiting!n');
response.end();
}
}
http.createServer(onRequest).listen(8080);
console.log('Server started');
这里我们使用了”子线程”模块来调用新的线程,也就是阻塞程序block.js,而我们的主线程则可以高高兴兴地处理别的请求。我们调用子线程模块 的.exec函数启动阻塞线程并运行之,.exec()函数有两个参数第一个是调用block.js的node命令,第二个是回调函数。当.exec() 执行完毕之后会调用回调函数myCallback打印”Thanks for waiting!”。
把上面的代码保存为”nonblocking.js”,然后执行”node nonblocking.js”测试。
现在你应该注意到,在先访问http://localhost:8080/wait 后访问http://localhost:8080这个过程中,后者不再等待前者15秒了。很明显这又是一个没啥意思的程序,”Thanks for waiting!”这几个字并不比”Hello World!”要有趣多少。那么,让我们利用上述结构的程序来写一个稍微有那么一点意思的程序吧。
幸运的是Node.js内置很多非阻塞的异步回调以供我们在应用程序中方便地使用,下面是一个非阻塞文件读取器的代码:
var http = require('http');
var fileSystem = require('fs');
http.createServer(function (request, response) {
response.writeHead(200, {'Content-Type': 'text/plain'});
var read_stream = fileSystem.createReadStream('myfile.txt');
read_stream.on('data', writeCallback);
read_stream.on('close', closeCallback);
function writeCallback(data){
response.write(data);
}
function closeCallback(){
response.end();
}
}).listen(8080);
console.log('Server started');
这里我们使用了Node.js内置的文件系统模块,用.createReadStream()读取我们的文件,并绑定两个函数到”data”和”close”事件。这些函数将在事件引发时执行。
把上述代码存为”fileReader.js”,在同级目录下新建一个文本文件”myfile.txt”, 执行”node fileReader.js”命令,现在你可以在http://localhost:8080 看到浏览器里显示了myfile.txt内容了。
总结
无论你是否有需要执行一个耗时很长的程序与否,你都应该使用非阻塞模型,并且记住,正确使用回调和异步可以让代码的速度和稳定性都能得到提高。
转自:http://www.laonan.net/blog/64/