[现代Web] [论文] 服务器端JavaScript框架研究

引言

       “Any application that can be writtenin JavaScript, will eventually be written in JavaScript.(任何可以用JavaScript编写的应用最终都会由JavaScript来编写)”2007年,一个叫做Jeff Atwood [1]的美国程序员在他的博客中写下了这样一句话,这也被人们称为Atwood’s Law。实际上在当时,Javascript的主要运行环境是浏览器,浏览器作为JS代码的解析器,为JS提供了操作DOM对象和window对象的接口。

        两年之后,Node.js,一个能够在服务器端运行JavaScript的开放源码、跨平台的运行环境诞生了,它是服务器端JS的代码解析器,也是JS的一种运行环境,为JS提供操作文件、创建http服务、创建TCP/UDP服务等接口,所以Node.js可以完成其他后台语言能完成的一切工作。随著Node.js的发展,我们意识到Atwood’s Law的远见性──JavaScript的确越来越强大了。

 

第一章    概述

        Node.js 诞生自2009年,作为一个新兴的开发工具,历史远不如 Python、Ruby、PHP 等“老大哥”,但是它却是有史以来发展最快的开发工具,没有之一。在短短的几年间,我们看到了Node.js从最初一个小小的演示项目到如今拥有规模庞大的使用开发者,这是其他任何开发工具所不能企及的。

1    发展历程

1.1    诞生初期

       Node.js于2009年写成,其原始作者是Ryan Dahl。Node.js结合了Google的V8引擎、事件驱动模型和底层I/O接口[3],其设计灵感源自Flickr的一款上传进度栏:在上传过程中,浏览器并不清楚有多少文件已经发送到服务器,除非向服务器发送额外的请求进行查询,如何让服务器同时高效地处理两件事情呢?那么如果所有事情都是非阻塞的呢?如果我们从来就没有等待过任何I/O操作的完成呢?

        Dahl原本的工作是用C/C++写高性能Web服务,对于高性能,异步IO、事件驱动是基本原则,但是用C/C++写就太痛苦了。最终,Ryan选择了JavaScript [4]。选定了开发语言,还要有运行时引擎,V8作为开源的JavaScript引擎,可以很方便地进行改造并直接使用。

        2009年11月8日,Dahl在欧洲JSConf大会上展示了Node.js项目,并受到了广泛关注。在演讲中,Dahl针对Apache HTTP Server和顺序编程方式提出了批评,认为Apache处理大量并发连接(10,000甚至更多)的可能性有限,而且顺序编程方式在多连接情况下会造成阻塞,或者消耗更多资源;而Node.js提供了基于事件驱动和非阻塞的接口,可用于编写高并发状态下的程序,且JavaScript的匿名函数、闭包、回调函数等特性就是为事件驱动而设计的。

        2010年1月,Node.js的核心用户Isaac Z. Schlueter开发出奠定了Node.js如今地位的重要工具——npm。作为一个软件包管理系统,npm使程序员能够更方便地发布和分享Node.js类库及源代码,而且简化了类库安装、升级与卸载的过程。

        Node.js最初只支持Linux和Mac OS X操作系统,2011年6月,微软和Joyent公司合作,把Node.js移植到了Windows系统上面,并在7月发布了第一个正式支持Windows系统的版本。

1.2    成长分裂期

       随后,Connect、Express、Socket.IO等框架的出现吸引了一大波爱好者加入到 Node.js 的开发者阵营中来。 CoffeeScript的出现更是让不少Ruby和Python开发者找到了学习的理由。期间一大波以Node.js作为运行环境的CLI工具涌现,其中不乏有用于加速前端开发的优秀工具,如less,、UglifyJS,、browserify、grunt等等,Node.js的发展势如破竹。[2]

       另一方面,2012年1月,Dahl离开了Node.js项目,由于对Joyent的管理感到不满,Node.js核心开发者Fedor Indutny在2014年12月制作了分支版本,并起名为“io.js”。与Node.js相对的是,io.js采用开放管理模式进行管理,并计划始终采用最新版的V8引擎。

