20.Nodejs基础知识(上)——2019年12月16日

2019年12月16日18:58:55

2019年10月04日12:20:59

1. nodejs简介

Node.js是一个让JavaScript运行在服务器端的开发平台,它让JavaScript的触角伸到了服务器端,可以与PHP、JSP、Python、Ruby平起平坐。

但Node似乎有点不同:

Node.js不是一种独立的语言,与PHP、JSP、Python、Perl、Ruby的“既是语言,也是平台”不同,Node.js的使用JavaScript进行编程,运行在JavaScript引擎上(V8)。

● 与PHP、JSP等相比(PHP、JSP、.net都需要运行在服务器程序上,Apache、Naginx、Tomcat、IIS。),Node.js跳过了Apache、Naginx、IIS等HTTP服务器,它自己不用建设在任何服务器软件之上。Node.js的许多设计理念与经典架构(LAMP = Linux + Apache + MySQL + PHP)有着很大的不同,可以提供强大的伸缩能力。一会儿我们就将看到,Node.js没有web容器。

Node.js自身哲学,是花最小的硬件成本,追求更高的并发,更高的处理性能。

特点:

所谓的特点,就是Node.js是如何解决服务器高性能瓶颈问题的。

1.1 单线程

在Java、PHP或者.net等服务器端语言中,会为每一个客户端连接创建一个新的线程。而每个线程需要耗费大约2MB内存。也就是说,理论上,一个8GB内存的服务器可以同时连接的最大用户数为4000个左右。要让Web应用程序支持更多的用户,就需要增加服务器的数量,而Web应用程序的硬件成本当然就上升了。

Node.js不为每个客户连接创建一个新的线程,而仅仅使用一个线程。当有用户连接了,就触发一个内部事件,通过非阻塞I/O、事件驱动机制,让Node.js程序宏观上也是并行的。使用Node.js,一个8GB内存的服务器,可以同时处理超过4万用户的连接。

另外,单线程的带来的好处,还有操作系统完全不再有线程创建、销毁的时间开销。

坏处,就是一个用户造成了线程的崩溃,整个服务都崩溃了,其他人也崩溃了。

image-20191004123038963

多线程、单线程的一个对比。

也就是说,单线程也能造成宏观上的“并发”。

1.2 非阻塞I/O non-blocking I/O

例如,当在访问数据库取得数据的时候,需要一段时间。在传统的单线程处理机制中,在执行了访问数据库代码之后,整个线程都将暂停下来,等待数据库返回结果,才能执行后面的代码。也就是说,I/O阻塞了代码的执行,极大地降低了程序的执行效率。

由于Node.js中采用了非阻塞型I/O机制,因此在执行了访问数据库的代码之后,将立即转而执行其后面的代码,把数据库返回结果的处理代码放在回调函数中,从而提高了程序的执行效率。

当某个I/O执行完毕时,将以事件的形式通知执行I/O操作的线程,线程执行这个事件的回调函数。为了处理异步I/O,线程必须有事件循环,不断的检查有没有未处理的事件,依次予以处理。

阻塞模式下,一个线程只能处理一项任务,要想提高吞吐量必须通过多线程。而非阻塞模式下,一个线程永远在执行计算操作,这个线程的CPU核心利用率永远是100%。所以,这是一种特别有哲理的解决方案:与其人多,但是好多人闲着;还不如一个人玩命,往死里干活儿。

1.3 事件驱动 event-driven

在Node中,客户端请求建立连接,提交数据等行为,会触发相应的事件。在Node中,在一个时刻,只能执行一个事件回调函数,但是在执行一个事件回调函数的中途,可以转而处理其他事件(比如,又有新用户连接了),然后返回继续执行原事件的回调函数,这种处理机制,称为“事件环”机制。

Node.js底层是C++(V8也是C++写的)。底层代码中,近半数都用于事件队列、回调函数队列的构建。用事件驱动来完成服务器的任务调度,这是鬼才才能想到的。针尖上的舞蹈,用一个线程,担负起了处理非常多的任务的使命。

image-20191004124914552

单线程,单线程的好处,减少了内存开销,操作系统的内存换页。

如果某一个事情,进入了,但是被I/O阻塞了,所以这个线程就阻塞了。

非阻塞I/O,不会傻等I/O语句结束,而会执行后面的语句。

