手动搭建node服务(一)

一、node.js

什么是Node.js?

Node.js不是一个编程语言或者框架,而是一个环境,能够脱离浏览器构建一个能让js跑起来的环境.简单来说,就是用js写后端。也就是说Node.js是一个Javascript运行环境(实际上它是对Google V8引擎进行了封装)。运行在服务器端,主要用于操作服务器文件、数据库、http协议等系统底层的一些东西。

Nodej.s为了更好让Javascript运行在浏览器之外的平台,其实现了很多的模块:文件系统,模块,包,操作系统API,网络通信等CoreJavascript中没有或者不完善的功能。

特性:

  • 单线程
  • 异步I/O /非阻塞
  • 跨平台
  • 运行速度快(依赖于Chrome V8引擎)

1、单线程:

Node保持了JavaScript再浏览器中单线程的特点。

现今多数的Web 服务器中,有一条新的链接就会申请一条线程来负责处理至到这个Request 周期结束,接着执行其他流程。可以想象,成千上万个链接便有成千上万条线程(Thread-spawning )。每条线程姑且以堆栈2MB 的消耗去计算,一条条线程它们的累加都是不小的数目。如何优化和改进本身就是一个大问题,此外,使用系统线程,必须考虑线程锁的问题,否则造成堵塞主进程又是一个令人操心的难题。NodeJS 则通过基于事件的异步模型绕开了基于线程模型的所带来的问题。

NodeJS 使用JavaScript 单线程(Single-threaded )轮询事件,设计上比较简单,高并发时,不仅根本性的减少了线程创建和切换的开销(因而没有吓人的消耗),而且由于没有锁,也不会造成进程阻塞。

单线程的优点:

  • 没有死锁存在
  • 没有线程上下文交换所产生的性能开销

单线程的缺点:

  • 无法利用多核CPU
  • 错误会引起整个应用退出,应用的健壮性无法保证
  • 大量的计算占用CPU导致无法继续调用异步I/O

2、异步IO

操作系统内核对于I/O只有两种方式:阻塞与非阻塞.在调用阻塞IO时,应用程序需要等待I/O完成才能返回结果.

阻塞I/O造成CPU等待I/O,浪费等待时间,CPU的处理能力不能得到充分利用.为了提高性能,内核提供了非阻塞I/O.非阻塞I/O会在调用后立即返回.

前端中的异步

关于异步前端开发工程师非常了解,因为我们发起一个ajax请求就是一个异步调用:

Node中的异步

在Node中,异步I/O也很常见。以读取文件为例,我们可以看到它与前端ajax调用的方式极其类似:

在Node底层构建了很多异步I/O的API,从文件读取到网络请求等,均是如此。这样的意义在于,在Node中,我们可以从语言层面很自然的进行并行I/O操作。每个调用之间无需等待之前的I/O调用结束。在编程模型上可以极大的提高效率。

下面的两个文件读取任务的耗时取决于最慢的那个文件读取的耗时:

fs.readFile("/file1",function(err,file){ 
    console.log("file1读取完成") 
})
fs.readFile("/file2",function(err,file){ 
    console.log("file2读取完成") 
})

对于同步I/O而言,它们的耗时是两个任务的耗时之和。异步带来的优势是显而易见的。

3、跨平台

Node.js 是跨平台的,也就是说它能运行在 Windows、OSX 和 Linux 平台上。

4、运行速度快

V8是谷歌开发的,目前公认最快的 Javascript 解析引擎,libuv 是一个开源的、为 Node 定制而生的跨平台的异步 IO 库。

劣势:

1. 不适合CPU密集型应用;

CPU密集型应用给Node带来的挑战主要是:由于JavaScript单线程的原因,如果有长时间运行的计算(比如大循环),将会导致CPU时间片不能释放,使得后续I/O无法发起;

什么是cpu密集型操作(复杂的运算、图片的操作)

// 这就是一个cpu密集型的操作 
for (let i = 0; i < 100000000; i++) { 
    console.log(i); 
}

解决方案:分解大型运算任务为多个小任务,使得运算能够适时释放,不阻塞I/O调用的发起;

2. 只支持单核CPU,不能充分利用CPU

虽然nodejs的I/O操作开启了多线程,但是所有线程都是基于node服务进程开启的,并不能充分利用cpu资源

pm2进程管理器可以解决这个问题

pm2 是一个带有负载均衡功能的Node应用的进程管理器.

3. 可靠性低,一旦代码某个环节崩溃,整个系统都崩溃

原因:单进程,单线程

