第一天
Node简介
-什么是Javascript
+脚本语言
+运行在浏览器中
+一般用来做客户端页面的交互(Interactive)
-JaveScript的运行环境?
+是不是运行在浏览器呢?
,不够严谨
+是运行在浏览器内核中的JS引擎(Engine)(而不是浏览器,浏览器是一个大的概念)
浏览器的作用
-浏览器中的JavaScript可以做什么?
+操作DOM(对DOM的增删改、注册事件)
+AJAX/跨域
+BOM(页面跳转、历史记录、console.log()、alert())
+ECMAScript
-浏览器中的Javascript不可以做什么?
+文件操作(文件和文件夹的CRUD)
+没有办法操作系统信息
+由于运行环境特殊,并不是在服务器执行,而是到不认识的人的浏览器客户端中执行
-在开发人员能力相同的情况下编程语言的能力取决于什么
+语言本身?
+语言本身只是提供定义变量,定义函数,定义类型,流程控制,循环结构之类的操作
+取决于运行该语言的平台(环境)
+对于JS来说,我们常说JS实际是ES,大部分能力都是由浏览器的执行引擎决定
+BOM和DOM可以说是浏览器开发出来的接口
+比如:Cordova中提供JS调用摄像头,操作本地文件的API
+Java既是语言也是平台
+Java运行在Java虚拟机(跨操作系统)
+PHP 即使语言也是平台
+C#语言 平台: .net framework(Windows)
+C#可以运行在Mono平台
+因为有人需要将C#运行在Linux平台,所以出现了MONO
-JavaScript只可以运行在浏览器中吗?
+不是
+能运行在哪取决于,这个环境有没有特定的平台
### 什么是Node
Node.js® is a JavaScript runtime built on Chrome's V8 JavaScript engine. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient. Node.js' package ecosystem, npm, is the largest ecosystem of open source libraries in the world.
·Node就是服务器端的一个Javascript运行环境
Node在Web中的用途
·分发数据请求,渲染HTML
(因为NodeJS并发数相比传统平台很优秀)
常见命令行操作
Path变量
添加到Path变量后可以直接在CMD中打开
*Windows默认使用反斜线
*在Linux标准中一般使用/正斜线(除法的那个斜线)
Node REPL环境
浏览器中 全局对象是Window对象 在node中是没有window对象的。
process.argv
打印一个数组,第一个是node的路径,第二个是当前运行的程序的路径,后面的内容是数组中的参数。
process.stdout
process.stdout(msg) = console.log(`${msg} `);
Debug
process.stdin.on(‘data’,(input)=>{
…
})
//输入的字符最后肯定是一个回车符
所以要trim掉
process.stdin.readline() //用户的操作是无状态的,所以一般都不用这个
process.stdout.write();
异步操作
console.time(‘’)
console.timeEnd(‘’)
事件队列:
var fs = require(‘fs’);
fs.readFile(‘./typings/node/aa.ts’,’utf8’,(err,data)=>{
if(err) throw err;
console.log(data);
})
第二天
错误优先的回调函数
因为操作大多数都是异步形式,没法通过trycatch来捕获异常
所以写错误优先 的回掉函数
进程和线程
进程:进行中的程序
创建线程,就像当项目经理招人一样。需要时间,并不是那么容易。
不要太较真,没有任何一个人是那么完美。
多线程都是假的,因为只有一个CPU(单核)
线程之间共享某些数据,同步某个状态都很麻烦
更致命的是:
-创建线程耗费时间
-线程数量有限
-CPU
单线程产品nginx Redis
事实证明这些单线程产品比多线程产品性能要好。
Node.js的优势
事件驱动,非阻塞
如果使用Java或者PHP需要创建新线程,分配资源,需要的代码和其他资源也比较多而复杂,而Node实现起来非常简单。在每一个异步操作后都会有一个回调。
非阻塞I/O
Node的核心特性
const fs = require(‘fs’);
事件驱动:
const fs = require(‘fs’)
//判断是否存在list文件
fs.stat(‘./list.md’,(err,stats)=>{
if(err) throw err;
//存在删除
/*
fs.unlink(‘./list.md’,(err,)=>{
if(err) throw err;});
*/ console.log(stats);
})
const fs = require('fs');
console.time('timer');
fs.stat('./1.txt',(err,stats)=>{
if(err) console.error(err);
//创建
fs.writeFile('./list.md',new Date(),(err)=>{
if(err) console.error(err);
console.log('创建成功');
console.timeEnd('timer');
})
});
事件队列: Event Queue
主线程执行,遇到异步事件(阻塞操作)会塞到事件队列里,
主线程执行完开始到事件队列中拿第一个异步事件进行执行
如果在这个异步事件的callback中有异步操作,那么把他放到事件队列中,
阻塞操作是交给内部线程池来完成的
Node在底层维护了一个线程池(里面有很多线程)如果有需要耗时的操作,就会交给线程来操作。
不用的线程再次关回小黑屋(图片最下面的部分)
Node为什么能实现非阻塞,
原因是他在实现调度的工作,本身并没有实质的读文件 读网络的工作。
调度。
非阻塞的优势
·提高代码的相应效率
·充分利用单核CPU的优势(但是目前市场上大多数是多核CPU)
·改善I/O的不可预测带来的问题
·如何提高一个人的工作效率
web中的单线程
//Node 开发服务器的阻塞情况
const http = require('http');
let count = 0;
const server = http.createServer((req,res)=>{
//此回调回在有任何用户请求时触发
res.writeHead(200,{'Content-Type':'text/html;charset=utf-8'});
res.write(`你是第${count++}个访问用户`,);
res.end();
});
server.listen(2080,(err)=>{
if(err) throw err;
console.log('成功启动web服务,端口:2080');
});
下面把代码改成这样,
因为事件队列对阻塞事件的处理,
在第十个请求的时候会一直卡死。
如果是PHP就没事儿,因为那个是不同的线程去执行的。
注意:node开发时一定要谨慎防止这种情况的出现。
模块化的开始
//实现命令行计算器
//1.接收参数
const args = process.argv.slice(2);
//['node 执行程序所在路径 ','当前脚本所在路径',...参数]
//2.分析参数
if(args.length != 3){
console.log('参数不合法');
throw new Error('err'); //到这里跳出这个文件
}
//parameter
let p1 = args[0];
let operator = args[1];
let p2 = args[2];
let result;
switch (operator) {
case '+':
result = parseFloat(p1) + parseFloat(p2);
break;
case '-':
result = parseFloat(p1) - parseFloat(p2);
break;
case 'x':
case '*':
result = parseFloat(p1) * parseFloat(p2);
break;
case '÷':
case '/':
result = parseFloat(p1) / parseFloat(p2);
break;
default:
throw new Error('不被支持的操作符' + operator);
break;
}
console.log(result);
其他的社区规范.. CMD规范
CommonJS规范
在Node中实现的是CommonJS规范
CommonJS与CMD规范的区别就是不需要用define了。
文件模块 require(‘./../’)
核心模块 require(‘fs’) //并没有写什么目录
所有的文件操作必须是绝对路径(物理路径)
module1.js
//获取当前脚本所在路径
console.log(__dirname);
//文件路径
console.log(__filename);
const fs = require('fs');
//所有的文件操作必须是绝对路径(物理路径)
fs.readFile(__dirname+'/../list.md','utf8',(err,content)=>{
if (err) throw err;
console.log(content);
});
2.js
//模块中的全局成员
const modul = require('./module/module1.js');
为什么文件操作必须是绝对路径?
答:因为require以后执行的话module1.js中的相对路径就会产生错误进而报错。
//module对象
console.log(module);
Module {
id: '.',
exports: {},
parent: null,
filename: 'C:\Users\BDSOFT\Desktop\nodelearn\4.js', loaded: false,
children: [],
paths:
[ 'C:\Users\BDSOFT\Desktop\nodelearn\node_modules',
'C:\Users\BDSOFT\Desktop\node_modules',
'C:\Users\BDSOFT\node_modules',
'C:\Users\node_modules',
'C:\node_modules' ]
}
Node.js 全局对象
JavaScript 中有一个特殊的对象,称为全局对象(Global Object),它及其所有属性都可以在程序的任何地方访问,即全局变量。
在浏览器 JavaScript 中,通常 window 是全局对象, 而 Node.js 中的全局对象是 global,所有全局变量(除了 global 本身以外)都是 global 对象的属性。
在 Node.js 我们可以直接访问到 global 的属性,而不需要在应用中包含它。
__filename
__dirname
setTimeout(cb, ms)
clearTimeout(t)
setInterval(cb, ms)
console
process
为什么说是伪全局变量呢?
答:因为在Node REPL里输__dirname是没有的。__dirname存在于node模块(.js文件)内部。
模块本身就是一个封闭的作用域,没有必要写自执行函数。
自己实现一个require
module4.js:
function say(msg){
console.log(msg);
}
module.exports = {say};
1.js:
//自己写一个require函数
function $require(id){
// 1.先找到文件 如果文件不存在 Error: Cannot find module 'xxx.js'
// 2.读取文件内容 内容是JS代码
const fs = require('fs');
const path = require('path');
//要加载的js文件路径(完整路径)
const filename = path.join(__dirname,id);
const dirname = path.dirname(filename);
let code = fs.readFileSync(filename,'utf8');
// 3.执行代码,所要执行的代码 需要营造一个私有空间
// 定义一个数据容器,用容器去装模块导出的成员
let module = {id:filename,exports:{}};
let exports = module.exports;
code = `
(function($require,module,exports,__dirname,__filename){
${code}
})($require,module,exports,dirname,filename)`;
eval(code);
// 4.返回值
return module.exports;
}
var m4 = $require('./module/module4.js');
m4.say('hello');
(然而没有讲export是怎么实现的.)
require扩展名
先加载js,如果没有按顺序往下面加在。
//require不仅仅可以载入js模块,也可以用来读取配置信息(JSON)
如果require了一个文件夹,会默认加载这个文件夹下的
package.json中main指向的文件,
如果没有定义就会载入index.js
require模块加载机制
从当前目录向上搜索node_modules目录中的文件(就近加载)
模块的require缓存
date.js:
module.exports = new Date();
1.js:
//模块的缓存
setInterval(()=>{
var date = require('./date.js');
console.log(date.getTime());
},1000);
运行结果:
说明模块有缓存机制。
console.log(module.require)
打印出一个对象,被引入的模块将被缓存在这个对象中。
缓存结构示例:
{ 'C:\Users\BDSOFT\Desktop\nodelearn\require.js':
Module {
id: '.',
exports: {},
parent: null,
filename: 'C:\Users\BDSOFT\Desktop\nodelearn\require.js',
loaded: true,
children: [
[Module
]
],
paths: [ 'C:\Users\BDSOFT\Desktop\nodelearn\node_modules',
'C:\Users\BDSOFT\Desktop\node_modules',
'C:\Users\BDSOFT\node_modules',
'C:\Users\node_modules',
'C:\node_modules'
]
},
'C:\Users\BDSOFT\Desktop\nodelearn\date.js':
Module {
id: 'C:\Users\BDSOFT\Desktop\nodelearn\date.js',
exports: 2018-04-27T09: 31: 32.836Z,
parent:
Module {
id: '.',
exports: {},
parent: null,
filename: 'C:\Users\BDSOFT\Desktop\nodelearn\require.js',
loaded: true,
children: [Array
],
paths: [Array
]
},
filename: 'C:\Users\BDSOFT\Desktop\nodelearn\date.js',
loaded: true,
children: [],
paths: [ 'C:\Users\BDSOFT\Desktop\nodelearn\node_modules',
'C:\Users\BDSOFT\Desktop\node_modules',
'C:\Users\BDSOFT\node_modules',
'C:\Users\node_modules',
'C:\node_modules'
]
}
}
清空一个缓存试试:
Object.keys(require.cache).forEach((key)=>{
delete require.cache[key];
});
*一般情况下不会手动清空它(闲得蛋疼),这里就是示例一下
加缓存:
function $require(id) {
// 1.先找到文件 如果文件不存在 Error: Cannot find module 'xxx.js'
// 2.读取文件内容 内容是JS代码
const fs = require('fs');
const path = require('path');
//要加载的js文件路径(完整路径)
const filename = path.join(__dirname, id);
$require.cache =$require.cache || {};
if($require.cache[filename]){
return $require.cache[filename].exports;
}
const dirname = path.dirname(filename);
let code = fs.readFileSync(filename, 'utf8');
// 3.执行代码,所要执行的代码 需要营造一个私有空间
// 定义一个数据容器,用容器去装模块导出的成员
let module = { id: filename, exports: {} };
let exports = module.exports;
code = `
(function($require,module,exports,__dirname,__filename){
${code}
})($require,module,exports,dirname,filename)`;
eval(code);
$require.cache[filename] = module;
// 4.返回值
return module.exports;
}
第三天
学编程要学会看API文档
NPM包管理工具
where node
npm config ls
全局配置文件 npmrc
prefix前缀
设置npm install –g安装到哪里…
npm config set prefix C:Develop vm pm
npm config get prefix
NodeResourceManager介绍
npm install nrm –g
rm ls
这个星球所有的源!
nrm use cnpm
再看npm config ls
文件系统操作
文件操作必须使用绝对路径
fs:
path:
readline:
Windows默认编码 GBK
笔记本保存的话显示ANSI,其实就是GBK
path模块操作
详见文档
path.relative(from,to)
path.resolve
同步调用和异步调用
fs.readFile
fs.readFileSync
读文件没有指定编码默认读取的是一个buffer(缓冲区)
buffer
这个”瓢”只能装4个字节
LE、BE
little-endian 小字节序 符合人的思维,地址低位存储值的低位,地址高位存储值的高位。
big-endian 大字节序 最直观的字节序,地址低位存储值的高位,地址高位存储值的低位。
转换成Base64
通过Data:URL协议来打开看
DATA-URI 是指可以在Web 页面中包含图片但无需任何额外的HTTP 请求的一类URI.
一般的图片都会单独请求
(这里是一个简单的介绍)
文件编码的问题
滚动显示歌词
// 动态显示歌词
const fs = require('fs')
const path = require('path')
fs.readFile(path.join(__dirname, './nineteen.lrc'), (err, data) => {
if (err) throw err;
var lines = data.toString('utf8').split(' ');
// [00:17.78]走过初识你的地点
// 第一组,第二组,第三组,第四组
var regex = /[(d{2}):(d{2}).(d{2})](.+)/;
var begin = new Date().getTime();
//遍历
lines.forEach((line) => {
var matches = regex.exec(line);
// 如果匹配上了
if (matches) {
var m = parseFloat(matches[1]);
var s = parseFloat(matches[2]);
var f = parseFloat(matches[3]);
var lyric = matches[4]; //当前行歌词其实不是立即执行
//由于下达输出任务的时刻不同,设置定时器其实有一些几毫秒的误差
var offset = new Date().getTime() - begin;
setTimeout(() => {
console.log(lyric);
}, m * 60 * 1000 + s * 1000 + f - offset);
} else {
// 不是一行歌词
console.log(line);
}
});
});
流的方式读取
node会根据系统资源情况自动生成buffer大小 先把数据写到缓冲区,然后再从缓冲区写入磁盘中。
const fs = require('fs')
const path = require('path')
const readline = require('readline')
var filename = path.join(__dirname, './nineteen.lrc');
var streamReader = fs.createReadStream(filename);
var data = '';
streamReader.on('data', (chunk) => {
//chunk只是文档的一个片段 不完整
data += chunk.toString();
});
streamReader.on('end',()=>{
//通知你已经结束了 此时data是完整的
console.log(data);
});
利用ReadLine读取:
var streamReader = fs.createReadStream(filename);
//利用readline读取
var rl = readline.createInterface({input:streamReader})
var begin = new Date().getTime();
rl.on('line',(line)=>{
//function()...
});
//经过console.log(typeof line)来看,
readline读出来的已经是string了,不是buffer。不是buffer
文件写入
//文件写入
const fs = require('fs');
const path = require('path');
//fs.writeFile();
//JSON.stringify 序列化
//JSON.parse 反序列化
/*
//默认覆盖文件
fs.writeFile(path.join(__dirname, './temp.txt'), { id: 10 }, (err) => {
//要是写入对象,其实就是toString()了
if (err) {
//读文件是不存在报错
//写文件可能出现的..意外错误
//..文件权限问题
//..文件夹找不到(不会自动创建文件夹)
console.log('error');
} else {
console.log('success');
}
});
*/
//fs.writeFileSync();
//java c
// try{
// } catch(error){
// }
//fs.createWriteStream();
// var streamWriter = fs.createWriteStream(path.join(__dirname,'./temp.txt'));
// setInterval(()=>{
// 不断的往里面写,代码操作的是内存中的数据,(不是磁盘中的数据),然后这个副本会写到磁盘中
// streamWriter.write('hello',()=>{
// console.log('+1');
// });
// },1000);
/*追加
fs.appendFile(path.join(__dirname, './temp.txt'), JSON.stringify({ id: 10 }), (err) => {
//要是写入对象,其实就是toString()了
if (err) {
//读文件是不存在报错
//写文件可能出现的..意外错误
//..文件权限问题
//..文件夹找不到(不会自动创建文件夹)
console.log('error');
} else {
console.log('success');
}
});
*/
其他文件操作
fs.stat.isFile()…
.isDirectory…
…
重命名
fs.rename
fs.renameSync
删除文件
fs.unlink
fs.unlinkSync
//移动文件和重命名
//移动文件和重命名
const fs = require('fs');
const path = require('path');
var currentPath = path.join(__dirname,'./temp1.txt');
var targetPath = path.join(__dirname,'./temp2.txt');
fs.rename(currentPath,targetPath,(err)=>{
if(err) {console.log(err)}
});
//和命令CMD中的mv操作一样。
//删除文件和rm操作差不多
打印当前目录文件列表
//打印当前目录所有文件
const fs = require('fs');
const path = require('path');
//获取当前有没有传入目标路径
var target = path.join(__dirname,process.argv[2] || './');
fs.readdir(target,(err,files)=>{
files.forEach(file=>{
//console.log(path.join(target,file));
fs.statSync(path.join(target,file),(err,stats)=>{
console.log(`${stats.mtime} ${stats.size} ${file})`);
});
});
});
递归目录树
//递归目录树
//1.先写一层的情况
//2.抽象出递归参数
//3.找到突破点(避免死循环)
//自己调自己, 某种情况(肯定会存在的)不调用
const fs = require('fs');
const path = require('path');
var target = path.join(__dirname, process.argv[2] || './');
var level = 0;
load(target,0);
function load(target,depth) {
//depth 0 = ''
//depth 1 = '| '
//depth 2 = '| | '
var prefix = new Array(depth+1).join('| ');
var dirinfos = fs.readdirSync(target);
var dirs = [];
var files = [];
dirinfos.forEach(info => {
var stats = fs.statSync(path.join(target, info));
if (stats.isFile()) {
files.push(info);
} else {
dirs.push(info);
}
});
//│└
dirs.forEach(dir => {
console.log(`${prefix}├─${dir}`);
// 当前是一个目录 需要深入进去
load(path.join(target,dir),depth+1);
});
var count = files.length;
files.forEach(file => {
console.log(`${prefix}${--count ? '├' : '└'}─${file}`);
});
}
总结一下:
这部分最骚的操作就是递归,和打印目录列表
循环创建文件夹
node中的文件创建并不能像CMD中的mkdir a/b这样连续创建文件夹,一次只能创建一个,所以这里写了一个函数,目的是能实现连续创建文件夹的效果。
1.js
const fs = require('fs');
const path = require('path');
const mkdirs = require('./mkdirs');
mkdirs('demo2/demo3');
mkdirs.js
//创建层及目录
const fs = require('fs');
const path = require('path');
//创建文件,定义模块成员,导出模块成员,载入模块,使用模块
function mkdirs(pathname, callback) {
// 1.判断传入的是否是一个绝对路径
// D:ademo2demo3
pathname = path.isAbsolute(pathname) ? pathname : path.join(__dirname, pathname);
//获取要创建的部分
// pathname = pathname.replace(__dirname,'');
var relativepath = path.relative(__dirname,pathname);
// ['demo2','demo3']
var folders = relativepath.split(path.sep);
try{
var pre = '';
folders.forEach(folder =>{
fs.mkdirSync(path.join(__dirname,pre,folder));
pre = path.join(pre,folder); //demo2
});
callback&&callback(null);
}catch(error){
callback && callback(error);
}
}
module.exports = mkdirs;
为啥node_modules里面有那么多文件夹?
顺带一提:windows下如果有好多文件夹嵌套会产生好多奇怪的bug
node里面使用的是平行依赖。
平行依赖与递归依赖
__dirname不能乱用
如果创立了一个module文件夹,然后把写好的mkdir放到里面,那么引用的时候当解析到mkdir.js的__dirname字段时会被认为是module文件夹,所以__dirname不能乱用,这里我们使用了path.dirname(module.parent.filename)。
最终的mkdirs.js
//创建层及目录
const fs = require('fs');
const path = require('path');
//创建文件,定义模块成员,导出模块成员,载入模块,使用模块
function mkdirs(pathname, callback) {
//module.parent 拿到的是调用我的对象 02.js
// console.log(module.parent);
var root = path.dirname(module.parent.filename);
// console.log(root);
// 1.判断传入的是否是一个绝对路径
// D:ademo2demo3
pathname = path.isAbsolute(pathname) ? pathname : path.join(root, pathname);
//获取要创建的部分
// pathname = pathname.replace(root,'');
var relativepath = path.relative(root,pathname);
// ['demo2','demo3']
var folders = relativepath.split(path.sep);
try{
var pre = '';
folders.forEach(folder =>{
try{
//如果不存在则报错
fs.statSync(path.join(root,pre,folder));
} catch(error){
fs.mkdirSync(path.join(root,pre,folder));
}
pre = path.join(pre,folder); //demo2
});
callback&&callback(null);
}catch(error){
callback && callback(error);
}
}
module.exports = mkdirs;
监视文件变化变为HTML文件
利用文件监视实现markdown文件转换
4.js
//Markdown文件自动转换
/*
思路
1.利用fs模块的文件监视功能监视指定MD文件
2.当文件发生变化后,借助marked包提供的markdown to html功能改变后的MD文件转换为HTML
3.再将得到的HTML替换到模板中
4.最后利用BrowserSync模块实现浏览器自动刷新
*/
const fs = require('fs');
const path = require('path');
const marked = require('marked');
//接收需要转换的文件路径
const target = path.join(__dirname,process.argv[2]||'./readme.md');
//监视文件变化
fs.watchFile(target,(curr,prev)=>{
// console.log(`current:${curr.size};previous:${prev.size}`)
// 判断文件到底有没有变化
if(curr.mtime === prev.mtime){
return false;
}
// 读取文件 转换为新的HTML
fs.readFile(target,'utf8',(err,content)=>{
if(err){throw err;}
var html = marked(content);
console.log(html);
html = template.replace('{{{content}}}',html);
fs.writeFile(target.replace('.md','.html'),html,'utf8');
});
var template = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div>
{{{content}}}
</div>
</body>
</html>
`;
});
文件流的概念
装着字节的大水流
node中没有copy的方法,我们可以用流来复制。
写文件及进度条功能的实现
// 文件流的方式复制
const fs = require('fs');
const path = require('path');
// 创建文件的读取流,并没有读出真实的数据,开始了读取文件的任务
console.time('start');
//执行到这里的时候已经从线程池中调度线程,往缓冲区中塞数据了
var reader = fs.createReadStream('D:\1.txt');
console.timeEnd('start');
// 都文件内容与读文件信息不一样,都文件信息很快fsstat读取的是文件的元信息,所以很快
fs.stat('D:\1.txt', (err, stats) => {
if (err) throw err;
if (stats) {
var total = 0;
//当缓冲区中塞了数据,就开始处理
reader.on('data', (chunk) => {
//chunk是一个buffer(字节数组)
total += chunk.length;
console.log('读了一点 进度:' + total / stats.size *100 + '%');
});
}
})
文件流写入
…
在内存中再建立一个缓冲区,是写入的缓冲区。
代码:
// 文件流的方式复制
const fs = require('fs');
const path = require('path');
// 创建文件的读取流,并没有读出真实的数据,开始了读取文件的任务
//执行到这里的时候已经从线程池中调度线程,往缓冲区中塞数据了
var reader = fs.createReadStream('D:\1.txt');
var writer = fs.createWriteStream('D:\2.txt');
// 都文件内容与读文件信息不一样,都文件信息很快fsstat读取的是文件的元信息,所以很快
fs.stat('D:\1.txt', (err, stats) => {
if (err) throw err;
if (stats) {
var totalRead = 0;
var totalWrite = 0;
//当缓冲区中塞了数据,就开始处理
reader.on('data', (chunk) => {
//chunk是一个buffer(字节数组)
writer.write(chunk,(err)=>{
totalWrite += chunk.length;
console.log('写了一点 进度:' + totalWrite/ stats.size *100 + '%');
});
totalRead += chunk.length;
console.log('读了一点 进度:' + totalRead / stats.size *100 + '%');
});
}
})
pipe方法
之前的方式就像是拿一个水瓢(缓冲区),舀出来一点,到出去一点,现在介绍一种方法叫pipe,管道,直接拿个管子把俩连起来。
node progress 进度条
这里讲了一下node progress包
https://www.npmjs.com/package/progress
回顾一下npm命令
npm init –y
npm ls展示当前目录所有依赖包
npm ls –depth 0 展示第0层目录
学个新单词
Token令牌 标识
Socket基础
Socket
网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket。
建立网络通信连接至少要一对端口号(socket)。socket本质是编程接口(API),对TCP/IP的封装,TCP/IP也要提供可供程序员做网络开发所用的接口,这就是Socket编程接口;HTTP是轿车,提供了封装或者显示数据的具体形式;Socket是发动机,提供了网络通信的能力。
Socket可以理解为两个插孔,中间连上一根线就可以打电话啦。
(在这个教程中视频讲师说socket是这个双向的连接,这个双向连接叫socket,和百度百科定义不太一样)
*介绍一下nodemon 会根据文件变化自动运行,而不用每次都用node命令运行。
npm install nodemon –g
nodemon 4.js
(然而并没有什么卵用,后面还是用的node命令来运行)
一个简单的server和client利用socket通讯示例:
server.js:
// 建立一个Socket服务端
const net = require('net');
var server = net.createServer((socket)=> {
console.log(socket); console.log(socket); console.log(socket);
//有客户端连接时触发
var clientIp = socket.address();
console.log(`${clientIp.address} connectioned`);
//监听数据
socket.on('data', (chunk) => {
process.stdout.write(' client> ');
console.log(chunk.toString());
socket.write('你说啥?');
});
});
var port = 2080;
server.listen(port, (err) => {
if (err) {
console.log('端口被占用了');
return false;
}
console.log(`服务器正常启动监听【${port}】端口`);
});
client.js:
//建立socket客户端
const net = require('net');
const socket = net.createConnection({ port: 2080 }, () => {
//'connect' listener
console.log('Already connected to server');
process.stdout.write(' client> ');
process.stdin.on('data', (chunk) => {
// 控制台输入回车
var msg = chunk.toString().trim();
socket.write(msg);
});
});
socket.on('data', (data) => {
process.stdout.write(' server> ');
console.log(data.toString());
process.stdout.write(' client> ');
//socket.end();
});
// socket.on('end', () => {
// console.log('disconnected from server');
// });
Usage:
node server.js
node client.js
因为只有Socket服务端启动了监听。Socket客户端没有启动监听,所以客户端之间不能互相通信。 上面例子完成了与服务器通信的简单功能,还可以通过指定一些特定的数据格式(协议),来实现更复杂的功能。
通过制定一个协议实现聊天室服务端
sever.js
// 建立一个Socket服务端
const net = require('net');
// 用于存储所有的连接
var clients = [];
var server = net.createServer((socket) => {
//socket对象push进去
clients.push(socket);
//广播方法
function broadcast(signal){
console.log(signal);
// 肯定有用户名和消息
var username = signal.from;
var message = signal.message;
// 我们要发给客户端的东西
var send = {
protocal:signal.protocal,
from:username,
message:signal.message
};
// 广播消息
clients.forEach(client => {
client.write(JSON.stringify(send));
});
}
socket.on('data', (chunk) => {
// chunk:broadcast|张三|弄啥咧!
// 协议 用户名 消息
// chunk:{'protocal':'broadcast','from':'张三','message':''}
// chunk:{'protocal':'p2p',from:'张三',to:'李四',message':''}
try {
var signal = JSON.parse(chunk.toString().trim());
var protocal = signal.protocal;
switch (protocal) {
case 'broadcast':
broadcast(signal);
break;
case 'p2p':
p2p(signal);
break;
case 'shake':
shake(signal);
break;
default:
socket.write('协议的protocal字段有问题!');
break;
}
// var username = signal.from;
// var message = signal.message;
}
catch (err) {
socket.write('数据格式有问题!');
throw err;
}
// 有任何客户端发消息都会触发
// var username = chunk.username;
// var message = chunk.messge;
// broadcast(username.message)
});
});
var port = 2080;
server.listen(port, (err) => {
if (err) {
console.log('端口被占用了');
return false;
}
console.log(`服务器正常启动监听【${port}】端口`);
});
client.js
//客户端
const net = require('net');
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.question('What is your name? ', (name) => {
name = name.trim();
if (!name) throw new Error('姓名有问题!');
//创建与服务端的连接
var socket = net.createConnection({ port: 2080 }, () => {
console.log(`Welcome ${socket.remoteAddress} ${name} to 2080 chatroom`);
//监听服务端发过来的数据
socket.on('data', (chunk) => {
try {
var signal = JSON.parse(chunk.toString().trim());
var protocal = signal.protocal;
switch (protocal) {
case 'broadcast':
console.log(`[broadcast]"${signal.from}"说了:${signal.message}`);
rl.prompt();
break;
default:
server.write('数据协议字段有问题');
break;
}
}
catch (err) {
socket.write('数据格式有问题!');
throw err;
}
});
rl.setPrompt(`${name}> `);
rl.prompt();
rl.on('line', (line) => {
// chunk:{'protocal':'broadcast','from':'张三','message':''}
var send = {
protocal: 'broadcast',
from: name,
message: line.toString().trim()
};
socket.write(JSON.stringify(send));
rl.prompt();
}).on('close', () => {
//console.log('Have a great day!');
//process.exit(0);
});
});
});
HTTP是超文本传输协议
上面的例子也是一种协议。
第五天
CMD的netstat命令
Netstat是在内核中访问网络连接状态及其相关信息的程序,它能提供TCP连接,TCP和UDP监听,进程内存管理的相关报告。
处理服务器异常
之前写的代码中,如果有客户机取消,那么所有都取消了,这个怎么处理呢?
答:很简单,只要在socket on data的监听事件中添加error监听事件,就不会全部取消了。
socket.on('error',(err)=>{
//console.log(err) 这个错误也可以不log出来,只要捕获就没事儿
});
最终写好的聊天程序,支持p2p协议。
server.js:
// 建立一个Socket服务端
const net = require('net');
// 用于存储所有的连接
var clients = [];
// var clients = [
// {
// name:..
// socket:..
// }
// ]
var signin = false;
var server = net.createServer((socket) => {
//建立了连接,代表登录
signin = true;
var logName;
//socket对象push进去
clients.push({ socket: socket });
socket.on('data', clientData).on('error', (err) => {
for (let i = 0; i < clients.length; i++) {
if (clients[i].socket == socket) {
logName = clients[i].name;
clients.splice(i, 1);
}
}
console.log(`${socket.remoteAddress} ${logName} 下线了 当前在线${clients.length}人`);
});
//广播方法
function broadcast(signal) {
console.log(signal);
// 肯定有用户名和消息
var username = signal.from;
var message = signal.message;
// 我们要发给客户端的东西
var send = {
protocal: signal.protocal,
from: username,
message: message
};
// 广播消息
clients.forEach( client => {
client.socket.write(JSON.stringify(send));
});
}
//点对点消息
function p2p(signal) {
console.log(signal);
// 肯定有用户名和消息
var username = signal.from;
var target = signal.to;
var message = signal.message;
// 我们要发给客户端的东西
var send = {
protocal: signal.protocal,
from: username,
message: message
};
// console.log(`${username}要发给${target}的内容是${message}`);
clients.forEach(client=>{
if(client.name == target)
{
client.socket.write(JSON.stringify(send));
}
})
// clients[target].write(JSON.stringify(send));
}
function clientData(chunk) {
//如果是第一次连接,就是在登录,在传入用户名
if (signin == true) {
//第一次回传入用户名
var JSONname = chunk.toString();
var Objname = JSON.parse(JSONname);
logName = Objname.name;
clients[clients.length - 1]['name'] = logName;
signin = false;
console.log(`${socket.remoteAddress} ${logName} 上线了,当前在线${clients.length}人`);
}
// chunk:broadcast|张三|弄啥咧!
// 协议 用户名 消息
// chunk:{'protocal':'broadcast','from':'张三','message':''}
// chunk:{'protocal':'p2p',from:'张三',to:'李四',message':''}
try {
var signal = JSON.parse(chunk.toString().trim());
var protocal = signal.protocal;
// console.log(chunk.toString());
switch (protocal) {
case 'broadcast':
//console.log('要broadcast了');
broadcast(signal);
break;
case 'p2p':
// console.log('要p2p了!');
p2p(signal);
break;
// case 'shake':
// shake(signal);
// break;
// default:
// socket.write('协议的protocal字段有问题!');
// break;
}
// var username = signal.from;
// var message = signal.message;
}
catch (err) {
socket.write('数据格式有问题!');
throw err;
}
// 有任何客户端发消息都会触发
// var username = chunk.username;
// var message = chunk.messge;
// broadcast(username.message)
};
});
var port = 2080;
server.listen(port, (err) => {
if (err) {
console.log('端口被占用了');
return false;
}
console.log(`服务器正常启动监听【${port}】端口`);
});
client.js:
//客户端
const net = require('net');
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.question('What is your name? ', (name) => {
name = name.trim();
if (!name) throw new Error('姓名有问题!');
//创建与服务端的连接
//还可以传入一个参数host:192.xx...
var socket = net.createConnection({ port: 2080 }, () => {
console.log(`Welcome ${socket.remoteAddress} ${name} to 2080 chatroom`);
//登录
socket.write(JSON.stringify({name:name}));
//监听服务端发过来的数据
socket.on('data', (chunk) => {
try {
var signal = JSON.parse(chunk.toString().trim());
var protocal = signal.protocal;
switch (protocal) {
case 'broadcast':
console.log(`[broadcast]"${signal.from}"说了:${signal.message}`);
rl.prompt();
break;
case 'p2p':
console.log(`[p2p]${signal.from}说了:${signal.message}`);
rl.prompt();
break;
default:
server.write('数据协议字段有问题');
break;
}
}
catch (err) {
socket.write('数据格式有问题!');
throw err;
}
});
rl.setPrompt(`${name}> `);
rl.prompt();
rl.on('line', (line) => {
line = line.toString().trim();
var temp = line.split(':');
var send;
if (temp.length === 2) {
//点对点消息
//console.log('这是一个点对点消息');
send = {
protocal: 'p2p',
from: name,
to: temp[0],
message: temp[1]
};
} else {
//广播消息
// chunk:{'protocal':'broadcast','from':'张三','message':''}
send = {
protocal: 'broadcast',
from: name,
message: line.toString().trim()
};
}
socket.write(JSON.stringify(send));
rl.prompt();
}).on('close', () => {
console.log('Have a great day!');
process.exit(0);
});
});
});
usage
node server.js
node client.js
总结一下,复习,自己写一下简单的net模块使用流程:
server.js:
const net = require('net');
var server = net.createServer((socket) => {
console.log(`${socket.remoteAddress}连接上了我的服务器`);
socket.on('data', (chunk) => {
console.log(chunk.toString());
}).on('err', (err) => { console.log(err) });
});
server.listen({ port: 2888 }, (err) => {
if (err) throw err;
console.log('在2888端口创立了服务器');
})
client.js:
const net = require('net');
const readline = require('readline');
const rl = readline.createInterface({
input:process.stdin,
output:process.stdout
});
rl.setPrompt('> ');
rl.prompt();
rl.question('what is your name?',(name)=>{
name = name.toString().trim();
var socket = net.createConnection({port:2888},(err)=>{
if(err) throw err;
console.log('连接上服务器了');
rl.prompt();
socket.write(name);
rl.on('line',(line)=>{
socket.write(line.toString());
rl.prompt();
});
});
});
浏览器的作用
HTTP也是一种协议,
浏览器的本质作用:
将用户输入的URL封装为一个请求报文。
建立与服务端的Socket链接
将刚刚封装好的请求报文通过socket发送到服务端。
socket.write(JSON.stringify(send));
。。。。。。。。
接收到服务端返回的响应报文。
解析响应报文(类似于我们写的那个JSON.parse…)
渲染内容到页面中(类似于我们的console.log(msg))
浏览器就是一个socket客户端
CR+LF = 有一个回车符,有一个换行符
URI(统一资源标识符)是URL(统一资源定位符)的超集
统一资源标志符URI就是在某一规则下能把一个资源独一无二地标识出来。
那统一资源定位符URL是什么呢。也拿人做例子然后跟HTTP的URL做类比,就可以有:
动物住址协议://地球/中国/浙江省/杭州市/西湖区/某大学/14号宿舍楼/525号寝/张三.人
可以看到,这个字符串同样标识出了唯一的一个人,起到了URI的作用,所以URL是URI的子集。URL是以描述人的位置来唯一确定一个人的。
在上文我们用身份证号也可以唯一确定一个人。对于这个在杭州的张三,我们也可以用:
身份证号:1234567
http模块发送
const http = require('http');
const fs = require('fs');
//利用node做一个客户端 请求m.baidu.com
http.get('http://m.baidu.com',(response)=>{
var rawContent = '';
response.on('data',(chunk)=>{
rawContent += chunk.toString();
});
response.on('end',()=>{
//后面可以做爬虫哟
fs.writeFile('./1.txt',rawContent,(err)=>{
if(err) throw err;
console.log('出来了');
})
console.log(response.headers['content-length']);
//下面是不包含头的长度
console.log(rawContent.length);
});
});
看一下get方法:
返回值是一个叫IncomingMessage的类
IncomingMessage继承于流。
所以会有on(‘data’)和on(‘end’)事件。