-
Notifications
You must be signed in to change notification settings - Fork 73
/
README.md
2635 lines (2001 loc) · 75.4 KB
/
README.md
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!--NodeJS-cheat-sheet-->
最近读《重学前端》,开篇就是让你拥有自己的知识体系图谱,后续学的东西补充到相应的模块,既可以加深对原有知识的理解,又可以强化记忆,很不错的学习方案。
这篇文章主要知识点来自:
- [《Node.js硬实战:115个核心技巧》](https://www.amazon.cn/dp/B01MYX8XG1)
- [i0natan/nodebestpractices](https://github.com/i0natan/nodebestpractices)
- 后续学习的一些知识点
<!--more-->
# docsify 文档
<https://static.chenng.cn/#/%E5%9F%BA%E7%A1%80-%E5%90%8E%E7%AB%AF/NodeJS>
# 更新记录
[CHANGE LOG](https://github.com/ringcrl/node-point/commits/master)
# 说明
比较好的 markdown 的查看方式是直接用 VSCode 打开大纲,这样整个脉络一目了然,后续补充知识点也很快定位到相应的位置:
![01.png](https://qiniu.chenng.cn/2019-01-28-09-44-31.png)
这个 markdown 文件已经丢到 Github,有更新会直接推这里:
https://github.com/ringcrl/node-point
博客上也会记录一些好玩的东西:
www.chenng.cn/archives/
# 安装
```sh
# 使用 nvm 安装
https://github.com/creationix/nvm#install-script # Git install
nvm install
nvm alias default
# 卸载 pkg 安装版
sudo rm -rf /usr/local/{bin/{node,npm},lib/node_modules/npm,lib/node,share/man/*/node.*}
```
# 全局变量
## require(id)
- 内建模块直接从内存加载
- 文件模块通过文件查找定位到文件
- 包通过 package.json 里面的 main 字段查找入口文件
### module.exports
```js
// 通过如下模块包装得到
(funciton (exports, require, module, __filename, __dirname) { // 包装头
}); // 包装尾
```
### JSON 文件
- 通过 `fs.readFileSync()` 加载
- 通过 `JSON.parse()` 解析
### 加载大文件
- require 成功后会缓存文件
- 大量使用会导致大量数据驻留在内存中,导致 GC 频分和内存泄露
## module.exports 和 exports
### 执行时
```js
(funciton(exports, require, module, __filename, __dirname) { // 包装头
console.log('hello world!') // 原始文件
}); // 包装尾
```
### exports
- exports 是 module 的属性,默认情况是空对象
- require 一个模块实际得到的是该模块的 exports 属性
- exports.xxx 导出具有多个属性的对象
- module.exports = xxx 导出一个对象
### 使用
```js
// module-2.js
exports.method = function() {
return 'Hello';
};
exports.method2 = function() {
return 'Hello again';
};
// module-1.js
const module2 = require('./module-2');
console.log(module2.method()); // Hello
console.log(module2.method2()); // Hello again
```
## 路径变量
```js
console.log('__dirname:', __dirname); // 文件夹
console.log('__filename:', __filename); // 文件
path.join(__dirname, 'views', 'view.html'); // 如果不希望自己手动处理 / 的问题,使用 path.join
// HOME 目录
const homeDir = require('os').homedir();
```
## console
| 占位符 | 类型 | 例子 |
| :----: | :----: | :---------------------------------: |
| %s | String | console.log('%s', 'value') |
| %d | Number | console.log('%d', 3.14) |
| %j | JSON | console.log('%j', {name: 'Chenng'}) |
## process
### 查看 PATH
```js
node
console.log(process.env.PATH.split(':').join('\n'));
```
### 设置 PATH
```js
process.env.PATH += ':/a_new_path_to_executables';
```
### 获取信息
```js
// 获取平台信息
process.arch // x64
process.platform // darwin
// 获取内存使用情况
process.memoryUsage();
// 获取命令行参数
process.argv
```
### nextTick
process.nextTick 方法允许你把一个回调放在下一次时间轮询队列的头上,这意味着可以用来延迟执行,结果是比 setTimeout 更有效率。
```js
const EventEmitter = require('events').EventEmitter;
function complexOperations() {
const events = new EventEmitter();
process.nextTick(function () {
events.emit('success');
});
return events;
}
complexOperations().on('success', function () {
console.log('success!');
});
```
# Buffer
如果没有提供编码格式,文件操作以及很多网络操作就会将数据作为 Buffer 类型返回。
## toString
默认转为 `UTF-8` 格式,还支持 `ascii`、`base64` 等。
## data URI
```js
// 生成 data URI
const fs = require('fs');
const mime = 'image/png';
const encoding = 'base64';
const base64Data = fs.readFileSync(`${__dirname}/monkey.png`).toString(encoding);
const uri = `data:${mime};${encoding},${base64Data}`;
console.log(uri);
// data URI 转文件
const fs = require('fs');
const uri = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgA...';
const base64Data = uri.split(',')[1];
const buf = Buffer(base64Data, 'base64');
fs.writeFileSync(`${__dirname}/secondmonkey.png`, buf);
```
# events
```js
const EventEmitter = require('events').EventEmitter;
const AudioDevice = {
play: function (track) {
console.log('play', track);
},
stop: function () {
console.log('stop');
},
};
class MusicPlayer extends EventEmitter {
constructor() {
super();
this.playing = false;
}
}
const musicPlayer = new MusicPlayer();
musicPlayer.on('play', function (track) {
this.playing = true;
AudioDevice.play(track);
});
musicPlayer.on('stop', function () {
this.playing = false;
AudioDevice.stop();
});
musicPlayer.emit('play', 'The Roots - The Fire');
setTimeout(function () {
musicPlayer.emit('stop');
}, 1000);
// 处理异常
// EventEmitter 实例发生错误会发出一个 error 事件
// 如果没有监听器,默认动作是打印一个堆栈并退出程序
musicPlayer.on('error', function (err) {
console.err('Error:', err);
});
```
# util
## promisify
```js
const util = require('util');
const fs = require('fs');
const readAsync = util.promisify(fs.readFile);
async function init() {
try {
let data = await readAsync('./package.json');
data =JSON.parse(data);
console.log(data.name);
} catch (err) {
console.log(err);
}
}
```
# 流
## 理解流
流是基于事件的 API,用于管理和处理数据。
- 流是能够读写的
- 是基于事件实现的一个实例
理解流的最好方式就是想象一下没有流的时候怎么处理数据:
- `fs.readFileSync` 同步读取文件,程序会阻塞,所有数据被读到内存
- `fs.readFile` 阻止程序阻塞,但仍会将文件所有数据读取到内存中
- 希望少内存读取大文件,读取一个数据块到内存处理完再去索取更多的数据
### 流的类型
- 内置:许多核心模块都实现了流接口,如 `fs.createReadStream`
- HTTP:处理网络技术的流
- 解释器:第三方模块 XML、JSON 解释器
- 浏览器:Node 流可以被拓展使用在浏览器
- Audio:流接口的声音模块
- RPC(远程调用):通过网络发送流是进程间通信的有效方式
- 测试:使用流的测试库
## 使用内建流 API
### 静态 web 服务器
想要通过网络高效且支持大文件的发送一个文件到一个客户端。
#### 不使用流
```js
const http = require('http');
const fs = require('fs');
http.createServer((req, res) => {
fs.readFile(`${__dirname}/index.html`, (err, data) => {
if (err) {
res.statusCode = 500;
res.end(String(err));
return;
}
res.end(data);
});
}).listen(8000);
```
#### 使用流
```js
const http = require('http');
const fs = require('fs');
http.createServer((req, res) => {
fs.createReadStream(`${__dirname}/index.html`).pipe(res);
}).listen(8000);
```
- 更少代码,更加高效
- 提供一个缓冲区发送到客户端
#### 使用流 + gzip
```js
const http = require('http');
const fs = require('fs');
const zlib = require('zlib');
http.createServer((req, res) => {
res.writeHead(200, {
'content-encoding': 'gzip',
});
fs.createReadStream(`${__dirname}/index.html`)
.pipe(zlib.createGzip())
.pipe(res);
}).listen(8000);
```
### 流的错误处理
```js
const fs = require('fs');
const stream = fs.createReadStream('not-found');
stream.on('error', (err) => {
console.trace();
console.error('Stack:', err.stack);
console.error('The error raised was:', err);
});
```
## 使用流基类
### 可读流 - JSON 行解析器
可读流被用来为 I/O 源提供灵活的 API,也可以被用作解析器:
- 继承自 steam.Readable 类
- 并实现一个 `_read(size)` 方法
json-lines.txt
```
{ "position": 0, "letter": "a" }
{ "position": 1, "letter": "b" }
{ "position": 2, "letter": "c" }
{ "position": 3, "letter": "d" }
{ "position": 4, "letter": "e" }
{ "position": 5, "letter": "f" }
{ "position": 6, "letter": "g" }
{ "position": 7, "letter": "h" }
{ "position": 8, "letter": "i" }
{ "position": 9, "letter": "j" }
```
JSONLineReader.js
```js
const stream = require('stream');
const fs = require('fs');
const util = require('util');
class JSONLineReader extends stream.Readable {
constructor(source) {
super();
this._source = source;
this._foundLineEnd = false;
this._buffer = '';
source.on('readable', () => {
this.read();
});
}
// 所有定制 stream.Readable 类都需要实现 _read 方法
_read(size) {
let chunk;
let line;
let result;
if (this._buffer.length === 0) {
chunk = this._source.read();
this._buffer += chunk;
}
const lineIndex = this._buffer.indexOf('\n');
if (lineIndex !== -1) {
line = this._buffer.slice(0, lineIndex); // 从 buffer 的开始截取第一行来获取一些文本进行解析
if (line) {
result = JSON.parse(line);
this._buffer = this._buffer.slice(lineIndex + 1);
this.emit('object', result); // 当一个 JSON 记录解析出来的时候,触发一个 object 事件
this.push(util.inspect(result)); // 将解析好的 SJON 发回内部队列
} else {
this._buffer = this._buffer.slice(1);
}
}
}
}
const input = fs.createReadStream(`${__dirname}/json-lines.txt`, {
encoding: 'utf8',
});
const jsonLineReader = new JSONLineReader(input); // 创建一个 JSONLineReader 实例,传递一个文件流给它处理
jsonLineReader.on('object', (obj) => {
console.log('pos:', obj.position, '- letter:', obj.letter);
});
```
### 可写流 - 文字变色
可写的流可用于输出数据到底层 I/O:
- 继承自 `stream.Writable`
- 实现一个 `_write` 方法向底层源数据发送数据
```sh
cat json-lines.txt | node stram_writable.js
```
stram_writable.js
```js
const stream = require('stream');
class GreenStream extends stream.Writable {
constructor(options) {
super(options);
}
_write(chunk, encoding, cb) {
process.stdout.write(`\u001b[32m${chunk}\u001b[39m`);
cb();
}
}
process.stdin.pipe(new GreenStream());
```
### 双工流 - 接受和转换数据
双工流允许发送和接受数据:
- 继承自 `stream.Duplex`
- 实现 `_read` 和 `_write` 方法
### 转换流 - 解析数据
使用流改变数据为另一种格式,并且高效地管理内存:
- 继承自 `stream.Transform`
- 实现 `_transform` 方法
## 测试流
使用 Node 内置的断言模块测试
```js
const assert = require('assert');
const fs = require('fs');
const CSVParser = require('./csvparser');
const parser = new CSVParser();
const actual = [];
fs.createReadStream(`${__dirname}/sample.csv`)
.pipe(parser);
process.on('exit', function () {
actual.push(parser.read());
actual.push(parser.read());
actual.push(parser.read());
const expected = [
{ name: 'Alex', location: 'UK', role: 'admin' },
{ name: 'Sam', location: 'France', role: 'user' },
{ name: 'John', location: 'Canada', role: 'user' },
];
assert.deepEqual(expected, actual);
});
```
# 文件系统
## fs 模块交互
- POSIX 文件 I/O
- 文件流
- 批量文件 I/O
- 文件监控
## POSIX 文件系统
| fs 方法 | 描述 |
| :----------- | :--------------------------------------------------------- |
| fs.truncate | 截断或者拓展文件到制定的长度 |
| fs.ftruncate | 和 truncate 一样,但将文件描述符作为参数 |
| fs.chown | 改变文件的所有者以及组 |
| fs.fchown | 和 chown 一样,但将文件描述符作为参数 |
| fs.lchown | 和 chown 一样,但不解析符号链接 |
| fs.stat | 获取文件状态 |
| fs.lstat | 和 stat 一样,但是返回信息是关于符号链接而不是它指向的内容 |
| fs.fstat | 和 stat 一样,但将文件描述符作为参数 |
| fs.link | 创建一个硬链接 |
| fs.symlink | 创建一个软连接 |
| fs.readlink | 读取一个软连接的值 |
| fs.realpath | 返回规范的绝对路径名 |
| fs.unlink | 删除文件 |
| fs.rmdir | 删除文件目录 |
| fs.mkdir | 创建文件目录 |
| fs.readdir | 读取一个文件目录的内容 |
| fs.close | 关闭一个文件描述符 |
| fs.open | 打开或者创建一个文件用来读取或者写入 |
| fs.utimes | 设置文件的读取和修改时间 |
| fs.futimes | 和 utimes 一样,但将文件描述符作为参数 |
| fs.fsync | 同步磁盘中的文件数据 |
| fs.write | 写入数据到一个文件 |
| fs.read | 读取一个文件的数据 |
```js
const fs = require('fs');
const assert = require('assert');
const fd = fs.openSync('./file.txt', 'w+');
const writeBuf = new Buffer('some data to write');
fs.writeSync(fd, writeBuf, 0, writeBuf.length, 0);
const readBuf = new Buffer(writeBuf.length);
fs.readSync(fd, readBuf, 0, writeBuf.length, 0);
assert.equal(writeBuf.toString(), readBuf.toString());
fs.closeSync(fd);
```
## 读写流
```js
const fs = require('fs');
const readable = fs.createReadStream('./original.txt');
const writeable = fs.createWriteStream('./copy.txt');
readable.pipe(writeable);
```
## 文件监控
`fs.watchFile` 比 `fs.watch` 低效,但更好用。
## 同步读取与 require
同步 fs 的方法应该在第一次初始化应用的时候使用。
```js
const fs = require('fs');
const config = JSON.parse(fs.readFileSync('./config.json').toString());
init(config);
```
require:
```js
const config = require('./config.json);
init(config);
```
- 模块会被全局缓冲,其他文件也加载并修改,会影响到整个系统加载了此文件的模块
- 可以通过 `Object.freeze` 来冻结一个对象
## 文件描述
文件描述是在操作系统中管理的在进程中打开文件所关联的一些数字或者索引。操作系统通过指派一个唯一的整数给每个打开的文件用来查看关于这个文件
| Stream | 文件描述 | 描述 |
| ------ | -------- | -------- |
| stdin | 0 | 标准输入 |
| stdout | 1 | 标准输出 |
| stderr | 2 | 标准错误 |
`console.log('Log')` 是 `process.stdout.write('log')` 的语法糖。
一个文件描述是 open 以及 openSync 方法调用返回的一个数字
```js
const fd = fs.openSync('myfile', 'a');
console.log(typeof fd === 'number'); // true
```
## 文件锁
协同多个进程同时访问一个文件,保证文件的完整性以及数据不能丢失:
- 强制锁(在内核级别执行)
- 咨询锁(非强制,只在涉及到进程订阅了相同的锁机制)
- `node-fs-ext` 通过 `flock` 锁住一个文件
- 使用锁文件
- 进程 A 尝试创建一个锁文件,并且成功了
- 进程 A 已经获得了这个锁,可以修改共享的资源
- 进程 B 尝试创建一个锁文件,但失败了,无法修改共享的资源
Node 实现锁文件
- 使用独占标记创建锁文件
- 使用 mkdir 创建锁文件
### 独占标记
```js
// 所有需要打开文件的方法,fs.writeFile、fs.createWriteStream、fs.open 都有一个 x 标记
// 这个文件应该已独占打开,若这个文件存在,文件不能被打开
fs.open('config.lock', 'wx', (err) => {
if (err) { return console.err(err); }
});
// 最好将当前进程号写进文件锁中
// 当有异常的时候就知道最后这个锁的进程
fs.writeFile(
'config.lock',
process.pid,
{ flogs: 'wx' },
(err) => {
if (err) { return console.error(err) };
},
);
```
### mkdir 文件锁
独占标记有个问题,可能有些系统不能识别 `0_EXCL` 标记。另一个方案是把锁文件换成一个目录,PID 可以写入目录中的一个文件。
```js
fs.mkidr('config.lock', (err) => {
if (err) { return console.error(err); }
fs.writeFile(`/config.lock/${process.pid}`, (err) => {
if (err) { return console.error(err); }
});
});
```
### lock 模块实现
https://github.com/npm/lockfile
```js
const fs = require('fs');
const lockDir = 'config.lock';
let hasLock = false;
exports.lock = function (cb) { // 获取锁
if (hasLock) { return cb(); } // 已经获取了一个锁
fs.mkdir(lockDir, function (err) {
if (err) { return cb(err); } // 无法创建锁
fs.writeFile(lockDir + '/' + process.pid, function (err) { // 把 PID写入到目录中以便调试
if (err) { console.error(err); } // 无法写入 PID,继续运行
hasLock = true; // 锁创建了
return cb();
});
});
};
exports.unlock = function (cb) { // 解锁方法
if (!hasLock) { return cb(); } // 如果没有需要解开的锁
fs.unlink(lockDir + '/' + process.pid, function (err) {
if (err) { return cb(err); }
fs.rmdir(lockDir, function (err) {
if (err) return cb(err);
hasLock = false;
cb();
});
});
};
process.on('exit', function () {
if (hasLock) {
fs.unlinkSync(lockDir + '/' + process.pid); // 如果还有锁,在退出之前同步删除掉
fs.rmdirSync(lockDir);
console.log('removed lock');
}
});
```
## 递归文件操作
一个线上库:[mkdirp](https://github.com/substack/node-mkdirp)
递归:要解决我们的问题就要先解决更小的相同的问题。
```
dir-a
├── dir-b
│ ├── dir-c
│ │ ├── dir-d
│ │ │ └── file-e.png
│ │ └── file-e.png
│ ├── file-c.js
│ └── file-d.txt
├── file-a.js
└── file-b.txt
```
查找模块:`find /asset/dir-a -name="file.*"`
```js
[
'dir-a/dir-b/dir-c/dir-d/file-e.png',
'dir-a/dir-b/dir-c/file-e.png',
'dir-a/dir-b/file-c.js',
'dir-a/dir-b/file-d.txt',
'dir-a/file-a.js',
'dir-a/file-b.txt',
]
```
```js
const fs = require('fs');
const join = require('path').join;
// 同步查找
exports.findSync = function (nameRe, startPath) {
const results = [];
function finder(path) {
const files = fs.readdirSync(path);
for (let i = 0; i < files.length; i++) {
const fpath = join(path, files[i]);
const stats = fs.statSync(fpath);
if (stats.isDirectory()) { finder(fpath); }
if (stats.isFile() && nameRe.test(files[i])) {
results.push(fpath);
}
}
}
finder(startPath);
return results;
};
// 异步查找
exports.find = function (nameRe, startPath, cb) { // cb 可以传入 console.log,灵活
const results = [];
let asyncOps = 0; // 2
function finder(path) {
asyncOps++;
fs.readdir(path, function (er, files) {
if (er) { return cb(er); }
files.forEach(function (file) {
const fpath = join(path, file);
asyncOps++;
fs.stat(fpath, function (er, stats) {
if (er) { return cb(er); }
if (stats.isDirectory()) finder(fpath);
if (stats.isFile() && nameRe.test(file)) {
results.push(fpath);
}
asyncOps--;
if (asyncOps == 0) {
cb(null, results);
}
});
});
asyncOps--;
if (asyncOps == 0) {
cb(null, results);
}
});
}
finder(startPath);
};
console.log(exports.findSync(/file.*/, `${__dirname}/dir-a`));
console.log(exports.find(/file.*/, `${__dirname}/dir-a`, console.log));
```
## 监视文件和文件夹
想要监听一个文件或者目录,并在文件更改后执行一个动作。
```js
const fs = require('fs');
fs.watch('./watchdir', console.log); // 稳定且快
fs.watchFile('./watchdir', console.log); // 跨平台
```
## 逐行地读取文件流
```js
const fs = require('fs');
const readline = require('readline');
const rl = readline.createInterface({
input: fs.createReadStream('/etc/hosts'),
crlfDelay: Infinity
});
rl.on('line', (line) => {
console.log(`cc ${line}`);
const extract = line.match(/(\d+\.\d+\.\d+\.\d+) (.*)/);
});
```
# 网络
## 获取本地 IP
```js
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
```js
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` 发送数据。
### 文件发送服务
```js
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
```js
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) { // 创建请求