Node.js入门学习笔记

389 阅读22分钟

Node.js入门学习笔记

一、Node.js概述

1、什么是Node.js

  • Node.js是一个基于Chrome V8 引擎JavaScript运行环境
  • Node.js使用了一个时间驱动、非阻塞式I/O的模型,使其轻量又高效

简单说Node.js类似于在Chrome里写JavaScript控制浏览器,Node.js用类似的方式,控制整个计算机。

2、Node.js用来做什么

  • Web服务
  • 构造工作流
  • 开发工具
  • 游戏
  • 客户端应用

3、Node.js优势

  1. 利用事件循环机制对请求进行处理的
  2. 大量使用异步和回调方式
  3. Node.js的主线程是单线程,主线程接受到请求并放到队列中使用事件循环机制处理

二、Node.js入门使用

1、环境搭建

推荐使用nvm安装node.js,方便以后的版本管理

2、简单服务器搭建

2.1、createServer方法

//导入http模块
const http = require('http');
//创建一个服务器,有一个回调函数来执行请求
const server = http.createServer(function(request,response){
    //设置响应头
    response.writeHead(200,{'Content-Type':'text/plain'});
    //将字符串返回客户端
    response.end('hello Node.js');
});
//监听端口号
server.listen(8090,'localhost');
console.log('node Server run');

控制台cd到该文件的目录,运行 node app.js(项目名) 运行效果 客户端

控制台

http模块其他事件