非阻塞就能解决问题了么?比如执行着小红的业务,执行过程中,小刚的I/O回调完成了,此时怎么办??

事件机制,事件环,不管是新用户的请求,还是老用户的I/O完成,都将以事件方式加入事件环,等待调度。

2. http模块

2.1 例子 req,res
1//require表示引包,引包就是引用自己的一个特殊功能
2var http = require("http");

3//创建服务器,参数是一个回调函数,表示如果有请求进来,要做什么
4var server = http.createServer(function(req,res){
5	//req表示请求,request;  res表示响应,response
6	//设置HTTP头部,状态码是200,文件类型是html,字符集是utf8
7	res.writeHead(200,{"Content-type":"text/html;charset=UTF-8"});
8	res.end("哈哈哈哈,我买了一个iPhone" + (1+2+3) + "s");
9});
10
11//运行服务器,监听3000端口(端口号可以任改)
server.listen(3000,"127.0.0.1");

req表示请求,request; res表示响应,response

如果想修改程序,必须中断当前运行的服务器,重新node一次,刷新,才行。

ctrl+c,就可以打断挂起的服务器程序。

你会发现,我们本地写一个js,打死都不能直接拖入浏览器运行,但是有了node,我们任何一个js文件,都可以通过node来运行。也就是说,node就是一个js的执行环境。

1//这个案例简单讲解http模块
2//引用模块
3var http = require("http");
4
5//创建一个服务器,回调函数表示接收到请求之后做的事情
6var server = http.createServer(function(req,res){
7	//req参数表示请求,res表示响应
8	console.log("服务器接收到了请求" + req.url);
9	res.end();
10});
11//监听端口
server.listen(3000,"127.0.0.1");
2.2 req.url

我们现在来看一下req里面能够使用的东西。

最关键的就是req.url属性,表示用户的请求URL地址。所有的路由设计,都是通过req.url来实现的。

我们比较关心的不是拿到URL,而是识别这个URL。

识别URL,用到两个新模块,第一个就是url模块,第二个就是querystring模块

2.3 res.end(),write(),writeHead()
2.4 总结

response得先writeHead()才可以写其他语句。

res.writeHead(200,{"Content-Type":"text/html;charset=UTF8"});

3. querystring 模块

querystring.decode() == querystring.parse()

新增于: v0.1.99

querystring.decode() 函数是 querystring.parse() 的别名。

querystring.encode() == querystring.stringify()

新增于: v0.1.99

querystring.encode() 函数是 querystring.stringify() 的别名。

var querystring=require("querystring");

var string1=querystring.parse('foo=bar&abc=xyz&abc=123');
console.log(string1)

var string2=querystring.decode('foo=bar&abc=xyz&abc=123');
console.log(string2)
//[Object: null prototype] { foo: 'bar', abc: [ 'xyz', '123' ] }
3.1 querystring.parse(str[, sep[, eq[, options]]])

querystring.parse() 方法将 URL 查询字符串 str 解析为键值对的集合。

例如,查询字符串 'foo=bar&abc=xyz&abc=123' 被解析为:

{
  foo: 'bar',
  abc: ['xyz', '123']
}

querystring.parse() 方法返回的对象不是原型继承自 JavaScript Object。 这意味着典型的 Object 方法如 obj.toString()obj.hasOwnProperty() 等都没有定义并且不起作用。

3.2 querystring.stringify(obj[, sep[, eq[, options]]])

querystring.stringify() 方法通过迭代对象的自身属性从给定的 obj 生成 URL 查询字符串。

querystring.stringify({ foo: 'bar', baz: ['qux', 'quux'], corge: '' });
// 返回 'foo=bar&baz=qux&baz=quux&corge='

querystring.stringify({ foo: 'bar', baz: 'qux' }, ';', ':');
// 返回 'foo:bar;baz:qux'
3.3总结:

parse是相当于编译成json,stringfy相当于是把json变成了字符串。

4.url模块

4.1 网址构成

URL 'http://user:pass@sub.example.com:8080/p/a/t/h?query=string#hash'

image-20191004135129123

