Node.js:创建应用+回调函数(阻塞/非阻塞)

Posted 古兰精

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Node.js:创建应用+回调函数(阻塞/非阻塞)相关的知识,希望对你有一定的参考价值。

一、创建应用

  如果我们使用php来编写后端的代码时,需要Apache 或者 nginx 的HTTP 服务器,并配上 mod_php5 模块和php-cgi。从这个角度看,整个"接收 HTTP 请求并提供 Web 页面"的需求根本不需要 PHP 来处理。

  不过对 Node.js 来说,概念完全不一样了。使用 Node.js 时,我们不仅仅在实现一个应用,同时还实现了整个 HTTP 服务器。事实上,我们的 Web 应用以及对应的 Web 服务器基本上是一样的。

  在我们创建 Node.js 第一个 "Hello, World!" 应用前,让我们先了解下 Node.js 应用是由哪几部分组成的:

  1. 引入 required 模块我们可以使用 require 指令来载入 Node.js 模块。

  2. 创建服务器服务器可以监听客户端的请求,类似于 Apache 、Nginx 等 HTTP 服务器。

  3. 接收请求与响应请求 服务器很容易创建,客户端可以使用浏览器或终端发送 HTTP 请求,服务器接收请求后返回响应数据

1、引入 required 模块

  我们使用 require 指令来载入 http 模块,并将实例化的 HTTP 赋值给变量 http,实例如下:

2、创建服务器

  接下来我们使用 http.createServer() 方法创建服务器,并使用 listen 方法绑定 8888 端口。 函数通过 request, response 参数来接收和响应数据。

  实例如下,在你项目的根目录下创建一个叫 server.js 的文件,并写入以下代码:

var http = require("http")

