zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    借助baidu的jsonp接口,做一个自己的候选词组件
    Cannot set property 'innerHTML' of null
    Win下端口占用问题:OSError: [WinError 10013] 以一种访问权限不允许的方式做了一个访问套接字的尝试
    一文读懂ES6(附PY3对比)
    Wireshark:couldn't run dumpcap in child process(附带Linux下探索过程)
    一个模块导入的简单小测试
    万物互联之~网络编程加强篇
    网罗天下之~正则表达
    (转)RTSP协议详解
    (转)HLS协议,html5视频直播一站式扫盲
  • 原文地址:https://www.cnblogs.com/oneapple/p/12050722.html
Copyright © 2011-2022 走看看