1.3    飞速发展期

       为了在用户、厂商和开发者之间获取平衡,Node.js基金会于2015年初成立。基金会得到了IBM、Intel、微软、Joyent等公司的支持。6月,Node.js和io.js开发者社区共同决定合并到Node.js基金会之下。同年9月,Node 4.0发布,Node.js和io.js正式合并。到了2016年,io.js宣布不再发布新版本,并建议开发者换回Node.js。

       随著ES2015的发展和最终定稿,一大批利用ES2015特性开发的新模块出现,如原express核心团队所开发的koa。Node.js始终紧跟着时代和标准的发展,在经历了成长、分裂和合并之后进入到飞速发展阶段,社区用户数量激增、npm也成为全球最大的开源库管理系统。


2    Node.js特点

       Node.js官网首页上的简介是这样三句话:Node.js是基于Chrome V8引擎建立的一个JavaScript运行时平台。它采用事件驱动和非阻塞I/O模型使其轻量且高效。其自带的包管理系统npm是世界上最大的开源库管理系统。下面简要介绍Node.js的两个特点:

2.1    非阻塞I/O与事件驱动

       传统Web服务器采用多线程模型,为每一个业务逻辑提供一个系统线程,通过系统线程切换弥补同步式I/O调用的时间开销。[5] 而Node.js采用单线程模型,对于所有I/O都采用异步请求方式,避免了频繁的上下文切换。

图1 Node.js非阻塞I/O与事件循环

        如图1所示[6],Nodejs中的异步I/O操作是通过libuv这个库来实现的,包含了window和linux下面的异步I/O实现[7]。当涉及到I/O操作的时候,Node会开一个独立的线程来进行异步I/O操作,操作结束以后将结果压入事件队列。随后,它通过一个事件循环来逐个取出事件队列中的结果进行处理,处理过程基本上就是去调用该消息对应的回调函数,最后将最终结果返回给主程序。

2.2    模块、包与npm

模块

        每个文件都是它自己的模块,要导入模块使用全局变量的require函数;Node模块允许从被引用文件中选择要暴露给程序的函数和变量,如果模块返回的函数或变量不止一个,那它可以通过设定exports对象的属性来指明它们[8]。其中有两点值得注意:

        第一,require是Node中少数几个同步I/O操作之一,因为经常用到模块,并且一般都是在文件顶端引入,所以把require做成同步的有助于保持代码的整洁、有序,还能增强可读性。但在程序中I/O密集的地方尽量不要用require,所有同步调用都会阻塞Node,直到调用完成才能做其他事情,所以通常都只在程序最初加载时才使用require和其他同步操作。

        第二,不允许重写exports对象,因为在程序里导出的是module.exports,exports只是对module.exports的一个全局引用,最初被定义为一个可以添加属性的空对象,如果把exports设定为别的,就打破了module.exports和exports之间的引用关系。

        在Node.js中,可以通过包来对一组具有相互依赖关系的模块进行统一管理,Node模块打包代码是为了重用,但它们不会改变全局作用域。一个包其实就是一个目录,目录下有一个用于对包进行描述的JSON格式的package.json文件,其中定义了包的名称、版本、依赖和其他信息。

npm

        npm是随同Node.js一起安装的包管理工具,能解决Node.js代码部署上的很多问题,常见的使用场景有以下几种:[9]

        (1)   允许用户从npm服务器下载别人编写的第三方包到本地使用。

        (2)   允许用户从npm服务器下载并安装别人编写的命令行程序到本地使用。

        (3)   允许用户将自己编写的包或命令行程序上传到npm服务器供别人使用。

        npm的使用极大地加快了开发的速度,也是Node.js被广泛使用的重要因素之一。

第二章 Express框架

       前面提到,在Node.js的发展过程中诞生了一系列以其为基础的框架,Express就是其中运用最为广泛的一个轻量级Web应用框架,它底层为Node的HTTP服务器,构建在Connect中间件层之上,主导思想是程序的需求和实现变化非常大,尽量不要引入任何不需要的东西。

       Express框架核心特性有:可以设置中间件来响应HTTP请求;定义了路由表用于执行不同的HTTP请求动作;可以通过向模板传递参数来动态渲染HTML页面[10]。另外,Express还提供了统一的视图系统使开发者几乎可以使用任何想用的模板引擎。

       使用 Express 可以快速地搭建一个功能完整的网站,本文的Demo首先就用Express搭建了一个“宠物交流论坛”,下面通过阐述这个论坛网站的搭建过程并结合具体的代码来了解和分析Express的主要用法和特性。

