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万用户的连接。
另外,单线程的带来的好处,还有操作系统完全不再有线程创建、销毁的时间开销。
坏处,就是一个用户造成了线程的崩溃,整个服务都崩溃了,其他人也崩溃了。
多线程、单线程的一个对比。
也就是说,单线程也能造成宏观上的“并发”。
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++写的)。底层代码中,近半数都用于事件队列、回调函数队列的构建。用事件驱动来完成服务器的任务调度,这是鬼才才能想到的。针尖上的舞蹈,用一个线程,担负起了处理非常多的任务的使命。
单线程,单线程的好处,减少了内存开销,操作系统的内存换页。
如果某一个事情,进入了,但是被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'
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