Node-1

作者: hellomyshadow | 来源:发表于2019-04-21 10:37 被阅读0次

    事件驱动

    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 => {});
        });
        

    相关文章

      网友评论

          本文标题:Node-1

          本文链接:https://www.haomeiwen.com/subject/pdudgqtx.html