1    生成骨架

      Express中有可执行的express()脚本,预先设置了程序的模板、公共资源文件、配置等很多东西,十分方便。本文的Demo就是在Express自动生成的程序骨架上进行构建,前端使用EJS模板引擎。当然,实际上Express不会在程序结构上强迫开发者。

      首先使用“npm install –g express”安装Express,在Express4.0之后需要再执行“npm install –g express-generator”单独安装express-generator以便在命令行使用express指令。

       要使用EJS模板,需要指定-e标记,执行“express –e<project name>”就可以生成一个功能完备的程序,其中会有一个描述项目和依赖项的package.json文件、程序文件本身、public文件目录,以及一个放路由的目录。进入到生成程序目录中,通过“npm install”安装package.json中的依赖项后,通过“npm start”启动程序,浏览器地址栏输入对应ip和端口即可以访问该程序、得到对应的页面。最终生成的程序结构如图2所示:

图2  Express自动生成的程序结构

       其中比较关键的有三个文件,app.js引入了express模块,通过其导出的顶层方法express()创建了一个Express的程序,在进行一系列配置之后,最后导出了这个程序;package.json中定义了程序的启动命令,即直接执行./ bin/www文件; www文件引入了app.js导出的程序,同时引入http模块,通过“http.createServer(app)”创建HTTP服务器。


2    路由与视图渲染

       将Demo部署在本地,浏览器访问对应ip和端口(这里为localhost:3000)就会显示项目的首页,如图3所示,这个过程中发生了什么呢?这里涉及到了Express的路由和视图渲染机制。

图3 Demo网站首页

    Express路由的主要功能是匹配URL模式和响应逻辑,使用express.Router类可以创建模块化、可挂载的路由句柄。在routes/router.js文件中创建Router实例、并添加一条路由:

    该路由将处理所有对本服务器默认(/)路径的GET请求,导出router并在app.js中加载这个路由模块,通过app.use('/',router)将其挂载到应用的根路径下就可以了。作为对于这个请求的响应,为response对象添加一个render方法。这个方法可以处理很多事情,但最主要的还是加载模板引擎和对应的视图文件,然后渲染成普通的HTML文档并返回[13]。这里我们渲染了一个“login”模板,实际上这个模板应该是“./views/login.ejs”,因为我们在app.js中进行了视图系统的配置:

       通过指定视图目录为当前文件所在目录下的views文件夹,可以省略模板前面的路径;通过设置模板引擎为ejs可以省略模板的.ejs后缀。

       在获取页面之后,浏览器会继续向服务器请求页面中引入的静态资源,包括css、javascript、图片等文件,所以同样需要在app.js中设置静态资源的文件目录:


3    中间件

       上文用到了app.use('/',router)来挂载路由模块,其中的app.use就是express调用中间件的方法;而在配置静态资源文件目录的时候,用到了express.static这个Express中唯一的内建中间件,负责托管应用内的静态资源。

        所谓中间件(middleware),就是处理HTTP请求的函数,它可以访问请求对象(request)、响应对象(response)和 web 应用中处于请求-响应循环流程中被命名为 next 的变量,用来完成各种特定的任务,比如检查用户是否登录、分析数据、以及其他在需要最终将数据发送给用户之前完成的任务。[11]

       Express中间件大致可以分为五类:应用级中间件、路由级中间件、错误处理中间件、内置中间件和第三方中间件。

应用级中间件

       应用级中间件绑定到app对象,使用app.use()和app.METHOD(),其中,METHOD是需要处理的HTTP请求的方法,例如get、post等等。下面的例子展示了如何在一个挂载点装载一组中间件,对任何指向/user/:id的HTTP请求依次打印请求的URL和方法类型[12]:

路由级中间件

路由级中间件和应用级中间件一样,只是它绑定的对象为 express.Router()。以Demo中网站注册的表单验证为例:

       注册表单通过POST方法提交到服务器后,需要通过三个表单数据的验证中间件,分别对用户名、邮箱地址和密码进行验证。我们来看validate模块导出的checkName函数:

       它接收一个field参数,返回了一个中间件函数,这个中间件函数取出request表单中对应field的值,对其进行正则校验,如果验证通过,调用next()执行下一个中间件组件,如果验证不通过,返回错误信息,随后将响应重定向回请求的HTTP referer[1],当没有referer的情况下,默认为“/”。