Url {
  protocol: 'http:',    // 指的是底层使用的协议是http
  slashes: true,        // 说明是否有协议的双斜线
  auth: null,
  host: 'imooc.com',    // 指的是http协议的一个Ip地址或者域名
  port: null,           // 端口,默认是 80 端口,如果使用了别的端口就必须指明
  hostname: 'imooc.com', // 主机名
  hash: null,			 // hash值,通常对应页面上某个锚点,加#号之后将页面滚动到当前位置的
  search: null,			 // 查询字符串参数
  query: null,			 // 发送给http服务器的数据,通常是被等号分隔开的键值对称之为参数串
  pathname: '/course/list', // 访问资源路径名
  path: '/course/list',   // 路径
  href: 'http://imooc.com/course/list' // 没被解析的完整的超链接
}
4.2 url.parse()

使用url.parse()方法来将url解析成一个对象

url.parse()后面加一个true,可以将query参数解析成参数对象

let obj = url.parse(str,true);
var server = http.createServer(function (req, res) {
    console.log("服务器收到请求" + req.url);
    var querystring1=url.parse(req.url,true).query;
    res.end(querystring1.name+"+"+querystring1.age);
});
4.3 pathname

得到路由路径

//得到用户的路径
	var pathname = url.parse(req.url).pathname;
	//默认首页
	if(pathname == "/"){
		pathname = "index.html";
	}
	//拓展名
	var extname = path.extname(pathname);

5.fs模块

5.1 fs.readFile(),fs.readFileSync()

读文件

var fs = require("fs");

res.writeHead(200,{"Content-Type":"text/html;charset=UTF8"});

	//两个参数,第一个是完整路径,当前目录写./
	//第二个参数,就是回调函数,表示文件读取成功之后,做的事情

	fs.readFile("./test/1.txt",function(err,data){
		if(err){
			throw err;
		}
		console.log(userid + "文件读取完毕");
		res.end(data);
	});
5.2 处理图标路由的输出
//不处理小图标
	if(req.url == "/favicon.ico"){
		return;
	}
5.3 mkdir(),mkdirSync()

异步地创建目录。 除了可能的异常,完成回调没有其他参数。

可选的 options 参数可以是指定模式(权限和粘滞位)的整数,也可以是具有 mode 属性和 recursive 属性(指示是否应创建父文件夹)的对象。

// 创建 /tmp/a/apple 目录,无论是否存在 /tmp 和 /tmp/a 目录。
fs.mkdir('/tmp/a/apple', { recursive: true }, (err) => {
  if (err) throw err;
});
5.4 fs.stat类

stats.isDirectory()

如果 fs.Stats 对象描述文件系统目录,则返回 true

stats.isFile()

如果 fs.Stats 对象描述常规文件,则返回 true

