网站首页 > 基础教程 正文
网络
获取本地 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.js
const 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.js
process.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. 点头像关注后多多评论,转发给有需要的朋友。
谢谢!!
- 上一篇: 获取 NodeJS 程序退出码
- 下一篇: 不要在nodejs中阻塞event loop
猜你喜欢
- 2024-11-25 Deno 1.30 正式发布
- 2024-11-25 用 WasmEdge 和 Rust 在边缘云上构建高性能且安全的微服务
- 2024-11-25 通过浏览器工作台启动本地项目
- 2024-11-25 r2frida:基于Frida的远程进程安全检测和通信工具
- 2024-11-25 NPM 使用介绍
- 2024-11-25 使用Hexo在github上搭建静态博客
- 2024-11-25 Android动态调试(1)-Radare2和lldb
- 2024-11-25 Metasploit渗透测试之MSFvenom
- 2024-11-25 浅析CTF中的Node.js原型链污染
- 2024-11-25 首个SSRF漏洞开篇学习
- 最近发表
- 标签列表
-
- gitpush (61)
- pythonif (68)
- location.href (57)
- tail-f (57)
- pythonifelse (59)
- deletesql (62)
- c++模板 (62)
- css3动画 (57)
- c#event (59)
- linuxgzip (68)
- 字符串连接 (73)
- nginx配置文件详解 (61)
- html标签 (69)
- c++初始化列表 (64)
- exec命令 (59)
- canvasfilltext (58)
- mysqlinnodbmyisam区别 (63)
- arraylistadd (66)
- node教程 (59)
- console.table (62)
- c++time_t (58)
- phpcookie (58)
- mysqldatesub函数 (63)
- window10java环境变量设置 (66)
- c++虚函数和纯虚函数的区别 (66)