错误处理中间件

       错误处理中间件和其他中间件定义类似,但是有4个参数,其签名为(err, req, res,next),即使不需要next对象,也必须在签名中声明它,否则中间件会被识别为一个常规中间件,不能处理错误。下面的代码为Express框架自动生成的一个错误处理中间件,发生错误时返回error页面,同时将错误信息作为属性存放在了response对象的locals变量中,该变量包含了用于渲染视图的上下文,在渲染模板时可以直接使用,显示相关信息:

内置中间件

        从Express4.x版本开始, Express已经不再依赖Connect了。除了express.static,Express 以前内置的中间件现在已经全部单独作为第三方中间件安装使用。

第三方中间件

        第三方中间件需要单独安装,前文的用户名验证函数之所以能够从req.body中取出表单中对应控件的值,就是因为安装并加载了body-parser中间件。作为Node.js的消息体中间件,body-parser这个模块提供了对JSON、Text、URL-encoded form等形式的消息解析器,所有中间件都会把解析好的消息体封装到req.body中,如果没有消息体可解析那么返回的就是一个空对象。Demo添加了一个原生的JSON和URL-encoded解析器去解析消息体:


4    数据存储

4.1    文件上传

       包含文件的表单需要以enctype为multipart/form-data的形式上传,body-parser不会解析这类很大且很复杂的消息体,这里选择加载connect-multiparty中间件,同时设置上传文件存放的路径:

        经过connect-multiparty中间件处理后,可以从req.files获取相应的临时文件及相关信息,最后将文件复制到指定的目标位置、删除临时文件,整个文件上传的主流程就结束了:

4.2    数据库操作

       Demo选择MongoDB存储数据,加载Mongoose模块以利用其提供的一系列操作MongoDB集合的接口。首先连接数据库,再定义Schema(这里以user为例)、生成对应Model并导出,就可以在其他文件中加载这个Model并进行相关操作了:

       下面的代码创建了一个新用户并将其保存至数据库:


5    小

       Demo的核心问答页面如下图所示,前文的介绍基本涵盖了用户逻辑、问答评论所涉及到的所有知识点。

图4 Demo问答页面

        由于本文的Demo是由之前一个课程PJ改造而成,原使用Java+MySql,在改造成Node.js+MongDB之后,很明显的一点是代码量变少了,这一方面是由于Express有很多封装好的模块可以直接使用,另一方面是由于前后台包括数据库均采用JSON的数据格式传输和存取数据,省去了很多Bean类的设置和SQL语句。除此之外,由于Node自带一个轻量级的HTTP服务器,每次重启都十分迅速,一般两到三秒就可以了,而使用Java需要额外配置服务器,重新部署一次十几秒到半分钟不等。这样对比下来,Express还是有一定的优势的。

 

第三章    Socket.IO框架

        由于Node的异步天性,它很适合用来执行那些在同步环境中比较困难或效率低下的I/O密集型任务,其中使用比较广泛的一个模块就是Socker.IO。作为Node社区中最著名的模块,Socket.IO允许开发者用服务器和客户端之间的双向通讯通道编写实时的Web程序。

1     Socket.IO概述

1.1   HTTP的不足与WebSocket的诞生

        HTTP是无状态的协议,客户端只能向服务器发起单个、短时的请求,并且服务器上也没有真正意义上的已连接或断开连接的用户。要实现浏览器与服务器的实时通讯,如果不使用flash、applet等浏览器插件的话,就需要定期轮询服务器来获取信息,这造成了一定的延迟和大量的网络通讯,这些限制推动了WebSocket协议的标准化工作。

        WebSocket为浏览器指定了一种维持到服务器的全双工连接的办法,允许双方同时发送和接受数据。为了建立一个 WebSocket 连接,客户端浏览器首先要向服务器发起一个 HTTP 请求,这个请求和通常的HTTP请求不同,包含了一些附加头信息,服务器端解析这些附加的头信息,然后产生应答信息返回给客户端,这样连接就建立起来了。双方可以通过这个连接通道自由地传递信息,并且这个连接会持续存在直到客户端或者服务器端某一方主动关闭连接。因为WebSocket连接本质上就是一个TCP连接,所以在数据传输的稳定性和数据传输量的大小方面,和传统的轮询相比较,具有很大的性能优势。[14]