fs.stat("./album/" + thefilename , function(err,stats){
				//如果他是一个文件夹,那么输出它:
				if(stats.isDirectory()){
					wenjianjia.push(thefilename);
				}
5.5 fs.readdir(path[, options], callback)

读取目录的内容。 回调有两个参数 (err, files),其中 files 是目录中的文件名的数组(不包括 '.''..')。

可选的 options 参数可以是指定编码的字符串,也可以是具有 encoding 属性的对象,该属性指定用于传给回调的文件名的字符编码。 如果 encoding 设置为 'buffer',则返回的文件名是 Buffer 对象。

如果 options.withFileTypes 设置为 true,则 files 数组将包含 [fs.Dirent] 对象。

path <string> | <Buffer> | <URL>
options <string> | <Object>

	encoding <string> 默认值: 'utf8'。
	withFileTypes <boolean> 默认值: false。
callback <Function>

	err <Error>
	files <string[]> | <Buffer[]> | <fs.Dirent[]>
5.6 异步编程思想——循环语句,异步语句

错误输出:循环语句嵌套异步,输出出错

var server = http.createServer(function(req,res){
	//不处理小图标
	if(req.url == "/favicon.ico"){
		return;
	}
	//存储所有的文件夹
	var wenjianjia = [];
	//stat检测状态
	fs.readdir("./album",function(err,files){
		//files是个文件名的数组,并不是文件的数组,表示./album这个文件夹中的所有东西
		//包括文件、文件夹
		for(var i = 0 ; i < files.length ;i++){
			var thefilename = files[i];
			//又要进行一次检测
			fs.stat("./album/" + thefilename , function(err,stats){
				//如果他是一个文件夹,那么输出它:
				if(stats.isDirectory()){
					wenjianjia.push(thefilename);
				}
				console.log(wenjianjia);
			});
		}
	});
});

5.7 把异步变成同步

使用迭代器iterator()

迭代器就是强行把异步的函数,变成同步的函数

var http = require("http");
var fs = require("fs");

var server = http.createServer(function(req,res){
	//不处理收藏夹小图标
	if(req.url == "/favicon.ico"){
		return;
	}
	//遍历album里面的所有文件、文件夹
	fs.readdir("./album/",function(err,files){
		//files : ["0.jpg","1.jpg" ……,"aaa","bbb"];
		//files是一个存放文件(夹)名的数组
		//存放文件夹的数组
		var wenjianjia = [];
		//迭代器就是强行把异步的函数,变成同步的函数
		//1做完了,再做2;2做完了,再做3
		(function iterator(i){
			//遍历结束
			if(i == files.length){
				console.log(wenjianjia);
				return;
			}
			fs.stat("./album/" + files[i],function(err,stats){
				//检测成功之后做的事情
				if(stats.isDirectory()){
					//如果是文件夹,那么放入数组。不是,什么也不做。
					wenjianjia.push(files[i]);
				}
				iterator(i+1);
			});
		})(0);
	});
	res.end();
});

server.listen(3000,"127.0.0.1");

6. path模块

path 模块提供用于处理文件路径和目录路径的实用工具。

6.1 dirname(path)

path.dirname() 方法返回 path 的目录名,类似于 Unix 的 dirname 命令。

path.dirname('/foo/bar/baz/asdf/quux');
// 返回: '/foo/bar/baz/asdf'
6.2 path.extname(path)

path.extname() 方法返回 path 的扩展名,从最后一次出现 .(句点)字符到 path 最后一部分的字符串结束。 如果在 path 的最后一部分中没有 . ,或者如果 path 的基本名称除了第一个字符以外没有 .,则返回空字符串。

path.extname('index.html');
// 返回: '.html'

path.extname('index.coffee.md');
// 返回: '.md'

path.extname('index.');
// 返回: '.'

path.extname('index');
// 返回: ''

path.extname('.index');
// 返回: ''

path.extname('.index.md');
// 返回: '.md'
6.3 path.join([...paths])

path.join() 方法使用平台特定的分隔符作为定界符将所有给定的 path 片段连接在一起,然后规范化生成的路径。

零长度的 path 片段会被忽略。 如果连接的路径字符串是零长度的字符串,则返回 '.',表示当前工作目录。

path.join('/foo', 'bar', 'baz/asdf', 'quux', '..');
// 返回: '/foo/bar/baz/asdf'

path.join('foo', {}, 'bar');
// 抛出 'TypeError: Path must be a string. Received {}'
6.4 path.parse(path)

path.parse() 方法返回一个对象,其属性表示 path 的重要元素。

path.parse('/home/user/dir/file.txt');
// 返回:
// { root: '/',
//   dir: '/home/user/dir',
//   base: 'file.txt',
//   ext: '.txt',
//   name: 'file' }
6.5 静态资源文件管理
var http = require("http");
var url = require("url");
var fs = require("fs");
var path = require("path");

http.createServer(function(req,res){
	//得到用户的路径
	var pathname = url.parse(req.url).pathname;
	//默认首页
	if(pathname == "/"){
		pathname = "index.html";
	}
	//拓展名
	var extname = path.extname(pathname);

	//真的读取这个文件
	fs.readFile("./static/" + pathname,function(err,data){
		if(err){
			//如果此文件不存在,就应该用404返回
			fs.readFile("./static/404.html",function(err,data){
				res.writeHead(404,{"Content-type":"text/html;charset=UTF8"});
				res.end(data);
			});
			return;
		};
		//MIME类型,就是
		//网页文件:  text/html
		//jpg文件 :   image/jpg
		var mime = getMime(extname);
		res.writeHead(200,{"Content-type":mime});
		res.end(data);
	});

}).listen(3000,"127.0.0.1");

function getMime(extname){
	switch(extname){
		case ".html" :
			return "text/html";
			break;
		case ".jpg" : 
			return "image/jpg";
			break;
		case ".css":
			return "text/css";
			break;
	}
}

2019年10月04日16:56:17

原文地址:https://www.cnblogs.com/oneapple/p/12050722.html