一、Buffer

什么是Buffer?


  1. 缓冲区Buffer是暂时存放输入/输出数据的一段内存
  2. JS语言没有二进制数据类型,而在处理TCP和文件流的时候,必须要处理二进制数据
  3. NodeJS提供了一个Buffer对象来提供对二进制数据的操作
  4. Buffer是一个表示固定内存分配的全局对象,也就是说要放到缓存区中的字节数需要提前确定
  5. Buffer就好比由一个8位字节元素组成的数组,可以有效的在JavasScript中表示二进制数据

什么是字节?


  1. 字节(Byte)是计算机存储时的一种计量单位,一个字节等于8位二进制数
  2. 一个位就代表一个0或1,每8个位(bit)组成一个字节(Byte)
  3. 字节是通过网络传输信息的单位
  4. 一个字节最大值十进制数是255​​2^8-1​
  5. 图示
    Node.js:Node核心模块_数据
  6. 单位
    8位 = 1字节
    1024字节 = 1K
    1024K = 1M
    1024M = 1G
    1024G = 1T

定义Buffer的3种方式


  1. 通过长度定义buffer​​// 创建一个长度为 10、且用 0 填充的 Buffer const buf1 = Buffer.alloc(10); // 创建一个长度为 10、且用 1 填充的 Buffer const buf2 = Buffer.alloc(10, 1); // 创建一个长度为 10、且未初始化的 Buffer const buf3 = Buffer.allocUnsafe(10); ​
  2. 通过数组定义buffer​​// 创建一个包含 [0x1, 0x2, 0x3] 的 Buffer const buf4 = Buffer.from([1, 2, 3]); ​​ 正常情况下为0-255之间
  3. 字符串创建​​const buf5 = Buffer.from('撩课教育'); ​

buffer常用方法



fill方法

buf.fill(value[, offset[, end]][, encoding])

手动初始化,一般用于将buffer内容清0
buffer.fill(0);



write方法

buf.write(string[, offset[, length]][, encoding])

buffer.write(‘撩’,0,3,‘utf8’);
buffer.write(‘课’,3,3,‘utf8’);
Node.js:Node核心模块_数据_02



writeInt8()、readInt8()以八进制读写

let buf = new Buffer(4);
buf.writeInt8(0,0);
buf.writeInt8(16,1);
buf.writeInt8(32,2);
buf.writeInt8(48,3);//16*3*/
console.log(buf);
console.log(buf.readInt8(0));
console.log(buf.readInt8(1));
console.log(buf.readInt8(2));
console.log(buf.readInt8(3));


isBuffer:判断是否是buffer

Buffer.isBuffer()


length :获取字节长度(显示是字符串所代表buffer的长度)

Buffer.byteLength("撩课");
buffer.length;


Base64


  1. Base64是网络上最常见的用于传输8Bit字节码的编码方式之一
  2. Base64就是一种基于64个可打印字符来表示二进制数据的方法
  3. Base64要求把每三个8Bit的字节转换为四个6Bit的字节(3​8 = 4​6 = 24)
  4. 然后把6Bit再添两位高位0,组成四个8Bit的字节,也就是说,转换后的字符串理论上将要比原来的长1/3

二、FS模块

什么是fs模块?


  1. 在Node.js中,使用fs模块来实现所有有关文件及目录的创建、写入及删除操作
  2. 在fs模块中,所有的方法都分为同步和异步两种实现
  3. 具有sync后缀的方法为同步方法,不具有sync后缀的方法为异步方法
  4. 在进行文件操作的时候,首先需要引入文件系统模块。
    ​let fs = require('fs ');​
  5. 通过​​__dirname​​获取当前文件夹目录

读取文件


  1. 同步读取
    ​fs.readFileSync(path[, options]) ​​举例:
    ​let fd = fs.readFileSync(__dirname + '/source/a.txt'); ​
  2. 异步读取
    ​fs.readFile(path[, options], callback) ​​举例:
    ​fs.readFile(__dirname + '/source/a.txt', (err, data)=>{ if(!err){ console.log(data); console.log(data.toString()); } }); ​