1.2   Socket.IO的机制与特点

        Node.js提供了高效的服务器端运行环境,但是由于浏览器端对HTML5的支持不一,为了兼容所有浏览器、提供卓越的实时的用户体验,并为程序员提供客户端与服务器端一致的编程体验,于是诞生了Socket.IO。Socket.IO除了支持WebSocket通讯协议外,还支持许多种轮询(Polling)机制和其它实时通信方式,并封装成了通用的接口、在服务器端实现了这些实时机制的相应代码。Socket.IO支持的轮询机制包括Adobe Flash Socket、AJAX长轮询、AJAXmultipart streaming、持久Iframe和JSON 轮询[15]。Socket.IO会根据浏览器对通讯机制的支持情况,选择最佳方式实现网络实时通信,当浏览器可以使用WebSocket时,Socket.IO就使用它,而在老版不支持WebSocket的浏览器中,则借助其它特定的浏览器技巧模拟WebSocket的行为。

       Socket.IO实现了实时、双向、基于事件的通讯机制,解决了实时的通信问题,并统一了服务端与客户端的编程方式。它还能够和Express.js提供的传统请求方式很好的结合,即可以在同一个域名,同一个端口提供两种连接方式,Demo中就通过将Socket.IO服务器搭载在已有的HTTP服务器上向网站添加了一个实时弹幕功能,尽管这个功能不会用Ajax发送和接受弹幕消息,但它仍要用HTTP发送用在浏览器中的HTML、CSS和JavaScript文件。另外,学习Socket.IO框架也是了解Node.js的事件驱动特性和事件发射器模型的一种很好的途径。


2     基于Socket.IO的实时弹幕

       现在各种社交平台上“云养”宠物非常流行,网友可以通过观看up主上传的宠物视频远程“吸猫吸狗”,就好像自己在养它们一样。Demo中就添加了这样一个“云养”模块,如下图所示,用户可以上传和观看宠物视频,同时发送实时的弹幕评论,Demo在弹幕生成时使用了一个现成的组件[16]。

图5 Demo弹幕评论

2.1   设置Socket.IO服务器

        在命令行使用“npm install socket.io --save”安装好Socket.IO模块后,新建cloud_server.js文件,加载Socket.IO模块并导出listen方法,该方法将Socker.IO服务器搭载在已有的HTTP服务器上:

        在客户端需要建立socket连接的页面上引入Socket.IO模块对应的JavaScript文件,这样就可以在客户端发起连接、发送和接收事件:

2.2   socket的建立与事件处理

       在加载好视频播放页面后,客户端立即与服务器建立socket连接,然后向服务器发送观看视频事件:

       另一方面,服务器端每当接受到一个socket的连接,就向其注册一些事件,即绑定一些事件监听器、监听对应的事件。这里,服务器在接收到socket连接后、对socket进行初始化时让其监听了观看视频、关闭连接和发送弹幕这三个事件:

       客户端一旦建立连接就会发射“观看视频”事件,服务器端的socket在接收到这个事件后会执行对应的回调函数,存储客户端发过来的一系列信息、修改对应的数据和状态、最后将改变告知其它用户:

2.3   Room与Namespace

       在处理“观看视频”事件的时候,服务器首先将这个socket加入到了其观看视频id对应的分组中,这里涉及到room和namespace的概念。每个客户端连接时都要属于某个namespace,客户端可以自己指定,Demo中并没有指定,那就加入到默认的“/”namespace中。对于room也是一样,一个socket可以通过join方法加入到指定的room中,如果客户端没有指定哪个room,则会被放入默认room中。

       room和namespace给socket进行了分组,组内的广播和通信都不会影响到这个组以外的客户端。所以,当服务器端要向某个room中广播人数变动时,可以使用in函数通知这一组的所有用户:

        其中,message中携带的videoid即为socket所要加入的room的标识。同样,当某个socket要向room中的其它socket广播自己发送的弹幕时,使用broadcast.to函数(其中,users[socket.id].room为服务器端存储的该socket对应的room标识。):

3    小结

        前端引入socket的JavaScript文件时,我对于路径“/socket.io/socket.io.js”的写法有一些疑惑,之前使用express.static设置了静态资源文件夹后,默认请求的静态文件都是从public目录查找的,这里究竟是怎么查找到在node_modules文件夹下对应模块的呢?在搜索相关资料后发现Socket.IO模块会把以“/socket.io”开头的请求都截拦下来、自己处理,其他的请求才让Express处理。

       另外,在使用Socket.IO框架的时候发现,不同版本的API略有不同,在做Demo的时候“本能”地选择安装了最新的2.0.4版本,然而参考书使用的是0.9.6版本的,在代码写好尝试运行的时候报了好几个API deprecated的错误。从中我也认识到一些框架或者类库一直在不断更新、发布新的版本,没必要执着于各种API的参数和使用方法,重要的是理解其构建思想和核心概念、以便在写代码的时候快速找到自己需要的API。

        整个弹幕功能的开发,是基于[8]中聊天室程序的拓展,代码量只有200行左右,重点在于对各个事件的划分和处理上,在这方面其事件驱动的特性表露无遗。在建立好连接之后,所有代码都是依照“什么时候发生什么事件、由谁来如何处理这个事件”的思路完成的,只要熟悉了对应的API,上手非常容易。


