《Node.js核心技术教程》学习笔记 1.       第1章 模块化编程 2019.2.19 13:30' 2.       第2章 初识Node.js 2019.2.22 17:30' 3.       第3章 异步编程和包资源管理 2019.2.22 21.30' 4.       第4章 Node.js 文件操作 2019.2.23 2:40' 5.       第5章 Node.js中处理数据I/O 2019.2.23 12:00' 6.       第6章 Node.js网络编程 2019.2.23 16:20’ 7.       第7章 Node.js中实现HTTP服务 2019.2.23 22:35' 8.       第8章 综合项目--我的音乐 2019.2.24 16:00

Node.js核心技术教程》

1.    1模块化编程 2019.2.19 13:30'1

1.1.    1.1初识模块化编程... 1

1.1.1.     模块化是一种设计思想,把一个非常复杂的系统结构细化到具体的功能点,每个功能点看做一个模块,然后通过某种规则把这些小的模块组合到一起,构成模块化系统。    1

1.1.2.     模块化编程可有效解决命名冲突问题和文件依赖的关系和顺序问题。    1

1.2.    1.2模块化编程的演变... 1

1.2.1.     1.2.1全局函数... 1

1.2.2.     1.2.2对象的命名空间... 3

1.2.3.     1.2.3函数的作用域(闭包)    5

1.2.4.     1.2.4维护和扩展... 7

2.    2初识Node.js 2019.2.22 17:30'9

2.1.    2.1 Node.js概述... 9

2.1.1.     有了Node.js,用JavaScript既可以客户端开发,又可以服务器端开发,还可以与数据库交互。减少学习成本,快速打造全栈工程师。    9

2.1.2.     客户端将用户请求发送给服务器端。服务器端根据用户的请求进行逻辑处理、数据处理并将结果响应给客户端。现在,用Node.js来代替传统的服务器语言,开发服务器端的Web框架。... 9

2.1.3.     JavaScript是一种脚本语言,一般运行在客户端,而Node.js可使JavaScript运行在服务器端。... 9

2.1.4.     JavaScript组成... 9

2.1.5.     JavaScript作用... 10

2.2.    2.2 Node.js简介... 11

2.2.1.     概念... 11

2.2.2.     特点和优势... 11

2.3.    2.3 Node.js安装和配置... 12

2.3.1.     下载和安装... 12

2.3.2.     CMD命令台... 12

2.3.3.     Path环境变量... 12

2.3.4.     快速体验Node.js. 12

2.4.    2.4 Node.js基础入门... 13

2.4.1.     REPL运行环境... 13

2.4.2.     global对象和模块作用域... 14

2.4.3.     全局可用变量、函数和对象... 16

2.4.4.     Node.js模块化重写计算器案例    19

2.4.5.     require()模块的加载规则... 21

2.4.6.     模块的缓存 require.cache. 23

3.    3异步编程和包资源管理 2019.2.22 21.30'24

3.1.    异步编程... 24

3.1.1.     同步和异步... 24

3.1.2.     回调函数... 26

3.2.    Node.js的包和NPM... 32

3.2.1.     包的概念... 32

3.2.2.     NPM的概念... 33

3.2.3.     NPM的基本应用... 35

3.2.4.     包模块加载规则... 35

4.    4 Node.js 文件操作 2019.2.23 2:40'36

4.1.    基本文件操作... 36

4.1.1.     文件写入... 36

4.1.2.     向文件追加内容... 39

4.1.3.     文件读取... 42

4.1.4.     文件复制... 42

4.1.5.     获取文件信息... 45

4.2.    案例-控制歌词滚动... 45

4.2.1.     var fs = require('fs'); //读取歌词文件 fs.readFile('./lrc.txt', function(err, data) {   if (err) {     return console.log('读取歌词文件失败了');   }   data = data.toString();    var lines = data.split(' ');    // 遍历所有行,通过正则匹配里面的时间,解析出毫秒   // 需要里面的时间和里面的内容   var reg = /[(d{2}):(d{2}).(d{2})]s*(.+)/;    for (var i = 0; i < lines.length; i++) {     (function(index) {       var line = lines[index];       var matches = reg.exec(line);       if (matches) {         // 获取分         var m = parseFloat(matches[1]);         // 获取秒         var s = parseFloat(matches[2]);         // 获取毫秒         var ms = parseFloat(matches[3]);         // 获取定时器中要输出的内容         var content = matches[4];         // 将分++毫秒转换为毫秒         var time = m * 60 * 1000 + s * 1000 + ms;    //使用定时器,让每行内容在指定的时间输出         setTimeout(function() {           console.log(content);         }, time);       }     })(i);   } });46

4.3.    文件相关操作... 48

4.3.1.     路径字符串操作... 48

4.3.2.     目录操作... 48

5.    5 Node.js中处理数据I/O 2019.2.23 12:00'52

5.1.    Buffer缓冲区限制大小1GB.. 52

5.1.1.     二进制数据和乱码... 52

5.1.2.     Buffer的构造函数... 52

5.1.3.     写入缓冲区... 53

5.1.4.     从缓冲区读取数据... 53

5.1.5.     拼接缓冲区... 55

5.2.    Stream文件流... 55

5.2.1.     文件流的概念... 55

5.2.2.     Node.js的可读流和可写流... 56

5.2.3.     使用pipe()处理大文件... 60

6.    6 Node.js网络编程 2019.2.23 16:20’62

6.1.    6.1Node.js网络编程基础... 62

6.1.1.     IP地址和端口号... 62

6.1.2.     套接字Socket简单模型... 62

6.2.    6.2Node.js中实现套接字服务... 64

6.2.1.     Net模块的API64

6.2.2.     Net.Server对象... 65

6.2.3.     Net.Socket对象... 68

6.3.    6.3Node.js进程管理... 78

6.3.1.     Process模块获取终端输入... 78

6.3.2.     多人广播消息... 78

6.4.    6.4案例--终端聊天室... 81

6.4.1.     配置文件:config. 81

6.4.2.     客户端文件:client.js. 81

6.4.3.     服务器端文件:server.js. 83

6.4.4.     用户注册模块:signup.js. 85

6.4.5.     广播消息模块:broadcast.js. 87

6.4.6.     点对点消息模块:p2p.js. 89

7.    7 Node.js中实现HTTP服务 2019.2.23 22:35'91

7.1.    7.1HTTP协议... 91

7.1.1.     HTTP协议简介... 91

7.1.2.     HTTP请求响应流程... 93

7.1.3.     HTTP的请求报文和响应报文    94

7.2.    7.2Node.jsHTTP服务... 96

7.2.1.     HTTP模块常用API96

7.2.2.     使用HTTP模块构建Web服务器    99

7.2.3.     03_http.js. 101

7.2.4.     04_浏览器的本质.js. 101

7.3.    7.3HTTP服务请求处理... 103

7.3.1.     根据不同的URL发送不同响应消息    103

7.3.2.     HTTP处理静态资源服务... 105

7.3.3.     动态处理静态资源请求... 110

7.4.    Underscore的模板引擎template. 113

7.4.1.     template用于将JavaScript模板编译为可以用于页面呈现的函数,通过JSON数据源生成复杂的HTML并呈现出来... 113

7.4.2.     语法:_.template(templateString,[settings])113

7.4.3.     赋值:... 113

7.4.4.     需要转义:... 113

7.4.5.     Node.js中使用Underscore需要用NPM安装... 113

8.    8综合项目--我的音乐 2019.2.24 16:00. 118

8.1.    项目简介... 118

8.1.1.     项目功能展示... 118

8.1.2.     项目开发流程... 118

8.1.3.     需求分析... 118

8.1.4.     项目结构... 119

8.2.    项目实现... 120

8.2.1.     项目初始化... 120


1.1.         1.1初识模块化编程

1.1.1.          模块化是一种设计思想,把一个非常复杂的系统结构细化到具体的功能点,每个功能点看做一个模块,然后通过某种规则把这些小的模块组合到一起,构成模块化系统。

1.1.2.          模块化编程可有效解决命名冲突问题和文件依赖的关系和顺序问题。

1.2.         1.2模块化编程的演变

1.2.1.          1.2.1全局函数

1.2.1.1.             //定义用于计算的函数:所有的变量和函数都暴露在全局,无法保证全局变量与其他模块的变量发生冲突,也看不出全局变量与模块成员之间的直接关系。         function add(x, y) {           return parseInt(x) + parseInt(y);         }          function subtract(x, y) {           return parseInt(x) - parseInt(y);         }          function multiply(x, y) {           return parseInt(x) * parseInt(y);         }          function divide(x, y) {           return parseInt(x) / parseInt(y);         }  //引用:result = add(x, y);//

1.2.2.          1.2.2对象的命名空间

1.2.2.1.              /*    * 对象命名空间:内部成员的状态可以随意被外部改写,不安全。代码可读性随着子命名空间延长可读性差。    * 只是从理论意义上减少了命名冲突的问题,但是命名冲突还是存在    */   var calculator = {};    //加法   calculator.add = function(x, y) {     return parseInt(x) + parseInt(y);   }   //减法   calculator.subtract = function(x, y) {     return parseInt(x) - parseInt(y);   }   //乘法   calculator.multiply = function(x, y) {     return parseInt(x) * parseInt(y);   }   //除法   calculator.divide = function(x, y) {     return parseInt(x) / parseInt(y);   }  //引用:result = calculator.add(x, y);

1.2.3.          1.2.3函数的作用域(闭包)

1.2.3.1.            /*函数的作用域(闭包):通过封装函数的私有空间可以让一些属性和方法私有化。通过匿名自执行函数,进行私有变量隔离。 利用匿名自执行函数形成的封闭的函数作用域空间,达到自优化的目的*/          var calculator = ( function () {             function add(x,y) {                 return parseInt(x)+parseInt(y);             }             function subtract(x,y) {                 return parseInt(x)-parseInt(y);             }             function multiply(x,y) {                 return parseInt(x)*parseInt(y);             }             function divide(x,y) {                 return parseInt(x)/parseInt(y);             }             return {                 add:add,                 subtract:subtract,                 multiply:multiply,                 divide:divide             }             })();  //引用与命名空间相同:result=calculator.add(x,y);

1.2.4.          1.2.4维护和扩展

1.2.4.1.            //如果有第三方依赖的时候,可通过参数的形式将原来的模块和第三方库传递进去。        //传递参数cal     var calculator = (function(cal) {         //加法         function add(x, y) {             return parseInt(x) + parseInt(y);         }         // 减法         function subtract(x, y) {             return parseInt(x) - parseInt(y);         }         //乘法         function multiply(x, y) {             return parseInt(x) * parseInt(y);         }         //除法         function divide(x, y) {             return parseInt(x) / parseInt(y);         }         cal.add = add;         cal.subtract = subtract;         cal.multiply = multiply;         cal.divide = divide;          return cal;     })(calculator || {}); //当扩展该模块时,优先查找要扩展的对象是否已存在      // 从代码上来看:下面的 calculator 已经把上面的 calculator 给覆盖掉了     // 注意:在进行扩展的时候,优先查找要扩展的对象是否已存在     // 如果已存在,就使用已经存在的     // 如果不存在,就创建一个新的     // 最大的好处:加载的时候不用考虑顺序了     var calculator = (function(cal) {         //取余方法         cal.mod = function(x, y) {             return x % y;         }         return cal;      })(calculator || {});    //引用:result = calculator.add(x, y);

2.       2章 初识Node.js 2019.2.22 17:30'

2.1.         2.1 Node.js概述

2.1.1.          有了Node.js,用JavaScript既可以客户端开发,又可以服务器端开发,还可以与数据库交互。减少学习成本,快速打造全栈工程师。

2.1.2.          客户端将用户请求发送给服务器端。服务器端根据用户的请求进行逻辑处理、数据处理并将结果响应给客户端。现在,用Node.js来代替传统的服务器语言,开发服务器端的Web框架。

2.1.3.          JavaScript是一种脚本语言,一般运行在客户端,而Node.js可使JavaScript运行在服务器端。

2.1.4.          JavaScript组成

2.1.4.1.            核心语法是ECMAScript

2.1.4.2.            DOMHTML的应用程序接口,是文档对象模型

2.1.4.3.            BOM是浏览器对象模型,可以对浏览器窗口进行访问和操作

2.1.5.          JavaScript作用

2.1.5.1.            在客户端主要用来处理页面交互

2.1.5.1.1.             解析:依赖浏览器提供的JavaScript引擎解析执行
2.1.5.1.2.             操作对象:对浏览器提供的DOMBOM的解析进行操作
2.1.5.1.3.             常见操作:用户交互、动画特效、表单验证、Ajax请求等

2.1.5.2.            在服务器端主要用来处理数据交互

2.1.5.2.1.             解析:由特定的JavaScript引擎解析执行,如Node.js
2.1.5.2.2.             G不依赖浏览器,不操作DOMBOM
2.1.5.2.3.             主要操作:客户端做不到的事情,如操作数据库和文件等

2.2.         2.2 Node.js简介

2.2.1.          概念

2.2.1.1.            在服务器端的运行环境或运行时平台

2.2.1.2.            解析和执行JavaScript代码

2.2.1.3.            提供一些功能性的API,如文件操作和网络通信API

2.2.1.4.            20095月由RyanDahlChromeV8引擎移植出来,在其上加上API

2.2.2.          特点和优势

2.2.2.1.            它是一个JavaScript运行环境,可脱离浏览器在服务器端单独执行,代码可共用。

2.2.2.2.            依赖ChromeV8引擎,在非浏览器下解析JavaScript代码

2.2.2.3.            事件驱动Event-Driven

2.2.2.4.            非阻塞I/O(non-blocking I/O):使用事件回调的方式避免阻塞I/O所需的等待

2.2.2.5.            轻量、可伸缩,适用于实时数据交互,Socket可实现双向通信

2.2.2.6.            单进程单线程,异步编程模式,实现非阻塞I/O

2.3.         2.3 Node.js安装和配置

2.3.1.          下载和安装

2.3.2.          CMD命令台

2.3.3.          Path环境变量

2.3.4.          快速体验Node.js

2.3.4.1.            输出内容到终端: 建立文件:demo2-1.js    console.log('hello world'); 执行:    node demo2-1.js

2.3.4.2.            输出内容到网页: 建立文件demo2-2.js //加载http模块 var http = require('http'); //创建http服务器 http.createServer(function(req, res) {   //响应结束   res.end('hello world');   //监听网址127.0.0.1 端口号3000 }).listen(3000,'127.0.0.1'); 在命令行执行:node demo2-2.js     光标闪烁.... 打开浏览器,输入网址:http://127.0.0.1:3000 便会看到输出的内容。

2.4.         2.4 Node.js基础入门

2.4.1.          REPL运行环境

2.4.1.1.            node Enter >

2.4.1.2.            打开Chrome,按【F12】打开Console控制台 >

2.4.2.          global对象和模块作用域

2.4.2.1.            //demo2-3.js global对象和模块的作用域 var foo = 'bar'; console.log(foo); //global对象这时是没有foo属性的 console.log('global:foo '+global.foo); //global对象挂载一个foo变量,并将该文件模块中foo的值赋值给它 global.foo = foo; //这是global.foo的值为'bar' console.log('global:foo '+global.foo);

2.4.2.2.            //require()exportsmodule exports

2.4.2.2.1.             //require()exportsmodule exports //require()从外部获取一个模块的接口./是相对路径,默认js文件 //demo2-4.js //加载模块 var myModule = require('./info'); console.log(myModule); //输出模块中的变量值 console.log('name:'+myModule.name); console.log('type:'+myModule.type); console.log('age:'+myModule.age); //调用模块的方法 myModule.sayHello();  //info.js被加载模块 //向外暴漏变量name exports.name = 'itcast'; exports.type='edu'; //向外暴漏变量age module.exports.age='10'; //向外暴漏函数 module.exports.sayHello= function () {     console.log('hello'); }
2.4.2.2.2.             //demo2-5.js //加载模块 var myModule = require('./test'); console.log(myModule); //输出数组长度 console.log('length:'+myModule.length);  //test.js被加载模块 //使用module.exports可以单独定义数组,并成功对外开放 // module.exports=['name','type','age'];  //使用exports不能单独定义 exports=['name','type','age'];

2.4.3.          全局可用变量、函数和对象

2.4.3.1.            Node.js v10.15.1 Documentation Table of Contents Global Objects ******************************** Class: Buffer __dirname __filename clearImmediate(immediateObject) clearInterval(intervalObject) clearTimeout(timeoutObject) console exports global module process require() setImmediate(callback[, ...args]) setInterval(callback, delay[, ...args]) setTimeout(callback, delay[, ...args]) URL URLSearchParams WebAssembly

2.4.3.2.            _dirname_filename变量

2.4.3.2.1.             // 输出全局变量 __dirname 的值 console.log('文件的目录是:'+ __dirname ); // 输出全局变量 __filename 的值 console.log('文件的绝对路径是:'+__filename );

2.4.3.3.            全局函数

2.4.3.3.1.             setImmediate(callback[, ...args]) setInterval(callback, delay[, ...args]) setTimeout(callback, delay[, ...args])
clearImmediate(immediateObject)
clearInterval(intervalObject)
clearTimeout(timeoutObject)

2.4.3.4.            console对象

2.4.3.4.1.             console.log([data][, ...args])
2.4.3.4.2.             onsole.info([data][, ...args])
2.4.3.4.3.             console.error([data][, ...args])
2.4.3.4.4.             console.dir(obj[, options])
2.4.3.4.5.             console.time([label])
2.4.3.4.6.             console.timeEnd([label])
2.4.3.4.7.             console.trace([message][, ...args])
2.4.3.4.8.             console.assert(value[, ...message])

2.4.4.          Node.js模块化重写计算器案例

2.4.4.1.            add.js

2.4.4.1.1.             //加法 module.exports = function (x, y) {   return parseInt(x) + parseInt(y) }

2.4.4.2.            subtract.js

2.4.4.2.1.             //减法 module.exports = function (x, y) {   return parseInt(x) - parseInt(y) }

2.4.4.3.            multiply.js

2.4.4.3.1.             //乘法 module.exports = function (x, y) {   return parseInt(x) * parseInt(y) }

2.4.4.4.            divide.js

2.4.4.4.1.             //除法 module.exports = function (x, y) {   return parseInt(x) / parseInt(y) }

2.4.4.5.            index.js

2.4.4.5.1.             //入口模块 module.exports = {   add: require('./add'),   subtract: require('./subtract'),   multiply: require('./multiply'),   divide: require('./divide') }

2.4.4.6.            testCal.js

2.4.4.6.1.             //测试计算器功能 var cal = require('./index'); //在终端输出计算结果 console.log(cal.add(1, 2)); // => 3 console.log(cal.subtract(1, 2)) ;// => -1 console.log(cal.multiply(1, 2)); // => 2 console.log(cal.divide(1, 2)) ;// => 0.5

2.4.4.7.            node testCal.js

2.4.5.          require()模块的加载规则

2.4.5.1.            文件模块的加载

2.4.5.1.1.             /开头为根路径 ./../为相对路径 .js扩展名可不加 查找顺序.js.json.node

2.4.5.2.            核心模块的加载