写入文件



同步写入

fs.writeFileSync(file, data[, options])

举例:

let fs = require('fs');

// 1. 打开文件
let fd = fs.openSync(__dirname + '/source/b.txt', 'w');
console.log(fd);

// 2. 同步写入内容
fs.writeFileSync(fd, '哈哈哈!');

// 3. 保存并退出
fs.closeSync(fd);

// 4. 后续操作
console.log('后续的操作');


异步写入

fs.writeFile(file, data[, options], callback)

举例:

let fs = require('fs');

// 1. 打开文件
fs.open(__dirname + '/source/c.txt', 'a', (err, fd) => {
if (!err) {
// 2. 写入文件
fs.writeFile(fd, '小撩漂亮!哈哈哈哈' + Date.now() + '\n', (err)=>{
if(!err){
console.log('写入文件成功');
}else {
throw err;
}
});

// 3. 关闭文件
fs.close(fd, (err)=>{
if(!err){
console.log('文件已经保存并关闭!');
}else {
throw err;
}
})
}
});

// 后续操作
console.log('后续操作');

options encoding
flag flag 默认 = ‘w’
图示
Node.js:Node核心模块_字符串_03
助记
Node.js:Node核心模块_服务器_04
mode 读写权限,默认为0666



追加文件

fs.appendFile(file, data[, options], callback)

操作

fs.appendFile('./01.txt',Date.now()+'\n',function(){
console.log('ok');
})


拷贝文件

function copy(src,target){
fs.readFile(src,function(err,data){
fs.writeFile(target,data);
})
}


读取、写入图片

fs.readFile('C:/Users/yejianhua/Desktop/girl.jpg', (err, data)=>{
if(!err){
// 2. 写入图片文件
fs.writeFile(__dirname + '/source/girl.jpg', data, (err)=>{
if(err) throw err;
console.log('写入成功');
});
}
});

读取、写入音频

fs.readFile('C:/Users/yejianhua/Desktop/lk.mp4', (err, data)=>{
console.log(data);
if(!err){
// 2. 写入 文件
fs.writeFile(__dirname + '/source/lk.mp4', data, (err)=>{
if(err) throw err;
console.log('写入成功');
});
}
});

三、Stream

什么是流?


  1. 流是一组有序的,有起点和终点的字节数据传输手段
  2. 它不关心文件的整体内容,只关注是否从文件中读到了数据,以及读到数据之后的处理
  3. 流是一个抽象接口,被 Node 中的很多对象所实现;比如HTTP 服务器request和response对象都是流。

可读流createReadStream

概念

实现了stream.Readable接口的对象,将对象数据读取为流数据,当监听data事件后,开始发射数据

fs.createReadStream = function(path, options) {
return new ReadStream(path, options);
};
util.inherits(ReadStream, Readable);

创建可读流

let rs = fs.createReadStream(path,[options]);

参数

path读取文件的路径

options

flags打开文件要做的操作,默认为’r’

encoding默认为null

start开始读取的索引位置

end结束读取的索引位置(包括结束位置)

监听data事件

流切换到流动模式,数据会被尽可能快的读出

rs.on('data', function (data) {
console.log(data);
});

监听end事件

该事件会在读完数据后被触发

rs.on('end', function () {
console.log('读取完成');
});

监听error事件

rs.on('error', function (err) {
console.log(err);
});

监听open事件

rs.on('open', function () {
console.log(err);
});

监听close事件

rs.on('close', function () {
console.log(err);
});

暂停和恢复触发data

通过pause()方法和resume()方法

rs.on('data', function (data) {
rs.pause();
console.log(data);
});
setTimeout(function () {
rs.resume();
}, 2000);

可写流createWriteStream

概念