第四章    Meteor框架

       前文结合Demo的代码详细介绍了Express和Socket.IO这两个框架的使用,除此之外,出于好奇,又了解了一下Meteor [17],一个构建在Node.js之上、全栈JS响应式编程的框架。由于Meteor中浏览器是一个智能客户端,几乎所有的业务逻辑和数据处理都移到了客户端,甚至为了减少延迟在浏览器的内存中运行了一个微型数据库,极大模糊了服务器和客户端的界线,所以介绍时难免涉及到客户端的一些东西,下面就两个我认为比较有特色的点简单介绍一下。

1    响应式编程

        现在的客户端已经具有相当强大的计算能力,通过将大部分处理过程移到客户端可以使得服务器和客户端之间传输的数据更少、请求响应更快、减少服务器因为耗时的请求而被阻塞的可能。而把处理工作从单一的服务器转移到多个客户端之后,就涉及到了分布式计算的问题,即某一个客户端对数据进行的操作需要同步到所有客户端。传统基于无状态连接的客户端-服务器架构显然不支持这种双向的数据传输,所以Meteor采用了一个基于JSON的专用协议——DDP。DDP基于WebSocket实现了全双工的数据传输,使得服务器端可以主动向客户端发送数据,是响应式功能的基础。

        Meteor使用声明的方法来定义数据和函数之间的关系,通过添加依赖关系将任何常规的数据源变成响应式数据源,客户端的Tracker包负责创建和跟踪依赖关系。Tracker是一个简单的约定,即允许响应数据源(比如数据库的数据)连接到数据的消费者。在处理响应的时候,Tracker会建立一个响应上下文,其中记录了数据和函数之间的依赖关系,当数据变化时Tracker会使该响应上下文无效,然后重新运行相关的函数。

       Meteor响应式的流程如图6所示——客户端订阅某些数据源(这里涉及到Meteor的发布-订阅机制,不赘述了),服务器端通过一个叫做Livequery的组件检测数据库中的变化,通过DDP推送更新到所有订阅了变更数据的客户,客户的Tracker库观察到数据变化并通过Blaze(一个响应式UI库)触发UI 层的DOM更新[17]。

图6 Meteor响应式编辑的工作流程

2    内置纤维

       Node.js通过使用一个线程应对所有请求避免了多线程资源上的高开销,但服务器通常需要在同一时间处理多个请求,尤其是有多个处理器在等待做事的时候。于是,Node引入了纤维(Fiber)的概念,与由中央调度程序为线程分配CPU时间的抢占式多任务处理方法不同,纤维是由单线程的Node.js服务器管理的,它在事件循环中引入协作式多任务处理,一个线程会根据上下文确定是否正在等待另一个操作的结果(例如,调用一个远程API或写入数据库),然后可以在等待的时间里将CPU 时间让给别的线程。

       默认情况下,Meteor为每个DDP连接创建一个专用的纤维。使用纤维还可以避免一层层的回调嵌套,图8将图7的一段“查询数据库->调用外部API->存储返回结果”的回调嵌套代码改造成了使用纤维的形式:

图7 数据库操作的回调地狱