解决方案:

Nnigx反向代理,负载均衡,开多个进程,绑定多个端口;

开多个进程监听同一个端口,使用cluster模块;

4. 开源组件库质量参差不齐,更新快,向下不兼容

二、模块

模块是es系列标准之一,在这里,每个javascript文件我们都可以将其视为一个模块,或者一个模块的生产者,一个文件可以选择自己想要抛出哪些东西,形成一个其他文件可以require的对象

例如:

// 命名为app1.js的文件,里面要抛出两个东西,变量a和函数fun 
var a = 1; 
function fun ( ) { 
    console.log("2"); 
} 
module.exports={ 
    a:a, 
    fun:fun 
} 

//把这两个东西以一个对象的形式抛出,就可以在其他的模块中读取到 
var app = require("./app1.js"); 
app.fun();

在其他的文件中,以对象的形式进行引入,就可以调用这个模块内的属性,函数之类的东西,很类似java中的导包,cpp中的头文件

模块化开发是一个非常重要的思想,后面常用的模块包括文件管理fs,路由管理url,服务器网络管理http协议,都是内置的模块,可以直接使用require语句调用

Node.js模块系统

为了让Node.js的文件可以相互调用,Node.js提供了一个简单的模块系统。

模块化的好处:

  • 减少文件体积
  • 命名冲突问题
  • 文件依赖问题
  • 提高代码可复用性

Node模块分类

NodeJS中模块分为2类:原生模块和文件模块。

原生模块:即Node.jsAPI提供的原生模块,原生模块在启动时已经被加载。(如:os模块、http模块、fs模块、buffer模块、path模块等)

文件模块:为动态加载模块,加载文件模块的主要由原生模块module来实现和完成。原生模块在启动时已经被加载,而文件模块则需要通过调用Node.js的require方法来实现加载。

调用原生模块

//调用原生模块不需要指定路径 
var http = require('http');

调用文件模块

//调用文件模块必须指定路径,否则会报错 
var sum = require('./sum.js');

文件模块三种类型

在文件模块中,又分为3类模块。这三类文件模块以后缀来区分,Node.js会根据后缀名来决定加载方法。

类型

描述

.js

通过fs模块同步读取js文件并编译执行。

.node

通过C/C++进行编写的Addon。通过dlopen方法进行加载。

.json

读取文件,调用JSON.parse解析加载。

模块的操作:

在编写每个模块时,都有require、exports、module三个预先定义好的变量可供使用。

require 加载模块:

require方法接受以下几种参数的传递:

  • http、fs、path等,原生模块。
  • ./mod或../mod,相对路径的文件模块。
  • /pathtomodule/mod,绝对路径的文件模块。
  • mod,非原生模块的文件模块。

require函数用于在当前模块中加载和使用别的模块,传入一个模块名,返回一个模块导出对象。模块名可使用相对路径(以./开头),或者是绝对路径(以/或C:之类的盘符开头)。另外,模块名中的.js扩展名可以省略。以下是一个例子。

var foo1 = require('./foo');
var foo2 = require('./foo.js'); 
var foo3 = require('/home/user/foo');
var foo4 = require('/home/user/foo.js'); 
//foo1 ~ foo4 中保存的是同一个模块的导出对象。

nodejs核心模块可以不加路径直接通过require导入:

//加载node 核心模块 
var fs = require('fs'); 
var http = require('http'); 
var os = require('os'); 
var path = require('path');

加载和使用json文件

var data = require('./data.json');

exports 创建模块:

exports对象是当前模块的导出对象,用于导出模块公有方法和属性。别的模块通过require函数使用当前模块时得到的就是当前模块的exports对象。以下例子中导出了一个公有方法。

//sum.js 
exports.sum = function(a,b){ 
    return a+b; 
} 
//main.js 
var m = require("./sum"); 
var num = m.sum(10,20); 
console.log(num);

module对象:

通过module对象可以访问到当前模块的一些相关信息,但最多的用途是替换当前模块导出对象。例如模块默认导出对象默认是一个普通对象,如果想改为一个函数可以通过如下方式:

导出一个普通函数:

//sum.js 
function sum(a,b){ 
    return a+b;
} 
module.exports= sum; 
//main.js 
var sum = require('./sum'); 
sum(10,20);// 30

导出一个构造函数:

//hello.js 
function hello(){ 
    this.name ="你的名字";
    this.setName = function(name){ 
        this.name = name; 
    } 
    this.sayName = function(){ 
        alert(this.name); 
    } 
} 
module.exports= hello; 
//main.js 
var hello = require('./hello.js'); 
var o = new hello(); 
o.setName('张三'); 
o.sayName(); // 张三