实现了stream.Writable接口的对象来将流数据写入到对象中

fs.createWriteStream = function(path, options) {
return new WriteStream(path, options);
};
util.inherits(WriteStream, Writable);

创建可写流

let ws = fs.createWriteStream(path,[options]);

参数

path写入的文件路径

options


  1. flags打开文件要做的操作,默认为’w’
  2. encoding默认为utf8
  3. highWaterMark写入缓存区的默认大小16kb

write方法

ws.write(chunk,[encoding],[callback]);

  1. chunk写入的数据buffer/string
  2. encoding编码格式chunk为字符串时有用,可选
  3. callback 写入成功后的回调

监听end事件

ws.end(chunk,[encoding],[callback]);

表明接下来没有数据要被写入 Writable 通过传入可选的 chunk 和 encoding 参数,可以在关闭流之前再写入一段数据 如果传入了可选的 callback 函数,它将作为 ‘finish’ 事件的回调函数

finish方法

在调用了 stream.end() 方法,且缓冲区数据都已经传给底层系统之后, ‘finish’ 事件将被触发。

var writer = fs.createWriteStream('./2.txt');
for (let i = 0; i < 100; i++) {
writer.write(`hello, ${i}!\n`);
}
writer.end('结束\n');
writer.on('finish', () => {
console.error('所有的写入已经完成!');
});

drain方法


  1. 当一个流不处在 drain 的状态, 对 write() 的调用会缓存数据块, 并且返回 false
  2. 一旦所有当前所有缓存的数据块都排空了(被操作系统接受来进行输出), 那么 ‘drain’ 事件就会被触发
  3. 建议, 一旦 write() 返回 false, 在 ‘drain’ 事件触发前, 不能写入任何数据块

读入、写入流案例

let fs = require('fs');

// 1. 创建读写流
let rs = fs.createReadStream('C:/Users/yejianhua/Desktop/lk1.mp4');
let ws = fs.createWriteStream(__dirname + '/source/lk888.mp4');

// 2. 监听流的打开
rs.once('open', ()=>{
console.log('写入流已经打开了~');
});

ws.once('open', ()=>{
console.log('读入流已经打开了~');
});

// 3. 监听data
rs.on('data', (data)=>{
console.log(data);
ws.write(data);
});

// 4. 监听流的关闭
rs.once('end', ()=>{
console.log('读入数据已经完成~');
});

rs.once('close', ()=>{
console.log('读入数据通道关闭~');
});

pipe方法的使用

pipe用法

readStream.pipe(writeStream);

let from = fs.createReadStream('./a.txt');
let to = fs.createWriteStream('./b.txt');
from.pipe(to);

案例:

let fs = require('fs');

// 1. 创建读写流
let rs = fs.createReadStream('C:/Users/yejianhua/Desktop/lk1.mp4');
let ws = fs.createWriteStream(__dirname + '/source/lk999.mp4');

// 2. 创建管道
rs.pipe(ws);

pipe方法的原理

let fs = require('fs');
let rs = fs.createReadStream("C:/Users/yejianhua/Desktop/lk.mp4");
let ws = fs.createWriteStream(__dirname + "/source/new.mp4");
rs.on('data', (data)=> {
let flag = ws.write(data);
console.log(flag);
if(!flag)
rs.pause();
});

ws.on('drain', ()=> {
rs.resume();
});

rs.on('end', ()=> {
ws.end();
});

四、 HTTP模块

HTTP协议和TCP协议?

Node.js:Node核心模块_数据_05

长链接

Node.js:Node核心模块_数据_06

Node.js:Node核心模块_数据_07

管道化

Node.js:Node核心模块_服务器_08

URI和URL

URI

URI(Uniform Resource Identifier)是统一资源标识符,在某个规则下能把这个资源独一无二标示出来,比如人的身份证号

使用特点


  1. Uniform 不用根据上下文来识别资源指定的访问方式
  2. Resource 可以标识的任何东西
  3. Identifier 表示可标识的对象