图8 纤维避免回调嵌套

       改造后的代码看起来更清晰、容易明白,即使使用异步函数,一个纤维内的执行也是同步的,同步执行不会影响或阻塞其他纤维(如图9所示,纤维#1在灰色的等待时间里会把资源让给别的纤维)。

图9 对每个DDP连接,Meteor在事件循环中使用一个纤维


3    小结

       Meteor的主要优势是整个技术栈通用的单一语言、内置的响应支持、高可重用的代码,这使得Meteor非常适合需要快速构建的项目。但是个人认为由于它志在建立一个完整的生态系统,不仅仅是服务器进程和库的组合,还包括提供CLI工具等一般Web框架不能提供的功能,要熟悉掌握Meteor的一整套从开发、测试到部署的操作需要较长的时间。

 

第五章    总结与展望

        本文主要介绍了三个基于Node.js的框架,其中Express完美地结合了路由和中间件,请求经过一系列中间件处理后再响应给用户,职责分明且逻辑清晰;Socket.IO为Web的实时通信提供了兼容且简单的API;Meteor在同一个框架下捆绑并提供了所有必需的组件,为使用JavaScript进行全栈式编程提供了新的可能。

        Node.js由于其事件驱动和异步I/O的特性,非常适合构建数据密集型的实时应用系统,但其单线程的本质导致其可靠性低,主线程挂了整个就崩溃了;另外,单线程模式也不能很好地利用多CPU,所以不能提供很强的计算能力。但是现在已经有了一些可以充分利用CPU的成熟模块,例如PM2 [18]就是一个带有负载均衡功能的Node应用的进程管理器。

        Node.js目前用户超过八百万,并且每年翻一番[19],各种新的框架也层出不穷,在JavaScript已经占领前端高地的情况下,就算服务器端已经有了Java、PHP等积淀深厚的强劲“对手”,Node.js的发展前景还是被看好的。然而Node.js跟所有技术一样,并不是万能灵药,它有自己的优势也有自己的局限性,对于我这种“佛系”软件开发者来说,还是更乐意看到“百花齐放、百家争鸣”的景象。



参考文献

[1] Jeff Atwood. Wikipedia. https://en.wikipedia.org/wiki/Jeff_Atwood
[2] 时见疏星. Node.js发展历程与版本演化[EB/OL]. 简书. http://www.jianshu.com/p/5b9b245fcefa, 2017-12-06
[3] Node.js. Wikipedia. https://zh.wikipedia.org/wiki/Node.js
[4] Node.js. 廖雪峰的官方网站. https://www.liaoxuefeng.com/wiki/001434446689867b27157e896e74d51a89c25cc8b43bdb3000/001434501245426ad4b91f2b880464ba876a8e3043fc8ef000. 2017-12-07
[5] daisy_Hawen. 从头开始讲Node.js——异步与事件驱动. http://blog.csdn.net/sinat_25127047/article/details/54016931. 2017-01-04
[6] wolf12. nodejs的异步非阻塞IO. https://www.cnblogs.com/wolf12/p/6959399.html. 2017-06-07
[7] qeesung. nodejs 异步I/O和事件驱动. http://blog.csdn.net/ii1245712564/article/details/51473803. 2016-05-22
[8] Cantelon M等著; 吴海星译. Node.js实战[M]. 北京:人民邮电出版社, 2014.5
[9] 菜鸟教程. NPM 使用介绍. http://www.runoob.com/nodejs/nodejs-npm.html
[10] Ddaidai. Node.js Express 框架. http://www.jianshu.com/p/ce2fcdcdf4e2. 2017-12-10
[11] Evan M.Hahn. Express in Action[M]. Manning Publications,2013.3.1
[12] Express中文文档. 使用中间件. http://www.expressjs.com.cn/guide/using-middleware.html#middleware.router,2017-12-13
[13] 深入理解Express.js. http://blog.jobbole.com/41325/,2013-06-14
[14] SOCKET.IO,理解SOCKET.IO. https://www.cnblogs.com/xiezhengcai/p/3957314.html. 2017-12-16
[15] 北京记忆. websocket 与Socket.IO介绍. https://www.cnblogs.com/mazg/p/5467960.html. 2016-05-07
[16] 弹幕核心通用构件 CommentCoreLibrary. https://github.com/jabbany/CommentCoreLibrary
[17] Stephan Hochhaus, Manuel Schoebel著; 杨学辉译, Meteor实战[M], 北京:人民邮电出版社, 2017.4
[18] PM2. https://github.com/Unitech/pm2
[19] Michelle Gienow. Mikeal Rogers: Node.js Will Overtake Java Within a Year. https://thenewstack.io/open-source-profile-mikeal-rogers-node-js/. 2017-06-20

 



[1] HTTPreferer是header的一部分,当浏览器向web服务器发送请求的时候,一般会带上referer,告诉服务器它是从哪个页面链接过来的,服务器基于此可以获得一些信息用于处理。

猜你喜欢

转载自blog.csdn.net/Blanchedingding/article/details/80150448
今日推荐