2.4.5.2.1.             Node.js提供的基本API 保存在lib目录的源码文件 可直接加载,不用路径
全局对象
常用工具
事件机制
文件访问系统
HTTP服务器与客户端
2.4.5.2.2.             //demo2-7.js // 核心模块就是一个固定标识 // 如果写错,就无法加载 var os = require('os'); //输出CPU信息 console.log(os.cpus());

2.4.6.          模块的缓存 require.cache

2.4.6.1.            foo.js

2.4.6.1.1.             console.log("foo模块被加载了"); //清除缓存 delete require.cache[module.filename] ;

2.4.6.2.            demo2-7.js

2.4.6.2.1.             // 对于同一个模块标识,node 在第一次加载完成之后就会缓存该模块 // 下次继续加载该模块的时候,直接从缓存中获取 require('./foo'); require('./foo'); require('./foo'); require('./foo');

3.       3章 异步编程和包资源管理 2019.2.22 21.30'

3.1.         异步编程

3.1.1.          同步和异步

3.1.1.1.            /**  * 同步代码  */ console.log('起床');  console.log('背单词'); //吃早餐 function eatBreakfast() {     console.log('早餐吃完了'); } eatBreakfast(); console.log('去上学');  

3.1.1.2.            /**  * 异步代码  */ console.log('起床');  console.log('背单词'); function eatBreakfast() {     console.log('开始吃早餐了');     // setTimeout 执行的时候,不会阻塞后面代码的继续执行     setTimeout(function () {    //异步函数         console.log('早餐吃完了');     }, 0); } eatBreakfast() console.log('去上学');

3.1.2.          回调函数

3.1.2.1.            概念:是指函数可以被传递到 另一个函数中,然后被调用的形式。

3.1.2.2.            同步代码中使用 try...catch处理异常

3.1.2.2.1.             /**  * 同步代码处理异常  */ function parseJsonStrToObj(str) {     return JSON.parse(str) } // 对于同步代码,我们可以使用 try-catch 来捕获代码执行可能出现的异常 try {     var obj = parseJsonStrToObj('foo')     console.log(obj) } catch (e) {     console.log('转换失败了')  }

3.1.2.3.            异步代码中无法使用 try...catch处理异常

3.1.2.3.1.             /**  *异步代码无法使用try-catch处理异常  */ function parseJsonStrToObj(str) {     setTimeout(function() {         return JSON.parse(str);     }, 0); } //对于异步代码的执行来说,try-catch 是无法捕获异步代码中出现的异常的 try {     var obj = parseJsonStrToObj('foo');     console.log('执行结果是:' + obj); } catch (e) {     console.log('转换失败了'); }

3.1.2.4.            使用回调函数接收 异步代码的执行结果

3.1.2.4.1.             /**  * try-catch写在异步代码中  */ function parseJsonStrToObj(str) {     setTimeout(function() {         try{             return JSON.parse(str);         }catch(e){             console.log('转换失败了');         }     }, 0); } //调用方法输出结果 var obj = parseJsonStrToObj('foo');  //这种写法无法接收到第7行的返回值 console.log('执行结果是:' + obj);
3.1.2.4.2.             //通过回调函数来接收异步代码执行的处理结果  function parseJsonStrToObj(str,callback) {     setTimeout(function() {         try {             var obj = JSON.parse(str);             callback(null, obj);         } catch (e) {             callback(e, null);         }     }, 0); } //注意区分错误信息和正确的数据信息 parseJsonStrToObj('{"foo":"bar"}',function (err, result) {     if (err) {         return console.log('转换失败了');     }     console.log('数据转换成功,没有问题可以直接使用了:' + result); });
3.1.2.4.3.             回调函数:即当使用异步代码去做一件事时,不能预测这件事什么时候做完,其他的事情还在继续,这时,可给异步代码准备一个包裹,当异步代码有了执行结果时可以将结果放到这个包裹里,需要在哪里使用这个结果就从包裹取出。 回调函数的三个约定: 1、函数名称通常为callback,在封装异步执行代码时,优先把callback作为函数的最后一个参数出现: function 函数名 (arg1,arg2,callback{} 2、把代码中出现的错误作为callback回调函数的第一个参数进行传递:callback(err,result); 3、把真正的返回的结果数据,传递给callback的第二个参数。callback(err,result);
理解异步编程的事件驱动思路: 在异步编程中,当异步函数执行时,不确定何时执行完毕,回调函数会被压入到一个事件循环(EventLoop)的队列,然后往下执行其他代码,直到异步函数执行完成后,才会开始处理事件循环,调用相应的回调函数。这个事件循环队列是一个先进先出的队列,这说明回调是按照它们被加入队列的顺序执行的。

3.2.         Node.js的包和NPM

3.2.1.          包的概念

3.2.1.1.            包是在模块的基础上更进一步的组织JavaScript代码的目录,有出口模块,遵循CommonJS规范

3.2.1.1.1.             目录结构
package.json在顶层目录的包描述文件,说明文件

name

description

version

keywords

author

dependencies

scripts

bin 存放可执行二进制文件的目录
lib 存放JavaScript文件的目录
doc 存放文档的目录
test 存放单元测试用例的代码

3.2.2.          NPM的概念

3.2.2.1.            全称是Node.js Package Manage,

3.2.2.1.1.             含义一:是Node.js的开放模块登记和管理系统,是一个NPM网站,http://www.npmjs.com,是全球最大的模块生态系统,里面的包都是通过Node.js实现的,开源免费,即查即用。
3.2.2.1.2.             含义二:是Node.js的包管理工具,一个命令行下的软件,提供了一些命令用于快速安装和管理模块。
npm init[-y] 初始化一个package.json文件
npm install 包名  安装一个包
npm install -save 包名 将安装的包添加到package.json的依赖中
npm install -g 包名 安装一个命令行工具
npm docs 包名 查看包的文档
npm root -g 查看全局包安装路径
npm config set prefix "路径" 修改全局包安装路径
npm list 查看当前目录下安装的所有包
npm list -g 查看全局包的安装路径下所有的包
npm uninstall 包名 卸载当前目录下的某个包
npm uninstall -g 包名 卸载全局安装路径下的某个包
npm update 包名 更新当前目录下的某个包
3.2.2.1.3.             包管理的使用场景
NPM服务器下载别人编写的第三方包到本地使用
NPM服务器下载并安装别人编写的命令行程序到本地使用
允许将自己编写的包或命令行程序上传到NPM 服务器供别人使用

3.2.3.          NPM的基本应用

3.2.3.1.            安装npm install markdown

3.2.3.2.            node-moudules目录自动创建。存放第三方包,目录名和内容不能修改 除了markdown,另外的包是其依赖包

3.2.4.          包模块加载规则

3.2.4.1.            1、在加载时,Node.js会默认当做核心模块去加载。如果发现标识名不是核心模块,就会在当前目录的node_modules目录下寻找。如果没有,则从当前目录的父目录的node_modules里搜索,递归下去直到根目录。

3.2.4.2.            2、如果找到了该标识名的子目录,Node.js将会找到该子目录下的package.json文件,获取其main属性的值,根据main属性指定的路径值进行加载。用户不用关心入口模块是哪一个文件。

4.       4 Node.js 文件操作 2019.2.23 2:40'

4.1.         基本文件操作

4.1.1.          文件写入

4.1.1.1.            加载fsFileSystem)模块 var fs=require('fs');

4.1.1.2.            //同步写入 fs.writeFileSync(file,data[,option]); //异步写入 fs.writeFile(file,data[,option],callback);

4.1.1.2.1.             /*  *  同步方式写入文件  */ var fs = require('fs'); // 在进行文件操作的时候,如果是同步 API,必须使用 try-catch 来捕获异常 // 防止程序因为异常退出,导致后续代码无法继续执行  try {      console.log('写入文件...')      fs.writeFileSync('D:/a.txt', '传智播客');  } catch (e) {    console.log('不好意思,文件写入失败了')  }
4.1.1.2.2.             /*  *  异步方式写入文件  */ var fs = require('fs');  console.log(1); //该方法中回调函数的第一个参数为错误对象 fs.writeFile('d:/b.txt', '传智播客', function(err) {     //判断是否出现错误,进行错误提示   if (err) {     console.log('不好意思,文件写入失败了');   }   console.log(2); }) console.log(3);

4.1.2.          向文件追加内容

4.1.2.1.            appendFile(file,data[,option],callback); option默认为:utf8,0o666,'a'

4.1.2.1.1.             /* * 向文件追加内容 */ var fs = require('fs'); //定义需要追加的数据 var data = '欢迎您'; //调用文件追加函数 fs.appendFile('D:/a.txt', data, function(err) {   if (err) {     // 出错的情况下,回调函数中的代码就不要继续往后执行了     // 所以可以使用return 的方式,阻止代码继续执行     return console.log('文件追加失败了');   }   // 希望在文件追加成功之后做一些事情   console.log('文件追加成功了'); });

4.1.3.          文件读取

4.1.3.1.            /*  * 文件读取  */ var fs = require('fs');  //读取文件  fs.readFile('D:/a.txt', function(err, data) {    if (err) {      return console.log('文件读取失败');    }    // 因为计算机中所有的数据最终保存的都是 二进制 数据    // 所以可以通过调用 toString() 方法将二进制数据转换为人类可以识别的字符    console.log(data.toString());  });

4.1.4.          文件复制

4.1.4.1.            /*  * 文件复制案例  */ var fs = require('fs'); //读取a.txt文件数据   fs.readFile('D:/a.txt', function(err, data) {     if (err) {       return console.log('读取文件失败了');     }     //将数据写入c.txt文件     fs.writeFile('D:/c.txt', data.toString(), function(err) {       if (err) {         return console.log('写入文件失败了');       }     });     console.log('文件复制成功了');   });

4.1.4.2.            //封装copy再复制 //创建封装文件demo4-6.js /*  * 文件复制模块  */ var fs = require('fs'); /*定义文件复制函数copy() * src:需要读取的文件 * dist:目标文件 * callback:回调函数 * */ function copy(src, dist, callback) {   //读取文件   fs.readFile(src, function(err, data) {     if (err) {       return callback(err);     }     //写入文件     fs.writeFile(dist, data.toString(), function(err) {       if (err) {         return callback(err);       }       callback(null);     });   }); } module.exports = copy;  //测试文件复制test.js /*  * 测试文件复制  */ //加载封装好的文件复制功能模块 var copy = require('./demo4-6');      //调用copy()函数     copy('D:/a.txt','D:/d.txt',function(err){       if(err){        return console.log('文件复制失败了');       }       console.log('文件复制成功了');     });

4.1.5.          获取文件信息

4.1.5.1.            var fs = require('fs');  fs.stat('D:/a.txt', function (err, stats) {   //判断是否是文件   console.log("是否是文件:"+stats.isFile());   console.log(stats);        //输出文件信息 })

4.2.         案例-控制歌词滚动

4.2.1.          var fs = require('fs'); //读取歌词文件 fs.readFile('./lrc.txt', function(err, data) {   if (err) {     return console.log('读取歌词文件失败了');   }   data = data.toString();    var lines = data.split(' ');    // 遍历所有行,通过正则匹配里面的时间,解析出毫秒   // 需要里面的时间和里面的内容   var reg = /[(d{2}):(d{2}).(d{2})]s*(.+)/;    for (var i = 0; i < lines.length; i++) {     (function(index) {       var line = lines[index];       var matches = reg.exec(line);       if (matches) {         // 获取分         var m = parseFloat(matches[1]);         // 获取秒         var s = parseFloat(matches[2]);         // 获取毫秒         var ms = parseFloat(matches[3]);         // 获取定时器中要输出的内容         var content = matches[4];         // 将分++毫秒转换为毫秒         var time = m * 60 * 1000 + s * 1000 + ms;    //使用定时器,让每行内容在指定的时间输出         setTimeout(function() {           console.log(content);         }, time);       }     })(i);   } });

4.2.1.1.            [ti:我想]  [ar:张杰]  [t_time:(03:46)]  [00:00.00] 我想 - 张杰 [00:02.00] 词:王笠人 [00:04.00] 曲:王笠人 [00:06.00] 编曲:梁思桦 [00:08.00] 歌词编辑:果果   [00:10.00] QQ:765708831  [00:13.00] 中文歌词库 www.cnLyric.com [00:18.23] 每件事情都有发生的理由 [00:21.72] 可无法解释 遇见你 [00:26.15] 再多的心理准备 都抵抗不了 [00:29.89] 被现实打败的爱情 [00:34.27] 我们都习惯了同一个温度 [00:38.08] 你说这叫幸福 [00:42.32] 同时也忽略了一种残酷 [00:45.39] 我觉得 好无助 [00:50.69] 我想 我想 [00:53.54] 我想一起越过所有困难和阻挡 [00:57.64] 而我们 却不一样 [01:01.58] 虽然都有共同的理想 [01:06.10] 窗外 有阳光 [01:09.76] 透过了一丝缝隙照亮了一点希望 [01:14.07] 而晚上 的月亮 [01:17.94] 让我们再次陷入了彷徨 [01:25.66] 你问为什么喜欢拍照记录 [01:29.27] 答案我却说不出 [01:33.17] 怕如果我们走了不同方向 [01:37.06] 有照片让我回顾 [01:41.36] 回忆我们去过的每一个地方 [01:45.41] 和时而停顿的脚步 [01:49.43] 就这么停停顿顿一步接一步 [01:53.75] 直到没有路 [01:57.91] 我想 我想 [02:00.86] 我想一起越过所有困难和阻挡 [02:04.95] 而我们 却不一样 [02:08.74] 虽然都有共同的理想 [02:13.15] 窗外 有阳光 [02:16.83] 透过了一丝缝隙照亮了一点希望 [02:21.01] 而晚上 的月亮 [02:25.30] 让我们再次陷入了彷徨 [02:30.44] 如果每一个清晨 [02:33.82] 在你的温度里苏醒 [02:37.82] 闭眼聆听 有节奏的呼吸 [02:41.63] 哪怕只是一瞬间 [02:43.68] 哪怕只是一场梦 [02:50.74] 我想 我想 [02:53.60] 我想一起越过所有困难和阻挡 [02:57.73] 而我们 却不一样 [03:01.43] 虽然都有共同的理想 [03:06.16] 窗外 有阳光 [03:09.81] 透过了一丝缝隙照亮了一点希望 [03:13.72] 而晚上 的月亮 [03:18.04] 让我们再次陷入了彷徨 [03:23.35] 每件事情都有发生的理由 [03:26.88] 可无法解释 遇见你 [03:31.17] 再多的心理准备 都抵抗不了 [03:35.11] 被命运安排的相遇

4.3.         文件相关操作

4.3.1.          路径字符串操作

4.3.1.1.            获取路径模块 var path=require('path');

4.3.1.2.            > str='D:Node.js' 'D:Node.js' > path.basename(str) 'Node.js' > path.dirname(str) 'D:' > path.extname(str) '.js' >

4.3.1.3.            拼接路径字符串和转换标准路径path.join()

4.3.2.          目录操作

4.3.2.1.            创建目录fs.mkdir(path[,model],callback);

4.3.2.1.1.             /*  *创建目录,必须逐级创建目录,如果越级则出错  */ var fs = require('fs'); console.log('C:/Course目录下创建目录testDemo4-8'); fs.mkdir('D:/Course/testDemo4-8/',function(err){     if (err) {         return console.error(err);     }     console.log("目录创建成功。"); });

4.3.2.2.            读取目录

4.3.2.2.1.             /*  *读取目录  */ var fs = require('fs');  console.log('查看/testDemo4-8目录'); fs.readdir('/Node.js/code/',function(err, files){     if (err) {         return console.error(err);     }     //遍历所有文件     files.forEach( function (file){       //  输出文件名         console.log( file );     }); });

4.3.2.3.            删除目录

4.3.2.3.1.             /*  *删除目录  */  var fs=require('fs');          console.log('读取 /testDemo4-8 目录');         fs.readdir('/Course/testDemo4-8/',function(err, files){             if (err) {                 return console.error(err);             }             //遍历所有文件             files.forEach( function (file){                 //  输出文件名                 console.log( file );                 //删除文件                 fs.unlink('/Course/testDemo4-8/'+file, function(err) {                     if (err) {                         return console.error(err);                     }                     console.log(file+'文件删除成功!');                 });             });             console.log('准备删除/testDemo4-8目录');             fs.rmdir('/Course/testDemo4-8/',function(err){                 if (err) {                     return console.error(err);                 }                 console.log("目录删除成功!");             });         });

5.       5 Node.js中处理数据I/O 2019.2.23 12:00'

5.1.         Buffer缓冲区 限制大小1GB

5.1.1.          二进制数据和乱码

5.1.1.1.            乱码是指计算机二进制数据在转换字符的过程中,使用了不合适的字符集,而造成的部分或所有的字符无法被阅读。

5.1.2.          Buffer的构造函数

5.1.2.1.            缓冲区是在内容中操作数据的容器。Node.jsBuffer模块是全局性的,不需要require()函数来加载

5.1.2.2.            创建缓冲区

5.1.2.2.1.             传入字节:var buf=new Buffer(size);
5.1.2.2.2.             传入数组:var buf=new Buffer([10,20,30,40,50]);
5.1.2.2.3.             传入字符串和编码:var buf=new Buffer("hello","utf-8");

5.1.3.          写入缓冲区

5.1.3.1.            /*  * 写入缓冲区:格式 buf.write(string[,offset[,length]][,encoding]);  */ //创建一个可以存储 5 个字节的内存空间对象  var buf = new Buffer(5);  // 通过 buffer 对象的 length 属性可以获取 buffer 缓存中的字节大小  console.log(buf.length); //向缓冲区写入a  buf.write('a'); //输出缓冲区数据  console.log(buf); //向缓冲区写入b  buf.write('b', 1, 1, 'ascii'); //输出缓冲区数据  console.log(buf);

5.1.4.          从缓冲区读取数据

5.1.4.1.            /*  * 读取缓冲区:格式:buf.toString([encoding[,start[,end]]]);  */ //创建一个可以存储26个字节的内存空间对象  var buf = new Buffer(26); //buffer数组中存入26个字母对应的编码 for (var i = 0 ; i < 26 ; i++) {  buf[i] = i + 97; } //输出全部字母 console.log( buf.toString('ascii'));       // 输出: abcdefghijklmnopqrstuvwxyz //输出前五个字母 console.log( buf.toString('ascii',0,5));   // 输出: abcde // 输出: 'abcde' console.log(buf.toString('utf8',0,5)); // 输出: 'abcde', 默认编码为 'utf8' console.log(buf.toString(undefined,0,5));

5.1.5.          拼接缓冲区

5.1.5.1.            /*  * 拼接缓冲区:buf.concat(list[,totalLength]);  */ //创建两个缓冲区  var buf = new Buffer('世上无难事,');  var buf1 = new Buffer('只怕有心人'); //执行拼接操作 var buf2= Buffer.concat([buf,buf1]); //输出拼接后缓冲区的内容 console.log("buf2 内容: " + buf2.toString());

5.2.         Stream文件流

5.2.1.          文件流的概念

5.2.1.1.            四种流类型

5.2.1.1.1.             Readable可读操作可读流
5.2.1.1.2.             Writable可写操作可写流
5.2.1.1.3.             Duplex可读可写操作双向流、双工流
5.2.1.1.4.             Transform操作被写入数据,然后读出结果(变换流)

5.2.1.2.            所有的Stream对象都是EventEmitter(时间触发器)的实例

5.2.1.2.1.             事件
data当有数据可读时触发
end没有更多的数据可读时触发
error在接收和写入发生错误时触发
finish所有数据已被写入到底层系统时触发

5.2.2.          Node.js的可读流和可写流

5.2.2.1.            可读流

5.2.2.1.1.             /**  *  从流中读取数据  */ var fs = require("fs"); var total = ''; // 创建可读流 var readableStream = fs.createReadStream('input.txt'); // 设置编码为 utf8 readableStream.setEncoding('UTF8'); // 处理流事件 dataendanderror //绑定data事件,附加回调函数,流开始流动 readableStream.on('data', function(chunk) {       total += chunk; }); //读取结束后,输出total readableStream.on('end',function(){     console.log(total); }); //如果出错,输出提示信息 readableStream.on('error', function(err){     console.log(err.stack); }); console.log("程序执行完毕");

5.2.2.2.            可写流

5.2.2.2.1.             /**  *  使用文件流进行文件拷贝  */ var fs = require('fs'); //创建可读流 var readableStream = fs.createReadStream('input.txt'); //创建可写流 var writableStream = fs.createWriteStream('output.txt'); readableStream.setEncoding('utf8'); readableStream.on('data', function(chunk){     //将读出的数据块写入可写流     writableStream.write(chunk); }); readableStream.on('error', function(err){     console.log(err.stack); }); readableStream.on('end',function(){     //将剩下的数据全部写入,并且关闭写入的文件     writableStream.end(); }); writableStream.on('error', function(err){     console.log(err.stack); });

5.2.3.          使用pipe()处理大文件

5.2.3.1.            /**  *  使用pipe()进行文件拷贝  */ var fs = require('fs') //源文件路径 var srcPath = 'd:/node.js/code/chapter05/demo5-7/input.txt'; //目标文件路径 var distPath = 'd:/node.js/code/chapter05/demo5-7/input.txtoutput.txt'; var readableStream = fs.createReadStream(srcPath); var writableStream = fs.createWriteStream(distPath); // 可以通过使用可读流 的函数 pipe ()接入到可写流中 // pipe()是一个很高效的数据处理方式  if(readableStream.pipe(writableStream)){     console.log('文件复制成功了') }else{     console.log('文件复制失败了') }

6.       6 Node.js网络编程 2019.2.23 16:20’

6.1.         6.1Node.js网络编程基础

6.1.1.          IP地址和端口号

6.1.1.1.            IP地址是用来定位一台计算机的,既可以是服务器,也可以是客户端 端口号是用来定位应用程序的

6.1.2.          套接字Socket简单模型

6.1.2.1.            TCP/IP协议

6.1.2.1.1.             TCPTransfer Control Protocol,传输控制协议,是一种稳定可靠的传送方式。TCP负责发现传输的问题,一有问题就发出信号,要求重新传输,直到所有数据安全正确地传输到目的地为止。
6.1.2.1.2.             IP是给互联网的每一台联网设备规定一个地址。
6.1.2.1.3.             TCP/IP协议包含因特网整个TCP/IP协议簇
应用层面:TFTP,HTTP,SNMP,FTP,SMTP,DNS,Telnet协议等。

6.1.2.2.            Socket孔插座,可理解为接口对象,网络编程中也称套接字,常用于描述IP地址和端口等。

6.1.2.3.            Socket是支持TCP/IP的网络通信的基本操作单元,可以看做是不同主机之间的进程进行双向通信的端点,简单的说就是通信两方的一种约定。

6.1.2.4.            Socket就是对TCP/IP协议的封装,Socket本身并不是协议,而是一个调用接口(API)。

6.1.2.5.            Socket进行网络通信必须的5种信息

6.1.2.5.1.             连接使用的协议
6.1.2.5.2.             客户端设备的IP地址
6.1.2.5.3.             客户端的端口号
6.1.2.5.4.             服务期端的IP地址
6.1.2.5.5.             服务器端口

6.1.2.6.            套接字地址就是IP地址和端口号的组合

6.1.2.6.1.             套接字服务不需处理getpost请求,而是采用点对点的传输数据方式,是一个轻量级的网络通信解决方案

6.1.2.7.            套接字服务

6.1.2.7.1.             服务器用来监听连接,客户端用来打开一个到服务器的连接。

6.1.2.8.            Node.js本身就是一个服务器

6.2.         6.2Node.js中实现套接字服务

6.2.1.          Net模块的API

6.2.1.1.            net.createServer([options][, connectionListener])创建一个TCP服务器

6.2.1.2.            net.connect(options[, connectListener])

6.2.1.3.            net.createConnection(options[, connectListener])

6.2.1.4.            net.connect(port[, host][, connectListener])

6.2.1.5.            net.createConnection(port[, host][, connectListener])

6.2.1.6.            net.connect(path[, connectListener])

6.2.1.7.            net.createConnection(path[, connectListener])

6.2.1.8.            net.isIP(input)

6.2.1.9.            net.isIPv4(input)

6.2.1.10.        net.isIPv6(input)

6.2.2.          Net.Server对象

6.2.2.1.            创建Net.Server对象: var server = net.createServer([options][, connectionListener])

6.2.2.1.1.             Net.Server对象函数
server.listen([port[, host[, backlog]]][, callback])
server.listen(path[, backlog][, callback])
server.listen(handle[, backlog][, callback])
server.listen(options[, callback])
server.close([callback])
server.address()
server.ref()
server.unref()
server.getConnections(callback)
6.2.2.1.2.             Server对象的事件
'listening' 事件
'connection' 事件
'close' 事件
'error' 事件

6.2.2.2.            示例程序

6.2.2.2.1.             1步:创建服务器端文件demo6-1.js
/**  * Net.Servet创建服务器  */ // 1. 加载 manychat 核心模块 var net = require('net'); // 2. 创建一个服务应用程序,得到一个服务器实例对象 var server = net.createServer(); // 3. 监听客户端的连接事件connection,连接成功就会执行回调处理函数 server.on('connection',function () {   console.log('有客户端连接上来了'); }); // 5. 服务器有一个事件叫做 listening ,表示开启监听成功之后回调处理函数 server.on('listening',function () {   console.log('服务器开启监听成功了,正在等待客户端连接'); }); // 4. 启动服务器,开启监听 // 监听 127.0.0.13000 只能被本机所访问 server.listen(3000,'127.0.0.1');
6.2.2.2.2.             2步:在命令行窗口运行:node demo6-1.js
6.2.2.2.3.             3步:安装Telnet
设置->应用->右侧 相关设置:程序和功能->左侧 启动或关闭windows功能 在Telnet客户端前的复选框里打钩按确定
6.2.2.2.4.             4步:另开终端窗口运行:telnet 127.0.0.1 3000 记住:端口3000前面是空格而不是冒号
这时运行demo6-1窗口会显示有客户连接上来了表示客户端与服务器端连接成功!!!

6.2.3.          Net.Socket对象

6.2.3.1.            Duplex(双工)流接口,是可读可写流

6.2.3.2.            Socket事件

6.2.3.2.1.             lookup
6.2.3.2.2.             connect
6.2.3.2.3.             data
6.2.3.2.4.             end
6.2.3.2.5.             timeout
6.2.3.2.6.             drain
6.2.3.2.7.             error
6.2.3.2.8.             close

6.2.3.3.            Socket属性

6.2.3.3.1.             socket.bufferSize
6.2.3.3.2.             socket.remoteAddress
6.2.3.3.3.             socket.remoteFamily
6.2.3.3.4.             socket.remotePort
6.2.3.3.5.             socket.localAddress
6.2.3.3.6.             socket.localPort
6.2.3.3.7.             socket.bytesRead
6.2.3.3.8.             socket.bytesWritten

6.2.3.4.            Socket函数

6.2.3.4.1.             new net.Socket([options])
6.2.3.4.2.             socket.connect(port[, host][, connectListener])
6.2.3.4.3.             socket.connect(path[, connectListener])
6.2.3.4.4.             socket.setEncoding([encoding])
6.2.3.4.5.             socket.write(data[, encoding][, callback])
6.2.3.4.6.             socket.destroy([exception])
6.2.3.4.7.             socket.pause()
6.2.3.4.8.             socket.resume()
6.2.3.4.9.             socket.setTimeout(timeout[, callback])
6.2.3.4.10.         socket.setNoDelay([noDelay])
6.2.3.4.11.         socket.setKeepAlive([enable][, initialDelay])
6.2.3.4.12.         socket.address()
6.2.3.4.13.         socket.ref()
6.2.3.4.14.         socket.unref()

6.2.3.5.            服务器向客户端发送消息

6.2.3.5.1.             /**  * 在服务器端使用Socket  */ // 1. 加载 manychat 核心模块 var net = require('net'); // 2. 创建一个服务应用程序,得到一个服务器实例对象 var server = net.createServer(); // 3. 监听客户端的连接事件,连接成功就会执行回调处理函数 // 每次回调函数被调用,就会有一个新的 socket 对象在回调函数中 server.on('connection',function (socket) {   console.log('有客户端连接上来了');  //在服务端可以获取到客户端的IP地址等信息   console.log('客户端IP地址:' + socket.remoteAddress + '连接到了当前服务器');   // 当前连接成功之后的客户端发送一个 hello world   socket.write('hello world'); }); // 5. 服务器有一个事件叫做 listening ,表示开启监听成功之后回调处理函数 server.on('listening',function () {   console.log('服务器开启监听成功了,正在等待客户端连接'); }); // 4. 启动服务器,开启监听 server.listen(3000,'127.0.0.1');
6.2.3.5.2.             服务器端运行node demo6-1
6.2.3.5.3.             客户端运行 telnet 127.0.0.1 3000

6.2.3.6.            统计在线人数

6.2.3.6.1.             /**  * 服务端统计在线人数  */ var net = require('net'); var server = net.createServer(); var count = 0; server.on('connection', function(socket) {   count++;   console.log('welcome , 当前在线人数:' + count);   socket.write('remoteAddress'+socket.remoteAddress+' ');   socket.write('remotePort'+socket.remotePort); }); server.listen(3000, '127.0.0.1', function() {   console.log('server listening at port 3000'); });
6.2.3.6.2.             服务器端运行 node demo6-3.js 打开多个命令窗口分别运行 telnet 127.0.0.1 3000

6.2.3.7.            客户端与服务器端双向通信

6.2.3.7.1.             创建客户端
/*  * 双向通信-客户端  */ var net = require('net');  // 当调用 createConnection 之后,就会得到一个与服务器进行通信的 socket 对象 // 该对象中包含当前客户端与服务器通信的 ip地址和端口号  var client = net.createConnection({   port: 3000 }); // 什么时候客户端和服务器连接成功了 // 可以通过监听 client connect 事件来处理 client.on('connect',function () {   // 客户端与服务器连接成功了   console.log('客户端与服务器连接成功了');   client.write('你吃了吗?'); }); client.on('data',function (data) {   //输出服务器发送给当前客户端的数据   console.log(data.toString()); });
6.2.3.7.2.             创建服务器端
/**  * 双向通信-服务器  */  var net = require('net');  var server = net.createServer(); // 每一个客户端与服务器建立连接成功之后,都会触发一次 connection 事件 server.on('connection', function(socket) {    /*以下部分应用于双向通信*/   //通过监听 socket 对象的 data 事件来获取客户端发送给服务器的数据   socket.on('data', function(data) {     console.log(data.toString());     socket.write('我吃的小豆包');   }); }); server.listen(3000, '127.0.0.1', function() {   console.log('server listening at port 3000'); });
6.2.3.7.3.             如果在服务器端启动telnet 127.0.0.1 3000 则可即时通讯

6.3.         6.3Node.js进程管理

6.3.1.          Process模块获取终端输入

6.3.1.1.            /**  * 测试获取终端输入  */ // 通过下面的方式就可以获取用户的输入 process.stdin.on('data',function (data) {   console.log(data.toString().trim()); });

6.3.2.          多人广播消息

6.3.2.1.            1、创建目录manychat,并创建两个文件server.jsclient.js

6.3.2.1.1.             //server.js /**  * 多人广播聊天服务端端  */ var net = require('net'); var server = net.createServer(); //该数组用来封装所有客户端的Socket var users = []; server.on('connection', function(socket) {   users.push(socket);   socket.on('data', function(data) {     data = data.toString().trim();     users.forEach(function(client) {       if (client !== socket) {         //由于同一台计算机上不同客户端端口号不同,所以可以通过端口号来区分是谁说的话         client.write(socket.remotePort+ '' + data);       }     });   });   // 当有客户端异常退出的时候,就会触发该函数   // 如果不监听客户端异常退出就会导致服务器崩溃   socket.on('error',function () {     console.log('有客户端异常退出了');   }); }) server.listen(3000, '127.0.0.1', function() {   console.log('server listening at port 3000'); });
6.3.2.1.2.             //client.js /**  * 多人广播聊天客户端  */ var net = require('net') //向服务端创建连接 var client = net.createConnection({   port:3000,   host:'127.0.0.1' }); //监听连接成功事件connent client.on('connect',function () {   // 通过当前进程的标准输入的 data 事件获取终端中的输入   process.stdin.on('data',function (data) {     data = data.toString().trim();     client.write(data);   }); }); //监听data事件输入服务器返回的数据 client.on('data',function (data) {   console.log(data.toString()); });

6.4.         6.4案例--终端聊天室

6.4.1.          配置文件:config

6.4.1.1.            module.exports = {   "port": 3000,   "host": "127.0.0.1" }

6.4.2.          客户端文件:client.js

6.4.2.1.            /**  * 终端聊天室客户端  */ var net = require('net'); var config = require('./config'); var client = net.createConnection({   port: config.port,   host: config.host }) //用户注册成功后为该属性赋值 var username; client.on('connect', function() {   console.log('请输入用户名:');   process.stdin.on('data', function(data) {     data = data.toString().trim();       // 当用户注册成功之后,下面的数据格式就不能再使用了       // 判断一下是否已经有用户名了,如果已经有了,则表示用户要发送聊天数据       // 如果没有,则表示用户要发送注册数据     if (!username) {       var send = {           protocal: 'signup',           username: data         }       client.write(JSON.stringify(send));       return;     }     //判断是广播消息还是点对点消息     // name:内容     var regex = /(.{1,18}):(.+)/;     var matches = regex.exec(data);     if (matches) {       var from = username;       var to = matches[1];       var message = matches[2];       var send = {         protocal: 'p2p',         from: username,         to: to,         message: message       }       client.write(JSON.stringify(send));     } else {       var send = {         protocal: 'broadcast',         from: username,         message: data       }       client.write(JSON.stringify(send));     }   }); }); client.on('data', function(data) {   data = JSON.parse(data);   switch (data.protocal) {     case 'signup':       var code = data.code;       switch (code) {         case 1000:           username = data.username;           console.log(data.message);           break;         case 1001:           console.log(data.message);           break;         default:           break;       }       break;     case 'broadcast':       console.log(data.message);       break;     case 'p2p':       var code = data.code;       switch (code) {         case 2000:           var from = data.from;           var message = data.message;           message = from + '对你说:' + message;           console.log(message);           break;         case 2001:           console.log(data.message);           break;         default:           break;       }       break;     default:       break;   }; });

6.4.3.          服务器端文件:server.js

6.4.3.1.            /**  * 终端聊天室服务端  */ var net = require('net'); var config = require('./config'); var broadcast=require('./broadcast.js'); var p2p=require('./p2p.js'); var signup=require('./signup.js'); var server = net.createServer(); var users = {}; server.on('connection', function(socket) {   socket.on('data', function(data) {     // 解析客户端发送的数据     data = JSON.parse(data);       // 根据客户端发送的数据类型,做对应的操作     switch (data.protocal) {       case 'signup':           //处理用户注册         signup.signup(socket,data,users);         break;       //处理广播消息       case 'broadcast':         broadcast.broadcast(data,users);         break;       case 'p2p':          // 处理点对点消息         p2p.p2p(socket, data,users);         break;       default:         break;     }   });   socket.on('error', function() {     console.log('有客户端异常退出了');   }); }); // 3. 启动服务器,开启监听 server.listen(config.port, config.host, function() {   console.log('server listening at port ' + config.port); });

6.4.4.          用户注册模块:signup.js

6.4.4.1.            /**  * 用户注册  * @param socket  * @param data 用户名  *  {protocal: 'signup',      username: '小明'}  * @param users 用户组  */ exports.signup=function (socket,data,users) {     // 处理用户注册请求     var username = data.username;     // 如果用户名不存在,则将该用户名和它的Socket地址保存起来     if (!users[username]) {         users[username] = socket;         var send = {             protocal: 'signup',             code: 1000,             username: username,             message: '注册成功'         }         socket.write(JSON.stringify(send));     } else {         var send = {             protocal: 'signup',             code: 1001,             message: '用户名已被占用,请重新输入用户名:'         }         socket.write(JSON.stringify(send));     } }

6.4.5.          广播消息模块:broadcast.js

6.4.5.1.            /**  * 广播消息  * @param data 广播消息发送过来的JSON数据  * {   "protocal": "broadcast",//消息类型为广播   "from": "小红",//发送消息的用户   "message": "大家早上好"//用户发送的消息内容 }  */ exports.broadcast= function(data,users) {     var from = data.from;     var message = data.message     message = from + '说:' + message;     var send = {         protocal: 'broadcast',         message: message     }     send = new Buffer(JSON.stringify(send));      for (var username in users) {         var tmpSocket = users[username];         tmpSocket.write(send);     } }

6.4.6.          点对点消息模块:p2p.js

6.4.6.1.            /**  * 点对点消息  * @param socket  * @param data 点对点消息的JSON数据  * {   "protocal": "p2p", //消息类型为点对点   "from": "小红", //发送消息的用户   "to": "小明",   "message": "你早上吃的什么" }  * @param users 用户组  */ exports.p2p=function (socket,data,users) {     var from = data.from;     var to = data.to;     var message = data.message;     // 找到要发送给某个人的 Socket 地址对象     var receiver = users[to];     // 如果接收人不存在,告诉客户端没有该用户     if (!receiver) {         var send = {             protocal: 'p2p',             code: 2001,             message: '用户名不存在'         }         socket.write(new Buffer(JSON.stringify(send)));     } else {         // xxx 对你说: xxx         var send = {             protocal: 'p2p',             code: 2000,             from: data.from,             message: message         }         receiver.write(new Buffer(JSON.stringify(send)));     }     // 如果接收人存在,则将消息发送给该用户 }

7.       7 Node.js中实现HTTP服务 2019.2.23 22:35'

7.1.         7.1HTTP协议

7.1.1.          HTTP协议简介

7.1.1.1.            HTTP:Hyper Text Transfer Protocol 超文本传输协议

7.1.1.1.1.             1990年提出
7.1.1.1.2.             用于从WWW服务器传输超文本到本地浏览器的传输协议
7.1.1.1.3.             基于TCP的连接方式
7.1.1.1.4.             开放系统互连参考模型OSI/RM通信协议七层
应用层Application Layer

协议有HTTPFTPSMTP

表示层Presentation Layer

ASCII码、JPEGMPEG WAV等文件转换

会话层Session Layer

负责访问次序的安排等

传输层Transport Layer

协议有TCPUDP

网络层Network Layer

三层交换机、路由器等 协议有IPSPX

数据链路层Data Link Layer

二层交换机、网桥网卡等

物理层Physics Layer

集线器、中继器和传输线路等

7.1.1.1.5.             HTTP由请求和响应构成, 是一个标准的客户端服务器模型, 也是一个无状态的协议。 各大浏览器广泛基于HTTP1.1
7.1.1.1.6.             HTTP协议特点
支持客户/服务器模式
简单快速:GET/HEAD/POST
灵活:允许任意类型数据
无连接:处理完一个请求和响应即断开连接
无状态:对事务处理没有记忆能力

7.1.2.          HTTP请求响应流程

7.1.2.1.            URL由几部分组成:协议+域名+具体地址

7.1.2.1.1.             HTTPPOP3FTP
7.1.2.1.2.             域名或IP地址及端口号:www.itheima.com
通过DNS解析
7.1.2.1.3.             具体地址:index.html一般加密或不显示

7.1.2.2.            请求request,URL地址等封装成HTTP请求报文,存放在客户端Socket对象中

7.1.2.3.            响应response,把数据封装在HTTP响应报文,并存放在Socket对象中

7.1.3.          HTTP的请求报文和响应报文

7.1.3.1.            报文是有一定格式的字符串,查看报文需要借助工具,例如chrome内核版本 65.0.3325.181

7.1.3.2.            请求报文request

7.1.3.2.1.             GET / HTTP/1.1
7.1.3.2.2.             Host: www.itheima.com
7.1.3.2.3.             Connection: keep-alive
7.1.3.2.4.             Cache-Control: max-age=0
7.1.3.2.5.             Upgrade-Insecure-Requests: 1
7.1.3.2.6.             User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36
7.1.3.2.7.             Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
7.1.3.2.8.             Accept-Encoding: gzip, deflate
7.1.3.2.9.             Accept-Language: zh-CN,zh;q=0.9
7.1.3.2.10.         Cookie: UM_distinctid=1691db04

7.1.3.3.            响应报文response

7.1.3.3.1.             HTTP/1.1 200 OK
7.1.3.3.2.             Server: Tengine
7.1.3.3.3.             Content-Type: text/html; charset=UTF-8
7.1.3.3.4.             Transfer-Encoding: chunked
7.1.3.3.5.             Connection: keep-alive
7.1.3.3.6.             Date: Sun, 24 Feb 2019 04:11:14 GMT
7.1.3.3.7.             Accept-Ranges: bytes
7.1.3.3.8.             Ali-Swift-Global-Savetime: 1550981474
7.1.3.3.9.             Via: cache4.l2et15[26,200-0,M], cache14.l2et15[27,0], kunlun5.cn1259[77,200-0,M], kunlun5.cn1259[77,0]
7.1.3.3.10.         X-Cache: MISS TCP_MISS dirn:-2:-2
7.1.3.3.11.         X-Swift-SaveTime: Sun, 24 Feb 2019 04:11:14 GMT
7.1.3.3.12.         X-Swift-CacheTime: 0
7.1.3.3.13.         Timing-Allow-Origin: *
7.1.3.3.14.         EagleId: da5ed29915509814747132159e

7.2.         7.2Node.jsHTTP服务

7.2.1.          HTTP模块常用API

7.2.1.1.            加载语法:var http = require('http');

7.2.1.2.            http.Server

7.2.1.2.1.             HTTP服务器指的就是http.Server对象
7.2.1.2.2.             Node.js的所有基于HTTP协议的系统都是基于http.Server实现的
如网站、社交应用、代理服务器等
7.2.1.2.3.             创建语法:var server = http.createServer();
7.2.1.2.4.             函数
server.close([callback])
server.listen(port[,hostname][,backlog][,callback])
server.listen(handle[,callback])/server.listen(path[,callback])
7.2.1.2.5.             事件
request
connection
close

7.2.1.3.            http.IncomingMessage可读流req

7.2.1.3.1.             函数&属性
message.headers
message.httpVersion
Message.method
message.setTimeout(msecs,callback)
message.socket
message.url

7.2.1.4.            http.ServerResponse可写流res

7.2.1.4.1.             函数&属性
response.writeHead(statusCode,[headers])
response.write(data,[enconding])
response.end([data],[enconding])
response.addTrailers(headers)
response.finished
response.getHeader(name)
response.headersSent
response.removeHeader(name)
response.sendDate
response.setHeader(name, value)
response.setTimeout(msecs[, callback])
response.statusCode
response.statusMessage
response.writeContinue()

7.2.2.          使用HTTP模块构建Web服务器

7.2.2.1.            /**  * 使用HTTP构建Web服务器  */ var http = require('http'); // 1. 创建一个 HTTP 服务器 var server = http.createServer();  // 2. 监听 请求(request) 事件 // request 就是一个可读流,用来 获取 当前与服务器连接的客户端的一些请求报文数据 // response 就是一个可写流,用来 给 客户端 Socket 发送消息的,或者用来发送响应报文的 server.on('request',function (request, response) {    // 使用 HTTP 发送响应数据的时候,HTTP 服务器会自动把数据通过 HTTP 协议包装为一个响应报文然后发送到Socket   response.write('hello world');   // 在结束响应之前,我们可以多次向 客户端 发送数据   response.write('hello itheima');   // 对于 HTTP 请求响应模型来说,它们的请求和响应是一次性的   // 也就是说,每一次请求都必须结束响应,   // 标识断开当前连接   response.end();   // 在一次 HTTP 请求响应模型中,当结束了响应,就不能继续发送数据了,以下消息不会显示 }); // 3. 开启启动,监听端口 server.listen(3000,function () {   console.log('server is listening at port 3000'); });

7.2.3.          03_http.js

7.2.3.1.            var http = require('http')  var server = http.createServer(function (req,res) {   res.end('<h1> hello world </h1>')   })  server.listen(3000)

7.2.4.          04_浏览器的本质.js

7.2.4.1.            +++++++++++++++++var net = require('manychat')  var server = net.createServer()  server.on('connection',function (socket) {   socket.on('data',function (data) {     console.log(data.toString())     // 对于 客户端来说, 这个时候,服务器发送给自己的消息,有没有发送完毕,不确定     // 所以 浏览器客户端 保持了挂起的状态,继续等待 服务器给自己传输消息数据     socket.write('HTTP/1.1 200 成功了 hello world')      // 如果想告诉客户端,本次的数据已经给你发送完毕了,不用等了 ,结束响应     // 结束响应就意味着,本次请求响应连接断开,。     socket.end()   }) })  server.listen(3000)

7.3.         7.3HTTP服务请求处理

7.3.1.          根据不同的URL发送不同响应消息

7.3.1.1.            /**  * 根据不同URL响应不同消息  */ var http = require('http'); //创建服务器 var server = http.createServer(); //监听request事件 server.on('request', function(request, response) {   //获取资源路径,默认为'/'   var url = request.url;   //通过判断获取到的资源路径,发送指定响应消息   if (url === '/') {     response.end('hello index');   } else if (url === '/login') {     response.end('hello login');   } else if (url === '/register') {     response.end('hello register');   }else {     //如果路资源径找不到,提示错误信息     response.end('404 Not Found!');   } }); //开启启动,监听端口 server.listen(3000, function() {   console.log('server is listening at port 3000'); });

7.3.2.          HTTP处理静态资源服务

7.3.2.1.            主程序demo7-3.js

7.3.2.1.1.             /**  * 使用HTTP提供静态资源服务  */ var http = require('http'); var fs = require('fs');//用于读取静态资源 var path = require('path');//用于做路径拼接 var server = http.createServer();  server.on('request', function(request, response) {   //获取静态资源路径   var url = request.url;   if (url === '/') {     //读取相应静态资源内容     fs.readFile(path.join(__dirname, 'static/index.html'), 'utf8', function(err, data) {      //如果出现异常抛出异常       if (err) {         throw err;       }       //将读取的静态资源数据响应给浏览器       response.end(data);     });   } else if (url === '/login') {     fs.readFile(path.join(__dirname, 'static/login.html'), 'utf8', function(err, data) {       if (err) {         throw err;       }       response.end(data);     });   } else if (url === '/register') {     fs.readFile(path.join(__dirname, 'static/register.html'), 'utf8', function(err, data) {       if (err) {         throw err;       }       response.end(data);     });   } else if (url === '/login.html') {     fs.readFile(path.join(__dirname, 'static/404.html'), 'utf8', function(err, data) {       if (err) {         throw err       }       response.end(data);     });     //如果有图片、CSS文件等,浏览器会重新发送请求获取静态资源   } else if (url === '/css/main.css') {     var cssPath = path.join(__dirname, 'static/css/main.css')     fs.readFile(cssPath, 'utf8', function(err, data) {       if (err) {         throw err       }       response.end(data);     });   } else if (url === '/images/01.jpg') {     var imgPath = path.join(__dirname,'static/images/01.jpg')     fs.readFile(imgPath, function(err, data) {       if (err) {         throw err       }       response.end(data);     });   } else {     fs.readFile(path.join(__dirname, 'static/404.html'), 'utf8', function(err, data) {       if (err) {         throw err       }       response.end(data);     });   } }); server.listen(3000, function() {   console.log('server is listening at port 3000'); });

7.3.2.2.            index.html

7.3.2.2.1.             <!DOCTYPE html> <html lang="en"> <head>   <meta charset="UTF-8">   <title>首页</title>   <link rel="stylesheet" href="css/main.css"> </head> <body>   <h1>首页</h1> <img src="images/01.jpg" alt=""> </body> </html>

7.3.2.3.            login.html

7.3.2.3.1.             <!DOCTYPE html> <html lang="en"> <head>   <meta charset="UTF-8">   <title>登录</title>   <link rel="stylesheet" href="css/main.css"> </head> <body>   <h1>登录</h1>   <img src="images/01.jpg" alt=""> </body> </html>

7.3.2.4.            register.html

7.3.2.4.1.             <!DOCTYPE html> <html lang="en"> <head>   <meta charset="UTF-8">   <title>注册</title>   <link rel="stylesheet" href="css/main.css"> </head> <body>   <h1>注册</h1>   <img src="images/01.jpg" alt=""> </body> </html>

7.3.2.5.            404.html

7.3.2.5.1.             <!DOCTYPE html> <html lang="en"> <head>   <meta charset="UTF-8">   <title>404</title> </head> <style>   body {     background-color:pink;   } </style> <body>   <h1>404 Not Found.</h1> </body> </html>

7.3.2.6.            main.css

7.3.2.6.1.             body {     background-color: pink; }

7.3.2.7.            01.jpg

7.3.2.7.1.             Topic1

7.3.3.          动态处理静态资源请求

7.3.3.1.            demo7-4.js

7.3.3.1.1.             /**  * 动态处理静态资源请求  */ var http = require('http'); var fs = require('fs'); var path = require('path'); var server = http.createServer(); server.on('request', function(req, res) {   // 当用户访问 / 的时候,默认让用户访问 index.html   var url = req.url;   console.log(url);//每次请求获取资源路径在服务端输出。   var fullPath = path.join(__dirname,'static',url);   if (url==='/') {     fullPath = path.join(__dirname,'static/index.html');   }   fs.readFile(fullPath,function (err,data) {     if (err) {       // 在进行web开发的时候,如果发生了错误,我们可以直接把该错误消息输出到 客户端       return res.end(err.message);     }     res.end(data);   }); }); server.listen(3000, function() {   console.log('server is runnig at port 3000'); });

7.4.         Underscore的模板引擎template

7.4.1.          template用于将JavaScript模板编译为可以用于页面呈现的函数,通过JSON数据源生成复杂的HTML并呈现出来

7.4.2.          语法:_.template(templateString,[settings])

7.4.3.          赋值:

7.4.3.1.            var compiled = _.template("hello:<%=name%>"); compiled({name:'moe'}); =>"hello:moe"

7.4.4.          需要转义:

7.4.4.1.            var template = _.template("<b><%- value %></b>"); template({value:'<script>'}); =>"<b>&lt;script&gt;</b>"

7.4.5.          Node.js中使用Underscore需要用NPM安装

7.4.5.1.            static-server目录下初始化:npm init -y

7.4.5.1.1.             package.json
{   "name": "static-server",   "version": "1.0.0",   "description": "",   "main": "app.js",   "scripts": {     "test": "echo "Error: no test specified" && exit 1"   },   "keywords": [],   "author": "",   "license": "ISC",   "dependencies": {     "underscore": "^1.8.3"   } }

7.4.5.2.            在此目录下继续输入:npm install underscore --save

7.4.5.3.            在此目录下创建index.heml,并添加代码

7.4.5.3.1.             <!DOCTYPE html> <html> <head>   <meta charset="utf-8">   <body>     <ul>       <% arr.forEach(function(item){ %>         <li> <%= item.name %> </li>         <%}) %>     </ul>     <h1><%= title %></h1>   </body> 备注:在服务器端定义了一个数组arr,遍历该数组的每个元素, 为元素挂着name属性。再定义一个变量title。这个页面是作为 一个网页的模板使用,在服务器端会获得这个模板并为模板中的 变量赋值,最后,将完整的数据作为一个字符串响应给浏览器端, 由浏览器进行页面渲染和呈现

7.4.5.4.            在此目录下创建文件app.js,并添加服务器端代码

7.4.5.4.1.             /**  * 服务端代码  */ var http = require('http'); var fs = require('fs'); var path = require('path'); var _ = require('underscore'); var server = http.createServer(); server.on('request', function(req, res) {   var url = req.url;   if (url === '/') {     //备注:读取index.html模板的内容,并将返回的data传入模板函数_.template,传入后可以使用compiled()函数向该模板的数组和变量注入数据,最后将完整的HTML数据响应给客户端浏览器!!!     fs.readFile(path.join(__dirname, 'index.html'), 'utf8', function(err, data) {       if (err) {         return res.end(err.message);       }       // 现在的 data 就是 字符串       // 我把 html 字符串 整体的当成 模板字符串       var compiled = _.template(data);       var htmlStr = compiled({         title: 'hello world',         arr: [           { name: 'Jack' },           { name: 'rose' },           { name: 'mike' }         ]       });       res.end(htmlStr);     });   } }); server.listen(3000, function() {   console.log('server is runnig at port 3000'); });
子主题

7.4.5.5.            最后,启动服务器:node app.js 客户端打开浏览器查看

8.       8章 综合项目--我的音乐 2019.2.24 16:00

8.1.         项目简介

8.1.1.          项目功能展示

8.1.2.          项目开发流程

8.1.2.1.            1、产品创意

8.1.2.2.            2、产品原型

8.1.2.3.            3、美工设计

8.1.2.4.            4、前端实现

8.1.2.5.            5、后端实现

8.1.2.6.            6、测试、试运行、上线

8.1.3.          需求分析

8.1.3.1.            1、数据模型分析

8.1.3.2.            2、路由设计

8.1.3.3.            3、功能开发

8.1.3.3.1.             展示歌曲信息
8.1.3.3.2.             添加歌曲
8.1.3.3.3.             编辑歌曲信息
8.1.3.3.4.             删除歌曲

8.1.4.          项目结构

8.1.4.1.            1render.js解析模板标记语法

8.1.4.2.            2music.js封装音乐文件相关的逻辑处理函数

8.1.4.3.            3node_modules文件夹下的bootstrap响应式前端框架

8.1.4.4.            4node_modules文件夹下的underscore:模板引擎用于注入后台数据

8.1.4.5.            5node_modules文件夹下的formidate:用于表单的数据处理,尤其是表单的文件上传处理

8.1.4.6.            6uploads文件夹:用于存放MP3音频文件和jpg图片文件

8.1.4.7.            7views文件夹:用于存放页面

8.1.4.8.            8app.js:项目的入口文件

8.1.4.9.            9config.js配置端口

8.1.4.10.        10package.json项目的说明文件

8.1.4.11.        11router.js:路由模块,根据用户的请求判断路径,然后将请求分发到具体的处理函数

8.2.         项目实现

8.2.1.          项目初始化

8.2.1.1.            项目说明文件package.json

8.2.1.1.1.             {   "name": "music-player",   "version": "1.0.0",   "description": "一个简单的音乐播放器",   "main": "app.js",   "scripts": {     "test": "echo "Error: no test specified" && exit 1",     "start": "node app.js"   },   "author": "iroc <mail@lipengzhou.com> (https://github.com/iroc)",   "license": "MIT",   "dependencies": {     "bootstrap": "^3.3.6",     "formidable": "^1.0.17",     "underscore": "^1.8.3"   } }
8.2.1.1.2.             "start": "nodemon app.js" 当不知道入口文件时,会通过下面的命令自动找到start指令: npm start
8.2.1.1.3.               "dependencies": {     "bootstrap": "^3.3.6",     "formidable": "^1.0.17",     "underscore": "^1.8.3"   }需要选装第三方依赖包
npm install --save bootstrap
npm install --save formidable

8.2.1.2.            入口文件app.js

8.2.1.2.1.             var http = require('http') var config = require('./config') var router = require('./router') var render = require('./common/render')  var server = http.createServer()  server.on('request', function(req, res) {    // 首先动态的给 Response 对象挂载了一个 render 方法,该方法用来读取一个模板文件,注入数据,然后响应给当前请求   render(res)    // 在进入 router 模块之前   // 我们就已经给 res 对象加了一个属性方法叫做:render      // 然后请求和响应被传递到一个路由的模块中   // 该模块就是专门用来对不同的请求路径分发到具体的请求处理函数   router(req, res)  })  server.listen(config.port, config.host, function() {   console.log('server is listening at port ' + config.port)   console.log('pleast visit http://' + config.host + ':' + config.port) })

8.2.1.3.            配置文件config.js

8.2.1.3.1.             var path = require('path') module.exports = {   port: 3000,   host: '127.0.0.1',   viewPath: path.join(__dirname, 'views'),   uploadPath: path.join(__dirname, 'uploads') }

8.2.1.4.            路由文件router.js

8.2.1.4.1.             /**  * 路由模块:负责把具体的请求路径分发的具体的请求处理函数  * 分发到具体的业务处理逻辑  * 用户的每一个请求都会对应后台的一个具体的请求处理函数  */  var fs = require('fs') var path = require('path') var _ = require('underscore') var handler = require('./handler') var musicController = require('./controllers/music') var userController = require('./controllers/user') var url = require('url')  module.exports = function(req, res) {   // 首页 / 呈递音乐列表页面   // 添加音乐页面 /add 呈递添加音乐页面   // 编辑音乐页面 /edit 呈递编辑音乐页面   // 删除音乐 /remove 处理删除音乐请求   // 当使用 url核心模块的 parse方法之后,该方法会自动帮你把路径部分解析到 pathname 属性中   // 同时他会把 查询字符串部分 解析到 query 属性   // 对于我们的 url.parse 方法来说,它还有第二个参数,我们可以给它指定为 true,那么这个时候它会自动帮我们把 query 属性查询字符串转换为一个对象   var urlObj = url.parse(req.url, true)   req.query = urlObj.query   console.log(urlObj.query)   // 获取当前请求路径   // pathname 不包含查询字符串   var pathname = urlObj.pathname   var method = req.method   console.log(method)    if (method === 'GET' && pathname === '/') {     musicController.showIndex(req, res)   } else if (method === 'GET' && pathname === '/index.html') {     musicController.showIndex(req, res)   } else if (method === 'GET' && pathname.startsWith('/node_modules/')) {     var staticPath = path.join(__dirname, pathname)     fs.readFile(staticPath, 'utf8', function(err, data) {       if (err) {         return res.end(err.message)       }       res.end(data)     })   } else if (method === 'GET' && pathname === '/add') {     musicController.showAdd(req, res)   } else if (method === 'GET' && pathname === '/edit') {     musicController.showEdit(req, res)   } else if (method === 'GET' && pathname === '/login') {     userController.showLogin(req, res)   } else if (method === 'GET' && pathname === '/register') {     userController.showRegister(req, res)   } else if (method === 'POST' && pathname === '/add') {     musicController.doAdd(req, res)   } else if (method === 'GET' && pathname ==='/remove') {     musicController.doRemove(req, res)   } else if (method === 'POST' && pathname === '/edit') {     musicController.doEdit(req, res)   } }  // res.render('path',{})  // render('path',{ //   title: 'Index' // },res)  // function render(viewPath, obj, res) { //   fs.readFile(viewPath, 'utf8', function(err, data) { //     if (err) { //       return res.end(err.message) //     } //     var compiled = _.template(data) //     var htmlStr = compiled(obj) //     res.end(htmlStr) //   }) // }

8.2.1.5.            解析模板标记语法render.js

8.2.1.5.1.             var fs = require('fs') var path = require('path') var _ = require('underscore') var config = require('../config')  module.exports = function(res) {   res.render = function(viewName, obj) {     fs.readFile(path.join(config.viewPath, viewName) + '.html', 'utf8', function(err, data) {       if (err) {         return res.end(err.message)       }       var compiled = _.template(data)       var htmlStr = compiled(obj || {})       res.end(htmlStr)     })   } }

8.2.1.6.            封装处理函数模块music.js

8.2.1.6.1.             var qstring = require('querystring') var formidable = require('formidable') var config = require('../config') var path = require('path')  var storage = [   { id: 1, title: '富士山下', singer: '陈奕迅', music: '陈奕迅 - 富士山下.mp3', poster: '陈奕迅.jpg' },   { id: 2, title: '石头记', singer: '达明一派', music: '达明一派 - 石头记.mp3', poster: '达明一派.jpg' },   { id: 3, title: '青城山下白素贞', singer: '好妹妹乐队', music: '好妹妹乐队 - 青城山下白素贞.mp3', poster: '好妹妹乐队.jpg' },   { id: 4, title: '友情岁月', singer: '黄耀明', music: '黄耀明 - 友情岁月.mp3', poster: '黄耀明.jpg' },   { id: 5, title: '梦里水乡', singer: '江珊', music: '江珊 - 梦里水乡.mp3', poster: '江珊.jpg' },   { id: 6, title: 'Blowing In The Wind', singer: '南方二重唱', music: '南方二重唱 - Blowing In The Wind.mp3', poster: '南方二重唱.jpg' },   { id: 7, title: '女儿情', singer: '万晓利', music: '万晓利 - 女儿情.mp3', poster: '万晓利.jpg' },   { id: 8, title: '王馨平', singer: '别问我是谁', music: '王馨平 - 别问我是谁.mp3', poster: '王馨平.jpg' },   { id: 9, title: '五环之歌', singer: '岳云鹏', music: '岳云鹏,MC Hotdog - 五环之歌.mp3', poster: '岳云鹏.jpg' } ]  exports.showIndex = function(req, res) {   res.render('index', {     title: '首页',     musicList: storage   }) }  exports.showAdd = function(req, res) {   res.render('add', {     title: '添加音乐'   }) }  exports.doAdd = function(req, res) {   // 表单post提交的时候,没有enctype的情况下,可以使用下面这种方式来接收和处理post提交的数据   // var data = ''   // req.on('data',function (chunk) {   //   data += chunk   // })   // req.on('end',function () {   //   var postBody = qstring.parse(data)   //   console.log(postBody)   //   res.end(JSON.stringify(postBody))   // })    // 如果要处理有文件的表单,那么可以使用社区提供的一个包:formidable   var form = new formidable.IncomingForm()   form.uploadDir = config.uploadPath   form.keepExtensions = true   form.parse(req, function(err, fields, files) {     if (err) {       return res.end(err.message)     }     var title = fields.title     var singer = fields.singer     var music = path.basename(files.music.path)     var poster = path.basename(files.poster.path)     var id = 0     storage.forEach(function(item) {       if (item.id > id) {         id = item.id       }     })     storage.push({       id: id + 1,       title: title,       singer: singer,       music: music,       poster: poster     })     res.writeHead(302, {       'Location': 'http://127.0.0.1:3000/'     })     res.end()   })  }  exports.showEdit = function(req, res) {   var id = req.query.id   var music = {}     // 根据 id 查询出该id在数组中对应的项   storage.forEach(function(item, index) {     if (item.id == id) {       music = item     }   })   res.render('edit', {     title: '编辑音乐',     music: music   }) }  exports.doEdit = function(req, res) {   console.log('doedit 被执行了')   var id = req.query.id     // 获取用户提交的数据   var data = ''   req.on('data', function(chunk) {     data += chunk   })   req.on('end', function() {     var postBody = qstring.parse(data)       // 根据id找到数据中该项的索引       var music_index = 0       storage.forEach(function (item, index) {         if (item.id == id) {           music_index = index         }       })       storage[music_index].title = postBody.title       storage[music_index].singer = postBody.singer       res.writeHead(302, {         'Location': 'http://127.0.0.1:3000/'       })       res.end()   })     // 然后做修改操作 }   // 如何获取和解析get请求提价的查询字符串中参数 url模块的parse方法的第二个参数 // 如何从数组中删除一个元素  splice exports.doRemove = function(req, res) {   // 获取查询字符串中的 id   var id = req.query.id   var music_index = 0     // 通过该 id 找到数组中的该项   storage.forEach(function(item, index) {       if (item.id == id) {         music_index = index       }     })     // 然后进行真正的删除操作,根据索引下标进行删除   storage.splice(music_index, 1)   res.writeHead(302, {     'Location': 'http://127.0.0.1:3000/'   })   res.end() }

8.2.1.7.            页面显示文件夹view下的index.html

8.2.1.7.1.             <!DOCTYPE html> <html lang="en">  <head>   <meta charset="UTF-8">   <title>Document</title>   <link rel="stylesheet" href="/node_modules/bootstrap/dist/css/bootstrap.css"> </head>  <body>   <div class="container">     <div class="page-header">       <h1><a href="/">我的音乐</a> <small><%= title %></small></h1>     </div>     <a class="btn btn-success" href="/add">添加歌曲</a>     <table class="table">       <thead>         <tr>           <th>编号</th>           <th>标题</th>           <th>歌手</th>           <th>音乐名称</th>           <th>海报</th>           <th>操作</th>         </tr>       </thead>       <tbody>         <% musicList.forEach(function(music){ %>           <tr>             <td><%= music.id %></td>             <td><%= music.title %></td>             <td><%= music.singer %></td>             <td><%= music.music %></td>             <td><%= music.poster %></td>             <td>               <a href="/edit?id=<%= music.id %>">修改</a>               <a href="/remove?id=<%= music.id %>">删除</a>             </td>           </tr>           <% }) %>       </tbody>     </table>   </div> </body>  </html>

8.2.1.8.            添加歌曲页面add.html

8.2.1.8.1.             <!DOCTYPE html> <html lang="en">  <head>   <meta charset="UTF-8">   <title>Document</title>   <link rel="stylesheet" href="node_modules/bootstrap/dist/css/bootstrap.css"> </head>  <body>   <div class="container">     <div class="page-header">       <h1><a href="/">我的音乐</a> <small><%= title %></small></h1>     </div>     <!--      表单提交三要素:       action 用来指定提交到那个请求地址       method 默认是get方式,当表单提交的时候,表单会把表单内的所有具有 name 属性的 input 元素的值 以 name=value&name=value 的格式放到 url 地址中,然后发出请求       如果指定 method post 提交方式,         表单同样的将表单内所有具有name属性的input元素的值以             name=value&name=value  的格式 放到请求报问体 中,然后发出请求     如果要使用表单来上传文件,那么必须指定两点:       1. 表单提交方法必须为 post       2. 一定要 指定表单的 enctype 属性 为 multipart/form-data        上面两点缺一不可,否则后台收不到提交的文件数据        如果表单提交没有file类型的input元素,那么就不要手动的指定 enctype       如果有file类型的input元素,则必须指定enctype 属性 为 multipart/form-data      -->     <form action="/add" method="post" enctype="multipart/form-data">       <div class="form-group">         <label for="title">标题</label>         <input type="text" class="form-control" >       </div>       <div class="form-group">         <label for="artist">歌手</label>         <input type="text" class="form-control" >       </div>       <div class="form-group">         <label for="music_file">音乐</label>         <input type="file" >.</p>       </div>       <div class="form-group">         <label for="image_file">海报</label>         <input type="file" >.</p>       </div>       <button type="submit" class="btn btn-success">点击添加</button>     </form>   </div> </body>  </html>

8.2.1.9.            编辑歌曲页面edit.html

8.2.1.9.1.             <!DOCTYPE html> <html lang="en">  <head>   <meta charset="UTF-8">   <title>Document</title>   <link rel="stylesheet" href="node_modules/bootstrap/dist/css/bootstrap.css"> </head>  <body>   <div class="container">     <div class="page-header">       <h1><a href="/">我的音乐</a> <small><%= title %></small></h1>     </div>     <form action="/edit?id=<%= music.id %>" method="post">       <div class="form-group">         <label for="title">标题</label>         <input type="text" class="form-control" placeholder="请输入音乐标题" value="<%= music.title %>">       </div>       <div class="form-group">         <label for="artist">歌手</label>         <input type="text" class="form-control" placeholder="请输入歌手名称" value="<%= music.singer %>">       </div>       <!-- <div class="form-group">         <label for="music_file">音乐</label>         <input type="file" >         <p class="help-block">请选择要上传的音乐文件.</p>       </div>       <div class="form-group">         <label for="image_file">海报</label>         <input type="file" >.</p>       </div> -->       <button type="submit" class="btn btn-success">确定修改</button>     </form>   </div> </body>  </html> 

《Node.js核心技术教程》第1章 模块化编程2019.2.19 13:30'1.1初识模块化编程1.2模块化编程的演变模块化是一种设计思想,把一个非常复杂的系统结构细化到具体的功能点,每个功能点看做一个模块,然后通过某种规则把这些小的模块组合到一起,构成模块化系统。模块化编程可有效解决命名冲突问题和文件依赖的关系和顺序问题。1.2.1全局函数1.2.2对象的命名空间  /*   * 对象命名空间:内部成员的状态可以随意被外部改写,不安全。代码可读性随着子命名空间延长可读性差。   * 只是从理论意义上减少了命名冲突的问题,但是命名冲突还是存在   */  var calculator = {};   //加法  calculator.add = function(x, y) {    return parseInt(x) + parseInt(y);  }  //减法  calculator.subtract = function(x, y) {    return parseInt(x) - parseInt(y);  }  //乘法  calculator.multiply = function(x, y) {    return parseInt(x) * parseInt(y);  }  //除法  calculator.divide = function(x, y) {    return parseInt(x) / parseInt(y);  }//引用:result = calculator.add(x, y); //定义用于计算的函数:所有的变量和函数都暴露在全局,无法保证全局变量与其他模块的变量发生冲突,也看不出全局变量与模块成员之间的直接关系。        function add(x, y) {          return parseInt(x) + parseInt(y);        }        function subtract(x, y) {          return parseInt(x) - parseInt(y);        }        function multiply(x, y) {          return parseInt(x) * parseInt(y);        }        function divide(x, y) {          return parseInt(x) / parseInt(y);        }//引用:result = add(x, y);//加1.2.3函数的作用域(闭包)/*函数的作用域(闭包):通过封装函数的私有空间可以让一些属性和方法私有化。通过匿名自执行函数,进行私有变量隔离。利用匿名自执行函数形成的封闭的函数作用域空间,达到自优化的目的*/        var calculator = ( function () {            function add(x,y) {                return parseInt(x)+parseInt(y);            }            function subtract(x,y) {                return parseInt(x)-parseInt(y);            }            function multiply(x,y) {                return parseInt(x)*parseInt(y);            }            function divide(x,y) {                return parseInt(x)/parseInt(y);            }            return {                add:add,                subtract:subtract,                multiply:multiply,                divide:divide            }            })();//引用与命名空间相同:result=calculator.add(x,y);1.2.4维护和扩展//如果有第三方依赖的时候,可通过参数的形式将原来的模块和第三方库传递进去。       //传递参数cal    var calculator = (function(cal) {        //加法        function add(x, y) {            return parseInt(x) + parseInt(y);        }        // 减法        function subtract(x, y) {            return parseInt(x) - parseInt(y);        }        //乘法        function multiply(x, y) {            return parseInt(x) * parseInt(y);        }        //除法        function divide(x, y) {            return parseInt(x) / parseInt(y);        }        cal.add = add;        cal.subtract = subtract;        cal.multiply = multiply;        cal.divide = divide;        return cal;    })(calculator || {}); //当扩展该模块时,优先查找要扩展的对象是否已存在    // 从代码上来看:下面的 calculator 已经把上面的 calculator 给覆盖掉了    // 注意:在进行扩展的时候,优先查找要扩展的对象是否已存在    // 如果已存在,就使用已经存在的    // 如果不存在,就创建一个新的    // 最大的好处:加载的时候不用考虑顺序了    var calculator = (function(cal) {        //取余方法        cal.mod = function(x, y) {            return x % y;        }        return cal;    })(calculator || {});  //引用:result = calculator.add(x, y);第2章 初识Node.js2019.2.22 17:30'2.1 Node.js概述2.2 Node.js简介2.3 Node.js安装和配置2.4 Node.js基础入门有了Node.js,用JavaScript既可以客户端开发,又可以服务器端开发,还可以与数据库交互。减少学习成本,快速打造全栈工程师。客户端将用户请求发送给服务器端。服务器端根据用户的请求进行逻辑处理、数据处理并将结果响应给客户端。现在,用Node.js来代替传统的服务器语言,开发服务器端的Web框架。JavaScript是一种脚本语言,一般运行在客户端,而Node.js可使JavaScript运行在服务器端。JavaScript组成核心语法是ECMAScriptDOM是HTML的应用程序接口,是文档对象模型BOM是浏览器对象模型,可以对浏览器窗口进行访问和操作JavaScript作用在客户端主要用来处理页面交互在服务器端主要用来处理数据交互解析:依赖浏览器提供的JavaScript引擎解析执行操作对象:对浏览器提供的DOM、BOM的解析进行操作常见操作:用户交互、动画特效、表单验证、Ajax请求等解析:由特定的JavaScript引擎解析执行,如Node.jsG不依赖浏览器,不操作DOM和BOM。主要操作:客户端做不到的事情,如操作数据库和文件等概念在服务器端的运行环境或运行时平台解析和执行JavaScript代码提供一些功能性的API,如文件操作和网络通信API等2009年5月由RyanDahl把Chrome的V8引擎移植出来,在其上加上API特点和优势它是一个JavaScript运行环境,可脱离浏览器在服务器端单独执行,代码可共用。依赖ChromeV8引擎,在非浏览器下解析JavaScript代码事件驱动Event-Driven非阻塞I/O(non-blocking I/O):使用事件回调的方式避免阻塞I/O所需的等待轻量、可伸缩,适用于实时数据交互,Socket可实现双向通信单进程单线程,异步编程模式,实现非阻塞I/O下载和安装CMD命令台Path环境变量快速体验Node.js输出内容到终端:建立文件:demo2-1.js   console.log('hello world');执行:   node demo2-1.js输出内容到网页:建立文件demo2-2.js//加载http模块var http = require('http');//创建http服务器http.createServer(function(req, res) {  //响应结束  res.end('hello world');  //监听网址127.0.0.1 端口号3000}).listen(3000,'127.0.0.1');在命令行执行:node demo2-2.js    光标闪烁....打开浏览器,输入网址:http://127.0.0.1:3000便会看到输出的内容。REPL运行环境node 【Enter】>打开Chrome,按【F12】打开Console控制台>global对象和模块作用域//demo2-3.js global对象和模块的作用域var foo = 'bar';console.log(foo);//global对象这时是没有foo属性的console.log('global:foo '+global.foo);//为global对象挂载一个foo变量,并将该文件模块中foo的值赋值给它global.foo = foo;//这是global.foo的值为'bar'console.log('global:foo '+global.foo);//require()、exports、module exports//require()从外部获取一个模块的接口./是相对路径,默认js文件//demo2-4.js//加载模块var myModule = require('./info');console.log(myModule);//输出模块中的变量值console.log('name:'+myModule.name);console.log('type:'+myModule.type);console.log('age:'+myModule.age);//调用模块的方法myModule.sayHello();//info.js被加载模块//向外暴漏变量nameexports.name = 'itcast';exports.type='edu';//向外暴漏变量agemodule.exports.age='10';//向外暴漏函数module.exports.sayHello= function () {    console.log('hello');}//require()、exports、module exports//demo2-5.js//加载模块var myModule = require('./test');console.log(myModule);//输出数组长度console.log('length:'+myModule.length);//test.js被加载模块//使用module.exports可以单独定义数组,并成功对外开放// module.exports=['name','type','age'];//使用exports不能单独定义exports=['name','type','age'];全局可用变量、函数和对象Node.js v10.15.1 DocumentationTable of ContentsGlobal Objects********************************Class: Buffer__dirname__filenameclearImmediate(immediateObject)clearInterval(intervalObject)clearTimeout(timeoutObject)consoleexportsglobalmoduleprocessrequire()setImmediate(callback[, ...args])setInterval(callback, delay[, ...args])setTimeout(callback, delay[, ...args])URLURLSearchParamsWebAssembly_dirname和_filename变量// 输出全局变量 __dirname 的值console.log('文件的目录是:'+ __dirname );// 输出全局变量 __filename 的值console.log('文件的绝对路径是:'+__filename );全局函数setImmediate(callback[, ...args])setInterval(callback, delay[, ...args])setTimeout(callback, delay[, ...args])clearImmediate(immediateObject)clearInterval(intervalObject)clearTimeout(timeoutObject)console对象console.log([data][, ...args])onsole.info([data][, ...args])console.error([data][, ...args])console.dir(obj[, options])console.time([label])console.timeEnd([label])console.trace([message][, ...args])console.assert(value[, ...message])Node.js模块化重写计算器案例add.jssubtract.jsmultiply.jsdivide.jsindex.jstestCal.jsnode testCal.js//加法module.exports = function (x, y) {  return parseInt(x) + parseInt(y)}//减法module.exports = function (x, y) {  return parseInt(x) - parseInt(y)}//乘法module.exports = function (x, y) {  return parseInt(x) * parseInt(y)}//除法module.exports = function (x, y) {  return parseInt(x) / parseInt(y)}//入口模块module.exports = {  add: require('./add'),  subtract: require('./subtract'),  multiply: require('./multiply'),  divide: require('./divide')}//测试计算器功能var cal = require('./index');//在终端输出计算结果console.log(cal.add(1, 2)); // => 3console.log(cal.subtract(1, 2)) ;// => -1console.log(cal.multiply(1, 2)); // => 2console.log(cal.divide(1, 2)) ;// => 0.5require()模块的加载规则文件模块的加载/开头为根路径./../为相对路径.js扩展名可不加查找顺序.js.json.node核心模块的加载Node.js提供的基本API保存在lib目录的源码文件可直接加载,不用路径全局对象常用工具事件机制文件访问系统HTTP服务器与客户端//demo2-7.js// 核心模块就是一个固定标识// 如果写错,就无法加载var os = require('os');//输出CPU信息console.log(os.cpus());模块的缓存require.cachefoo.jsconsole.log("foo模块被加载了");//清除缓存delete require.cache[module.filename] ;// 对于同一个模块标识,node 在第一次加载完成之后就会缓存该模块// 下次继续加载该模块的时候,直接从缓存中获取require('./foo');require('./foo');require('./foo');require('./foo');demo2-7.js第3章 异步编程和包资源管理2019.2.22 21.30'异步编程Node.js的包和NPM同步和异步回调函数包的概念NPM的概念NPM的基本应用包模块加载规则/** * 同步代码 */console.log('起床');console.log('背单词');//吃早餐function eatBreakfast() {    console.log('早餐吃完了');}eatBreakfast();console.log('去上学');/** * 异步代码 */console.log('起床');console.log('背单词');function eatBreakfast() {    console.log('开始吃早餐了');    // setTimeout 执行的时候,不会阻塞后面代码的继续执行    setTimeout(function () {    //异步函数        console.log('早餐吃完了');    }, 0);}eatBreakfast()console.log('去上学');概念:是指函数可以被传递到另一个函数中,然后被调用的形式。同步代码中使用try...catch处理异常/** * 同步代码处理异常 */function parseJsonStrToObj(str) {    return JSON.parse(str)}// 对于同步代码,我们可以使用 try-catch 来捕获代码执行可能出现的异常try {    var obj = parseJsonStrToObj('foo')    console.log(obj)} catch (e) {    console.log('转换失败了')}异步代码中无法使用try...catch处理异常/** *异步代码无法使用try-catch处理异常 */function parseJsonStrToObj(str) {    setTimeout(function() {        return JSON.parse(str);    }, 0);}//对于异步代码的执行来说,try-catch 是无法捕获异步代码中出现的异常的try {    var obj = parseJsonStrToObj('foo');    console.log('执行结果是:' + obj);} catch (e) {    console.log('转换失败了');}使用回调函数接收异步代码的执行结果/** * try-catch写在异步代码中 */function parseJsonStrToObj(str) {    setTimeout(function() {        try{            return JSON.parse(str);        }catch(e){            console.log('转换失败了');        }    }, 0);}//调用方法输出结果var obj = parseJsonStrToObj('foo');  //这种写法无法接收到第7行的返回值console.log('执行结果是:' + obj);//通过回调函数来接收异步代码执行的处理结果function parseJsonStrToObj(str,callback) {    setTimeout(function() {        try {            var obj = JSON.parse(str);            callback(null, obj);        } catch (e) {            callback(e, null);        }    }, 0);}//注意区分错误信息和正确的数据信息parseJsonStrToObj('{"foo":"bar"}',function (err, result) {    if (err) {        return console.log('转换失败了');    }    console.log('数据转换成功,没有问题可以直接使用了:' + result);});回调函数:即当使用异步代码去做一件事时,不能预测这件事什么时候做完,其他的事情还在继续,这时,可给异步代码准备一个包裹,当异步代码有了执行结果时可以将结果放到这个包裹里,需要在哪里使用这个结果就从包裹取出。回调函数的三个约定:1、函数名称通常为callback,在封装异步执行代码时,优先把callback作为函数的最后一个参数出现: function 函数名 (arg1,arg2,callback){}2、把代码中出现的错误作为callback回调函数的第一个参数进行传递:callback(err,result);3、把真正的返回的结果数据,传递给callback的第二个参数。callback(err,result);理解异步编程的“事件驱动”思路:在异步编程中,当异步函数执行时,不确定何时执行完毕,回调函数会被压入到一个事件循环(EventLoop)的队列,然后往下执行其他代码,直到异步函数执行完成后,才会开始处理事件循环,调用相应的回调函数。这个事件循环队列是一个先进先出的队列,这说明回调是按照它们被加入队列的顺序执行的。包是在模块的基础上更进一步的组织JavaScript代码的目录,有出口模块,遵循CommonJS规范目录结构package.json在顶层目录的包描述文件,说明文件bin 存放可执行二进制文件的目录lib 存放JavaScript文件的目录doc 存放文档的目录test 存放单元测试用例的代码namedescriptionversionkeywordsauthordependenciesscripts全称是Node.js Package Manage,含义一:是Node.js的开放模块登记和管理系统,是一个NPM网站,http://www.npmjs.com,是全球最大的模块生态系统,里面的包都是通过Node.js实现的,开源免费,即查即用。含义二:是Node.js的包管理工具,一个命令行下的软件,提供了一些命令用于快速安装和管理模块。npm init[-y] 初始化一个package.json文件npm install 包名  安装一个包npm install -save 包名 将安装的包添加到package.json的依赖中npm install -g 包名 安装一个命令行工具npm docs 包名 查看包的文档npm root -g 查看全局包安装路径npm config set prefix "路径" 修改全局包安装路径npm list 查看当前目录下安装的所有包npm list -g 查看全局包的安装路径下所有的包npm uninstall 包名 卸载当前目录下的某个包npm uninstall -g 包名 卸载全局安装路径下的某个包npm update 包名 更新当前目录下的某个包包管理的使用场景从NPM服务器下载别人编写的第三方包到本地使用从NPM服务器下载并安装别人编写的命令行程序到本地使用允许将自己编写的包或命令行程序上传到NPM 服务器供别人使用安装npm install markdownnode-moudules目录自动创建。存放第三方包,目录名和内容不能修改除了markdown,另外的包是其依赖包1、在加载时,Node.js会默认当做核心模块去加载。如果发现标识名不是核心模块,就会在当前目录的node_modules目录下寻找。如果没有,则从当前目录的父目录的node_modules里搜索,递归下去直到根目录。2、如果找到了该标识名的子目录,Node.js将会找到该子目录下的package.json文件,获取其main属性的值,根据main属性指定的路径值进行加载。用户不用关心入口模块是哪一个文件。第4章 Node.js 文件操作2019.2.23 2:40'基本文件操作案例-控制歌词滚动文件相关操作路径字符串操作目录操作文件写入向文件追加内容文件读取文件复制获取文件信息加载fs(FileSystem)模块var fs=require('fs');//同步写入fs.writeFileSync(file,data[,option]);//异步写入fs.writeFile(file,data[,option],callback);/* *  同步方式写入文件 */var fs = require('fs');// 在进行文件操作的时候,如果是同步 API,必须使用 try-catch 来捕获异常// 防止程序因为异常退出,导致后续代码无法继续执行 try {     console.log('写入文件...')     fs.writeFileSync('D:/a.txt', '传智播客'); } catch (e) {   console.log('不好意思,文件写入失败了') }/* *  异步方式写入文件 */var fs = require('fs');console.log(1);//该方法中回调函数的第一个参数为错误对象fs.writeFile('d:/b.txt', '传智播客', function(err) {    //判断是否出现错误,进行错误提示  if (err) {    console.log('不好意思,文件写入失败了');  }  console.log(2);})console.log(3);appendFile(file,data[,option],callback);option默认为:utf8,0o666,'a'/** 向文件追加内容*/var fs = require('fs');//定义需要追加的数据var data = '欢迎您';//调用文件追加函数fs.appendFile('D:/a.txt', data, function(err) {  if (err) {    // 出错的情况下,回调函数中的代码就不要继续往后执行了    // 所以可以使用return 的方式,阻止代码继续执行    return console.log('文件追加失败了');  }  // 希望在文件追加成功之后做一些事情  console.log('文件追加成功了');});/* * 文件读取 */var fs = require('fs');//读取文件 fs.readFile('D:/a.txt', function(err, data) {   if (err) {     return console.log('文件读取失败');   }   // 因为计算机中所有的数据最终保存的都是 二进制 数据   // 所以可以通过调用 toString() 方法将二进制数据转换为人类可以识别的字符   console.log(data.toString()); });/* * 文件复制案例 */var fs = require('fs');//读取a.txt文件数据  fs.readFile('D:/a.txt', function(err, data) {    if (err) {      return console.log('读取文件失败了');    }    //将数据写入c.txt文件    fs.writeFile('D:/c.txt', data.toString(), function(err) {      if (err) {        return console.log('写入文件失败了');      }    });    console.log('文件复制成功了');  });//封装copy再复制//创建封装文件demo4-6.js/* * 文件复制模块 */var fs = require('fs');/*定义文件复制函数copy()* src:需要读取的文件* dist:目标文件* callback:回调函数* */function copy(src, dist, callback) {  //读取文件  fs.readFile(src, function(err, data) {    if (err) {      return callback(err);    }    //写入文件    fs.writeFile(dist, data.toString(), function(err) {      if (err) {        return callback(err);      }      callback(null);    });  });}module.exports = copy;//测试文件复制test.js/* * 测试文件复制 *///加载封装好的文件复制功能模块var copy = require('./demo4-6');//调用copy()函数    copy('D:/a.txt','D:/d.txt',function(err){      if(err){       return console.log('文件复制失败了');      }      console.log('文件复制成功了');    });var fs = require('fs');fs.stat('D:/a.txt', function (err, stats) {  //判断是否是文件  console.log("是否是文件:"+stats.isFile());  console.log(stats); //输出文件信息})var fs = require('fs');//读取歌词文件fs.readFile('./lrc.txt', function(err, data) {  if (err) {    return console.log('读取歌词文件失败了');  }  data = data.toString();  var lines = data.split(' ');  // 遍历所有行,通过正则匹配里面的时间,解析出毫秒  // 需要里面的时间和里面的内容  var reg = /[(d{2}):(d{2}).(d{2})]s*(.+)/;  for (var i = 0; i < lines.length; i++) {    (function(index) {      var line = lines[index];      var matches = reg.exec(line);      if (matches) {        // 获取分        var m = parseFloat(matches[1]);        // 获取秒        var s = parseFloat(matches[2]);        // 获取毫秒        var ms = parseFloat(matches[3]);        // 获取定时器中要输出的内容        var content = matches[4];        // 将分+秒+毫秒转换为毫秒        var time = m * 60 * 1000 + s * 1000 + ms;  //使用定时器,让每行内容在指定的时间输出        setTimeout(function() {          console.log(content);        }, time);      }    })(i);  }});[ti:我想] [ar:张杰] [t_time:(03:46)] [00:00.00] 我想 - 张杰[00:02.00] 词:王笠人[00:04.00] 曲:王笠人[00:06.00] 编曲:梁思桦[00:08.00] 歌词编辑:果果  [00:10.00] QQ:765708831 [00:13.00] 中文歌词库 www.cnLyric.com[00:18.23] 每件事情都有发生的理由[00:21.72] 可无法解释 遇见你[00:26.15] 再多的心理准备 都抵抗不了[00:29.89] 被现实打败的爱情[00:34.27] 我们都习惯了同一个温度[00:38.08] 你说这叫幸福[00:42.32] 同时也忽略了一种残酷[00:45.39] 我觉得 好无助[00:50.69] 我想 我想[00:53.54] 我想一起越过所有困难和阻挡[00:57.64] 而我们 却不一样[01:01.58] 虽然都有共同的理想[01:06.10] 窗外 有阳光[01:09.76] 透过了一丝缝隙照亮了一点希望[01:14.07] 而晚上 的月亮[01:17.94] 让我们再次陷入了彷徨[01:25.66] 你问为什么喜欢拍照记录[01:29.27] 答案我却说不出[01:33.17] 怕如果我们走了不同方向[01:37.06] 有照片让我回顾[01:41.36] 回忆我们去过的每一个地方[01:45.41] 和时而停顿的脚步[01:49.43] 就这么停停顿顿一步接一步[01:53.75] 直到没有路[01:57.91] 我想 我想[02:00.86] 我想一起越过所有困难和阻挡[02:04.95] 而我们 却不一样[02:08.74] 虽然都有共同的理想[02:13.15] 窗外 有阳光[02:16.83] 透过了一丝缝隙照亮了一点希望[02:21.01] 而晚上 的月亮[02:25.30] 让我们再次陷入了彷徨[02:30.44] 如果每一个清晨[02:33.82] 在你的温度里苏醒[02:37.82] 闭眼聆听 有节奏的呼吸[02:41.63] 哪怕只是一瞬间[02:43.68] 哪怕只是一场梦[02:50.74] 我想 我想[02:53.60] 我想一起越过所有困难和阻挡[02:57.73] 而我们 却不一样[03:01.43] 虽然都有共同的理想[03:06.16] 窗外 有阳光[03:09.81] 透过了一丝缝隙照亮了一点希望[03:13.72] 而晚上 的月亮[03:18.04] 让我们再次陷入了彷徨[03:23.35] 每件事情都有发生的理由[03:26.88] 可无法解释 遇见你[03:31.17] 再多的心理准备 都抵抗不了[03:35.11] 被命运安排的相遇获取路径模块var path=require('path');> str='D:Node.js''D:Node.js'> path.basename(str)'Node.js'> path.dirname(str)'D:'> path.extname(str)'.js'>拼接路径字符串和转换标准路径path.join()创建目录fs.mkdir(path[,model],callback);/* *创建目录,必须逐级创建目录,如果越级则出错 */var fs = require('fs');console.log('在C:/Course目录下创建目录testDemo4-8');fs.mkdir('D:/Course/testDemo4-8/',function(err){    if (err) {        return console.error(err);    }    console.log("目录创建成功。");});读取目录/* *读取目录 */var fs = require('fs');console.log('查看/testDemo4-8目录');fs.readdir('/Node.js/code/',function(err, files){    if (err) {        return console.error(err);    }    //遍历所有文件    files.forEach( function (file){      //  输出文件名        console.log( file );    });});删除目录/* *删除目录 */var fs=require('fs');        console.log('读取 /testDemo4-8 目录');        fs.readdir('/Course/testDemo4-8/',function(err, files){            if (err) {                return console.error(err);            }            //遍历所有文件            files.forEach( function (file){                //  输出文件名                console.log( file );                //删除文件                fs.unlink('/Course/testDemo4-8/'+file, function(err) {                    if (err) {                        return console.error(err);                    }                    console.log(file+'文件删除成功!');                });            });            console.log('准备删除/testDemo4-8目录');            fs.rmdir('/Course/testDemo4-8/',function(err){                if (err) {                    return console.error(err);                }                console.log("目录删除成功!");            });        });第5章 Node.js中处理数据I/O2019.2.23 12:00'Buffer缓冲区限制大小1GB二进制数据和乱码Buffer的构造函数从缓冲区读取数据拼接缓冲区Stream文件流文件流的概念Node.js的可读流和可写流使用pipe()处理大文件乱码是指计算机二进制数据在转换字符的过程中,使用了不合适的字符集,而造成的部分或所有的字符无法被阅读。缓冲区是在内容中操作数据的容器。Node.js的Buffer模块是全局性的,不需要require()函数来加载创建缓冲区传入字节:var buf=new Buffer(size);传入数组:var buf=new Buffer([10,20,30,40,50]);传入字符串和编码:var buf=new Buffer("hello","utf-8");写入缓冲区/* * 写入缓冲区:格式 buf.write(string[,offset[,length]][,encoding]); *///创建一个可以存储 5 个字节的内存空间对象 var buf = new Buffer(5); // 通过 buffer 对象的 length 属性可以获取 buffer 缓存中的字节大小 console.log(buf.length);//向缓冲区写入a buf.write('a');//输出缓冲区数据 console.log(buf);//向缓冲区写入b buf.write('b', 1, 1, 'ascii');//输出缓冲区数据 console.log(buf);/* * 读取缓冲区:格式:buf.toString([encoding[,start[,end]]]); *///创建一个可以存储26个字节的内存空间对象 var buf = new Buffer(26);//像buffer数组中存入26个字母对应的编码for (var i = 0 ; i < 26 ; i++) { buf[i] = i + 97;}//输出全部字母console.log( buf.toString('ascii'));       // 输出: abcdefghijklmnopqrstuvwxyz//输出前五个字母console.log( buf.toString('ascii',0,5));   // 输出: abcde// 输出: 'abcde'console.log(buf.toString('utf8',0,5));// 输出: 'abcde', 默认编码为 'utf8'console.log(buf.toString(undefined,0,5));/* * 拼接缓冲区:buf.concat(list[,totalLength]); *///创建两个缓冲区 var buf = new Buffer('世上无难事,'); var buf1 = new Buffer('只怕有心人');//执行拼接操作var buf2= Buffer.concat([buf,buf1]);//输出拼接后缓冲区的内容console.log("buf2 内容: " + buf2.toString());四种流类型Readable可读操作可读流Writable可写操作可写流Duplex可读可写操作双向流、双工流Transform操作被写入数据,然后读出结果(变换流)所有的Stream对象都是EventEmitter(时间触发器)的实例事件data当有数据可读时触发end没有更多的数据可读时触发error在接收和写入发生错误时触发finish所有数据已被写入到底层系统时触发可读流/** *  从流中读取数据 */var fs = require("fs");var total = '';// 创建可读流var readableStream = fs.createReadStream('input.txt');// 设置编码为 utf8。readableStream.setEncoding('UTF8');// 处理流事件 dataendanderror//绑定data事件,附加回调函数,流开始流动readableStream.on('data', function(chunk) {      total += chunk;});//读取结束后,输出totalreadableStream.on('end',function(){    console.log(total);});//如果出错,输出提示信息readableStream.on('error', function(err){    console.log(err.stack);});console.log("程序执行完毕");可写流/** *  使用文件流进行文件拷贝 */var fs = require('fs');//创建可读流var readableStream = fs.createReadStream('input.txt');//创建可写流var writableStream = fs.createWriteStream('output.txt');readableStream.setEncoding('utf8');readableStream.on('data', function(chunk){    //将读出的数据块写入可写流    writableStream.write(chunk);});readableStream.on('error', function(err){    console.log(err.stack);});readableStream.on('end',function(){    //将剩下的数据全部写入,并且关闭写入的文件    writableStream.end();});writableStream.on('error', function(err){    console.log(err.stack);});/** *  使用pipe()进行文件拷贝 */var fs = require('fs')//源文件路径var srcPath = 'd:/node.js/code/chapter05/demo5-7/input.txt';//目标文件路径var distPath = 'd:/node.js/code/chapter05/demo5-7/input.txtoutput.txt';var readableStream = fs.createReadStream(srcPath);var writableStream = fs.createWriteStream(distPath);// 可以通过使用可读流 的函数 pipe ()接入到可写流中// pipe()是一个很高效的数据处理方式if(readableStream.pipe(writableStream)){    console.log('文件复制成功了')}else{    console.log('文件复制失败了')}第6章 Node.js网络编程2019.2.23 16:20’6.1Node.js网络编程基础IP地址和端口号套接字Socket简单模型6.2Node.js中实现套接字服务Net.Server对象Net.Socket对象6.3Node.js进程管理Process模块获取终端输入多人广播消息6.4案例--终端聊天室IP地址是用来定位一台计算机的,既可以是服务器,也可以是客户端端口号是用来定位应用程序的TCP/IP协议TCP:Transfer Control Protocol,传输控制协议,是一种稳定可靠的传送方式。TCP负责发现传输的问题,一有问题就发出信号,要求重新传输,直到所有数据安全正确地传输到目的地为止。IP是给互联网的每一台联网设备规定一个地址。TCP/IP协议包含因特网整个TCP/IP协议簇应用层面:TFTP,HTTP,SNMP,FTP,SMTP,DNS,Telnet协议等。Socket孔插座,可理解为接口对象,网络编程中也称套接字,常用于描述IP地址和端口等。Socket是支持TCP/IP的网络通信的基本操作单元,可以看做是不同主机之间的进程进行双向通信的端点,简单的说就是通信两方的一种约定。Socket就是对TCP/IP协议的封装,Socket本身并不是协议,而是一个调用接口(API)。Socket进行网络通信必须的5种信息连接使用的协议客户端设备的IP地址客户端的端口号服务期端的IP地址服务器端口套接字地址就是IP地址和端口号的组合套接字服务不需处理get和post请求,而是采用点对点的传输数据方式,是一个轻量级的网络通信解决方案套接字服务服务器用来监听连接,客户端用来打开一个到服务器的连接。Node.js本身就是一个服务器Net模块的APInet.createServer([options][, connectionListener])创建一个TCP服务器net.connect(options[, connectListener])net.createConnection(options[, connectListener])net.connect(port[, host][, connectListener])net.createConnection(port[, host][, connectListener]) net.connect(path[, connectListener])net.createConnection(path[, connectListener])net.isIP(input)net.isIPv4(input)net.isIPv6(input)创建Net.Server对象:var server = net.createServer([options][, connectionListener])Net.Server对象函数Server对象的事件server.listen(handle[, backlog][, callback])server.listen(options[, callback])server.listen(path[, backlog][, callback])server.listen([port[, host[, backlog]]][, callback])server.address()server.close([callback])server.ref()server.unref()server.getConnections(callback)'close' 事件'connection' 事件'error' 事件'listening' 事件第3步:安装Telnet设置->应用->右侧 相关设置:程序和功能->左侧 启动或关闭windows功能在Telnet客户端前的复选框里打钩按确定示例程序第1步:创建服务器端文件demo6-1.js/** * Net.Servet创建服务器 */// 1. 加载 manychat 核心模块var net = require('net');// 2. 创建一个服务应用程序,得到一个服务器实例对象var server = net.createServer();// 3. 监听客户端的连接事件connection,连接成功就会执行回调处理函数server.on('connection',function () {  console.log('有客户端连接上来了');});// 5. 服务器有一个事件叫做 listening ,表示开启监听成功之后回调处理函数server.on('listening',function () {  console.log('服务器开启监听成功了,正在等待客户端连接');});// 4. 启动服务器,开启监听// 监听 127.0.0.1:3000 只能被本机所访问server.listen(3000,'127.0.0.1');第2步:在命令行窗口运行:node demo6-1.js第4步:另开终端窗口运行:telnet 127.0.0.1 3000记住:端口3000前面是空格而不是冒号这时运行demo6-1窗口会显示“有客户连接上来了”表示客户端与服务器端连接成功!!!Duplex(双工)流接口,是可读可写流Socket事件Socket属性Socket函数lookupconnectdataendtimeoutdrainerrorclosesocket.bufferSizesocket.remoteAddresssocket.remoteFamilysocket.remotePortsocket.localAddresssocket.localPortsocket.bytesReadsocket.bytesWrittennew net.Socket([options])socket.connect(path[, connectListener])socket.connect(port[, host][, connectListener])socket.setEncoding([encoding])socket.write(data[, encoding][, callback])socket.destroy([exception])socket.pause()socket.resume()socket.setTimeout(timeout[, callback])socket.setKeepAlive([enable][, initialDelay])socket.setNoDelay([noDelay])socket.address()socket.ref()socket.unref()服务器向客户端发送消息/** * 在服务器端使用Socket */// 1. 加载 manychat 核心模块var net = require('net');// 2. 创建一个服务应用程序,得到一个服务器实例对象var server = net.createServer();// 3. 监听客户端的连接事件,连接成功就会执行回调处理函数// 每次回调函数被调用,就会有一个新的 socket 对象在回调函数中server.on('connection',function (socket) {  console.log('有客户端连接上来了'); //在服务端可以获取到客户端的IP地址等信息  console.log('客户端IP地址:' + socket.remoteAddress + '连接到了当前服务器');  // 当前连接成功之后的客户端发送一个 hello world  socket.write('hello world');});// 5. 服务器有一个事件叫做 listening ,表示开启监听成功之后回调处理函数server.on('listening',function () {  console.log('服务器开启监听成功了,正在等待客户端连接');});// 4. 启动服务器,开启监听server.listen(3000,'127.0.0.1');服务器端运行node demo6-1客户端运行 telnet 127.0.0.1 3000统计在线人数/** * 服务端统计在线人数 */var net = require('net');var server = net.createServer();var count = 0;server.on('connection', function(socket) {  count++;  console.log('welcome , 当前在线人数:' + count);  socket.write('remoteAddress'+socket.remoteAddress+' ');  socket.write('remotePort'+socket.remotePort);});server.listen(3000, '127.0.0.1', function() {  console.log('server listening at port 3000');});服务器端运行 node demo6-3.js打开多个命令窗口分别运行 telnet 127.0.0.1 3000客户端与服务器端双向通信创建客户端/* * 双向通信-客户端 */var net = require('net');// 当调用 createConnection 之后,就会得到一个与服务器进行通信的 socket 对象// 该对象中包含当前客户端与服务器通信的 ip地址和端口号var client = net.createConnection({  port: 3000});// 什么时候客户端和服务器连接成功了// 可以通过监听 client 的 connect 事件来处理client.on('connect',function () {  // 客户端与服务器连接成功了  console.log('客户端与服务器连接成功了');  client.write('你吃了吗?');});client.on('data',function (data) {  //输出服务器发送给当前客户端的数据  console.log(data.toString());});创建服务器端/** * 双向通信-服务器 */var net = require('net');var server = net.createServer();// 每一个客户端与服务器建立连接成功之后,都会触发一次 connection 事件server.on('connection', function(socket) {  /*以下部分应用于双向通信*/  //通过监听 socket 对象的 data 事件来获取客户端发送给服务器的数据  socket.on('data', function(data) {    console.log(data.toString());    socket.write('我吃的小豆包');  });});server.listen(3000, '127.0.0.1', function() {  console.log('server listening at port 3000');});如果在服务器端启动telnet 127.0.0.1 3000 则可即时通讯/** * 测试获取终端输入 */// 通过下面的方式就可以获取用户的输入process.stdin.on('data',function (data) {  console.log(data.toString().trim());});1、创建目录manychat,并创建两个文件server.js和client.js//server.js/** * 多人广播聊天服务端端 */var net = require('net');var server = net.createServer();//该数组用来封装所有客户端的Socketvar users = [];server.on('connection', function(socket) {  users.push(socket);  socket.on('data', function(data) {    data = data.toString().trim();    users.forEach(function(client) {      if (client !== socket) {        //由于同一台计算机上不同客户端端口号不同,所以可以通过端口号来区分是谁说的话        client.write(socket.remotePort+ ':' + data);      }    });  });  // 当有客户端异常退出的时候,就会触发该函数  // 如果不监听客户端异常退出就会导致服务器崩溃  socket.on('error',function () {    console.log('有客户端异常退出了');  });})server.listen(3000, '127.0.0.1', function() {  console.log('server listening at port 3000');});//client.js/** * 多人广播聊天客户端 */var net = require('net')//向服务端创建连接var client = net.createConnection({  port:3000,  host:'127.0.0.1'});//监听连接成功事件connentclient.on('connect',function () {  // 通过当前进程的标准输入的 data 事件获取终端中的输入  process.stdin.on('data',function (data) {    data = data.toString().trim();    client.write(data);  });});//监听data事件输入服务器返回的数据client.on('data',function (data) {  console.log(data.toString());});配置文件:configmodule.exports = {  "port": 3000,  "host": "127.0.0.1"}客户端文件:client.js/** * 终端聊天室客户端 */var net = require('net');var config = require('./config');var client = net.createConnection({  port: config.port,  host: config.host})//用户注册成功后为该属性赋值var username;client.on('connect', function() {  console.log('请输入用户名:');  process.stdin.on('data', function(data) {    data = data.toString().trim();      // 当用户注册成功之后,下面的数据格式就不能再使用了      // 判断一下是否已经有用户名了,如果已经有了,则表示用户要发送聊天数据      // 如果没有,则表示用户要发送注册数据    if (!username) {      var send = {          protocal: 'signup',          username: data        }      client.write(JSON.stringify(send));      return;    }    //判断是广播消息还是点对点消息    // name:内容    var regex = /(.{1,18}):(.+)/;    var matches = regex.exec(data);    if (matches) {      var from = username;      var to = matches[1];      var message = matches[2];      var send = {        protocal: 'p2p',        from: username,        to: to,        message: message      }      client.write(JSON.stringify(send));    } else {      var send = {        protocal: 'broadcast',        from: username,        message: data      }      client.write(JSON.stringify(send));    }  });});client.on('data', function(data) {  data = JSON.parse(data);  switch (data.protocal) {    case 'signup':      var code = data.code;      switch (code) {        case 1000:          username = data.username;          console.log(data.message);          break;        case 1001:          console.log(data.message);          break;        default:          break;      }      break;    case 'broadcast':      console.log(data.message);      break;    case 'p2p':      var code = data.code;      switch (code) {        case 2000:          var from = data.from;          var message = data.message;          message = from + '对你说:' + message;          console.log(message);          break;        case 2001:          console.log(data.message);          break;        default:          break;      }      break;    default:      break;  };});服务器端文件:server.js/** * 终端聊天室服务端 */var net = require('net');var config = require('./config');var broadcast=require('./broadcast.js');var p2p=require('./p2p.js');var signup=require('./signup.js');var server = net.createServer();var users = {};server.on('connection', function(socket) {  socket.on('data', function(data) {    // 解析客户端发送的数据    data = JSON.parse(data);      // 根据客户端发送的数据类型,做对应的操作    switch (data.protocal) {      case 'signup':          //处理用户注册        signup.signup(socket,data,users);        break;      //处理广播消息      case 'broadcast':        broadcast.broadcast(data,users);        break;      case 'p2p':         // 处理点对点消息        p2p.p2p(socket, data,users);        break;      default:        break;    }  });  socket.on('error', function() {    console.log('有客户端异常退出了');  });});// 3. 启动服务器,开启监听server.listen(config.port, config.host, function() {  console.log('server listening at port ' + config.port);});用户注册模块:signup.js/** * 用户注册 * @param socket * @param data 用户名 *  {protocal: 'signup',     username: '小明'} * @param users 用户组 */exports.signup=function (socket,data,users) {    // 处理用户注册请求    var username = data.username;    // 如果用户名不存在,则将该用户名和它的Socket地址保存起来    if (!users[username]) {        users[username] = socket;        var send = {            protocal: 'signup',            code: 1000,            username: username,            message: '注册成功'        }        socket.write(JSON.stringify(send));    } else {        var send = {            protocal: 'signup',            code: 1001,            message: '用户名已被占用,请重新输入用户名:'        }        socket.write(JSON.stringify(send));    }}广播消息模块:broadcast.js/** * 广播消息 * @param data 广播消息发送过来的JSON数据 * {  "protocal": "broadcast",//消息类型为广播  "from": "小红",//发送消息的用户  "message": "大家早上好"//用户发送的消息内容} */exports.broadcast= function(data,users) {    var from = data.from;    var message = data.message    message = from + '说:' + message;    var send = {        protocal: 'broadcast',        message: message    }    send = new Buffer(JSON.stringify(send));    for (var username in users) {        var tmpSocket = users[username];        tmpSocket.write(send);    }}点对点消息模块:p2p.js/** * 点对点消息 * @param socket * @param data 点对点消息的JSON数据 * {  "protocal": "p2p", //消息类型为点对点  "from": "小红", //发送消息的用户  "to": "小明",  "message": "你早上吃的什么"} * @param users 用户组 */exports.p2p=function (socket,data,users) {    var from = data.from;    var to = data.to;    var message = data.message;    // 找到要发送给某个人的 Socket 地址对象    var receiver = users[to];    // 如果接收人不存在,告诉客户端没有该用户    if (!receiver) {        var send = {            protocal: 'p2p',            code: 2001,            message: '用户名不存在'        }        socket.write(new Buffer(JSON.stringify(send)));    } else {        // xxx 对你说: xxx        var send = {            protocal: 'p2p',            code: 2000,            from: data.from,            message: message        }        receiver.write(new Buffer(JSON.stringify(send)));    }    // 如果接收人存在,则将消息发送给该用户}第7章 Node.js中实现HTTP服务2019.2.23 22:35'7.1HTTP协议HTTP协议简介HTTP请求响应流程HTTP的请求报文和响应报文7.2Node.js的HTTP服务HTTP模块常用API使用HTTP模块构建Web服务器7.3HTTP服务请求处理根据不同的URL发送不同响应消息HTTP处理静态资源服务动态处理静态资源请求HTTP:Hyper Text Transfer Protocol 超文本传输协议1990年提出用于从WWW服务器传输超文本到本地浏览器的传输协议基于TCP的连接方式开放系统互连参考模型OSI/RM通信协议七层应用层Application Layer协议有HTTP、FTP、SMTP等表示层Presentation Layer会话层Session Layer传输层Transport Layer网络层Network Layer数据链路层Data Link Layer物理层Physics LayerASCII码、JPEG、MPEG、WAV等文件转换负责访问次序的安排等协议有TCP、UDP等三层交换机、路由器等协议有IP、SPX二层交换机、网桥网卡等集线器、中继器和传输线路等HTTP由请求和响应构成,是一个标准的客户端服务器模型,也是一个无状态的协议。各大浏览器广泛基于HTTP1.1HTTP协议特点支持客户/服务器模式简单快速:GET/HEAD/POST灵活:允许任意类型数据无连接:处理完一个请求和响应即断开连接无状态:对事务处理没有记忆能力URL由几部分组成:协议+域名+具体地址HTTP、POP3、FTP域名或IP地址及端口号:www.itheima.com具体地址:index.html一般加密或不显示通过DNS解析请求request,把URL地址等封装成HTTP请求报文,存放在客户端Socket对象中响应response,把数据封装在HTTP响应报文,并存放在Socket对象中报文是有一定格式的字符串,查看报文需要借助工具,例如chrome内核版本 65.0.3325.181请求报文requestGET / HTTP/1.1Host: www.itheima.comConnection: keep-aliveCache-Control: max-age=0Upgrade-Insecure-Requests: 1User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8Accept-Encoding: gzip, deflateAccept-Language: zh-CN,zh;q=0.9Cookie: UM_distinctid=1691db04响应报文responseHTTP/1.1 200 OKServer: TengineContent-Type: text/html; charset=UTF-8Transfer-Encoding: chunkedConnection: keep-aliveDate: Sun, 24 Feb 2019 04:11:14 GMTAccept-Ranges: bytesAli-Swift-Global-Savetime: 1550981474Via: cache4.l2et15[26,200-0,M], cache14.l2et15[27,0], kunlun5.cn1259[77,200-0,M], kunlun5.cn1259[77,0]X-Cache: MISS TCP_MISS dirn:-2:-2X-Swift-SaveTime: Sun, 24 Feb 2019 04:11:14 GMTX-Swift-CacheTime: 0Timing-Allow-Origin: *EagleId: da5ed29915509814747132159e加载语法:var http = require('http');http.ServerHTTP服务器指的就是http.Server对象Node.js的所有基于HTTP协议的系统都是基于http.Server实现的如网站、社交应用、代理服务器等创建语法:var server = http.createServer();函数server.close([callback])server.listen(port[,hostname][,backlog][,callback])server.listen(handle[,callback])/server.listen(path[,callback])事件requestconnectionclosehttp.IncomingMessage可读流req函数&属性message.headersmessage.urlmessage.httpVersionMessage.methodmessage.setTimeout(msecs,callback)message.sockethttp.ServerResponse可写流res函数&属性response.writeHead(statusCode,[headers])response.write(data,[enconding])response.end([data],[enconding])response.addTrailers(headers)response.finishedresponse.getHeader(name)response.headersSentresponse.removeHeader(name)response.sendDateresponse.setHeader(name, value)response.setTimeout(msecs[, callback])response.statusCoderesponse.statusMessageresponse.writeContinue()/** * 使用HTTP构建Web服务器 */var http = require('http');// 1. 创建一个 HTTP 服务器var server = http.createServer();// 2. 监听 请求(request) 事件// request 就是一个可读流,用来 获取 当前与服务器连接的客户端的一些请求报文数据// response 就是一个可写流,用来 给 客户端 Socket 发送消息的,或者用来发送响应报文的server.on('request',function (request, response) {  // 使用 HTTP 发送响应数据的时候,HTTP 服务器会自动把数据通过 HTTP 协议包装为一个响应报文然后发送到Socket  response.write('hello world');  // 在结束响应之前,我们可以多次向 客户端 发送数据  response.write('hello itheima');  // 对于 HTTP 请求响应模型来说,它们的请求和响应是一次性的  // 也就是说,每一次请求都必须结束响应,  // 标识断开当前连接  response.end();  // 在一次 HTTP 请求响应模型中,当结束了响应,就不能继续发送数据了,以下消息不会显示});// 3. 开启启动,监听端口server.listen(3000,function () {  console.log('server is listening at port 3000');});/** * 根据不同URL响应不同消息 */var http = require('http');//创建服务器var server = http.createServer();//监听request事件server.on('request', function(request, response) {  //获取资源路径,默认为'/'  var url = request.url;  //通过判断获取到的资源路径,发送指定响应消息  if (url === '/') {    response.end('hello index');  } else if (url === '/login') {    response.end('hello login');  } else if (url === '/register') {    response.end('hello register');  }else {    //如果路资源径找不到,提示错误信息    response.end('404 Not Found!');  }});//开启启动,监听端口server.listen(3000, function() {  console.log('server is listening at port 3000');});主程序demo7-3.js/** * 使用HTTP提供静态资源服务 */var http = require('http');var fs = require('fs');//用于读取静态资源var path = require('path');//用于做路径拼接var server = http.createServer();server.on('request', function(request, response) {  //获取静态资源路径  var url = request.url;  if (url === '/') {    //读取相应静态资源内容    fs.readFile(path.join(__dirname, 'static/index.html'), 'utf8', function(err, data) {     //如果出现异常抛出异常      if (err) {        throw err;      }      //将读取的静态资源数据响应给浏览器      response.end(data);    });  } else if (url === '/login') {    fs.readFile(path.join(__dirname, 'static/login.html'), 'utf8', function(err, data) {      if (err) {        throw err;      }      response.end(data);    });  } else if (url === '/register') {    fs.readFile(path.join(__dirname, 'static/register.html'), 'utf8', function(err, data) {      if (err) {        throw err;      }      response.end(data);    });  } else if (url === '/login.html') {    fs.readFile(path.join(__dirname, 'static/404.html'), 'utf8', function(err, data) {      if (err) {        throw err      }      response.end(data);    });    //如果有图片、CSS文件等,浏览器会重新发送请求获取静态资源  } else if (url === '/css/main.css') {    var cssPath = path.join(__dirname, 'static/css/main.css')    fs.readFile(cssPath, 'utf8', function(err, data) {      if (err) {        throw err      }      response.end(data);    });  } else if (url === '/images/01.jpg') {    var imgPath = path.join(__dirname,'static/images/01.jpg')    fs.readFile(imgPath, function(err, data) {      if (err) {        throw err      }      response.end(data);    });  } else {    fs.readFile(path.join(__dirname, 'static/404.html'), 'utf8', function(err, data) {      if (err) {        throw err      }      response.end(data);    });  }});server.listen(3000, function() {  console.log('server is listening at port 3000');});index.html<!DOCTYPE html><html lang="en"><head>  <meta charset="UTF-8">  <title>首页</title>  <link rel="stylesheet" href="css/main.css"></head><body>  <h1>首页</h1><img src="images/01.jpg" alt=""></body></html>login.html<!DOCTYPE html><html lang="en"><head>  <meta charset="UTF-8">  <title>登录</title>  <link rel="stylesheet" href="css/main.css"></head><body>  <h1>登录</h1>  <img src="images/01.jpg" alt=""></body></html>register.html<!DOCTYPE html><html lang="en"><head>  <meta charset="UTF-8">  <title>注册</title>  <link rel="stylesheet" href="css/main.css"></head><body>  <h1>注册</h1>  <img src="images/01.jpg" alt=""></body></html>404.html<!DOCTYPE html><html lang="en"><head>  <meta charset="UTF-8">  <title>404</title></head><style>  body {    background-color:pink;  }</style><body>  <h1>404 Not Found.</h1></body></html>main.cssbody {    background-color: pink;}01.jpgdemo7-4.js/** * 动态处理静态资源请求 */var http = require('http');var fs = require('fs');var path = require('path');var server = http.createServer();server.on('request', function(req, res) {  // 当用户访问 / 的时候,默认让用户访问 index.html  var url = req.url;  console.log(url);//每次请求获取资源路径在服务端输出。  var fullPath = path.join(__dirname,'static',url);  if (url==='/') {    fullPath = path.join(__dirname,'static/index.html');  }  fs.readFile(fullPath,function (err,data) {    if (err) {      // 在进行web开发的时候,如果发生了错误,我们可以直接把该错误消息输出到 客户端      return res.end(err.message);    }    res.end(data);  });});server.listen(3000, function() {  console.log('server is runnig at port 3000');});Underscore的模板引擎templatetemplate用于将JavaScript模板编译为可以用于页面呈现的函数,通过JSON数据源生成复杂的HTML并呈现出来语法:_.template(templateString,[settings])赋值:var compiled = _.template("hello:<%=name%>");compiled({name:'moe'});=>"hello:moe"需要转义:var template = _.template("<b><%- value %></b>");template({value:'<script>'});=>"<b>&lt;script&gt;</b>"在Node.js中使用Underscore需要用NPM安装在static-server目录下初始化:npm init -y在此目录下继续输入:npm install underscore --save在此目录下创建index.heml,并添加代码package.json<!DOCTYPE html><html><head>  <meta charset="utf-8">  <body>    <ul>      <% arr.forEach(function(item){ %>        <li> <%= item.name %> </li>        <%}) %>    </ul>    <h1><%= title %></h1>  </body>备注:在服务器端定义了一个数组arr,遍历该数组的每个元素,为元素挂着name属性。再定义一个变量title。这个页面是作为一个网页的模板使用,在服务器端会获得这个模板并为模板中的变量赋值,最后,将完整的数据作为一个字符串响应给浏览器端,由浏览器进行页面渲染和呈现{  "name": "static-server",  "version": "1.0.0",  "description": "",  "main": "app.js",  "scripts": {    "test": "echo "Error: no test specified" && exit 1"  },  "keywords": [],  "author": "",  "license": "ISC",  "dependencies": {    "underscore": "^1.8.3"  }}/** * 服务端代码 */var http = require('http');var fs = require('fs');var path = require('path');var _ = require('underscore');var server = http.createServer();server.on('request', function(req, res) {  var url = req.url;  if (url === '/') {    //备注:读取index.html模板的内容,并将返回的data传入模板函数_.template,传入后可以使用compiled()函数向该模板的数组和变量注入数据,最后将完整的HTML数据响应给客户端浏览器!!!    fs.readFile(path.join(__dirname, 'index.html'), 'utf8', function(err, data) {      if (err) {        return res.end(err.message);      }      // 现在的 data 就是 字符串      // 我把 html 字符串 整体的当成 模板字符串      var compiled = _.template(data);      var htmlStr = compiled({        title: 'hello world',        arr: [          { name: 'Jack' },          { name: 'rose' },          { name: 'mike' }        ]      });      res.end(htmlStr);    });  }});server.listen(3000, function() {  console.log('server is runnig at port 3000');});在此目录下创建文件app.js,并添加服务器端代码子主题最后,启动服务器:node app.js客户端打开浏览器查看第8章 综合项目--我的音乐2019.2.24 16:00var http = require('http')var server = http.createServer(function (req,res) {  res.end('<h1> hello world </h1>')  })server.listen(3000)03_http.js04_浏览器的本质.js+++++++++++++++++var net = require('manychat')var server = net.createServer()server.on('connection',function (socket) {  socket.on('data',function (data) {    console.log(data.toString())    // 对于 客户端来说, 这个时候,服务器发送给自己的消息,有没有发送完毕,不确定    // 所以 浏览器客户端 保持了挂起的状态,继续等待 服务器给自己传输消息数据    socket.write('HTTP/1.1 200 成功了 hello world')    // 如果想告诉客户端,本次的数据已经给你发送完毕了,不用等了 ,结束响应    // 结束响应就意味着,本次请求响应连接断开,。    socket.end()  })})server.listen(3000)项目简介项目功能展示项目开发流程1、产品创意2、产品原型3、美工设计4、前端实现5、后端实现6、测试、试运行、上线需求分析1、数据模型分析2、路由设计3、功能开发展示歌曲信息编辑歌曲信息删除歌曲添加歌曲项目结构1、render.js解析模板标记语法2、music.js封装音乐文件相关的逻辑处理函数3、node_modules文件夹下的bootstrap响应式前端框架4、node_modules文件夹下的underscore:模板引擎用于注入后台数据5、node_modules文件夹下的formidate:用于表单的数据处理,尤其是表单的文件上传处理6、uploads文件夹:用于存放MP3音频文件和jpg图片文件7、views文件夹:用于存放页面8、app.js:项目的入口文件9、config.js配置端口10、package.json项目的说明文件11、router.js:路由模块,根据用户的请求判断路径,然后将请求分发到具体的处理函数项目实现项目初始化项目说明文件package.json{  "name": "music-player",  "version": "1.0.0",  "description": "一个简单的音乐播放器",  "main": "app.js",  "scripts": {    "test": "echo "Error: no test specified" && exit 1",    "start": "node app.js"  },  "author": "iroc <mail@lipengzhou.com> (https://github.com/iroc)",  "license": "MIT",  "dependencies": {    "bootstrap": "^3.3.6",    "formidable": "^1.0.17",    "underscore": "^1.8.3"  }}"start": "nodemon app.js"当不知道入口文件时,会通过下面的命令自动找到start指令:npm start  "dependencies": {    "bootstrap": "^3.3.6",    "formidable": "^1.0.17",    "underscore": "^1.8.3"  }需要选装第三方依赖包npm install --save bootstrapnpm install --save formidable入口文件app.jsvar http = require('http')var config = require('./config')var router = require('./router')var render = require('./common/render')var server = http.createServer()server.on('request', function(req, res) {  // 首先动态的给 Response 对象挂载了一个 render 方法,该方法用来读取一个模板文件,注入数据,然后响应给当前请求  render(res)  // 在进入 router 模块之前  // 我们就已经给 res 对象加了一个属性方法叫做:render    // 然后请求和响应被传递到一个路由的模块中  // 该模块就是专门用来对不同的请求路径分发到具体的请求处理函数  router(req, res)})server.listen(config.port, config.host, function() {  console.log('server is listening at port ' + config.port)  console.log('pleast visit http://' + config.host + ':' + config.port)})配置文件config.jsvar path = require('path')module.exports = {  port: 3000,  host: '127.0.0.1',  viewPath: path.join(__dirname, 'views'),  uploadPath: path.join(__dirname, 'uploads')}路由文件router.js/** * 路由模块:负责把具体的请求路径分发的具体的请求处理函数 * 分发到具体的业务处理逻辑 * 用户的每一个请求都会对应后台的一个具体的请求处理函数 */var fs = require('fs')var path = require('path')var _ = require('underscore')var handler = require('./handler')var musicController = require('./controllers/music')var userController = require('./controllers/user')var url = require('url')module.exports = function(req, res) {  // 首页 / 呈递音乐列表页面  // 添加音乐页面 /add 呈递添加音乐页面  // 编辑音乐页面 /edit 呈递编辑音乐页面  // 删除音乐 /remove 处理删除音乐请求  // 当使用 url核心模块的 parse方法之后,该方法会自动帮你把路径部分解析到 pathname 属性中  // 同时他会把 查询字符串部分 解析到 query 属性  // 对于我们的 url.parse 方法来说,它还有第二个参数,我们可以给它指定为 true,那么这个时候它会自动帮我们把 query 属性查询字符串转换为一个对象  var urlObj = url.parse(req.url, true)  req.query = urlObj.query  console.log(urlObj.query)  // 获取当前请求路径  // pathname 不包含查询字符串  var pathname = urlObj.pathname  var method = req.method  console.log(method)  if (method === 'GET' && pathname === '/') {    musicController.showIndex(req, res)  } else if (method === 'GET' && pathname === '/index.html') {    musicController.showIndex(req, res)  } else if (method === 'GET' && pathname.startsWith('/node_modules/')) {    var staticPath = path.join(__dirname, pathname)    fs.readFile(staticPath, 'utf8', function(err, data) {      if (err) {        return res.end(err.message)      }      res.end(data)    })  } else if (method === 'GET' && pathname === '/add') {    musicController.showAdd(req, res)  } else if (method === 'GET' && pathname === '/edit') {    musicController.showEdit(req, res)  } else if (method === 'GET' && pathname === '/login') {    userController.showLogin(req, res)  } else if (method === 'GET' && pathname === '/register') {    userController.showRegister(req, res)  } else if (method === 'POST' && pathname === '/add') {    musicController.doAdd(req, res)  } else if (method === 'GET' && pathname ==='/remove') {    musicController.doRemove(req, res)  } else if (method === 'POST' && pathname === '/edit') {    musicController.doEdit(req, res)  }}// res.render('path',{})// render('path',{//   title: 'Index'// },res)// function render(viewPath, obj, res) {//   fs.readFile(viewPath, 'utf8', function(err, data) {//     if (err) {//       return res.end(err.message)//     }//     var compiled = _.template(data)//     var htmlStr = compiled(obj)//     res.end(htmlStr)//   })// }解析模板标记语法render.jsvar fs = require('fs')var path = require('path')var _ = require('underscore')var config = require('../config')module.exports = function(res) {  res.render = function(viewName, obj) {    fs.readFile(path.join(config.viewPath, viewName) + '.html', 'utf8', function(err, data) {      if (err) {        return res.end(err.message)      }      var compiled = _.template(data)      var htmlStr = compiled(obj || {})      res.end(htmlStr)    })  }}封装处理函数模块music.jsvar qstring = require('querystring')var formidable = require('formidable')var config = require('../config')var path = require('path')var storage = [  { id: 1, title: '富士山下', singer: '陈奕迅', music: '陈奕迅 - 富士山下.mp3', poster: '陈奕迅.jpg' },  { id: 2, title: '石头记', singer: '达明一派', music: '达明一派 - 石头记.mp3', poster: '达明一派.jpg' },  { id: 3, title: '青城山下白素贞', singer: '好妹妹乐队', music: '好妹妹乐队 - 青城山下白素贞.mp3', poster: '好妹妹乐队.jpg' },  { id: 4, title: '友情岁月', singer: '黄耀明', music: '黄耀明 - 友情岁月.mp3', poster: '黄耀明.jpg' },  { id: 5, title: '梦里水乡', singer: '江珊', music: '江珊 - 梦里水乡.mp3', poster: '江珊.jpg' },  { id: 6, title: 'Blowing In The Wind', singer: '南方二重唱', music: '南方二重唱 - Blowing In The Wind.mp3', poster: '南方二重唱.jpg' },  { id: 7, title: '女儿情', singer: '万晓利', music: '万晓利 - 女儿情.mp3', poster: '万晓利.jpg' },  { id: 8, title: '王馨平', singer: '别问我是谁', music: '王馨平 - 别问我是谁.mp3', poster: '王馨平.jpg' },  { id: 9, title: '五环之歌', singer: '岳云鹏', music: '岳云鹏,MC Hotdog - 五环之歌.mp3', poster: '岳云鹏.jpg' }]exports.showIndex = function(req, res) {  res.render('index', {    title: '首页',    musicList: storage  })}exports.showAdd = function(req, res) {  res.render('add', {    title: '添加音乐'  })}exports.doAdd = function(req, res) {  // 表单post提交的时候,没有enctype的情况下,可以使用下面这种方式来接收和处理post提交的数据  // var data = ''  // req.on('data',function (chunk) {  //   data += chunk  // })  // req.on('end',function () {  //   var postBody = qstring.parse(data)  //   console.log(postBody)  //   res.end(JSON.stringify(postBody))  // })  // 如果要处理有文件的表单,那么可以使用社区提供的一个包:formidable  var form = new formidable.IncomingForm()  form.uploadDir = config.uploadPath  form.keepExtensions = true  form.parse(req, function(err, fields, files) {    if (err) {      return res.end(err.message)    }    var title = fields.title    var singer = fields.singer    var music = path.basename(files.music.path)    var poster = path.basename(files.poster.path)    var id = 0    storage.forEach(function(item) {      if (item.id > id) {        id = item.id      }    })    storage.push({      id: id + 1,      title: title,      singer: singer,      music: music,      poster: poster    })    res.writeHead(302, {      'Location': 'http://127.0.0.1:3000/'    })    res.end()  })}exports.showEdit = function(req, res) {  var id = req.query.id  var music = {}    // 根据 id 查询出该id在数组中对应的项  storage.forEach(function(item, index) {    if (item.id == id) {      music = item    }  })  res.render('edit', {    title: '编辑音乐',    music: music  })}exports.doEdit = function(req, res) {  console.log('doedit 被执行了')  var id = req.query.id    // 获取用户提交的数据  var data = ''  req.on('data', function(chunk) {    data += chunk  })  req.on('end', function() {    var postBody = qstring.parse(data)      // 根据id找到数据中该项的索引      var music_index = 0      storage.forEach(function (item, index) {        if (item.id == id) {          music_index = index        }      })      storage[music_index].title = postBody.title      storage[music_index].singer = postBody.singer      res.writeHead(302, {        'Location': 'http://127.0.0.1:3000/'      })      res.end()  })  // 然后做修改操作}// 如何获取和解析get请求提价的查询字符串中参数 url模块的parse方法的第二个参数// 如何从数组中删除一个元素  spliceexports.doRemove = function(req, res) {  // 获取查询字符串中的 id  var id = req.query.id  var music_index = 0    // 通过该 id 找到数组中的该项  storage.forEach(function(item, index) {      if (item.id == id) {        music_index = index      }    })    // 然后进行真正的删除操作,根据索引下标进行删除  storage.splice(music_index, 1)  res.writeHead(302, {    'Location': 'http://127.0.0.1:3000/'  })  res.end()}页面显示文件夹view下的index.html<!DOCTYPE html><html lang="en"><head>  <meta charset="UTF-8">  <title>Document</title>  <link rel="stylesheet" href="/node_modules/bootstrap/dist/css/bootstrap.css"></head><body>  <div class="container">    <div class="page-header">      <h1><a href="/">我的音乐</a> <small><%= title %></small></h1>    </div>    <a class="btn btn-success" href="/add">添加歌曲</a>    <table class="table">      <thead>        <tr>          <th>编号</th>          <th>标题</th>          <th>歌手</th>          <th>音乐名称</th>          <th>海报</th>          <th>操作</th>        </tr>      </thead>      <tbody>        <% musicList.forEach(function(music){ %>          <tr>            <td><%= music.id %></td>            <td><%= music.title %></td>            <td><%= music.singer %></td>            <td><%= music.music %></td>            <td><%= music.poster %></td>            <td>              <a href="/edit?id=<%= music.id %>">修改</a>              <a href="/remove?id=<%= music.id %>">删除</a>            </td>          </tr>          <% }) %>      </tbody>    </table>  </div></body></html>添加歌曲页面add.html<!DOCTYPE html><html lang="en"><head>  <meta charset="UTF-8">  <title>Document</title>  <link rel="stylesheet" href="node_modules/bootstrap/dist/css/bootstrap.css"></head><body>  <div class="container">    <div class="page-header">      <h1><a href="/">我的音乐</a> <small><%= title %></small></h1>    </div>    <!--     表单提交三要素:      action 用来指定提交到那个请求地址      method 默认是get方式,当表单提交的时候,表单会把表单内的所有具有 name 属性的 input 元素的值 以 name=value&name=value 的格式放到 url 地址中,然后发出请求      如果指定 method 为 post 提交方式,        表单同样的将表单内所有具有name属性的input元素的值以            name=value&name=value  的格式 放到请求报问体 中,然后发出请求    如果要使用表单来上传文件,那么必须指定两点:      1. 表单提交方法必须为 post      2. 一定要 指定表单的 enctype 属性 为 multipart/form-data      上面两点缺一不可,否则后台收不到提交的文件数据      如果表单提交没有file类型的input元素,那么就不要手动的指定 enctype      如果有file类型的input元素,则必须指定enctype 属性 为 multipart/form-data     -->    <form action="/add" method="post" enctype="multipart/form-data">      <div class="form-group">        <label for="title">标题</label>        <input type="text" class="form-control" >确定修改</button>    </form>  </div></body></html>