URL

统一资源定位符,表示资源的地点,URL时使用浏览器访问WEB页面时需要输入的网页地址

使用特点


  1. Uniform 不用根据上下文来识别资源指定的访问方式
  2. Resource 可以标识的任何东西
  3. Location 定位

格式

Node.js:Node核心模块_数据_09

具体组成:协议类型、登录信息、服务器地址、服务器端口号、带层次的文件路径、查询字符串、片段标识符

HTTP

概念


  1. 请求的一方叫客户端,响应的一方叫服务器端
  2. 通过请求和响应达成通信
  3. HTTP是一种不保存状态的协议

请求报文


  1. 请求行
    主要方法:GET 获取资源、POST 向服务器端发送数据,传输实体、TRACE 追踪路径
    协议/版本号
    URL
  2. 请求头
    ① 通用首部(General Header)
    ② 请求首部(Request Header)
    ③ 响应首部(Response Header)
    ④ 实体首部(Entity Header Fields)
  3. 请求体

响应报文


  1. 响应行
  2. 响应头
  3. 响应体

状态码


  1. 概念
    状态码负责表示客户端请求的返回结果、标记服务器端是否正常、通知出现的错误
  2. 状态码类别
    Node.js:Node核心模块_字符串_10
  3. 2XX 成功
    200(OK 客户端发过来的数据被正常处理
    204(Not Content 正常响应,没有实体
    206(Partial Content 范围请求,返回部分数据,响应报文中由Content-Range指定实体内容
  4. 3XX 重定向
    301(Moved Permanently) 永久重定向
    302(Found) 临时重定向,规范要求方法名不变,但是都会改变
    303(See Other) 和302类似,但必须用GET方法
    304(Not Modified) 状态未改变 配合(If-Match、If-Modified-Since、If-None_Match、If-Range、If-Unmodified-Since)
    307(Temporary Redirect) 临时重定向,不该改变请求方法
  5. 4XX 客户端错误
    400(Bad Request) 请求报文语法错误
    401 (unauthorized) 需要认证
    403(Forbidden) 服务器拒绝访问对应的资源
    404(Not Found) 服务器上无法找到资源
  6. 5XX 服务器端错误
    500(Internal Server Error)服务器故障
    503(Service Unavailable) 服务器处于超负载或正在停机维护

HTTP服务器

概念

HTTP全称是超文本传输协议,构建于TCP之上,属于应用层协议

创建HTTP服务器

let server  = http.createServer([requestListener]);
server.on('request',requestListener);

requestListener 当服务器收到客户端的连接后执行的处理

启动HTTP服务器

server.listen(port,[host],[backlog],[callback]);
server.on('listening',callback);

1)参数

port 监听的端口号

host 监听的地址

backlog 指定位于等待队列中的客户端连接数

2)实例

let http = require('http');
let server = http.createServer(function(req,res){
}).listen(3000,'127.0.0.1',function(){console.log('服务器端开始监听!')});

关闭HTTP服务器

server.close();
server.on('close',function(){});

1)实例

let http = require('http');
let server = http.createServer(function(req,res){
});
server.on('close',function(){
console.log('服务器关闭');
});
server.listen(3000,'127.0.0.1',function(){
console.log('服务器端开始监听!')
server.close();
});

监听服务器错误

实例

