nodejs prototype_nodejs 的融会贯通,你学会了吗?

8ae474ab4563f6559b0300062879e774.png

网络

获取本地 IP

function get_local_ip() {    const interfaces = require('os').networkInterfaces();    let IPAdress = '';    for (const devName in interfaces) {        const iface = interfaces[devName];        for (let i = 0; i < iface.length; i++) {            const alias = iface[i];            if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal) {                IPAdress = alias.address;            }        }    }    return IPAdress;}复制代码

TCP 客户端

NodeJS 使用 net 模块创建 TCP 连接和服务。

启动与测试 TCP

const assert = require('assert');const net = require('net');let clients = 0;let expectedAssertions = 2;const server = net.createServer(function (client) {    clients++;    const clientId = clients;    console.log('Client connected:', clientId);    client.on('end', function () {        console.log('Client disconnected:', clientId);    });    client.write('Welcome client: ' + clientId);    client.pipe(client);});server.listen(8000, function () {    console.log('Server started on port 8000');    runTest(1, function () {        runTest(2, function () {            console.log('Tests finished');            assert.equal(0, expectedAssertions);            server.close();        });    });});function runTest(expectedId, done) {    const client = net.connect(8000);    client.on('data', function (data) {        const expected = 'Welcome client: ' + expectedId;        assert.equal(data.toString(), expected);        expectedAssertions--;        client.end();    });    client.on('end', done);}复制代码

UDP 客户端

利用 dgram 模块创建数据报 socket,然后利用socket.send发送数据。

文件发送服务