//一旦启用监听,就回调这个事件
server.on('listening'function ({
    console.log('service listening');
    //关闭服务
    //server.close();
})
//当客户端和服务器建立了链接就会回调这个事件
server.on('connection',function(){
    console.log('client connection')
});
//当服务器关闭回调的事件
server.on('close',function(){
    console.log('server close');
});

2.2、请求监听方法

const http = require('http');
//通过请求监听创建服务
const httpServer = new http.Server();
httpServer.on('request',function (request,response) {
    //设置响应头
    response.writeHead(200, {'Content-Type': 'text/plain'});
    //将字符串返回客户端
    response.end('hello Node.js');
});

httpServer.listen(8090,function () {
    console.log('node Server 运行了');
});

2.3、获取客户端请求数据

const http = require('http');
const server = http.createServer(function (request, response) {
    //获取客户端请求数据
    let data = '';
    //data事件:监听请求的事件,并获取数据
    request.on('data', (chunk) => {
        data += chunk;
    });
    //end事件:请求全部发送完再执行回调
    request.on('end', () => {
        //获取方法
        let method = request.method;
        //获取请求头
        let headers = JSON.stringify(request.headers);
        //获取版本
        let httpVersion = request.httpVersion;
        //获取url
        let url = request.url;

        //返回客户端
        response.writeHead(200, {'Context-Type': 'text/html'});
        let responseData = method + ',' + headers + ',' + httpVersion + ',' + url;
        response.end(responseData);
    });
});

server.listen(8090,function () {
    console.log('node Server 运行了');
});

运行结果

可以看出node.js写法大多都是以事件为中心,针对事件编写回调方法。

3、使用http模块创建客户端并接受服务端发送的数据

服务端js

const http = require('http');
const server = http.createServer(function (request, response) {
    response.writeHead(200, {'Content-Type': 'text/plain'});
    //将字符串返回客户端
    response.end('hello Node.js');
});
server.listen(8090, 'localhost');

客户端js

const http = require('http');
let strData = '';
//向客户端发送请求,第一个参数是服务器的信息,第二个参数是发送请求后的回调函数
http.request({
    'host': 'localhost',
    'port': '8090',
    'method': 'get'
}, (response) => {
    //服务器处理返回结果
    response.on('data', (chunk) => {
        strData += chunk;
    });

    response.on('end', () => {
        console.log(strData);
    });
}).end();//请求结束,此方法必须有

//get请求也可以这样写
/*
http.get({
    'host': 'localhost',
    'port': '8090'
}, (response) => {
    //服务器处理返回结果
    response.on('data', (chunk) => {
        strData += chunk;
    });

    response.on('end', () => {
        console.log(strData);
    });
}).end();
*/

返回结果

三、Node常用内置模块

1、url模块

url模块用于处理与解析 URL

1.1、url转为json

const http = require('url');
//定义一个url
const urlStr = 'https://www.test.com?id=123';
//解析url
let urlParse = http.parse(urlStr);
console.log(urlParse);

控制台打印

1.2、json转url

const http = require('url');
const urlObject = {
    protocol: 'https:',
    slashes: true,
    auth: null,
    host: 'www.test.com',
    port: null,
    hostname: 'www.test.com',
    hash: null,
    search: '?id=123',  //搜索字符串
    query: 'id=123',
    pathname: '/',
    path: '/?id=123',
    href: 'https://www.test.com/?id=123'
};
let realUrl = http.format(urlObject);
console.log(realUrl);

控制台结果

2、querystring模块

querystring 模块提供用于解析和格式化 URL 查询字符串的实用工具。

2.1、字符串转json对象

const querystring = require('querystring');
const str = 'name=abc&age=20';
const parsedUrlQuery = querystring.parse(str);
console.log(parsedUrlQuery);

控制台输出

2.2、json对象转字符串

const querystring = require('querystring');
const str ={
    name: 'abc',
    age: '20' }
const obj = querystring.stringify(str);
console.log(obj);

控制台结果

3、Util模块

util模块包含了很多开发中经常使用的工具。

3.1、将对象转换为字符串

const util = require('util');
const str = {
    name: 'jack',
    age: 55,
    isMan: true,
    getAge: () => {
        return this.age;
    }
};
//inspect()第一个参数是对象,第二个参数可以定制信息
const result = util.inspect(str,{
    //定制颜色信息
    'colors' : true
});
console.log(result);

结果,根据不同的类型显示不同的颜色

4、path模块

path 模块提供了一些用于处理文件与目录的路径的实用工具。

4.1、构建一个完整路径

const path = require('path');
const pathStr = path.join(__dirname,'myHome','hello.js');
console.log(pathStr);

其中 __dirname代表当前项目的绝对路径

运行结果

4.2、获取文件扩展名

当文件有扩展名就输出扩展名,没有就输出空字符串

const path = require('path');
const extname = path.extname(path.join(__dirname,'myHome','hello.js'));
console.log(extname);

运行结果

4.3、获取一个文件路径的完整信息

const path = require('path');
const filePath = '/abc/efg/hig/aa.mp4';
let parsedPath = path.parse(filePath);
console.log(parsedPath);

运行结果

5、DNS模块

DNS模块用来解析域名或ip地址

5.1、解析域名

const dns = require('dns');
const path = 'www.baidu.com';
//解析域名resolve()错误信息和域名地址
dns.resolve(path, (error, address) => {
    if (error) {
        console.log(error);
    }
    console.log(address);
});

解析成功结果

失败结果

5.2、反向解析

const dns = require('dns');
dns.reverse('114.114.114.114',(error,domain) => {
    console.log(domain);
});

运行结果

四、使用第三方模块

1、导出自定义的模块并使用

myMoudle.js自定义模块的js文件

var myInfo = {
    name: 'zhangsan',
    age: 20
};

var myFunction = (inputNum) =>{
    return inputNum + 5;
};
//导出方法和属性:exports.自定义名字 = 需要导出的对象
exports.myInfo = myInfo;
exports.myFunction = myFunction;

引用自定义模块的js

const myModule = require('./myModule');
console.log(myModule.myInfo);
console.log(myModule.myFunction(5));

输出结果

2、导入导出包含类的自定义模块

//定义类
class UserService {
    login(username,password) {
        console.log('UserService 执行了');
        console.log('username=' + username + 'password=' + password);
        return true;
    }
}
//导出整体
module.exports = new UserService();

五、NPM(node包管理器)

1、node模块加载机制

1.1、node配置文件package.json

使用npm的init命令生成package.json

{
  "name": "temp",
  "version": "1.0.0",
  "description": "",
  "main": "app.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}

可以通过package.json的main属性定义的入口,以便其他js来直接访问该模块,要求路径必须如下

此时就可以直接在app.js中使用myModule.js模块了 其中myModule下的ackage.json为

{
  "name": "mymodule",
  "version": "1.0.0",
  "description": "",
  "main": "myModule.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

在app.js调用

const myModule = require('mymodule');

2、使用第三方模块

2.1、查找模块

例如: 使用npm search uuid命令搜索uuid模块,npm view xxx 查看详细信息 或者在https://www.npmjs.com/ npm的官方中央仓库搜索想要的模块

2.2、安装删除和其他操作模块

使用npm install uuid安装 安装后的目录

如果没有node_modules目录则自动生成该目录 但这种安装方式为局部安装,只有当前工程可以使用该模块 全局安装 npm install uuid -g 为全局安装,在任何工程中都可以使用该模块

使用 npm list 查看当前工程安装的模块 npm list -g 查看全局安装的模块 npm uninstall uuid 删除模块

注意:这里的node_moudules目录不需要上传到版本库上,只要package.json中有该模块的依赖说明,就会自动下载需要的模块,类似于Java的maven。使用npm i命令就可以把所依赖的模块下载下来。

五、node操作文件系统

1、读取文件

异步版

//导入操作文件系统的模块fs
const fs = require('fs');
//readFile('读取文件的路径','以哪种编码格式读取',回调函数(第一个参数为读取错误的信息(固定的),第二个参数为读取的信息))
fs.readFile('test.txt','utf8',(error,data) =>{
    if (error) {
        console.log('error' + error);
    } else {
        console.log(data);
    }
});

对于fs模块中大多数api来说,node都提供了相同功能的连个版本,同步版和异步版。推荐使用异步版。同步版的命名会在异步版后加 Sync。 同步概念:当一个流程执行到同步的方法后必须等这个同步的方法全部执行完才会接着执行下面的流程。 异步概念:当一个流程执行到异步方法时,会直接执行下面的流程,不必等该方法执行完毕。

//同步版本,必须有异常处理
try {
    const  data = fs.readFileSync('test.txt', 'utf8');
    console.log(data);
} catch (e) {
    console.log(e);
}

2、写入文件

//导入操作文件系统的模块fs
const fs = require('fs');
//watchFile(被写入的文件,写入的内容,回调函数)
//如果没有该文件,会自动创建,该方法会覆盖文件内的内容
fs.writeFile('abc.txt','hello',error => {
    if (error) {
        console.log(error);
    } else {
        console.log("success");
    }
});
//追加
// writeFile(被写入的文件,写入内容,flag对象:文件系统标志,回调)
fs.writeFile('abc.txt', ' world', {flag: 'a'},error => {
    if (error) {
        console.log(error);
    } else {
        console.log("success");
    }
});

其他文件系统标志

append追加方式

const fs = require('fs');
//追加
fs.appendFile('world.txt','hello','utf8',error => {
    if (error) {
        throw error;
    }
    console.log('success');
});

3、打开关闭文件

//导入操作文件系统的模块fs
const fs = require('fs');
//打开文件 fd:文件标识符
fs.open('abc.txt','r+',(error,fd) => {
    if (error) {
        return console.error(error);
    } else {
        console.log('open success');
    }

    //执行关闭
    //回调函数中的回调
    fs.close(fd,error => {
        if (error) {
            console.log(error);
        } else {
            console.log('close success')
        }
    });
});

4、文件删除

//导入操作文件系统的模块fs
const fs = require('fs');
//文件删除
fs.unlink('abc.txt',error => {
    if (error) {
        throw error;
    } else {
        console.log("success");
    }
});

5、重命名

//导入操作文件系统的模块fs
const fs = require('fs');
//重命名
fs.rename('hello.txt',' world.txt',error=>{
    if (error) {
        throw error;
    } else {
        console.log('success');
    }
});

6、统计文件信息

const fs = require('fs');
fs.stat('world.txt',(error,stats) => {
    if (error) {
        throw error;
    } else {
        console.log(stats);
    }
});

7、拼接路径

const path = require('path');
//join('前面的字符串为需要拼接的路径','最后一个参数是..时,代表返回上到一层目录')
const myPath = path.join('hello','world','test/app.js','..');
console.log(myPath);

结果

8、创建文件夹

const fs = require('fs');
fs.mkdir('mkdir',error=>{
    if (error) {
        throw error;
    }
    console.log('success');
});

嵌套创建文件夹

const fs = require('fs');
fs.mkdir('mkdir/hello/a/b', {recursive: true}, error => {
    if (error) {
        throw error;
    }
    console.log('success');
});

运行效果

9、查看目录下子目录及文件

const fs = require('fs');
fs.readdir('./',(error,files) => {
   if (error) {
       throw error;
   }
   console.log(files);
});

运行结果

10、根据相对路径获得绝对路径

const fs = require('fs');
fs.realpath('test.txt',(error,realPath)=>{
    if (error) {
        throw error;
    }
    console.log(realPath);
})

11、删除目录

const fs = require('fs');
//删除目录,默认情况不能有子目录
fs.rmdir('mydir',error => {
    if (error) {
        throw error;
    }
});

删除目录及子目录

const fs = require('fs');
//删除目录 recursive:以递归的方式删除目录和其子目录,如果出错不会被报告
fs.rmdir('mydir', {recursive: true}, error => {
    if (error) {
        throw error;
    }
});

六、通过流的方式操作文件

和之前普通的文件操作方式不同,流的方式不用回调函数,而是先创建流的对象,在根据事件编写回调函数。 读取大文件使用这种方法

1、读入流

const fs = require('fs');
//读入流
const readStream = fs.createReadStream('app9.js', {encoding: 'utf8'});
//开启事件 fd为文件描述符
readStream.on('open', fd => {
    console.log(fd);
});
//准备事件
readStream.on('ready', () => {
    console.log('ready');
});
//接受参数事件
readStream.on('data', data => {
    console.log(data);
});
//结束事件
readStream.on('end', () => {
    console.log('end');
});
//关闭事件
readStream.on('close',()=>{
    console.log('close');
});
//错误事件
readStream.on('error',err => {
    console.log(err);
});

运行结果

2、写入流

直接读出数据并写入目标文件

const fs = require('fs');
//读入流
const readStream = fs.createReadStream('./app9.js', {encoding: "utf8"});
//写出流
const writeStream = fs.createWriteStream('mytest.js', {encoding: "utf8"});
readStream.on('data', data => {
    //将读取的数据用写出流存入另一个文件
    writeStream.write(data,() =>{
        console.log(data);
    });
});

七、上传自定义模块

1、创建模块

2、注册登录npm

使用npm adduser命令注册

3、模块的发布

使用npm publish --access=public命令发布

4、使用已发布的模块

使用npm i helloyylmmodule添加模块

5、下架模块

npm unpublish 再次上传需要版本加一

八、Buffer模块

在 Node.js 中, Buffer 对象用于以字节序列的形式来表示二进制数据。 许多 Node.js 的 API(例如流和文件系统操作)都支持 Buffer,因为与操作系统或其他进程的交互通常总是以二进制数据的形式发生。 Buffer 类在全局作用域中,因此无需使用 require('buffer').Buffer。

1、Buffer简单使用

创建一个buffer

//创建buffer缓冲区
const buffer = Buffer.alloc(128);
//调用write方法会返回一个写入长度
const number = buffer.write('helloWorld你好','utf8');
console.log(number);
//转为字符串
console.log(buffer.toString());

两个buffer做比较,小于返回-1,大于返回1,等于返回1

const buffer1 = Buffer.from('hello');
const buffer2 = Buffer.from('world');

let isCompare = buffer1.compare(buffer2);
console.log(isCompare);

2、buffer拼接

//创建buffer缓冲区
const buffer1 = Buffer.from('hello');
const buffer2 = Buffer.from('world');
const buffer3 = Buffer.from('node');
//拼接
const bufferArray = [buffer1,buffer2,buffer3];  //所有buffer存放到一个数组
const allBuffer = Buffer.concat(bufferArray);
console.log(allBuffer.toString());

3、buffer和json互转

//创建buffer缓冲区
const buffer1 = Buffer.from('hello');
//转为json字符串
const jsonString = JSON.stringify(buffer1);
console.log(jsonString);
//转为json对象
let jsonObject = JSON.parse(jsonString);
console.log(jsonObject);
//使用buffer把json对象转为字符串
const buffer2 = Buffer.from(jsonObject);
console.log(buffer2.toString());

运行结果

4、使用buffer判断是否是有效的编码格式

const str1 = 'utf8';
const str2 = 'utf-8';
const str3 = 'UTF-8';
const str4 = 'UTF-9';
console.log(Buffer.isEncoding(str1));   //true
console.log(Buffer.isEncoding(str2));   //true
console.log(Buffer.isEncoding(str3));   //true
console.log(Buffer.isEncoding(str4));   //false

注意:node不支持GB2312和GBK编码格式

5、判断某个对象是否为buffer对象

const buffer = Buffer.from('hello');
const str = 'abc';
console.log(Buffer.isBuffer(buffer));   //true
console.log(Buffer.isBuffer(str));      //false

九、node网络模块net

net模块用于创建基于流的 TCP 或 IPC 的服务器与客户端

1、简单使用net模块

const net = require('net');
//前两个参数默认,需要一个回调函数
const server = new net.Server();
server.on('connection',socket => {
    console.log('client is connected');
});

server.listen(8090);
server.on('listening',() =>{
   console.log('server is listening');
});
server.on('close',()=>{
    console.log('server close')
});
server.on('error',err => {
    console.log(err);
});

2、获取客户端地址信息

const net = require('net');
const net = require('net');
const server = net.createServer(socket => {
    console.log('client is connected');
});
server.listen(8090,()=>{
    let address = server.address();
    //端口号、ip地址、ipv4或ipv6
    console.log(address.port + ',' + address.address + ',' + address.family);
});

3、设置最大连接数

const net = require('net');
const server = net.createServer(socket => {
    console.log('client is connected');
    //设置拒绝连接
    server.maxConnections = 2;
    //获取连接数count:已连接的总数
    server.getConnections((error,count) => {
        console.log('client count:' + count)
    })
});
server.listen(8090,()=>{
    console.log('server is listening');
});

4、socket事件

4.1、data事件

const net = require('net');
const server = net.createServer(socket => {
    console.log('client is connected');
    //接受客户端发送的数据
    socket.on('data',data => {
       console.log(data.toString());
    });
});
server.listen(8090,()=>{
    console.log('server is listening');
});

5、客户端和服务端相互通信

使用命令行telnet localhost 8090 连接当作客户端

const net = require('net');
const server = net.createServer(socket => {
    console.log('client is connected');
    //获取服务器地址信息
    const address = socket.address();
    //把信息发回客户端
    const msg = 'server address is' + JSON.stringify(address);
    socket.write(msg, () => {
       //计算socket发送多少字节
       let written = socket.bytesWritten;
       console.log('msg is :' + msg);
       console.log('msg size is :' + written);
    });
    //读取客户端的数据
    socket.on('data',data => {
       console.log(data.toString());
       //读取数据
       let bytesRead = socket.bytesRead;
       console.log('data size is :' + bytesRead);
    });

});

server.listen(8090, () => {
    console.log('server is listening');
});

控制台

客户端

6、其他方法

const net = require('net');
const server = net.createServer(socket => {
    console.log('local port :' + socket.localPort);         //本机端口
    console.log('local address :' + socket.localAddress);   //本机地址
    console.log('remote port :' + socket.remotePort);       //客户端端口
    console.log('remote family :' + socket.remoteFamily);   //客户端 ipv4 或 ipv6
    console.log('remote address :' + socket.remoteAddress); //客户端地址
});

server.listen(8090, () => {
    console.log('server is listening');
});

7、TCP Server 创建客户端

const net = require('net');
const client = new net.Socket();

//客户端连接
client.connect(8090, 'localhost',() => {
    console.log('client is connected');
});

8、TCP Server客户端接受服务端的数据

客户端

const net = require('net');
const client = new net.Socket();
client.connect(8090, 'localhost', () => {
   console.log('client is connected');
});

//监听数据传输事件,data为服务器端传来的数据
client.on('data',data => {
    console.log('data :' + data.toString());
});

服务端

const net = require('net');
const server = net.createServer(socket => {
    console.log('client is connected');
    //获取服务器地址信息
    const address = socket.address();
    //把信息发回客户端
    const msg = 'server address is' + JSON.stringify(address);
    socket.write(msg, () => {
        //计算socket发送多少字节
        let written = socket.bytesWritten;
        console.log('msg is :' + msg);
        console.log('msg size is :' + written);
    });
    //读取客户端的数据
    socket.on('data',data => {
        console.log(data.toString());
        //读取数据
        let bytesRead = socket.bytesRead;
        console.log('data size is :' + bytesRead);
    });

});

server.listen(8090, () => {
    console.log('server is listening');
});

接受数据并向服务端发送数据

const net = require('net');
const client = new net.Socket();
//建立连接
client.connect(8090, 'localhost', () => {
   console.log('client is connect to server');
   //想服务器写数据
   client.write('msg from client');
});

client.on('data',data => {
   console.log('data from server :' + data.toString());
   //再次向服务器发送数据
    client.write('hello server');
});

client.on('end',() =>{
    console.log('end server');
});

服务器结果

客户端结果

十、dgram(数据报)模块

dgram 模块提供了 UDP 数据包 socket 的实现。UDP协议客户端和服务器无连接

1、udp服务器和客户端通信

服务器

const dgram = require('dgram');

const msg = Buffer.from('msg to server');
//创建一个服务器dgram.createSocket('socket的类型udp4或udp6','监听msg事件的回调(消息,发送数据方的地址信息)')
const socket = dgram.createSocket('udp4', (msg, info) => {
    //发送消息(消息,发送长度,端口,地址,回调(错误,发送字节数))
    socket.send(msg, 0, msg.length, info.port, info.address, (error, bytes) => {
        if (error) {
            console.log(error);
        }
        console.log('server has sent :' + bytes + ' bytes msg');
    });
});

//绑定
socket.bind(8090, 'localhost', () => {
    console.log('server is bind');
});

//消息监听
socket.on('message', (msg, info) => {
   console.log('msg go');
   console.log(msg.toString());
});

客户端

const dgram = require('dgram');
const msg = Buffer.from('msg from client');
const socket = dgram.createSocket('udp4');
//发送消息
socket.send(msg, 0, msg.length, 8090, 'localhost', (error, bytes) => {
    if (error) {
        console.log(error);
    }
    console.log('client has sent :' + bytes + ' bytes msg ');
});

//接受消息
socket.on('message', (msg, info) => {
    const msg2 = 'hello';
    socket.send(msg2,0,msg.length,8090,'localhost');
});

十一、socket.io

全双工、基于webSocket的长连接。属于第三方模块。 使用npm -i socket.io 安装socket模块

1、socket.io服务器

//webSocket需要依赖http模块
const http = require('http');
//socket.io模块
const io = require('socket.io');
//文件模块
const fs = require('fs');

//创建一个server,用户访问指定页面向用户返回改页面,访问非法页面返回错误信息
const server = http.createServer((request, response) => {
    //设置客户端响应结果
    response.writeHead(200, {'Content-Type': 'text/html'});
    //判断路由
    if (request.url === '/') {
        //用户访问的页面
        fs.readFile('./client.html', 'utf8', (error, data) => {
            if (error) {
                console.log('error');
                return;
            } else {
                //把html页面发送到客户端
                response.end(data.toString());
            }
        });
    } else {
        //访问的非法路径
        response.end('<html><body>Error</body></html>');
    }
});

//设置监听
server.listen(8090, 'localhost');

//创建socket,把http协议转为webSocket协议
const socket = io.listen(server);

//连接事件
socket.on('connection', (socket) => {
    console.log('socket is connected');
    //message事件
    socket.on('message', (message) => {
        console.log(message);
    });

    //断开连接的事件
    socket.on('disconnect', () => {
        console.log('connect is lost');
    });

    //向客户端发送消息
    socket.send('hello world');

    //自定义事件emit('自定义事件名字','自定义事件所带的数据(可以是字符串或者是对象)','调用自定义事件后执行的回调')
    socket.emit('serverEvent', 'server event');  //连接建立后服务器会发送一个serverEvent事件

    //接收客户端发送的自定义事件 除了on响应事件还有once也可以响应事件,once只会响应一次
    socket.on('clientEvent', data => {
        console.log('客户端自定义事件的数据' + data.name + ',' + data.age);
    });

    //响应客户端发的消息
    socket.on('broadcastEventClient', msg => {
        console.log(msg);
        //事件广播,broadcast:向所有的客户端发送这个自定义事件
        socket.broadcast.emit('broadcastEventServer', 'server 发射的广播事件');
    });
});

2、socket.io客户端

<!DOCTYPE html>
<html lang="en">
<head>

    <meta charset="UTF-8">
    <title>Title</title>
    <!-- 在客户端引入socket.io,只要导入了socket.io模块就会自动映射该路径 -->
    <script src="/socket.io/socket.io.js"></script>
    <script type="text/javascript">
        const socket = io('http://localhost:8090');
        //监听message事件
        socket.on('message', (msg) => {
            console.log('msg from server: ' + msg);
        });
        //监听disconnect事件
        socket.on('disconnect', () => {
            console.log('client disconnect');
        });

        //客户端响应自定义事件
        socket.on('serverEvent', (data) => {
            console.log('服务器自定义事件的数据' + data);
            //客户端向服务器发送自定义事件
            socket.emit('clientEvent', {name: 'jack', age: 20});
        });

        //自定义事件传给服务器
        socket.emit('broadcastEventClient', 'broadcast event client');

        //响应服务器的广播事件
        socket.on('broadcastEventServer', msg => {
            console.log(msg);
        });
    </script>
</head>
<body>

</body>
</html>

十二、events(事件)模块

所有能触发事件的对象都是 EventEmitter 类的实例。 这些对象有一个 eventEmitter.on() 函数,用于将一个或多个函数绑定到命名事件上。

1、on和addListener

const http = require('http');
const server = http.createServer();
server.on('request', (request, response) => {
    if (request.url === '/') {
        console.log('on 事件')
        response.end('end');
    }
});
server.addListener('request', (request, response) => {
    if (request.url === '/') {
        console.log('addListener 事件')
    }
});
server.listen(8090, () => {
    console.log('server is listen');
});

对同一个事件可以设置多个监听器,会形成一个监听器组,然后按顺序执行,客户端收到的响应只有第一次监听器发送的。

2、once

const http = require('http');
const server = http.createServer();
server.on('request', (request, response) => {
   if (request.url === '/') {
       console.log('on 事件')
       response.end('end');
   }
});
server.once('request', (request, response) => {
   if (request.url === '/') {
       console.log('once 事件')
       //response.end('end2');
   }
});
server.listen(8090,()=>{
   console.log('server is listen');
});

once事件在被调用一次后就失效

3、删除监听

const http = require('http');
const server = http.createServer();
server.on('request', (request, response) => {
   if (request.url === '/') {
       console.log('on 事件')
       response.end('end');
   }
});
const listener = (request,response) => {
    if (request.url === '/') {
        console.log('on addListener')
    }
};
server.addListener('request',listener);

//第一个参数是事件名,第二个是监听名,删除监听必须有监听的名字,所以要先有一个显示的箭头函数,  也可以用off()
server.removeListener('request',listener);

server.listen(8090,()=>{
   console.log('server is listen');
});

控制台输出

3、删除所有

const http = require('http');
const server = http.createServer();
server.on('request', (request, response) => {
   if (request.url === '/') {
       console.log('on 事件')
       response.end('end');
   }
});
const listener = (request,response) => {
    if (request.url === '/') {
        console.log('on addListener')
    }
};
server.addListener('request',listener);

//删除所有监听 参数:事件名,如果省略就删除所有事件
server.removeAllListeners('request');

server.listen(8090,()=>{
   console.log('server is listen');
});

4、最大监听数

默认一个事件可以监听十个监听器

//查看最多有几个监听器
const events = require('events');
console.log(events.EventEmitter.defaultMaxListeners);   //该默认的最大监听数是全局范围的 

设置最大监听数

server.setMaxListeners()

5、自定义事件

const http = require('http');
const server = http.createServer();
server.on('request', (request, response) => {
    if (request.url === '/') {
        console.log('on 事件')
        response.end('end');
    }
});

//自定义事件的监听,先要监听事件在发射,否则发射后数据会丢失
server.on('serverEvent', (param1, param2, param3) => {
    console.log(param1 + ',' + param2 + ',' + param3);
})

//自定义事件的发射emit('事件名','事件参数')
server.emit('serverEvent', 'hello', 'world');



server.listen(8090, () => {
    console.log('server is listen');
});

5.1、使用EventEmitter自定义事件并监听

const EventEmitter = require('events');
const emitter = new EventEmitter();
//自定义事件监听器
emitter.on('myEvent', function myEvent() {
    console.log('myEvent1');
});
emitter.on('myEvent', function myEvent2(param1, param2) {
    console.log(`myEvent2:${param1} , ${param2}`);
});
emitter.on('myEvent', function myEvent3(...param) {
    const paramArr = param.join(',');
    console.log(`myEvent3:${paramArr}`);
});

//获取myEvent所有的监听器
console.log(emitter.listeners('myEvent'));

//发射事件
emitter.emit('myEvent','a','b','c','d','e','f');

5.2、newListener

const EventEmitter = require('events');
const emitter = new EventEmitter();
//如果有新的监听器被触发,newListener就会执行
emitter.once('newListener',event =>{
    if (event === 'myEvent') {
        emitter.on('myEvent',()=>{
            console.log('hello');
        });
    }
});

emitter.on('myEvent',()=>{
    console.log('world');
});
//发射事件
emitter.emit('myEvent');

十三、Node操作数据库

1、MySQL的连接和基本操作

使用npm i mysql 安装所需依赖

const mysql = require('mysql');
const uuid = require('uuid');

//配置数据库信息
const connection = mysql.createConnection({
    host: 'localhost',
    user: 'root',
    password: 'admin',
    database: 'node'
});
//建立连接
connection.connect((error) => {
    if (error) {
        console.error(error);
        throw error;
    } else {
        console.log('连接成功');

        //插入数据
        const userId = uuid.v1();
        const username = 'ert';
        const realName = 'marry';
        const age = 20;
        const address = 'usb';
        //增删改查都用query方法
        connection.query('insert into users set ?', {
            id: userId,
            username: username,
            real_name: realName,
            age: age,
            address: address
        }, (error, result) => {
            if (error) {
                console.log(`插入失败:${error}`)
                throw error;
            } else {
                console.log(`添加成功:${result}`);
                //插入成功后查询
                connection.query('select * from users', (error, result) => {
                    if (error) {
                        console.log(`查询错误${error}`);
                        throw error;
                    } else {
                        console.log(result);
                        //关闭连接
                        //end方法等所有操作执行完才会关闭连接
                        connection.end(error => {
                            if (error) {
                                console.log(`关闭失败${error}`);
                                throw error;
                            }
                        });
                        //destroy方法不管是否还有操作直接关闭连接
                        // connection.destroy();
                    }
                });
            }
        });
    }
});

2、MongoDB数据库基本操作

2.1、安装MongoDB数据库并配置

安装略

配置

添加环境变量 在path中加上安装的全路径(到bin下) 改变默认的数据存储位置

mongod --dbpath D:\Program Files\MongoDB\Server\4.2\data(希望的数据存储位置)

命令行直接输入mongod启动服务器 在另一个命令行输入mongo 启动客户端

创建配置文件mongo.config,在D:\Program Files\MongoDB\Server\4.2下

配置并启动服务,以后就不用先启动服务器了。

2.2、在node中安装ModngoDB驱动依赖

命令npm i mongoose安装依赖

const mongoose = require('mongoose');
const uri = 'mongodb://localhost:27017/mytest';
//建立连接
mongoose.connect(uri, {useNewUrlParser: true, useUnifiedTopology: true}, error => {
    if (error) {
        console.log(`连接失败${error}`);
        throw error;
    } else {
        console.log('连接成功');

        //定义内层模式
        const parentSchema = new mongoose.Schema({
            name: String,
            number: Number,
            address: String
        });

        //定义外层模式
        const studentSchema = new mongoose.Schema({
            name: String,
            number: Number,
            address: String,
            married: Boolean,
            parents: parentSchema
        });

        //把模式放入mongoose,一个集合
        mongoose.model('student', studentSchema);

        //取出集合
        const Student = mongoose.model('student');

        //创建对象
        const student = new Student({
            name: 'jack',
            age: 20,
            address: 'usa',
            married: true,
            parents: {
                name: 'marry',
                age: 50,
                address: 'usb'
            }
        });

        //插入
        student.save(error => {
            if (error) {
                console.log(`插入失败${error}`);
                throw error;
            } else {
                console.log('插入成功');
                //查询所有数据,find('查询条件','回调(错误,查询出来的文档)')
                Student.find({}, (error,docs) => {
                    if (error) {
                        console.log(`查询失败${error}`);
                        throw error;
                    } else {
                        console.log(`查询成功:${docs}`);
                        //关闭连接
                        //mongoose.connection.close();
                         //删除文档
                        docs.forEach(doc=>{
                            doc.remove();
                        });
                    }
                });
            }
        });

    }
});

3、Redis数据库基本操作

安装redis node安装第三方组件ioreids

使用redis做一个小案例: 模拟用户登录,保存用户的session信息到redis数据库中 目录结构如同

dao层

const Redis = require('ioredis');
//前缀
const redisKeyPrefix = 'myRedis:info:user:';

//定义数据库操控组件
class UserRedisDao {
    //获取连接
    getRedisConnection() {
        return new Redis({
            host: 'localhost',
            port: 6379
        });
    }

    //async表示该函数异步执行,获取用户session id并存储,使用异步 storeUserId('服务器中的sessionId','数据库中的用户id')
    async storeUserId(userSessionId, userId) {
        //获取连接
        const redis = this.getRedisConnection();
        //set(key,value,设置存储时间30分钟,回调)
        redis.set(redisKeyPrefix + userSessionId, userId, 'ex', 1800, (error, result) => {
            //关闭连接
            redis.quit();
        });
    }

    //根据sessionId获取user的id
    async getUserIdFromUserSessionByUserSessionId(userSessionId) {
        const redis = this.getRedisConnection();
        return redis.get(redisKeyPrefix + userSessionId, (error, userId) => {
            redis.quit();
            return userId;
        });
    }

    //登录之后重置session的过期时间
    async resetUserSessionExpirationTime(userSessionId) {
        const redis = this.getRedisConnection();
        //expire:将指定的key设置为一个指定的过期时间
        redis.expire(redisKeyPrefix + userSessionId, 1800, (error, result) => {
            redis.quit();
        });
    }

    //用户退出时删除已存在的sessionId
    async removeUserSessionByUserSessionId(userSessionId) {
        const redis = this.getRedisConnection();
        redis.del(redisKeyPrefix + userSessionId, (error, result) => {
            redis.quit();
        });
    }
}
//导出
module.exports = new UserRedisDao();

service层

const userRedisDao = require('../db/redis/userRedisDao');

class UserService {
    //存入user session
    async storeUserId(userSessionId, userId){
        //service和dao都是异步方法,要加上await,表示要等待这个异步方法返回才会往下执行
        await userRedisDao.storeUserId(userSessionId,userId);
    }

    //获取user信息
    async getUserIdFromUserSessionByUserSessionId(userSessionId) {
        return await userRedisDao.getUserIdFromUserSessionByUserSessionId(userSessionId);
    }

    //重置user session
    async resetUserSessionExpirationTime(userSessionId) {
        await userRedisDao.resetUserSessionExpirationTime(userSessionId);
    }

    //删除用户session
    async removeUserSessionByUserSessionId(userSessionId) {
        await userRedisDao.removeUserSessionByUserSessionId(userSessionId);
    }
}

module.exports = new UserService();

controller层

const userService = require('../service/userService');
const uuid = require('uuid');

class userController {
    //登录
    async userLogin(username, password) {
        const userId = username;
        const userSessionId = uuid.v1();

        await userService.storeUserId(userSessionId, userId);
    }

    //登出
    async userLogout(userSessionId) {
        await userService.removeUserSessionByUserSessionId(userSessionId);
    }

    //其他操作
    async userOtherOperation(userSessionId) {
        const userId = await userService.getUserIdFromUserSessionByUserSessionId(userSessionId);
        console.log(`user is :${userId}`);
        //重置过期
        await userService.resetUserSessionExpirationTime(userSessionId);
    }
}
module.exports = new userController();

服务器

const http = require('http');
const queryString = require('querystring');
const url = require('url');
const userController = require('./user/controller/userController');

const server = http.createServer((request,response)=>{
    let data = '';
    request.on('data',chunk => {
        data += chunk;
    });
    request.on('end',()=>{
       //解析url
        const requestUrl = request.url;
        const requestMethod = request.method;
        console.log(`用户请求路径: ${requestUrl}`);

        //登录
        if (requestUrl.includes('login') && requestMethod === 'GET') {
            //解析传进来的数据
            const requestParams = url.parse(requestUrl);
            const queryObject = queryString.parse(requestParams.query);
            console.log(queryObject);

            userController.userLogin(queryObject.username,queryObject.password);
            //响应
            response.writeHead(200,{'Content-Type' : 'text/plain'});
            response.end('username:' + queryObject.username + ', password:' + queryObject.password);
        }
    });
});

server.listen(8090,()=>{
   console.log('监听8090成功');
});

运行效果: 控制台

页面返回

数据库 pttl 查询失效时间

调用登出方法删除session

        //登录
        if (requestUrl.includes('login') && requestMethod === 'GET') {
            //解析传进来的数据
            const requestParams = url.parse(requestUrl);
            const queryObject = queryString.parse(requestParams.query);
            console.log(queryObject);

            userController.userLogin(queryObject.username, queryObject.password);
            //响应
            response.writeHead(200, {'Content-Type': 'text/plain'});
            response.end('username:' + queryObject.username + ', password:' + queryObject.password);
        } else if (requestUrl.includes('logout') && requestMethod === 'GET') {
            //删除userSession
            const requestParams = url.parse(requestUrl);
            const queryObject = queryString.parse(requestParams.query);
            console.log(queryObject);
            userController.userLogout(queryObject.userSessionId);

            response.writeHead(200, {'Content-Type': 'text/plain'});
            response.end('user logout');
        }

访问

触发登出的方法,删除redis中的响应记录

重置session失效时间

if (requestUrl.includes('login') && requestMethod === 'GET') {
            //解析传进来的数据
            const requestParams = url.parse(requestUrl);
            const queryObject = queryString.parse(requestParams.query);
            console.log(queryObject);

            userController.userLogin(queryObject.username, queryObject.password);
            //响应
            response.writeHead(200, {'Content-Type': 'text/plain'});
            response.end('username:' + queryObject.username + ', password:' + queryObject.password);
        } else if (requestUrl.includes('logout') && requestMethod === 'GET') {
            //删除userSession
            const requestParams = url.parse(requestUrl);
            const queryObject = queryString.parse(requestParams.query);
            console.log(queryObject);
            userController.userLogout(queryObject.userSessionId);

            response.writeHead(200, {'Content-Type': 'text/plain'});
            response.end('user logout');
        } else {
            //重置时间,先排除/favicon.ico(收藏夹图标请求)
            if (!requestUrl.includes('favicon.ico')){
                const requestParams = url.parse(requestUrl);
                const queryObject = queryString.parse(requestParams.query);
                console.log(queryObject);
                userController.userOtherOperation(queryObject.userSessionId);
                response.writeHead(200, {'Content-Type': 'text/plain'});
                response.end('user other');
            }
        }

先判断是否为登录或登出操作,如果不是就重置session失效时间 访问

数据库查询结果为

十四、Process(进程)模块

process 对象是一个全局变量,它提供有关当前 Node.js 进程的信息并对其进行控制。 作为一个全局变量,它始终可供 Node.js 应用程序使用,无需使用 require()。

1、Process模块常用属性

//当前node的版本
console.log(process.version);
//当前版本和所依赖的其他版本信息
console.log(process.versions);
//当前操作系统平台
console.log(process.platform);
//node可执行文件的绝对路径
console.log(process.execPath);
//当前 Node.js 可执行文件的配置选项的 JavaScript 表示形式
console.log(process.config);
// 返回进程的 PID
console.log(process.pid);
//返回当前进程标题
console.log(process.title);
//返回当前操作系统的cpu架构
console.log(process.arch);
//返回包含用户环境的对象
console.log(process.env);

2、Process模块常用方法

//返回 Node.js 进程的内存使用情况的对象,该对象每个属性值的单位为字节。
console.log(process.memoryUsage());
//法变更 Node.js 进程的当前工作目录
//console.log(process.chdir('../'));
//返回 Node.js 进程的当前工作目录。
console.log(process.cwd());
//返回当前执行时间
console.log(process.uptime());

3、Process模块常用事件

//退出回调
process.on('exit', () => {
    console.log('退出');
});

//退出之前的回调
process.on('beforeExit', () => {
    console.log('退出前执行');
});

//当有未捕获的异常时,触发这个事件。
process.on('uncaughtException', error => {
    console.log(error);
    console.log('未捕获的异常');
});

//信号事件
process.on('SIGINT', () => {
    console.log('信号事件');
});

//使用一个延迟事件来触发信号事件
setTimeout(() => {
    console.log('延迟');
}, 10000);

4、nextTick方法

const fs = require('fs');

const myFunction = () =>{
    console.log('myFunction');
}

//nextTick() 方法将 callback 添加到下一个时间点的队列。在同步之前异步之后执行
process.nextTick(myFunction);

//同步读取
console.log(fs.readFileSync('./app2.js').toString('utf8'));
//异步读取
fs.readFile('./app2.js',(error,data) =>{
    console.log(data.toString('utf8'));
});

十五、Koa框架

1、使用koa创建web服务器

这里的request和response不是node原生的request和response,都是经过koa封装的

const Koa = require('koa');
const app = new Koa();
//创建一个服务    ctx:request对象和response对象都被封装到其中
app.use(async (ctx) => {
   //向客户端响应
   //ctx.body = 'hello koa';

   //响应一个html,
   //ctx.body 和 ctx.response 一样
   ctx.response.type = 'text/html';
   ctx.response.body = '<h2>abc</h2>';
});

app.listen(3000, () => {
   console.log('listen');
});

2、Koa级联

Koa 中间件以更传统的方式级联,您可能习惯使用类似的工具 - 之前难以让用户友好地使用 node 的回调。然而,使用 async 功能,我们可以实现 “真实” 的中间件。对比 Connect 的实现,通过一系列功能直接传递控制,直到一个返回,Koa 调用“下游”,然后控制流回“上游”。

const Koa = require('koa');
const app = new Koa();
app.use(async (ctx,next) => {
    console.log('开始1');
    await next();//等待执行下一个中间件,才会执行下面代码
    console.log('结束1');
});
app.use(async (ctx,next) => {
    console.log('开始2');
    await next();
    console.log('结束2');
});
app.use(async (ctx,next) => {
    console.log('开始3');
    await next();
    console.log('结束3');
});

app.listen(3000);

运行结果