server.on('error',function(){
if(e.code == 'EADDRINUSE'){
console.log('端口号已经被占用!);
}
});

setTimeout


  1. 概念:设置超时时间,超时后不可再复用已经建立的连接,需要发请求需要重新建立连接。默认超时时间时2分钟
  2. 案例
    ​server.setTimeout(msecs,callback); server.setTimeout(1000, ()=>{ console.log('设置超时时间为1s'); }); server.on('timeout',function(){ console.log('连接已经超时'); }); ​

获取客户端请求信息



request
method 请求的方法
url 请求的路径
headers 请求头对象
httpVersion 客户端的http版本
socket 监听客户端请求的socket对象



实操

let server = http.createServer((req, res) => {
if (req.url !== '/favicon.ico') {
let out = fs.createWriteStream(path.join(__dirname, 'request.log'));
out.write('1) method: ' + req.method + '\n');
out.write('2) url: ' + req.url + '\n');
out.write('3) headers: ' + JSON.stringify(req.headers) + '\n');
out.write('4) httpVersion: ' + req.httpVersion + '\n');
}
}).listen(8080, '127.0.0.1');


url模块

格式

​url.parse(urlStr,[parseQueryString]);​

Node.js:Node核心模块_数据_11

模块属性


  1. href 被转换的原URL字符串
  2. protocal 客户端发出请求时使用的协议
  3. slashes 在协议与路径之间是否使用了//分隔符
  4. host URL字符串中的完整地址和端口号
  5. auth URL字符串中的认证部分
  6. hostname URL字符串中的完整地址
  7. port URL字符串中的端口号
  8. pathname URL字符串的路径,不包含查询字符串
  9. search 查询字符串,包含?
  10. path 路径,包含查询字符串
  11. query 查询字符串,不包含起始字符串?
  12. hash 散列字符串,包含#

发送服务器响应流

概念

​http.ServerResponse对象表示响应对象​

writeHead

response.writeHead(statusCode,[reasonPhrase],[headers]);

常用属性


  1. content-type 内容类型
  2. location 将客户端重定向到另外一个URL地址
  3. content-disposition 指定一个被下载的文件名
  4. content-length 服务器响应内容的字节数
  5. set-cookie 在客户端创建Cookie
  6. content-encoding 指定服务器响应内容的编码方式
  7. cache-cache 开启缓存机制
  8. expires 用于制定缓存过期时间
  9. etag 指定当服务器响应内容没有变化不重新下载数据

Header

​设置、获取和删除Header​

常用属性


  1. response.setHeader(‘Content-Type’,‘text/html;charset=utf-8’);
  2. response.getHeader(‘Content-Type’);
  3. response.removeHeader(‘Content-Type’);
  4. response.headersSent

判断响应头是否已经发送

const http = require('http');

http.createServer((req, res)=>{
console.log(res.headersSent ? '响应头已经发送' : '响应头未发送');
// 设置隐式响应头
res.setHeader('Content-Type', 'text/html;charset=utf-8');
res.writeHead(200, 'ok');
res.write('<h1>Hello, ItLike</h1>');
res.write('<h1>Hello, ItLike</h1>');
res.write('<h1>Hello, ItLike</h1>');
res.write('<h1>Hello, ItLike</h1>');
// 本次响应结束
res.end('<h1>撩课学院</h1>');
console.log(res.headersSent ? '响应头已经发送' : '响应头未发送');
}).listen(3000, '127.0.0.1', ()=>{
console.log('服务器已经启动~')
});

write

​可以使用write方法发送响应内容​

常用操作


  1. response.write(chunk,[encoding]);
  2. response.end([chunk],[encoding]);

querystring

​querystring模块用来转换URL字符串和URL中的查询字符串​


  1. parse方法用来把字符串转换成对象
    querystring.parse(str,[sep],[eq],[options]);
  2. stringify方法用来把对象转换成字符串
    querystring.stringify(obj,[sep],[eq]);

post请求

服务器端

const http = require('http');
const queryString = require('querystring');
const util = require('util');

http.createServer((req, res)=>{
let postData;
// post请求, 得做事件监听
req.on('data', (data)=>{
postData += data;
});

// 监听数据接收完毕
req.on('end', ()=>{
postData = queryString.parse(postData);
console.log(util.inspect(postData));
res.end('数据接收成功!');
});

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

客户端

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<form action="http://localhost:3000/" method="post">
<label>用户名: <input type="text" name="user"></label><br>
<label>密 码: <input type="password" name="pwd"></label><br>
<input type="submit" value="提交">
</form>
</body>
</html>

补充

Node.js没有web容器

没有根目录, 不能像PHP, JavaWeb通过切换目录结构切换页面; 所有的页面资源都得通过路径配置

Node.js:Node核心模块_服务器_12

在Node中, 采用fs模块读入文件, 并手动配置路由

演示


  1. 演示1:加载静态界面
    Node.js:Node核心模块_字符串_13
  2. 演示2:加载图片,css等静态资源
    Node.js:Node核心模块_字符串_14

ejs的使用

​​

app.js

const http = require('http');
const fs = require('fs');
const ejs = require('ejs');
const path = require('path');

// 1. 创建服务器
http.createServer((req, res)=>{
// 1.1 读取文件
readDataJson((jsonData)=>{

// 1.2 读取模板信息
fs.readFile(path.join(__dirname, 'view/list.ejs'), (err, data)=>{
if(err) throw err;

// 实例化模板引擎
let ejsList = data.toString();
let tempList = ejs.render(ejsList, jsonData);

// 1.3 响应给客户端
res.writeHead(200, {'Content-Type': 'text/html;charset=utf-8'});
res.end(tempList);
});
});
}).listen(3000);

let readDataJson = (callback)=>{
fs.readFile(path.join(__dirname, 'model/data.json'), (err, data)=>{
if(err) throw err;
let jsonData = JSON.parse(data);
callback && callback(jsonData);
});
};

list.ejs

<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>百度风云排行版</title>
<style>
* {
margin: 0;
padding: 0;
list-style: none;
}

#panel {
width: 500px;
border: 1px solid #c6c8ca;
margin: 100px auto;
}

#panel_header {
display: flex;
justify-content: space-around;
border-bottom: 1px solid #ccc;
line-height: 44px;
color: #777;
}

#panel_body > li {
display: flex;
flex-direction: row;
justify-content: space-between;
line-height: 44px;
border-bottom: 1px solid #e8e8e8;
}