module的其它API:

每一个js文件都是一个模块,每一个模块都有一个对象这个对象的可以通过module访问到,该对象上保存了一些我们当前模块的状态:

module.id 模块的ID,通常是当前模块文件路径,含文件名

module.filename 当前模块文件路径,含文件名

module.loaded 判断模块当前是否已加载

module.parent 加载当前脚本的模块对象。

module.children 当前模块加载的模块对象集合,是一个数组

Node.js包:

由多个子模块组成的大模块称作包。

自定义包:

在组成一个包的所有子模块中,需要有一个入口模块,入口模块的导出对象被作为包的导出对象。例如有以下目录结构。

d:/node/calc/ /calc sum.js //加法 subtraction.js //减法 multiplication.js //乘法 division.js //除法 main.js //主模块 入口模块

calc目录定义了一个包,其中包含了4个子模块,main.js作为入口模块。

var sum = require('./sum.js'); 
var subtraction = reuqire('./subtraction.js'); 
var multiplication = require('./multiplication.js'); 
var division = require('./division.js'); 
function calc(a,tag,b){ 
    switch(tag){ 
        case '+': 
            return sum(a,b); 
            break; 
        case '-': 
            return subtraction(a,b); 
            break; 
        case '*': 
            return multiplication(a,b); 
            break; 
        case '/': 
            return division(a,b); 
    } 
} 
module.exports = calc;

然后我们要使用这个包

//d:/node/use.js 
var calc = require('./calc/main.js'); 
console.log(clac(10,'+',100));

但是这样使用感觉不像一个包。

var calc = require('./calc');

package.json

如果想自定义入口模块的文件名和存放位置,就需要在包目录下包含一个 package.json 文件,package.json是包的配置文件。

d:/node/calc/ 
/calc sum.js //加法 
subtraction.js //减法 
multiplication.js //乘法 
division.js //除法 
main.js //主模块 
入口模块 
//package.json 
{ 
    "name": "calc", 
    "main": "./calc/main.js" 
}

Package.json 属性说明

属性 描述

name 包名。

version 包的版本号。

description 包的描述。

homepage 包的官网 url 。

author 包的作者姓名。

contributors 包的其他贡献者姓名。

devDependencies 开发环境依赖包列表。

dependencies 生产环境依赖包列表。如果依赖包没有安装,npm 会自动将依赖包安装在 node_module 目录下。

repository 包代码存放的地方的类型,可以是 git 或 svn,git 可在 Github 上。

main main 字段是一个模块ID,它是一个指向你程序的主要项目。就是说,如果你包的名字叫 express,然后用户安装它,然后require(“express”)。

keywords 关键字

 

它是这样一个json文件(注意:json文件内是不能写注释的,复制下列内容请删除注释):
{ 
    "name": "test", //项目名称(必须) 
    "version": "1.0.0", //项目版本(必须) 
    "description": "This is for study gulp project !", //项目描述(必须) 
    "homepage": "", //项目主页 
    "repository": { //项目资源库 
        "type": "git", 
        "url": "https://git.oschina.net/xxxx" 
    },         
    "author": { //项目作者信息 
        "name": "surging", 
        "email": "surging2@qq.com" 
    },
    "license": "ISC", //项目许可协议 
    "devDependencies": { //项目开发依赖包 
        "gulp": "^3.8.11", 
        "gulp-less": "^3.0.0" 
    },     
    dependencies:{ //项目生产依赖包 } 
}

符合CommonJS规范的包

包是在模块基础上更深一步的抽象,Nodejs 的包类似于 C/C++ 的函数库或者 Java/.Net 的类库。它将某个独立的功能封装起来,用于发布、更新、依赖管理和版本控制。Nodejs 根 据 CommonJS 规范实现了包机制,开发了 npm来解决包的发布和获取需求。

Node.js 的包是一个目录,其中包含一个 JSON 格式的包说明文件 package.json。严格符 合 CommonJS 规范的包应该具备以下特征:

文件类型描述
package.json必须在包的顶层目录下

二进制文件

JavaScript

文档

单元测试

应该在 bin 目录下

应该在 lib 目录下

应该在 doc 目录下

应该在 test 目录下

Node.js 对包的要求并没有这么严格,只要顶层目录下有 package.json,并符合一些规范即可。当然为了提高兼容性,我们还是建议你在制作包的时候,严格遵守 CommonJS 规范。

  • 16
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值