const dgram = require('dgram');const fs = require('fs');const port = 41230;const defaultSize = 16;function Client(remoteIP) {    const inStream = fs.createReadStream(__filename); // 从当前文件创建可读流    const socket = dgram.createSocket('udp4'); // 创建新的数据流 socket 作为客户端    inStream.on('readable', function () {        sendData(); // 当可读流准备好,开始发送数据到服务器    });    function sendData() {        const message = inStream.read(defaultSize); // 读取数据块        if (!message) {            return socket.unref(); // 客户端完成任务后,使用 unref 安全关闭它        }        // 发送数据到服务器        socket.send(message, 0, message.length, port, remoteIP, function () {            sendData();        });    }}function Server() {    const socket = dgram.createSocket('udp4'); // 创建一个 socket 提供服务    socket.on('message', function (msg) {        process.stdout.write(msg.toString());    });    socket.on('listening', function () {        console.log('Server ready:', socket.address());    });    socket.bind(port);}if (process.argv[2] === 'client') { // 根据命令行选项确定运行客户端还是服务端    new Client(process.argv[3]);} else {    new Server();}复制代码

HTTP 客户端

使用 http.createServer 和 http.createClient 运行 HTTP 服务。

启动与测试 HTTP

const assert = require('assert');const http = require('http');const server = http.createServer(function (req, res) {    res.writeHead(200, {        'Content-Type': 'text/plain'    }); // 写入基于文本的响应头    res.write('Hello, world.'); // 发送消息回客户端    res.end();});server.listen(8000, function () {    console.log('Listening on port 8000');});const req = http.request({    port: 8000}, function (res) { // 创建请求    console.log('HTTP headers:', res.headers);    res.on('data', function (data) { // 给 data 事件创建监听,确保和期望值一致        console.log('Body:', data.toString());        assert.equal('Hello, world.', data.toString());        assert.equal(200, res.statusCode);        server.unref();        console.log('测试完成');    });});req.end();复制代码

重定向

HTTP 标准定义了标识重定向发生时的状态码,它也指出了客户端应该检查无限循环。

  • 300:多重选择
  • 301:永久移动到新位置
  • 302:找到重定向跳转
  • 303:参见其他信息
  • 304:没有改动
  • 305:使用代理
  • 307:临时重定向 const http = require('http'); const https = require('https'); const url = require('url'); // 有很多接续 URLs 的方法 // 构造函数被用来创建一个对象来构成请求对象的声明周期 function Request() { this.maxRedirects = 10; this.redirects = 0; } Request.prototype.get = function (href, callback) { const uri = url.parse(href); // 解析 URLs 成为 Node http 模块使用的格式,确定是否使用 HTTPS const options = { host: uri.host, path: uri.path }; const httpGet = uri.protocol === 'http:' ? http.get : https.get; console.log('GET:', href); function processResponse(response) { if (response.statusCode >= 300 && response.statusCode < 400) { // 检查状态码是否在 HTTP 重定向范围 if (this.redirects >= this.maxRedirects) { this.error = new Error('Too many redirects for: ' + href); } else { this.redirects++; // 重定向计数自增 href = url.resolve(options.host, response.headers.location); // 使用 url.resolve 确保相对路径的 URLs 转换为绝对路径 URLs return this.get(href, callback); } } response.url = href; response.redirects = this.redirects; console.log('Redirected:', href); function end() { console.log('Connection ended'); callback(this.error, response); } response.on('data', function (data) { console.log('Got data, length:', data.length); }); response.on('end', end.bind(this)); // 绑定回调到 Request 实例,确保能拿到实例属性 } httpGet(options, processResponse.bind(this)) .on('error', function (err) { callback(err); }); 复制代码 }; const request = new Request(); request.get('google.com/', function (err, res) { if (err) { console.error(err); } else { console.log( Fetched URL: ${res.url} with ${res.redirects} redirects ); process.exit(); } });

HTTP 代理

  • ISP 使用透明代理使网络更加高效
  • 使用缓存代理服务器减少宽带
  • Web 应用程序的 DevOps 利用他们提升应用程序性能 const http = require('http'); const url = require('url'); http.createServer(function (req, res) { console.log('start request:', req.url); const options = url.parse(req.url); console.log(options); options.headers = req.headers; const proxyRequest = http.request(options, function (proxyResponse) { // 创建请求来复制原始的请求 proxyResponse.on('data', function (chunk) { // 监听数据,返回给浏览器 console.log('proxyResponse length:', chunk.length); res.write(chunk, 'binary'); }); proxyResponse.on('end', function () { // 追踪代理请求完成 console.log('proxied request ended'); res.end(); }); res.writeHead(proxyResponse.statusCode, proxyResponse.headers); // 发送头部信息给服务器 }); req.on('data', function (chunk) { // 捕获从浏览器发送到服务器的数据 console.log('in request length:', chunk.length); proxyRequest.write(chunk, 'binary'); }); req.on('end', function () { // 追踪原始的请求什么时候结束 console.log('original request ended'); proxyRequest.end(); }); 复制代码 }).listen(8888); // 监听来自本地浏览器的连接

封装 request-promise

const https = require('https');const promisify = require('util').promisify;https.get[promisify.custom] = function getAsync(options) {    return new Promise((resolve, reject) => {        https.get(options, (response) => {            response.end = new Promise((resolve) => response.on('end', resolve));            resolve(response);        }).on('error', reject);    });};const rp = promisify(https.get);(async () => {    const res = await rp('https://jsonmock.hackerrank.com/api/movies/search/?Title=Spiderman&page=1');    let body = '';    res.on('data', (chunk) => body += chunk);    await res.end;    console.log(body);})();复制代码

DNS 请求

使用 dns 模块创建 DNS 请求。

  • A:`dns.resolve`,A 记录存储 IP 地址
  • TXT:`dns.resulveTxt`,文本值可以用于在 - DNS 上构建其他服务
  • SRV:`dns.resolveSrv`,服务记录定义服务的定位数据,通常包含主机名和端口号
  • NS:`dns.resolveNs`,指定域名服务器
  • CNAME:`dns.resolveCname`,相关的域名记录,设置为域名而不是 IP 地址 const dns = require('dns'); dns.resolve('www.chenng.cn', function (err, addresses) { if (err) { console.error(err); } console.log('Addresses:', addresses); 复制代码 });

crypto 库加密解密

const crypto = require('crypto')function aesEncrypt(data, key = 'key') {    const cipher = crypto.createCipher('aes192', key)    let crypted = cipher.update(data, 'utf8', 'hex')    crypted += cipher.final('hex')    return crypted}function aesDecrypt(encrypted, key = 'key') {    const decipher = crypto.createDecipher('aes192', key)    let decrypted = decipher.update(encrypted, 'hex', 'utf8')    decrypted += decipher.final('utf8')    return decrypted}复制代码

发起 HTTP 请求的方法

  • HTTP 标准库
  • 无需安装外部依赖
  • 需要以块为单位接受数据,自己监听 end 事件
  • HTTP 和 HTTPS 是两个模块,需要区分使用
  • Request 库
  • 使用方便
  • 有 promise 版本 request-promise
  • Axios
  • 既可以用在浏览器又可以用在 NodeJS
  • 可以使用 axios.all 并发多个请求
  • SuperAgent
  • 可以链式使用
  • node-fetch
  • 浏览器的 fetch 移植过来的

子进程

执行外部应用

基本概念

  • 4个异步方法:exec、execFile、fork、spawn
  • spawn:处理一些会有很多子进程 I/O 时、进程会有大量输出时使用
  • execFile:只需执行一个外部程序的时候使用,执行速度快,处理用户输入相对安全
  • exec:想直接访问线程的 shell 命令时使用,一定要注意用户输入
  • fork:想将一个 Node 进程作为一个独立的进程来运行的时候使用,是的计算处理和文件描述器脱离 Node 主进程
  • Node
  • 非 Node
  • 3个同步方法:execSync、execFileSync、spawnSync
  • 通过 API 创建出来的子进程和父进程没有任何必然联系

execFile

会把输出结果缓存好,通过回调返回最后结果或者异常信息

const cp = require('child_process');cp.execFile('echo', ['hello', 'world'], (err, stdout, stderr) => {    if (err) {        console.error(err);    }    console.log('stdout: ', stdout);    console.log('stderr: ', stderr);});复制代码

pawn

  • 通过流可以使用有大量数据输出的外部应用,节约内存
  • 使用流提高数据响应效率
  • spawn 方法返回一个 I/O 的流接口

单一任务

const cp = require('child_process');const child = cp.spawn('echo', ['hello', 'world']);child.on('error', console.error);child.stdout.pipe(process.stdout);child.stderr.pipe(process.stderr);复制代码

多任务串联

const cp = require('child_process');const path = require('path');const cat = cp.spawn('cat', [path.resolve(__dirname, 'messy.txt')]);const sort = cp.spawn('sort');const uniq = cp.spawn('uniq');cat.stdout.pipe(sort.stdin);sort.stdout.pipe(uniq.stdin);uniq.stdout.pipe(process.stdout);复制代码

exec

  • 只有一个字符串命令
  • 和 shell 一模一样 const cp = require('child_process'); cp.exec(cat ${__dirname}/messy.txt | sort | uniq, (err, stdout, stderr) => { console.log(stdout); });

fork

  • fork 方法会开发一个 IPC 通道,不同的 Node 进程进行消息传送
  • 一个子进程消耗 30ms 启动时间和 10MB 内存
  • 子进程:`process.on('message')`、`process.send()`
  • 父进程:`child.on('message')`、`child.send()`

父子通信

// parent.jsconst cp = require('child_process');const child = cp.fork('./child', {    silent: true});child.send('monkeys');child.on('message', function (message) {    console.log('got message from child', message, typeof message);})child.stdout.pipe(process.stdout);setTimeout(function () {    child.disconnect();}, 3000);// child.jsprocess.on('message', function (message) {    console.log('got one', message);    process.send('no pizza');    process.send(1);    process.send({        my: 'object'    });    process.send(false);    process.send(null);});console.log(process);复制代码

常用技巧

退出时杀死所有子进程

保留对由 spawn 返回的 ChildProcess 对象的引用,并在退出主进程时将其杀死

const spawn = require('child_process').spawn;const children = [];process.on('exit', function () {    console.log('killing', children.length, 'child processes');    children.forEach(function (child) {        child.kill();    });});children.push(spawn('/bin/sleep', ['10']));children.push(spawn('/bin/sleep', ['10']));children.push(spawn('/bin/sleep', ['10']));setTimeout(function () {    process.exit(0);}, 3000);复制代码

Cluster 的理解

  • 解决 NodeJS 单进程无法充分利用多核 CPU 问题
  • 通过 master-cluster 模式可以使得应用更加健壮
  • Cluster 底层是 child_process 模块,除了可以发送普通消息,还可以发送底层对象 TCP、UDP 等
  • TCP 主进程发送到子进程,子进程能根据消息重建出 TCP 连接,Cluster 可以决定 fork 出合适的硬件资源的子进程数

Node 多线程

单线程问题

  • 对 cpu 利用不足
  • 某个未捕获的异常可能会导致整个程序的退出

Node 线程

  • Node 进程占用了 7 个线程
  • Node 中最核心的是 v8 引擎,在 Node 启动后,会创建 v8 的实例,这个实例是多线程的
  • 主线程:编译、执行代码
  • 编译/优化线程:在主线程执行的时候,可以优化代码
  • 分析器线程:记录分析代码运行时间,为 Crankshaft 优化代码执行提供依据
  • 垃圾回收的几个线程
  • JavaScript 的执行是单线程的,但 Javascript 的宿主环境,无论是 Node 还是浏览器都是多线程的

异步 IO

  • Node 中有一些 IO 操作(DNS,FS)和一些 CPU 密集计算(Zlib,Crypto)会启用 Node 的线程池
  • 线程池默认大小为 4,可以手动更改线程池默认大小 process.env.UV_THREADPOOL_SIZE = 64

cluster 多进程

const cluster = require('cluster');const http = require('http');const numCPUs = require('os').cpus().length;if (cluster.isMaster) {  console.log(`主进程 ${process.pid} 正在运行`);  for (let i = 0; i < numCPUs; i++) {    cluster.fork();  }  cluster.on('exit', (worker, code, signal) => {    console.log(`工作进程 ${worker.process.pid} 已退出`);  });} else {  // 工作进程可以共享任何 TCP 连接。  // 在本例子中,共享的是 HTTP 服务器。  http.createServer((req, res) => {    res.writeHead(200);    res.end('Hello World');  }).listen(8000);  console.log(`工作进程 ${process.pid} 已启动`);}复制代码
  • 一共有 9 个进程,其中一个主进程,cpu 个数 x cpu 核数 = 2 x 4 = 8 个 子进程
  • 无论 child_process 还是 cluster,都不是多线程模型,而是多进程模型
  • 应对单线程问题,通常使用多进程的方式来模拟多线程

真 Node 多线程

Node 10.5.0 的发布,给出了一个实验性质的模块 worker_threads 给 Node 提供真正的多线程能力

worker_thread 模块中有 4 个对象和 2 个类

  • isMainThread: 是否是主线程,源码中是通过 threadId === 0 进行判断的。
  • MessagePort: 用于线程之间的通信,继承自 EventEmitter。
  • MessageChannel: 用于创建异步、双向通信的通道实例。
  • threadId: 线程 ID。
  • Worker: 用于在主线程中创建子线程。第一个参数为 filename,表示子线程执行的入口。
  • parentPort: 在 worker 线程里是表示父进程的 MessagePort 类型的对象,在主线程里为 null
  • workerData: 用于在主进程中向子进程传递数据(data 副本) isMainThread, parentPort, workerData, threadId, MessageChannel, MessagePort, Worker } = require('worker_threads'); function mainThread() { for (let i = 0; i < 5; i++) { const worker = new Worker(__filename, { workerData: i }); worker.on('exit', code => { console.log(main: worker stopped with exit code ${code}); }); worker.on('message', msg => { console.log(main: receive ${msg}); worker.postMessage(msg + 1); }); } } function workerThread() { console.log(worker: workerDate ${workerData}); parentPort.on('message', msg => { console.log(worker: receive ${msg}); }), parentPort.postMessage(workerData); } if (isMainThread) { mainThread(); } else { workerThread(); }

线程通信

const assert = require('assert');const {    Worker,    MessageChannel,    MessagePort,    isMainThread,    parentPort} = require('worker_threads');if (isMainThread) {    const worker = new Worker(__filename);    const subChannel = new MessageChannel();    worker.postMessage({        hereIsYourPort: subChannel.port1    }, [subChannel.port1]);    subChannel.port2.on('message', (value) => {        console.log('received:', value);    });} else {    parentPort.once('message', (value) => {        assert(value.hereIsYourPort instanceof MessagePort);        value.hereIsYourPort.postMessage('the worker is sending this');        value.hereIsYourPort.close();    });}复制代码

多进程 vs 多线程

进程是资源分配的最小单位,线程是CPU调度的最小单位

最后,咱给小编:

1. 点赞+关注

2. 点头像关注后多多评论,转发给有需要的朋友。

谢谢!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值