.c-icon {
background: url(https://ss1.bdstatic.com/5eN1bjq8AAUYm2zgoY3K/r/www/cache/static/protocol/https/global/img/icons_5859e57.png) no-repeat 0 0;
display: inline-block;
width: 14px;
height: 14px;
vertical-align: text-bottom;
font-style: normal;
overflow: hidden;
}

.opr-toplist-st {
margin-bottom: 2px;
}

.c-icon-up {
background-position: -720px -168px;
}

.c-icon-down {
background-position: -744px -168px;
}

.left {
margin-left: 10px;
display: flex;
flex-direction: row;
align-items: center;
}

.left .no {
display: flex;
justify-content: center;
align-items: center;
width: 18px;
height: 18px;
background-color: red;
color: #fff;
margin: 5px;
}

.right {
margin-right: 10px;
}

#panel_footer {
display: flex;
justify-content: flex-end;
margin: 10px;
color: #777;
font-size: 15px;
}
</style>
</head>
<body>
<section id="panel">
<div id="panel_header">
<span>排名</span>
<span>搜索指数</span>
</div>
<ul id="panel_body">
<% for(var i = 0; i < lists.length; i++) { %>
<li>
<div class="left">
<span class="no" style="background: <%= i > 2 ? 'gray' : 'red' %>;">
<%= i + 1 %>
</span>
<span>
<%= lists[i].title %>
</span>
</div>
<div class="right">
<span>
<%= lists[i].count %>
</span>
<% if(lists[i].up === 1){ %>
<i class="opr-toplist-st c-icon c-icon-up"></i>
<% }else { %>
<i class="opr-toplist-st c-icon c-icon-down"></i>
<% } %>
</div>
</li>
<% } %>
</ul>
<div id="panel_footer">
<span style="margin-right: 5px">来源:</span>
<span>
<%= source %>
</span>
</div>
</section>
</body>
</html>