Node-1

2019-04-21  本文已影响0人  hellomyshadow

事件驱动

  1. NodeJs是单线程、非阻塞I/O的事件驱动;
    • 不同于Java/PHP/.net等服务器语言,NodeJs不会为每个Client连接创建新线程;
    • 当有新的Client请求连接时会触发内部事件,通过非阻塞I/O、事件驱动机制,让Node应用程序在宏观上是并行的;
    • 使用Node.js,一个8G的服务器可以同时处理超过 4wClient连接。
  2. 处理异步的两种常用方式:回调函数、事件的订阅/发布
  3. 事件是整个Node的核心,Node中大部分模块都使用/继承 events 模块,类似WebAPIEventTarget
  4. node开发的监控工具nodemon,热启动
    npm i nodemon -g
    // 启动程序
    nodemon ./index.js
    
  5. 并发处理的发展及其代表
    • 多进程:LinuxC Apache
    • 多线程:Java
    • 异步IO:JavaScript
    • 协程:lua openresty go Deno TS

events模块

const { EventEmitter } = require('events');
const emitter = new EventEmitter();
  1. 通过 EventEmitter 实例来绑定和监听事件,以广播(订阅/发布)的形式处理异步;
  2. 订阅事件的方式
    emitter.on('toparent', (data) => {  // 方式一:订阅事件'toparent'
       console.log(data)  // 处理广播数据data
    })
    emitter.addListener('toparent', (data) => {  // 方式二
       console.log(data);
    })
    
    emitter.once(event, callback) 表示只注册一次
  3. 发布事件:emitter.emit('toparent', '广播数据')
  4. emitter.setMaxListeners(2); 设置最大的订阅个数

Process

  1. process对象是一个全局变量,提供了当前Node程序与系统的有关信息,还可以控制当前的Node进程
  2. process.argv 获取运行node程序的命令,是一个数组
    node app.js -i  --> ['node命令的路径', 'app.js的路径', '-i']
    
  3. process.evn 环境变量相关,比如通过配置环境变量控制开发模式与生产模式的切换
    • 在当前系统上新建一个环境变量:mode='dev'
    • 判断当前是否处于开发模式:process.env.mode == 'dev'
  4. process.exit() 结束当前进程
  5. process.stdout/stdin 标准输入输出流
    • 将数据输出到终端:process.stdout.write('hello'); --> console.log 的底层原理
    • 监听用户的输入:
      process.stdin.on('data', e=>{  # 可用于实现交互式的命令行,比如Vue.js的脚手架
            process.stdout.write('用户输入: ', e.toString());
      })
      
    • 在命令行输入内容后,还要点击 Enter 键作为输入完成的信号,process.stdin 才能监听到,所以接收到的数据其实是以回车符号结尾的;
    • 去除Windows系统的回车符:e.toString().replace('\r\n', '');
  6. 移动光标的位置:process.stdout.cursorTo(x, y);

os

os 操作系统相关的模块

  1. os.endianness(): CPU的字节序,可能的是 BELE
  2. os.hostname():操作系统的主机名;
  3. os.type():操作系统名;
  4. os.platform():编译时的操作系统名;
  5. os.arch():操作系统 CPU 架构,x64、arm、ia32
  6. os.release():操作系统的发行版本;
  7. os.uptime():操作系统运行的时间,以秒为单位;
  8. os.totalmem():系统内存总量,单位为字节;
  9. os.freemem():操作系统空闲内存量,单位是字节;
  10. os.networkInterfaces():获取网络接口列表;
  11. os.cpus():返回一个对象数组,包含所安装的每个 CPU/内核的信息。

stream

  1. 流是一种在Node中处理流式数据的抽象接口,fs、net、http、https 等模块都提供了流的实现;
  2. stream约定了一些基本特性(但并没有实现),所有实现流操作的对象都具备这些共同的特性;
  3. 流的基本类型
    • Writable:可写入数据的流,如fs.createWriteStream()
    • Readable:可读取数据流,如fs.createReadStream()
    • Duplex:可读又可写的流,又称为双工数据流
    • Transform:可修改的双工数据流,在读写过程中可修改/转换
  4. Writable:write()、end()、setDefaultEncoding()
  5. Readable:setEncoding()、read()、pipe()、pause()、resume()