http.createServer(function(request,response){
    // 发送 HTTP 头部 
    // HTTP 状态值: 200 : OK
    // 内容类型: text/plain
    response.writeHead(200, {\'Content-Type\': \'text/plain\'});

    // 发送响应数据 "Hello World"
    response.end(\'Hello World\\n\');
}).listen(8888)

// 终端打印如下信息
console.log(\'Server running at http://127.0.0.1:8888/\');

  以上代码我们完成了一个可以工作的 HTTP 服务器。使用 node 命令执行以上的代码,打开浏览器访问 http://127.0.0.1:8888/,你会看到一个写着 "Hello World"的网页。

  分析Node.js 的 HTTP 服务器:第一行请求(require)Node.js 自带的 http 模块,并且把它赋值给 http 变量。接下来我们调用 http 模块提供的函数: createServer 。这个函数会返回 一个对象,这个对象有一个叫做 listen 的方法,这个方法有一个数值参数, 指定这个 HTTP 服务器监听的端口号。

二、回调函数

  Node.js 异步编程的直接体现就是回调。异步编程依托于回调来实现,但不能说使用了回调后程序就异步化了。回调函数在完成任务后就会被调用,Node 使用了大量的回调函数,Node 所有 API 都支持回调函数。例如,我们可以一边读取文件,一边执行其他命令,在文件读取完成后,我们将文件内容作为回调函数的参数返回。这样在执行代码时就没有阻塞或等待文件 I/O 操作。这就大大提高了 Node.js 的性能,可以处理大量的并发请求。

1、阻塞代码实例

var fs = require("fs");
var data = fs.readFileSync(\'input.txt\');

console.log(data.toString());
console.log("程序执行结束!");
//结果:先打印input里面内容,再打印程序结束

2、非阻塞代码实例

var fs = require("fs");
fs.readFile(\'input.txt\', function (err, data) {
    if (err) return console.error(err);
    console.log(data.toString());
});

console.log("程序执行结束!");
//结果:先打印程序结束,再打印input里面内容

  以上两个实例我们了解了阻塞与非阻塞调用的不同。第一个实例在文件读取完后才执行完程序。 第二个实例我们不需要等待文件读取完,这样就可以在读取文件时同时执行接下来的代码,大大提高了程序的性能。

  因此,阻塞是按顺序执行的,而非阻塞是不需要按顺序的,所以如果需要处理回调函数的参数,我们就需要写在回调函数内。

3、阻塞和非阻塞,同步异步的理解

  阻塞和非阻塞,同步和异步是node.js里经常遇到的词汇,我举个简单的例子来说明:

  我要看足球比赛,但是妈妈叫我烧水,电视机在客厅,烧水要在厨房。家里有2个水壶,一个是普通的水壶,另一个是水开了会叫的那种水壶。我可以:

  (1)用普通的水壶烧,人在边上看着,水开了再去看球。(同步,阻塞)这个是常规做法,但是我看球不爽了。

  (2)用普通水壶烧,人去看球,隔几分钟去厨房看看。(同步,非阻塞)这个又大问题,万一在我离开的几分钟水开了,我就麻烦了。

  (3)用会叫的水壶,人在边上看着。(异步,阻塞)这个没有问题,但是我太傻了。

  (4)用会叫的水壶,人去看球,听见水壶叫了再去看。(异步,非阻塞)这个应该是最好的。

  等着看球的我:阻塞,看着电视的我:非阻塞

  普通水壶:同步,会叫的水壶:异步

  所以,异步往往配合非阻塞,才能发挥出威力。

三、事件循环

  Node.js 是单进程单线程应用程序,但是通过事件和回调支持并发,所以性能非常高。

  Node.js 的每一个 API 都是异步的,并作为一个独立线程运行,使用异步函数调用,并处理并发。

  Node.js 基本上所有的事件机制都是用设计模式中观察者模式实现。

  Node.js 单线程类似进入一个while(true)的事件循环,直到没有事件观察者退出,每个异步事件都生成一个事件观察者,如果有事件发生就调用该回调函数。

1、事件驱动程序

  Node.js 使用事件驱动模型,当web server接收到请求,就把它关闭然后进行处理,然后去服务下一个web请求。当这个请求完成,它被放回处理队列,当到达队列开头,这个结果被返回给用户

  这个模型非常高效可扩展性非常强,因为webserver一直接受请求而不等待任何读写操作。(这也被称之为非阻塞式IO或者事件驱动IO

  在事件驱动模型中,会生成一个主循环来监听事件,当检测到事件时触发回调函数。

  整个事件驱动的流程就是这么实现的,非常简洁。有点类似于观察者模式,事件相当于一个主题(Subject),而所有注册到这个事件上的处理函数相当于观察者(Observer)。

  Node.js 有多个内置的事件,我们可以通过引入 events 模块,并通过实例化 EventEmitter 类来绑定和监听事件,如下实例

// 引入 events 模块
var events = require(\'events\');
// 创建 eventEmitter 对象
var eventEmitter = new events.EventEmitter();
// 绑定事件及事件的处理程序
eventEmitter.on(\'eventName\', eventHandler);
//通过程序触发事件
eventEmitter.emit(\'eventName\');

  代码实例

// 引入 events 模块
var events = require(\'events\');
// 创建 eventEmitter 对象
var eventEmitter = new events.EventEmitter();

// 创建事件处理程序
var connectHandler = function connected() {
   console.log(\'连接成功。\');
   // 触发 data_received 事件 
   eventEmitter.emit(\'data_received\');
}

// 绑定 connection 事件处理程序
eventEmitter.on(\'connection\', connectHandler);
 
// 使用匿名函数绑定 data_received 事件
eventEmitter.on(\'data_received\', function(){
   console.log(\'数据接收成功。\');
});

// 触发 connection 事件 
eventEmitter.emit(\'connection\');

console.log("程序执行完毕。");

  接下来让我们执行以上代码:

node main.js
连接成功。
数据接收成功。
程序执行完毕。

2、概念理解

  首先,Node.js 是单进程单线程应用程序,但是通过事件和回调支持并发,所以性能非常高。

  什么是单进程单线程?直接读到再去敲实例,根本不理解到底是什么意思。这个问题就必须讲下什么是进程,什么是线程进程:CPU执行任务的模块;线程:模块中的最小单元例举:cpu比作我们每个人,到饭点吃饭了。可以点很多菜(cpu中的进程):宫保鸡丁,鱼香肉丝,酸辣土豆丝。每样菜具体包含了哪些内容(cpu每个进程中的线程):宫保鸡丁(详情:黄瓜、胡萝卜、鸡肉、花生米)。而详情构成了宫保鸡丁这道菜,吃了以后不饿。就可以干活了,cpu中的进程里的线程也是同理。当线程完成自己的内容将结果返回给进程,进程返回给cpu的时候。cpu就能处理日常需求。

  单进程单线程:一盘炒苦瓜,里面只有苦瓜。

  单进程多线程:一盘宫保鸡丁,里面有黄瓜、胡萝卜、鸡肉、花生米

  其次说事件,事件就是需要 eventEmitter.on 去绑定一个事件,再通过 eventEmitter.emit 去触发这个事件,其实说的是 事件的 接收 和 发生 是分开的,eventEmitter.emit 是触发事件(事件请求),eventEmitter.on是绑定处理事件的处理器(事件处理),事件的请求和处理是分开的,所以是异步。就像 一个外卖店你可以不停的接受很多订单, 接受以后开始告诉厨师去做外卖, 做好的外卖对应的外送给每个用户,如果单线程的话那只能是接收一个订单, 做好以后在接收下一个外卖订单,明显效率非常低。事件可以不停的接受不停的发生也是为了提高效率。

以上是关于Node.js:创建应用+回调函数(阻塞/非阻塞)的主要内容,如果未能解决你的问题,请参考以下文章

Node.js中的服务器架构&回调函数的非阻塞式应用

Node.js中的服务器架构&回调函数的非阻塞式应用

Node.js回调函数

5Node.js 回调函数

Node基础知识笔记

Node - 浅谈对非阻塞I/O事件循环的理解