Buffer

  1. Buffer:缓冲区,类似于数组,长度固定,专门用于操作二进制数据

v8JS引擎,内存有限,32位操作系统约0.7G64位约为1.4GNode使用的也是v8引擎。虽然v8有内存限制,但 Buffer 实际上是对底层内存的直接操作,它的大小不计入v8的内存开销;

  1. let buf = new Buffer(10); --> 10个字节的Buffer,但Buffer的所有构造函数已废弃。
    1. 虽然Buffer存储的是二进制数据,但显示时都是以十六进制的形式(二进制太长了);
    2. Buffer操作的是底层内存,大小一旦确定,就会分配一段连续的内存空间,不允许修改大小;
      buf[0] = 88   // 十进制转为十六进制:58,也可以直接赋值十六进制
      buf[11] = 255   // 角标越界不会报错,但不会有变化
      
    3. Buffer中每个元素的范围:00-ff,即十进制的 0-255,二进制的一个字节;
      buf[2] = 556   // 超过一个字节的最大数值255,会舍弃高位,只存储低8位:2c
      
    4. 控制台默认打印的数字都是十进制,buf[0].toString(16)表示以十六进制输出;
    5. buf.tostring():Buffer中的二进制数据转为字符串。
  2. Buffer.alloc(size[, fill, encoding]):创建 size 个字节的 Buffer 对象
    1. fillencoding 都是可选参数,encoding默认为utf8
    2. 不指定 fill 时,新创建的 Buffer 默认用 0 填充。
  3. Buffer.allocUnsafe(size):不安全的 BufferBuffer 中可能含有敏感数据;
    1. Buffer.alloc() 的默认值都是00,也即分配内存时会清空该内存中的原数据;
    2. Buffer.allocUnsafe() 在分配内存时不会清空内存中原数据,所以默认值可能不是00
  4. Buffer.from(str|array|buffer):将字符串/数组/buffer数据转化为一个新的Buffer`
    1. var str="Hello"; --> str.length 字符串长度
    2. var buf = Buffer.from(str); --> buf.length 占用内存的字节大小
    3. 英文字符串的每个字符占一个字节,所以 str.length==buf.length
    4. 查看字符串的字节长度:Buffer.byteLength('你好');
  5. Buffer.concat(array):合并 array 中的 Buffer,再转为一个新的 Buffer
  6. 比较两个 Buffer 的值是否完全相等:buf1.equals(buf2);
  7. ·Buffer·对象支持的编码格式非常有限,ascii、utf8、utf16le、ucs2、base64、binary、hex,不支持GBK
    • fs模块读取一个GBK编码的文件时,会出现报错信息
      fs.readFile(path, 'gbk', (err, data) => {
          // err: Unknown encoding: gbk
          // 不过不指定编码,默认是utf8编码,data.toString()得到的是乱码
      })
      
    • iconv-lite 用来帮助解决编码转换问题;用于在node中处理各种操作系统中出现的各种奇特编码,它不提供读写文件的操作,只提供文件编码转换的功能。
      const iconv = require('iconv-lite')
      
      // 坚持当前系统是否支持某种编码
      iconv.encodingExists("us-ascii")
      
      fs.readFile(path, (err, data) => {
          // 以GBK编码的形式解码成一个默认为utf8编码格式的字符串
          const gbkData = iconv.decode(data, 'gbk')
          gbkData.toString()  // 得到字符串内容
      })
      

fs

  1. fs:文件系统,node的核心模块,var fs = require("fs");
    1. fs 模块中的所有操作都可以选择同步/异步,同步会阻塞程序的执行,而异步则不会;
    2. fs 中,带有 Sync 的都是同步方法,不带的都是异步方法,且异步方法带有回调函数;
    3. 异步的错误在回调函数中,而同步的错误只能用 try-catch 捕获。
  2. open()/openSync():打开文件,返回一个文件的描述符,通过该描述符进行文件操作;
    1. fs.openSync(path, flags, mode); fs.open(path, flags, mode, callback);
    2. path:文件路径; flags:操作类型r/w; mode:设置文件的操作权限,一般不传;
    3. callback 回调 2 个参数 arguments:function(err, fd){ ... }
    4. err:错误对象,没有错误则为nullfd:文件描述符;
    5. Node的设计就是错误优先,所以回调函数的第一个参数是错误对象。
  3. write()/writeSync():向文件中写入内容;
    fs.writeSync(fd, string, position, encoding);
    fs.write(fd, string, position, encoding, callback);
    
    1. fd:文件描述符; string:写入的内容,如果不是字符串,则被强制转为字符串;
    2. position:写入指针的位置,默认为0encoding:写入的编码,默认为 utf-8
    3. callback 回调 3 个参数:function(err, written, string){ ... },其中 written 是传入的字符串被写入的字节数。
  4. close(fd, callback)/closeSync(fd):关闭文件操作;
    1. fd:文件描述符,表示要关闭的文件操作;
    2. callback 只回调一个参数:function(err){ ... }
  5. fs的写入过程:数据-->Buffer-->stream,为了提高效率,数据先写入缓冲区,再一次性写入文件.

简单读写

  1. 简单文件写入:fs.writeFile()/fs.writeFileSync(),原理仍是 write()/writeSync()
    writeFile(file, data, options, callback)
    
    • file:操作的文件路径。如果是绝对路径,下面两种方式是等效的。
      fs.writeFile("C:/workplace/test.txt", ...);
      fs.writeFile("C:\\workplace\\test.txt", ...);
      
    • data:待写入的数据,可以是字符串或Buffer,如果是bufferoptions中的encoding是无效的;
    • options:可选对象,设置写入动作,包括encoding(默认utf-8),mode(默认0o66),flag(默认w){ flag: 'a' } 表示向文件中追加写入;
    • callback 回调函数,不用手动关闭操作流。
  2. 流式文件写入:用于写入大文件,可以分多次写入文件。同步/异步/简单文件写入都不适合大文件的写入,性能较差,容易导致内存溢出;
    var ws = fs.createWriteStream(path, options);  // 创建一个写入流;
    ws.write(str);  // 写入数据
    ws.end();  // 等待写入完成再关闭流,不能用ws.close(),会造成流中的数据丢失;
    
    事件注册
    • ws.on("事件名", function) 绑定一个长期有效的事件;
    • ws.once("事件名", function) 绑定一个一次性的事件,触发一次之后自动失效;
    ws.once("open", function(){  // 监听写入流的打开事件
       console.log("流打开了...")
    });
    ws.once("close", function(){  // 监听写入流的关闭事件
       console.log("流关闭了...")
    });
    
  3. 简单文件读取:fs.readFile()/fs.readFileSync()
    fs.readFile(path, options, callback)
    
    1. path 表示读取文件的路径;
    2. callback:function(err, data) 回调的 data 是一个Buffer对象,因为读取到的内容可能是二进制文件,如图片、音频。data.toString()只适用于字符串,对二进制数据会乱码。
    fs.readFile("an.jpg", function(err, data){
       if(!err) {
          fs.writeFile("pn.jpg", data, function(err){ ... });
       }
    });
    
  4. 流式文件读取:用于读取大文件,可以分多次读取文件
    var rs = fs.createReadStream(path, options);  // 创建一个读取流;
    
    读取一个可读流中的数据,必须为可读流绑定一个 data 事件,绑定后会自动开始读取;
    rs.on("data", function(data){  // 读取过程不是一次性事件
      // data也是一个Buffer对象
    });
    
  5. 管道:把读取流的内容 通过管道 传递给写入流,并写入本地。
    var rs = fs.createReadStream('./avatar.png');
    var ws = fs.createWriteStream('./avatar-bak.png');
    rs.pipe(ws);
    
    这种管道的方式在读/写大文件的过程中,会非常有效率!

常用操作

  1. fs.exists(path, callback)/existsSync(path):文件/目录是否存在。已被 fs.stat()/fs.access() 替代。
  2. ·fs.stat(path, callback)/statSync(path):·获取文件信息,类似文件/目录的属性信息
    1. callback:function(err, stats) --> stats对象中保存了文件的状态信息
    2. stats.size:获取文件的大小;
    3. stats.isFile()/isDirectory():是否是一个文件/文件夹。
  3. fs.unlink(path, callback)/unlinkSync(path):删除文件;
  4. fs.readdir(path, options, callback)/readdirSync(path, options)
    1. 读取一个目录的结构,获取指定目录下的所有文件/文件夹;
    2. callback:function(err, files) --> files是一个数组,文件/文件夹的名称。
  5. fs.truncate(path, len, callback)/truncateSync(path, len) 截断文件,将文件修改为指定的大小,len表示字节大小,将文件大小设置为 len
  6. fs.mkdir(path, mode, callback)/mkdirSync(path, mode):创建目录,但不会递归创建
  7. fs.rmdir(path, callback)/rmdirSync(path):删除目录,但不能删除非空目录
  8. fs.rename(old, new, callback):重命名/剪切
  9. fs.watchFile(filename, options, listener):监视文件的修改
    1. 内部原理是一个定时机制,定时检查文件中的内容;
    2. options:{ persistent: true, interval: 5007 },默认 5s 检查一次;
    fs.watchFile("test.txt", {interval:1000}, function(curr, prev) {
        //curr是修改后的状态,prev是修改前的状态,它们都是stats对象
    });
    
  10. fs.watch():可以监听目录的状态变化。

fs Promise

node10.0 之后,fs模块中引入了Promise,文件操作不再区分异步和同步,而是返回Promise对象

const fsPromises = require('fs').promises;

UDP之dgram

  1. dgram模块:提供UDP数据包socket的实现,const dgram = require('dgram');
  2. 服务端
    1. 创建 socket 对象
      const server = dgram.createSocket();  // 静态方法创建socket对象
      const server = new dgram.Socket(type[, callback]);
      
    2. type:udp4 =>IPV4、udp6 =>IPV6; callback:接收到数据的回调;
    3. 绑定 IP 和端口号
      server.bind(3000, '127.0.0.1');
      
    4. 监听事件:close(关闭)、error(发生错误)、listening(启动监听)、message(收到消息)
      server.on('listening', () => { console.log('启动成功...'); });
      server.on('message', (data) => {
              //网络传输的数据是二进制的,在node上是一个buffer对象
      });
      
    5. 关闭socket
      server.close(callback);
      
  3. 客户端
    let client = dgram.createSocket('udp4');
    // 发送数据:
    client.send('Hello Server', 3000, '127.0.0.1');
    

TCP之net

  1. net 模块:提供了创建基于流的 TCP/IPC 服务器和客户端的异步网络API
    const net = require('net');
    
  2. 服务端
    let server = new net.Server(); / net.createServer();
    // 启动监听
    server.listen(3000, '127.0.0.1');   // IP默认为0.0.0.0
    
    server.listen(3000, '0.0.0.0')  // 监听当前设备上的所有IP收到的3000端口消息
    
    1. 一台电脑可能有多个网卡(对应多个IP),但端口号是唯一的,0.0.0.0 类似于通配符 *
    2. 连接事件、发送数据
      server.on('connect', (socket) => {  // 回调的socket表示当前客户端的socket对象
              console.log('有客户端连接...');
              socket.write('hello client');   // 向客户端写(发送)数据
              socket.on('data', (data) => {   // 监听客户端发来的数据
                      // ...
              });
      });
      
    3. 获取客户端的IP和端口号:socket.remoteAddress,socket.remotePort
  3. 客户端
// 创建客户端:new net.Socket(); / net.createConnection();
let client = net.createConnection(3000, '127.0.0.1');

client.on('data', data => {  // 监听服务器发来的数据
    // ...
});

client.write('hello server');  // 向服务器端发送数据
  1. socket数据的分包
    1. 当一个数据包很大时,并不会一次性传输,而是分多次,也即 data事件会被触发多次;
    2. 服务端告诉客户端数据传输完成:socket.end();
      server.on('connect', (socket) => {
              let data = fs.readFileSync('./a.mp4');   // 读取一个大文件
              socket.write(data);   // 开始传输,socket内部会分包传输
              socket.end();   // 通知客户端传输完成
      })
      
    3. socket.end(); 一旦执行,本次连接也会终止,客户端断开连接;
    4. 客户端收到数据传输完成的事件:client.on('end', () => { ... });
    5. 客户端需要把每次收到的数据(buffer),拼接成一个buffer对象,才是一个完整的数据包。
      let list = [];
      client.on('data', chunk => {
              list.push(chunk);
      });
      client.on('end', () => {  // 数据传输完成,连接已断开
              let data = Buffer.concat(list);   // 把多个buffer对象合并为一个buffer
              fs.writeFile('./b.mp4', data, err => {});
      });
      
上一篇下一篇

猜你喜欢

热点阅读