-
Notifications
You must be signed in to change notification settings - Fork 0
/
search.xml
1871 lines (901 loc) · 461 KB
/
search.xml
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
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>维护 Nginx 时,什么时候应该用 reload,什么时候应该用 restart?</title>
<link href="/2024/10/06/when-to-restart-and-not-reload-nginx/"/>
<url>/2024/10/06/when-to-restart-and-not-reload-nginx/</url>
<content type="html"><![CDATA[<blockquote><p>本文是“攻玉计划”的一部分,翻译自 <a href="https://stackoverflow.com/questions/13525465/when-to-restart-and-not-reload-nginx/20215497">https://stackoverflow.com/questions/13525465/when-to-restart-and-not-reload-nginx/20215497</a></p></blockquote><h2 id="问题描述"><a href="#问题描述" class="headerlink" title="问题描述"></a>问题描述</h2><p>使用 Nginx 时,什么情况下 reload 无法满足需求,而必须要使用 restart 命令呢?</p><p>如果 Nginx 进程占用了过多内存,是不是应该用 restart 来重启?如果修改了 Nginx 的核心配置,或者某些插件的配置,是不是也需要使用 restart 命令?</p><p>修改 Nginx 配置后,用户既可以使用 restart 也可以使用 reload 来使配置生效,见 Ubuntu 上 <code>/etc/init.d/nginx -h</code> 的输出。</p><p>那么,哪种方案更优呢?</p><h2 id="回答-1"><a href="#回答-1" class="headerlink" title="回答 1"></a>回答 1</h2><p>reload 比 restart 更安全,因为如果使用 reload 命令,在旧的进程终止之前,Nginx 会先解析配置文件,如果配置文件有问题,那么就会退出重启流程。</p><p>也就是说,如果你的配置文件有问题,比如存在语法错误,那么使用 restart 命令后,Nginx 会先停止,然后就无法再启动了。</p><p>reload 命令如果成功执行,同样会终止旧的进程,所以如果存在内存泄漏问题,也一样可以清除掉。</p><h2 id="回答-2-amp-3"><a href="#回答-2-amp-3" class="headerlink" title="回答 2 & 3"></a>回答 2 & 3</h2><p>我遇到过一种情形,如果我修改了监听的 IP 地址,也就是配置文件里的 listen 字段,那就必须使用 restart 命令。</p><p>从 1.6.x 版本开始,如果仅把监听的 IP 地址从一个改为另一个,那么 reload 可以生效,但如果把监听地址从 <code>listen *:80</code> 修改为 <code>listen x.x.x.x:80</code>,依然需要 restart。</p><p>我只验证了 IPv4 的场景,IPv6 应该类似。</p><blockquote><p>译者按:译者今天遇到过一样的问题,我修改了 <code>listen 80; </code> 到 <code>listen 172.0.0.1:80</code>,结果发现 reload 不生效,必须 restart。</p></blockquote>]]></content>
<categories>
<category> 攻玉计划 </category>
</categories>
<tags>
<tag> Nginx </tag>
<tag> Linux </tag>
</tags>
</entry>
<entry>
<title>批量修改 qbittorrent-nox 内种子的 tracker 地址</title>
<link href="/2024/09/13/batch-edit-tracker-urls-in-qbittorrent-nox/"/>
<url>/2024/09/13/batch-edit-tracker-urls-in-qbittorrent-nox/</url>
<content type="html"><![CDATA[<h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>馒头 PT 站的默认 tracker 出了问题,我到现在都没搞懂是被墙了还是我自己设备的问题,反正就是无法访问默认的 .cc 域名,但 .io 的域名是可以的。</p><p>但手动修改 .cc 倒 .io ,也太麻烦了,我有上百个种子。</p><p>我是在一台 Ubuntu 服务器上运行的原版 qbittorrent-nox,用 webui 访问。</p><h2 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h2><p>qbittorrent-nox 会在当前用户的家目录中的 <code>~/.local/share/qBittorrent/BT_backup</code> 目录内存放所有正在使用的种子(虽然我也不清楚为啥名字里有 <code>backup</code>),vim 看了一眼,就是很正常的种子文件格式。</p><p>所以,理论上,直接批量替换这些种子文件里面的 tracker URL,就能解决问题。</p><p>说干就干,<strong>先停掉 qb 的服务,然后备份 <code>BT_backup</code>,再在 <code>BT_backup</code> 内执行 <code>find . -type f -exec sed -i 's/example.cc/example.io/' {} \;</code> ,最后重启 qb 服务</strong>,果然解决问题。</p>]]></content>
<categories>
<category> TECHNOLOGY </category>
</categories>
<tags>
<tag> Linux </tag>
<tag> qbittorrent </tag>
<tag> PT </tag>
</tags>
</entry>
<entry>
<title>把 vim 的缩进设为 4 个字符,并且 tab 自动转空格</title>
<link href="/2024/05/07/vim-set-auto-indent-to-4-spaces/"/>
<url>/2024/05/07/vim-set-auto-indent-to-4-spaces/</url>
<content type="html"><![CDATA[<p>修改本用户的 <code>~/.vimrc</code> 文件,添加以下内容。如果要想 sudo vim 也生效,那 <code>/root/.vimrc</code> 也要改。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">filetype plugin indent on</span><br><span class="line">" show existing tab with 4 spaces width</span><br><span class="line">set tabstop=4</span><br><span class="line">" when indenting with '>', use 4 spaces width</span><br><span class="line">set shiftwidth=4</span><br><span class="line">" On pressing tab, insert 4 spaces</span><br><span class="line">set expandtab</span><br></pre></td></tr></table></figure><p>上面的注释已经解释了含义。</p>]]></content>
<categories>
<category> TECHNOLOGY </category>
</categories>
<tags>
<tag> Linux </tag>
<tag> Vim </tag>
</tags>
</entry>
<entry>
<title>不用 snap,在 Ubuntu 上安装 certbot</title>
<link href="/2024/05/07/Ubuntu-install-certbot-without-snap/"/>
<url>/2024/05/07/Ubuntu-install-certbot-without-snap/</url>
<content type="html"><![CDATA[<blockquote><p>Certbot 官网居然只提供 snap 方式安装,而 snap 是我在 Ubuntu 上最不喜欢的东西</p></blockquote><p>那就直接用 pip 安装吧~</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo apt install certbot python3-certbot-nginx</span><br></pre></td></tr></table></figure>]]></content>
<categories>
<category> TECHNOLOGY </category>
</categories>
<tags>
<tag> 网络安全 </tag>
<tag> Linux </tag>
<tag> Ubuntu </tag>
</tags>
</entry>
<entry>
<title>在 Ubuntu 中启用 swap</title>
<link href="/2024/05/07/Ubuntu-enable-swap-file/"/>
<url>/2024/05/07/Ubuntu-enable-swap-file/</url>
<content type="html"><![CDATA[<p>首先,swap 多大比较好?如果你有一个 2G 内存的服务器,偶尔内存有点吃紧,那就再开 2G 的 swap 吧。其他的情况,随缘。如果内存不紧张,就不用开。</p><p>依次执行:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">sudo fallocate -l 2G /swapfile <span class="comment"># 在根目录下创建一个 2G 大小的 swap 文件</span></span><br><span class="line">sudo <span class="built_in">chmod</span> 600 /swapfile <span class="comment"># 修改权限配置</span></span><br><span class="line">sudo mkswap /swapfile <span class="comment"># 把这个文件作为 swap</span></span><br><span class="line">sudo swapon /swapfile <span class="comment"># 启用 swap</span></span><br><span class="line"></span><br><span class="line">sudo vim /etc/fstab <span class="comment"># 编辑 fstab 以自动挂载 swap 文件</span></span><br><span class="line"><span class="comment"># 添加以下内容</span></span><br><span class="line">/swapfile swap swap defaults 0 0</span><br><span class="line"></span><br><span class="line">sudo swapon --show <span class="comment"># 查看 swap 是否已经开启成功</span></span><br></pre></td></tr></table></figure>]]></content>
<categories>
<category> TECHNOLOGY </category>
</categories>
<tags>
<tag> Linux </tag>
<tag> Ubuntu </tag>
</tags>
</entry>
<entry>
<title>让 Nginx 反向代理的程序获取客户端真实 IP</title>
<link href="/2024/05/07/Nginx-get-real-ip/"/>
<url>/2024/05/07/Nginx-get-real-ip/</url>
<content type="html"><![CDATA[<p>Nginx 配置添加以下内容:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">location / {</span><br><span class="line"> proxy_pass http://127.0.0.1:8000;</span><br><span class="line"> proxy_set_header X-Real-IP $remote_addr;</span><br><span class="line"> proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>其含义就是在反代时,在客户端发起的请求报文上添加 <code>X-Real-IP</code> 和 <code>X-Forwarded-For</code> 两个 HTTP 头。<code>X-Real-IP</code> 表示表观客户端地址,<code>X-Forwarded-For</code>,顾名思义,就是“为谁代理”的意思,这个可以用来嵌套式传输客户端真实地址。</p><p>被代理的服务,只要能正确解析这两个头,就能获得客户端真实地址。</p>]]></content>
<categories>
<category> TECHNOLOGY </category>
</categories>
<tags>
<tag> Nginx </tag>
</tags>
</entry>
<entry>
<title>在 Linux 中显示所有正在监听的 TCP 端口</title>
<link href="/2024/05/07/Linux-show-all-listening-ports/"/>
<url>/2024/05/07/Linux-show-all-listening-ports/</url>
<content type="html"><![CDATA[<h2 id="netstat"><a href="#netstat" class="headerlink" title="netstat"></a>netstat</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo netstat -tulpn | grep LISTEN</span><br></pre></td></tr></table></figure><p>其中 <code>-t</code> 表示显示 TCP,<code>-u</code> 表示显示 UDP,<code>-l</code> 表示显示监听的端口,<code>-p</code> 表示显示对应的程序名,<code>-n</code> 表示不去查询 IP 对应的主机名。</p><h2 id="lsof"><a href="#lsof" class="headerlink" title="lsof"></a>lsof</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo lsof -i -P -n | grep LISTEN</span><br></pre></td></tr></table></figure><p>其中 <code>-i</code> 表示显示 IP 协议,<code>-P</code> 表示把端口号保留为数字形式,<code>-n</code> 表示不去查询 IP 对应的主机名。</p>]]></content>
<categories>
<category> TECHNOLOGY </category>
</categories>
<tags>
<tag> Linux </tag>
<tag> 网络通信 </tag>
</tags>
</entry>
<entry>
<title>再一次理解 C++ 中的 extern "C"</title>
<link href="/2023/07/03/learn-extern-c-in-cpp-again/"/>
<url>/2023/07/03/learn-extern-c-in-cpp-again/</url>
<content type="html"><![CDATA[<blockquote><p>本文是“攻玉计划”的一部分,翻译自 <a href="https://stackoverflow.com/questions/1041866/what-is-the-effect-of-extern-c-in-c">https://stackoverflow.com/questions/1041866/what-is-the-effect-of-extern-c-in-c</a> 中 Ciro Santilli 的回答</p></blockquote><h2 id="通过反汇编了解-extern-“C”-的作用"><a href="#通过反汇编了解-extern-“C”-的作用" class="headerlink" title="通过反汇编了解 extern “C” 的作用"></a>通过反汇编了解 extern “C” 的作用</h2><p>main.cpp</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">f</span><span class="params">()</span> </span>{}</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">g</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">extern</span> <span class="string">"C"</span> {</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">ef</span><span class="params">()</span> </span>{}</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">eg</span><span class="params">()</span></span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">/* Prevent g and eg from being optimized away. */</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">h</span><span class="params">()</span> </span>{ <span class="built_in">g</span>(); <span class="built_in">eg</span>(); }</span><br></pre></td></tr></table></figure><p>将上述代码编译为 <a href="https://stackoverflow.com/questions/26294034/how-to-make-an-executable-elf-file-in-linux-using-a-hex-editor/30648229#30648229">ELF</a> 格式的二进制,然后反汇编:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">g++ -c -std=c++11 -Wall -Wextra -pedantic -o main.o main.cpp</span><br><span class="line">readelf -s main.o</span><br></pre></td></tr></table></figure><p>摘取一部分输出:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"> 8: 0000000000000000 7 FUNC GLOBAL DEFAULT 1 _Z1fv</span><br><span class="line"> 9: 0000000000000007 7 FUNC GLOBAL DEFAULT 1 ef</span><br><span class="line">10: 000000000000000e 17 FUNC GLOBAL DEFAULT 1 _Z1hv</span><br><span class="line">11: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _GLOBAL_OFFSET_TABLE_</span><br><span class="line">12: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _Z1gv</span><br><span class="line">13: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND eg</span><br></pre></td></tr></table></figure><p>可见:</p><ul><li><code>ef</code> 和 <code>eg</code> 在符号表中的名称与其在原先代码中的名称一致</li><li>其他符号都被修饰过了,我们可以用 <code>c++filt</code> 工具还原其本来的样子:</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">$ c++filt _Z1fv</span><br><span class="line">f()</span><br><span class="line">$ c++filt _Z1hv</span><br><span class="line">h()</span><br><span class="line">$ c++filt _Z1gv</span><br><span class="line">g()</span><br></pre></td></tr></table></figure><p>所以,在以下两种情况下,我们需要使用 <code>extern "C"</code>:</p><ul><li>在 C++ 中调用 C 代码:告诉 <code>g++</code> 可能会遇到由 <code>gcc</code> 生成的未修饰过的符号名</li><li>在 C 中调用 C++ 代码:让 <code>g++</code> 生成未修饰的符号名,以供 <code>gcc</code> 调用</li></ul><h2 id="某些不能在-extern-“C”-中使用的代码"><a href="#某些不能在-extern-“C”-中使用的代码" class="headerlink" title="某些不能在 extern “C” 中使用的代码"></a>某些不能在 extern “C” 中使用的代码</h2><p>显然,任何需要使用 C++ 名称修饰的语言特性,都不能写在 <code>extern "C"</code> 中:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">extern</span> <span class="string">"C"</span> {</span><br><span class="line"> <span class="comment">// 函数重载</span></span><br><span class="line"> <span class="comment">// 报错:f 的声明有冲突</span></span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">f</span><span class="params">()</span></span>;</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">f</span><span class="params">(<span class="type">int</span> i)</span></span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 模板</span></span><br><span class="line"> <span class="comment">// 报错:模板不能用于 C 的链接</span></span><br><span class="line"> <span class="keyword">template</span> <<span class="keyword">class</span> <span class="title class_">C</span>> <span class="function"><span class="type">void</span> <span class="title">f</span><span class="params">(C i)</span> </span>{ }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="在-C-中调用-C-代码的最小可运行代码样例"><a href="#在-C-中调用-C-代码的最小可运行代码样例" class="headerlink" title="在 C++ 中调用 C 代码的最小可运行代码样例"></a>在 C++ 中调用 C 代码的最小可运行代码样例</h2><p>在 C++ 中调用 C 代码很简单:每个 C 函数都只有一个未修饰的符号名,所以不需要额外的操作。</p><p>main.cpp</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><cassert></span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">"c.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="built_in">assert</span>(<span class="built_in">f</span>() == <span class="number">1</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>c.h</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">ifndef</span> C_H</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> C_H</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 这里,ifdef 可以让这个头文件既可以用于 C++ 工程,也能用于 C 工程,</span></span><br><span class="line"><span class="comment"> * 因为 C 标准里面没有 extern "C" 的定义 */</span></span><br><span class="line"><span class="meta">#<span class="keyword">ifdef</span> __cplusplus</span></span><br><span class="line"><span class="keyword">extern</span> <span class="string">"C"</span> {</span><br><span class="line"><span class="meta">#<span class="keyword">endif</span></span></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">f</span><span class="params">()</span></span>;</span><br><span class="line"><span class="meta">#<span class="keyword">ifdef</span> __cplusplus</span></span><br><span class="line">}</span><br><span class="line"><span class="meta">#<span class="keyword">endif</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">endif</span></span></span><br></pre></td></tr></table></figure><p>c.c</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">"c.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">f</span><span class="params">(<span class="type">void</span>)</span> { <span class="keyword">return</span> <span class="number">1</span>; }</span><br></pre></td></tr></table></figure><p>运行:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">g++ -c -o main.o -std=c++98 main.cpp</span><br><span class="line">gcc -c -o c.o -std=c89 c.c</span><br><span class="line">g++ -o main.out main.o c.o</span><br><span class="line">./main.out</span><br></pre></td></tr></table></figure><p>如果没有 <code>extern "C"</code> 的话,链接器会报错:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">main.cpp:6: undefined reference to `f()'</span><br></pre></td></tr></table></figure><p>因为 <code>g++</code> 会寻找一个修饰过的 <code>f</code>,但是 <code>gcc</code> 并不会编译出修饰过的符号名。</p><h2 id="在-C-中调用-C-代码的最小可运行代码样例-1"><a href="#在-C-中调用-C-代码的最小可运行代码样例-1" class="headerlink" title="在 C 中调用 C++ 代码的最小可运行代码样例"></a>在 C 中调用 C++ 代码的最小可运行代码样例</h2><p>在 C 中调用 C++ 代码稍微困难一点:我们需要手动管理所有暴露给 C 的函数接口,并且使它们在编译时不被修饰。</p><p>代码如下:</p><p>main.c</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><assert.h></span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">"cpp.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">(<span class="type">void</span>)</span> {</span><br><span class="line"> assert(f_int(<span class="number">1</span>) == <span class="number">2</span>);</span><br><span class="line"> assert(f_float(<span class="number">1.0</span>) == <span class="number">3</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>cpp.h</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">ifndef</span> CPP_H</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> CPP_H</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">ifdef</span> __cplusplus</span></span><br><span class="line"><span class="comment">// 这两个重载的函数不能暴露给 C 的编译群,否则会报错</span></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">f</span><span class="params">(<span class="type">int</span> i)</span></span>;</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">f</span><span class="params">(<span class="type">float</span> i)</span></span>;</span><br><span class="line"><span class="keyword">extern</span> <span class="string">"C"</span> {</span><br><span class="line"><span class="meta">#<span class="keyword">endif</span></span></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">f_int</span><span class="params">(<span class="type">int</span> i)</span></span>;</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">f_float</span><span class="params">(<span class="type">float</span> i)</span></span>;</span><br><span class="line"><span class="meta">#<span class="keyword">ifdef</span> __cplusplus</span></span><br><span class="line">}</span><br><span class="line"><span class="meta">#<span class="keyword">endif</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">endif</span></span></span><br></pre></td></tr></table></figure><p>cpp.cpp</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">"cpp.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">f</span><span class="params">(<span class="type">int</span> i)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> i + <span class="number">1</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">f</span><span class="params">(<span class="type">float</span> i)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> i + <span class="number">2</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">f_int</span><span class="params">(<span class="type">int</span> i)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">f</span>(i);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">f_float</span><span class="params">(<span class="type">float</span> i)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">f</span>(i);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>运行:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">gcc -c -o main.o -std=c89 -Wextra main.c</span><br><span class="line">g++ -c -o cpp.o -std=c++98 cpp.cpp</span><br><span class="line">g++ -o main.out main.o cpp.o</span><br><span class="line">./main.out</span><br></pre></td></tr></table></figure><p>如果不加 <code>extern "C"</code> 的话,会报错:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">main.c:6: undefined reference to `f_int'</span><br><span class="line">main.c:7: undefined reference to `f_float'</span><br></pre></td></tr></table></figure><p>因为 <code>g++</code> 会生成修饰后的符号名,但 <code>gcc</code> 无法理解。</p><h2 id="当我在-C-中包含标准库-C-头文件的时候,extern-“C”-在哪?"><a href="#当我在-C-中包含标准库-C-头文件的时候,extern-“C”-在哪?" class="headerlink" title="当我在 C++ 中包含标准库 C 头文件的时候,extern “C” 在哪?"></a>当我在 C++ 中包含标准库 C 头文件的时候,extern “C” 在哪?</h2><ul><li>C++ 版本的 C 头文件,比如 <code>cstdio</code> 可能靠 <code>#pragma GCC system_header</code> 这个编译宏实现的,根据 <a href="https://gcc.gnu.org/onlinedocs/cpp/System-Headers.html">https://gcc.gnu.org/onlinedocs/cpp/System-Headers.html</a> :在某些平台,例如 RS/6000 AIX 上编译 C++ 代码时,GCC 会隐式地将所有系统头文件用 extern “C” 包含起来。但我并不完全确定。</li><li>POSIX 标准的头文件,比如 <code>/usr/include/unistd.h</code> 会使用 <code>__BEGIN_DECLS</code> 宏,而 <code>__BEGIN_DECLS</code> 会定义为 <code>extern "C" {</code>。详见 <a href="https://stackoverflow.com/questions/8087438/do-i-need-an-extern-c-block-to-include-standard-posix-c-headers/8087539#8087539">https://stackoverflow.com/questions/8087438/do-i-need-an-extern-c-block-to-include-standard-posix-c-headers/8087539#8087539</a>。</li></ul>]]></content>
<categories>
<category> TECHNOLOGY </category>
</categories>
<tags>
<tag> C </tag>
<tag> C++ </tag>
</tags>
</entry>
<entry>
<title>航模舵机控制及其 PWM 调制的进一步理解</title>
<link href="/2023/06/30/deep-into-RC-plane-s-servo-control-and-PWM/"/>
<url>/2023/06/30/deep-into-RC-plane-s-servo-control-and-PWM/</url>
<content type="html"><![CDATA[<p>起因是这样的,前段时间,我同时开始玩履带车和固定翼航模。履带车的动力是直流电机加驱动板,PWM 调制,0-100% 占空比控制电机从静止到全速转动。</p><p>航模的话,虽说十年前我就开始接触了,但一直都是浮于表面的玩,能动就行。玩航模必须要有遥控器和接收机,接收机也是输出 PWM 信号的,连接舵机就能控制其在指定角度范围内运动,连接电调就能控制电机转速。</p><p>于是,我想当然的,航模接收机 <del>应该也是输出 0-100% 占空比的 PWM 信号,0 就是舵机一个极限角度,50% 就是中位,100% 就是另一个极限角度。</del></p><p>然而事实并不是,我把履带车的驱动板接上航模的接收机,不管我怎么推拉摇杆,履带车始终以一个非常低的速度运动。</p><p>这就引起了我的好奇。</p><h2 id="先简单看下航模接收机的-PWM-信号图形"><a href="#先简单看下航模接收机的-PWM-信号图形" class="headerlink" title="先简单看下航模接收机的 PWM 信号图形"></a>先简单看下航模接收机的 PWM 信号图形</h2><p>手头没有示波器,就拿一个简单的逻辑分析仪应付一下:</p><p>我只测量 1 通道数据(通常是副翼/横滚通道)。</p><p>这是摇杆在中点:</p><p><img src="/static/blog_images/deep-into-RC-plane-s-servo-control-and-PWM/0.png"></p><p>摇杆在最左侧:</p><p><img src="/static/blog_images/deep-into-RC-plane-s-servo-control-and-PWM/1.png"></p><p>摇杆在最右侧:</p><p><img src="/static/blog_images/deep-into-RC-plane-s-servo-control-and-PWM/2.png"></p><p>真有趣,占空比始终在 5%~10% 左右。说明航模遥控器的 PWM 信号肯定不是简单靠占空比来控制舵机的。不然放着 10%-100% 这么大的空间不用,有点暴殄天物。</p><h2 id="航模-PWM-信号的真实解释"><a href="#航模-PWM-信号的真实解释" class="headerlink" title="航模 PWM 信号的真实解释"></a>航模 PWM 信号的真实解释</h2><p>我查了很多论坛,一开始感觉很奇怪,他们(比如 <a href="https://www.societyofrobots.com/robotforum/index.php?topic=4299.0">这个</a> )基本上只谈 PWM 的脉冲时间长度为 1-2ms,以及频率“一般”为 50Hz,却几乎不谈占空比。</p><p>我看了一会突然醒悟了,因为 PWM 不是本来就叫“脉冲宽度调制”吗?只不过以前我都用它来调光、控制电机,所以才关注占空比,但 PWM 本身应该去关注“宽度”啊。</p><p>所以,就像 <a href="https://electronics.stackexchange.com/questions/176739/why-do-rc-applications-use-such-a-small-pwm-duty-cycle">这里</a> 回答的一样,航模舵机本身并不关注占空比是多少,只关注每个周期内,高电平的时间是多长。1ms 就代表最小角度,2ms 就是最大角度,1.5ms 就是中位。</p><p>因为一般频率是 50Hz,也就是周期为 20ms,所以占空比看起来才是 5%-10%。</p><h2 id="更多的解释"><a href="#更多的解释" class="headerlink" title="更多的解释"></a>更多的解释</h2><p>至于为什么选择 50Hz,解释是:这个频率是舵机的工作频率而已,舵机要按照这个周期动作。太快舵机会吃不消,太慢的话,响应就慢了。</p><p>也有 200Hz 的舵机,但脉冲宽度也是 1-2ms,于是占空比就变成了 20%-40%。</p><p>当然也有部分舵机会用 0.5-1ms 以及 2-2.5ms 这个区间的脉冲宽度,以达到更大的舵量。</p><p>而对于电调而言,信号也是一样的,沿用舵机的信号就很方便。</p><p>另外为什么选择 1-2ms?因为很久以前的遥控器就这么规定了,不多说。</p>]]></content>
<categories>
<category> TECHNOLOGY </category>
</categories>
<tags>
<tag> 航模 </tag>
<tag> 舵机 </tag>
</tags>
</entry>
<entry>
<title>如何自签名带 SAN 字段的 SSL/TLS 证书</title>
<link href="/2023/01/20/self-signed-certificate-with-SAN/"/>
<url>/2023/01/20/self-signed-certificate-with-SAN/</url>
<content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>在本站之前一篇文章 <a href="https://blog.vvzero.com/2021/01/24/Become-a-CA-and-generate-self-signed-certificate/">如何成为 CA,并签发自己的证书</a> 中,我们介绍了如何做一个“正规”的自签名证书。</p><p>但是,这个方法对于现代的浏览器不太管用了,因为 Chrome、Firefox 等浏览器已经不再判定证书的 CN (Common Name) 字段与域名是否一致了,而是改用判定 SAN (Subject Alternative Name) 字段。具体为什么这么做以及 SAN 的含义,网上有很多解释,很重要但是这里不谈,只谈如何在自签名证书中正确配置 SAN 字段。</p><p>网上的教程有很多,但很多没有说清楚,或者缺少一些关键步骤和参数,导致实际情况下出各种问题,这里我尽量整理了一个完善的版本。</p><h2 id="准备步骤"><a href="#准备步骤" class="headerlink" title="准备步骤"></a>准备步骤</h2><p>首先,CA 的证书与文首提到的文章一致,如果之前配置过,那就不需要再生成了。</p><p>然后,生成待签发证书的私钥,这一步也与上述文章相同:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">openssl genrsa -out example.key 4096</span><br></pre></td></tr></table></figure><h2 id="关键在于-CSR-的创建"><a href="#关键在于-CSR-的创建" class="headerlink" title="关键在于 CSR 的创建"></a>关键在于 CSR 的创建</h2><p>之前创建 CSR 的时候会手动输各个字段,但其中不包含 SAN 字段,所以我们需要一个配置文件来配置 SAN 字段(当然直接包含在命令行里面也行,但很麻烦)。</p><p>建议新建一个目录叫 <code>certreq</code>,然后在里面新建一个文件叫 <code>req.conf</code>,内容如下:</p><figure class="highlight ini"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="section">[req]</span></span><br><span class="line"><span class="attr">distinguished_name</span> = req_distinguished_name</span><br><span class="line"><span class="attr">req_extensions</span> = req_ext</span><br><span class="line"><span class="attr">prompt</span> = <span class="literal">no</span></span><br><span class="line"></span><br><span class="line"><span class="section">[req_distinguished_name]</span></span><br><span class="line"><span class="attr">C</span> = <修改为实际值></span><br><span class="line"><span class="attr">ST</span> = <修改为实际值></span><br><span class="line"><span class="attr">L</span> = <修改为实际值></span><br><span class="line"><span class="attr">O</span> = <修改为实际值> </span><br><span class="line"><span class="attr">OU</span> = <修改为实际值></span><br><span class="line"><span class="attr">CN</span> = <修改为实际值></span><br><span class="line"></span><br><span class="line"><span class="section">[req_ext]</span></span><br><span class="line"><span class="attr">subjectAltName</span> = @alt_names</span><br><span class="line"></span><br><span class="line"><span class="section">[alt_names]</span></span><br><span class="line"><span class="comment"># 这里就是 SAN 字段,以下修改为实际值</span></span><br><span class="line"><span class="attr">DNS.1</span> = vvzero.com</span><br><span class="line"><span class="attr">DNS.2</span> = *.vvzero.com</span><br><span class="line"><span class="attr">IP.1</span> = <span class="number">1.1</span>.<span class="number">1.1</span></span><br><span class="line"><span class="attr">IP.2</span> = <span class="number">1.1</span>.<span class="number">1.2</span></span><br><span class="line"><span class="attr">IP.3</span> = <span class="number">1.1</span>.<span class="number">1.3</span></span><br></pre></td></tr></table></figure><p>然后生成 CSR 文件:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">openssl req -new -key example.key -out example.csr -config req.conf</span><br></pre></td></tr></table></figure><h2 id="检查-CSR-文件中是否存在-SAN-字段"><a href="#检查-CSR-文件中是否存在-SAN-字段" class="headerlink" title="检查 CSR 文件中是否存在 SAN 字段"></a>检查 CSR 文件中是否存在 SAN 字段</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">openssl req -noout -text -<span class="keyword">in</span> example.csr | grep -A 1 <span class="string">"Subject Alternative Name"</span></span><br></pre></td></tr></table></figure><p>如果打印出了你的配置,则代表创建成功。如果没有输出,请检查有没有操作错误。</p><h2 id="用-CA-的私钥签发证书"><a href="#用-CA-的私钥签发证书" class="headerlink" title="用 CA 的私钥签发证书"></a>用 CA 的私钥签发证书</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">openssl x509 -req -days 365 -<span class="keyword">in</span> example.csr -CA /path/to/CAPrivate.pem -CAkey /path/to/CAPrivate.key -CAcreateserial -out example.crt -extensions req_ext -extfile req.conf</span><br></pre></td></tr></table></figure><p>注意其中的 <code>-extensions req_ext -extfile req.conf</code> 是关键,很多教程没有加这个参数,导致生成的证书丢了 SAN 字段。</p><h2 id="验证证书的-SAN-字段"><a href="#验证证书的-SAN-字段" class="headerlink" title="验证证书的 SAN 字段"></a>验证证书的 SAN 字段</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">openssl x509 -text -noout -<span class="keyword">in</span> example.crt | grep -A 1 <span class="string">"Subject Alternative Name"</span></span><br></pre></td></tr></table></figure><p>同上,需要打印出正确的 SAN 配置,才算成功。</p>]]></content>
<categories>
<category> TECHNOLOGY </category>
</categories>
<tags>
<tag> 证书 </tag>
<tag> HTTPS </tag>
</tags>
</entry>
<entry>
<title>深入理解以太网网线原理</title>
<link href="/2022/09/18/Ethernet-Wiring/"/>
<url>/2022/09/18/Ethernet-Wiring/</url>
<content type="html"><![CDATA[<blockquote><p>译者按:大部分人都知道,百兆以太网只用了 RJ45 端口中的 2 对 4 根线,分别为 TX、RX 的差分信号。<br>千兆以太网用了 RJ45 端口中的全部 4 对 8 根线,但是这 4 对 8 根线是怎么定义的?哪些属于 TX,哪些属于 RX?<br>我也不知道,而且以前居然没有认真去了解过,所以我决定找一篇与此相关的文章翻译一下,分享给大家。</p></blockquote><blockquote><p>本文是“攻玉计划”的一部分,翻译自 <a href="https://www.practicalnetworking.net/stand-alone/ethernet-wiring/">https://www.practicalnetworking.net/stand-alone/ethernet-wiring/</a></p></blockquote><p>当我们谈到“以太网”的时候,我们可能会讨论各种概念,包括所有线缆规格(10BASE-T, 100BASE-TX, 1000BASE-T 等等)。这些协议规定了导线上的电平(即 0/1 信号)是如何传递的,也规定了如何将电平信号解析为数据帧。</p><p>本来,此文只想简单介绍一下交叉线和直通线之间的基本区别,但基于我们的 <a href="https://www.practicalnetworking.net/about/">原则</a>,我们觉得应该更深入一些。</p><p>首先,我们会先介绍一些术语,并消除一些歧义,然后回答一些基本的问题:我们为什么要用交叉线或者直通线?到底什么是双绞线?一个个比特位是如何在线上传播的?最后,我们会综合这些概念,并探讨一下千兆以太网的相关标准。</p><h2 id="术语解释"><a href="#术语解释" class="headerlink" title="术语解释"></a>术语解释</h2><p>即使你刚接触网络通信不久,也应该听说了很多网线相关的概念,例如“以太网”“双绞线”“RJ45”“屏蔽线”“非屏蔽线”等。</p><p>但这些概念代表了什么含义?互相之间又有什么异同?有没有什么概念被误用了?坦白而言,这些概念经常被误用,不妨看看:</p><h3 id="8P8C"><a href="#8P8C" class="headerlink" title="8P8C"></a>8P8C</h3><p>这是网线两端接口的物理标准,表示它有 8 个卡口位(Position)和 8 个触点(Contacts)。这也定义了此塑料透明接口的外形设计和尺寸。</p><p><img src="/static/blog_images/Ethernet-Wiring/1.png"></p><h3 id="RJ45"><a href="#RJ45" class="headerlink" title="RJ45"></a>RJ45</h3><p>标准插座接口(Registered Jack)第 45 号标准定义了线缆中导线的个数以及线序,并规定使用 8P8C 的物理接口。</p><p>特别地,RJ45 定义了两种线序标准:T568a 和 T568b:</p><p><img src="/static/blog_images/Ethernet-Wiring/2.png"></p><p>请注意,两个标准唯一的实际区别是第 2 对线和第 3 对线的颜色不同。</p><blockquote><p>很多人经常用 RJ45 来指代 8P8C 插口,但这是不对的。还有另一种叫 RJ61 的类似标准,也使用了 8P8C 的插口,但其内部的线序不一样。标准插座接口定义家族中还有很多其他 RJxx 的接口,但接线定义和物理尺寸都不一样。</p></blockquote><h3 id="双绞线"><a href="#双绞线" class="headerlink" title="双绞线"></a>双绞线</h3><p>双绞线是一种组合线缆,包含了 8 根独立的导线,其中每两根作为一对,每对的两根线互相绞绕在一起。由此得到 4 对导线,每对导线作为一个数据传输通道。</p><p>导线成对出现这一概念很重要,我们在后文中会讲到,简而言之,这有助于减少电磁干扰(EMI)。</p><p>通常,双绞线有两种规格:<strong>屏蔽线</strong>及<strong>非屏蔽线</strong>。</p><p><img src="/static/blog_images/Ethernet-Wiring/3.png"></p><p>注意,不管哪种规格,网线中都有 4 对导线,也就是 4 个独立的数据通道。</p><h3 id="非屏蔽线"><a href="#非屏蔽线" class="headerlink" title="非屏蔽线"></a>非屏蔽线</h3><p>非屏蔽线(Unshielded Twisted Pair)(UTP)在实际工程部署中更为常见。它对外部的电磁噪声没有额外的防护,但得益于双绞线的固有特性,其数据传输也非常可靠。我们将在后文详细阐述。</p><p>非屏蔽线更便宜,物理韧性更好,也更软。这些优点使得非屏蔽线在大多数场合更受欢迎。</p><h3 id="屏蔽线"><a href="#屏蔽线" class="headerlink" title="屏蔽线"></a>屏蔽线</h3><p>屏蔽线(Shielded Twisted Pair)(STP)在每对双绞线、以及全部 4 对导线最外侧都包有额外的金属屏蔽壳,这有助于隔离信号传输时的电磁噪声。</p><p>但同时,如果屏蔽壳的某个地方出现了破损,或者屏蔽壳在网线两端没有都良好接地,它自身可能会成为一个天线,并且会因为空间中随处可见的无线电波(比如 Wi-Fi 信号)而给信号传输带来额外的电磁噪声。</p><p>更为甚者,屏蔽线必须与带屏蔽的 8P8C 插头一起使用,才能实现全链路端到端的屏蔽功能。</p><p>显然,屏蔽线肯定更贵,也比非屏蔽线更脆弱,因为如果屏蔽线被过度弯曲的话,其屏蔽壳很容易破损。因此,屏蔽线的使用场合比非屏蔽线少得多。</p><p>屏蔽线通常只会用在对电磁屏蔽高度敏感的场合,例如,网线紧挨着发电机或者重型机械的输电线等。</p><h3 id="以太网"><a href="#以太网" class="headerlink" title="以太网"></a>以太网</h3><p>就像我们之前说的,以太网(Ethernet)是一系列标准的合集,其中之一就是不同的接线规格:10BASE-T,100BASE-TX,1000BASE-T 等等。</p><p>以太网协议也定义了每个比特(1 和 0)如何在线缆上传输,以及如何将这些比特流组合为有意义的数据帧。例如,以太网规定每帧数据的前 56 个比特必须是交替出现的 1 和 0(即“前导码”),接下来 8 个比特必须是 10101011(即帧起始标志),再接下来 48 个比特是目标 MAC 地址,然后是 48 个比特的源 MAC 地址……直到整个数据帧被全部传输完毕。</p><p>接下来,我们将讨论以太网标准中不同规格的接线规格。</p><h3 id="BASE-T-相关术语"><a href="#BASE-T-相关术语" class="headerlink" title="BASE T* 相关术语"></a>BASE T* 相关术语</h3><p>本节讲述的概念都与网线内部的导线如何使用相关。例如,哪些用来发送数据,哪些用来接收数据,如何发送信号,以及电压等级。</p><p>BASE T* 这一概念有三个组成部分,所以在我们讲述特定的标准之前,先来单独了解一下它们,以 100 BASE-T 为例:</p><h4 id="100BASE-T-中的“100”"><a href="#100BASE-T-中的“100”" class="headerlink" title="100BASE-T 中的“100”"></a>100BASE-T 中的“100”</h4><p>开头的数字表示网线每秒可以传输多少“兆(百万)”比特,即 Mbps。100Mbps 的网线理论上每秒可传输 100,000,000 个比特,大概每秒 12.5 兆字节(MBps),注意大写的 B 和小写的 b 分别代表字节和比特。</p><p>这一速率的网线有时被称为“快速以太网”,这是相较于 10Mbps 的“普通以太网”以及 1000Mbps 的“吉比特以太网”而言的。</p><h4 id="100BASE-T-中的“BASE”"><a href="#100BASE-T-中的“BASE”" class="headerlink" title="100BASE-T 中的“BASE”"></a>100BASE-T 中的“BASE”</h4><p>base 这个概念是“基带”(baseband)信号的缩写,对应的概念是“宽带”(broadband)信号。这些概念刚出现的时候,其区别是:基带在介质中传输数字信号,宽带在介质中传输模拟信号。</p><p>数字信号和模拟信号的区别在于其可被解析的值个数。</p><p>模拟信号可以表示无数种不同的值,例如,我们可以用一根线上某个特定的电压值来表示一个绿色的像素点,而另一个电压值来表示红色的像素点,以此类推,这样,这根线就能传输一张图片上的每一个像素点。</p><p>数字信号可以表示有限个不同的值,通常就两个:1 和 0。如果上述的图片用一根数字信号线来传输的话,我们会传输一系列 1 和 0 的信号流。接收端可以解析这些二进制数据为一系列数字,例如基于 <a href="https://zh.wikipedia.org/wiki/%E4%B8%89%E5%8E%9F%E8%89%B2%E5%85%89%E6%A8%A1%E5%BC%8F">RGB 颜色编码</a>,就能构造出每一个像素点。</p><p>也就是说,数字信号和模拟信号的主要区别就是,模拟信号线上可获得无穷多中不同的值,而数字信号线上,要么是 0,要么是 1,不可能出现第三种情况。</p><p>如此一来,数字信号传输具有更高的容错率,因为导线上的电压范围只被分为了两种情况(1 或者 0)。</p><blockquote><p>译者按:原文在此处举了更多的例子来详细阐述“模拟信号”和“数字信号”的区别,但译者认为过于冗余,故略去这部分篇幅。</p></blockquote><h4 id="100BASE-T-中的“-T”"><a href="#100BASE-T-中的“-T”" class="headerlink" title="100BASE-T 中的“-T”"></a>100BASE-T 中的“-T”</h4><p>“-T”表示其为双绞线(Twisted Pair)。相似的标准还有“-2”及“-5”,表示其是最大长度为 200 和 500 米的同轴电缆,以及“-SR”和“-LR”,表示其为短距离(Short Range)和长距离(Long Range)光纤。</p><p>以上解释了 BASE T* 相关术语的三个独立部分,我们现在可以探讨下快速以太网的两个重要规范(对于吉比特以太网的相关规范,我们会在后文继续探讨):</p><h4 id="100BASE-T4"><a href="#100BASE-T4" class="headerlink" title="100BASE-T4"></a>100BASE-T4</h4><p>100BASE-T4 使用了网线中全部 4 对 8 根线。其中一对仅仅用于发送信号(TX),一对仅仅用于接收信号(RX)。剩下两对既可以用于 RX 也可以用于 TX,这通过网线两端设备的协商来决定具体用途。</p><p>T4 是双绞线早期的标准之一,但由于其过于复杂且必要性不强,如今已很少使用。</p><h4 id="100BASE-TX"><a href="#100BASE-TX" class="headerlink" title="100BASE-TX"></a>100BASE-TX</h4><p>100BASE-TX 只使用了网线中的 2 对 4 根线,其中一对用于 TX,另一对用于 RX,剩下两根线没有使用。你完全可以做一根只有 4 根线的网线以实现 100BASE-TX 的所有功能,只要插口触点位置正确即可(位号1,2,3,6),但通常网线铺设过程中,另外 4 根线也保留了下来,用于占位,并适配未来可能的场景升级。</p><p>100BASE-TX (包括全部 8 根线)是如今最常用的快速以太网标准。但是,它通常被简写成了 100BASE-T。再强调一下,T 只表示其为双绞线,而 TX 才表示其使用了 1&2 及 3&6 两对线。</p><p>以上介绍可从实用性和技术性的角度帮读者理解相关概念。而在实际情况中,即使你不理解原理,直接使用这些产品也非常简单,就算犯一些小错误,也是允许的。</p><h2 id="为什么使用交叉线"><a href="#为什么使用交叉线" class="headerlink" title="为什么使用交叉线"></a>为什么使用交叉线</h2><p>网上能找到很多“交叉线”及“直通线”应用场景的相关教程,但他们一般很少解释其原理。本节我们会深入探讨一下相关概念。</p><p>100BASE-TX 及 10BASE-T 标准中定义的网线,都包含 8 根导线,两两以双绞线的形式结合为 4 对。</p><p>在这四对线中,实际只用到两对:第 2、3 对。每根线都是单工的介质,也就是说,信号只能按照指定的单方向传输。</p><p>为了实现全双工通讯,某对线将始终沿某个方向传输数据,而另一对线将始终沿相反的方向传输数据。</p><p><img src="/static/blog_images/Ethernet-Wiring/4.png"></p><p>网络接口卡(Network Interface Card)(NIC)的配置会决定哪对线用于发送数据,哪对线用于接收数据。</p><p>使用第 2 对线(1 号和 2 号引脚)发送数据(TX)、且使用第 3 对线(3 号和 6 号引脚)接收(RX)数据的的 NIC 被称作介质相关接口(Media Dependent Interface)(MDI),与之相反,使用第 3 对线作为 RX、第 2 对线作为 TX 的 NIC 被称为交叉模式介质相关接口(Media Dependent Interface Crossover)(MDI-X)。</p><h3 id="电脑之间直连通讯"><a href="#电脑之间直连通讯" class="headerlink" title="电脑之间直连通讯"></a>电脑之间直连通讯</h3><p>假设一台电脑使用 MDI 模式的 NIC ,那么它就总是用第 2 对线发送数据,用第 3 对线接收数据。但如果两台用网线连接在一起的电脑都用第 2 对线发送数据,那么就会产生冲突。与此同时,两台电脑也都无法从第 3 对线上接收到数据。</p><p>因此,网线对需要交叉一下,以便从一台电脑的第 2 对线发送的数据,会被另一台电脑的第 3 对线接收到,反之亦然。</p><p>下图是一个简单的示意(无需在意示意图中线的颜色,这只是为了区分两个不通的路径而已):</p><p><img src="/static/blog_images/Ethernet-Wiring/5.png"></p><p>注意,两台电脑都在独立的通道上发送数据,并且依靠交叉线机制(如图所示中间的 X),两台电脑都能接收到对方发送的数据。</p><p>因此,<strong>两台电脑直连后,必须使用交叉线才能通讯</strong>。</p><h3 id="电脑之间通过交换机通讯"><a href="#电脑之间通过交换机通讯" class="headerlink" title="电脑之间通过交换机通讯"></a>电脑之间通过交换机通讯</h3><p>交换机使得同一网络下两台电脑的通讯变得更简单。交换机的 NIC 都采用 MDI-X 标准,也就是说,交换机总是在第 3 对线上发送数据,在第 2 对线上接收数据(与电脑的 NIC 相反)。</p><p>也就是说,交换机内部有一个交叉的机制,网线本身也就不需要交叉了:</p><p><img src="/static/blog_images/Ethernet-Wiring/6.png"></p><p>可见,<strong>连接在交换机上的电脑可以直接使用直通线</strong>,让交换机处理线序交叉即可。端到端的通讯路径也是一样的:每个设备都在自己的 TX 线上发送数据,在 RX 线上接收数据。</p><h3 id="电脑之间通过两个串联的交换机"><a href="#电脑之间通过两个串联的交换机" class="headerlink" title="电脑之间通过两个串联的交换机"></a>电脑之间通过两个串联的交换机</h3><p>我们刚刚讨论了,两台电脑直连,需要使用交叉线;类似的,两台交换机之间也需要交叉线:</p><p><img src="/static/blog_images/Ethernet-Wiring/7.png"></p><p>在这种情况下,端到端的通讯路径也与上述方式无异。</p><h3 id="路由器与集线器"><a href="#路由器与集线器" class="headerlink" title="路由器与集线器"></a>路由器与集线器</h3><p>那么,路由器和集线器呢?他们用了怎样的 NIC ?</p><p>实际情况是,路由器与电脑类似,使用了 MDI 标准(第 2 对线是 TX,第 3 对线是 RX),因此,你可以将上述图片中的任意电脑换成路由器,通讯路径分析也是一样的。</p><p>而集线器与交换机类似,使用 MDI-X 标准。</p><blockquote><p>译者按:此处的“路由器”是狭义上仅具有“路由”功能的设备,不等于常见的家用无线路由器</p></blockquote><h3 id="以太网线序图"><a href="#以太网线序图" class="headerlink" title="以太网线序图"></a>以太网线序图</h3><p>前文讲到,RJ45 的导线颜色有两种标准:T568a 和 T568b。双绞线两侧所使用的标准决定了其是交叉线还是直通线。</p><p>要想做一根直通线,只要保证线两端的标准一致就行了,都是 T568a 或者都是 T568b:</p><p><img src="/static/blog_images/Ethernet-Wiring/8.png"></p><p>要想做一根交叉线,只需其中一端为 T568a,另一端为 T568b 即可。</p><p><img src="/static/blog_images/Ethernet-Wiring/9.png"></p><p>注意,第 1 对线和第 4 对线没有使用(蓝色对和棕色对)。理论上你的网线中可以去掉这几根线,但是去掉之后剩下的线排列起来有些困难。</p><p>另外,这两对线因为用不到,所以无需交叉。但是,吉比特以太网标准需要用到全部 8 根线,所以为了一致性,通常所有网线对都被交叉。我们会在后文讨论吉比特以太网。</p><p>最后需要注意的是,数据信号本身并不在乎导线的颜色,只要它们连在了正确的接口上就能通讯。但能用不代表就是一个好主意,颜色乱接的话,后续维护起来就是噩梦。</p><h3 id="助记图"><a href="#助记图" class="headerlink" title="助记图"></a>助记图</h3><p>综上所述,我们可以把交叉线和直通线的用法画作一张图:</p><p><img src="/static/blog_images/Ethernet-Wiring/10.png"></p><p>之所以这么摆放,是因为这样画起来更方便。我们把 L1、L2 层的设备画在左右两侧,L3 层设备画在上下两边,然后两两连接。关于网络协议分层请参见 <a href="https://zh.wikipedia.org/wiki/OSI%E6%A8%A1%E5%9E%8B">OSI 模型</a>。</p><p>小结一下:</p><ul><li>L1/L2 层设备互相连接,需要<strong>交叉线</strong>;</li><li>L1/L2 层设备与 L3 层设备连接,需要<strong>直通线</strong>;</li><li>L3 层设备互相连接,需要<strong>交叉线</strong>。</li></ul><p>或者更简单:</p><ul><li>同则交叉</li><li>异则直通</li></ul><h3 id="自动-MDI-X"><a href="#自动-MDI-X" class="headerlink" title="自动 MDI-X"></a>自动 MDI-X</h3><p>即使知道了什么时候该用直通线,什么时候该用交叉线,对于网络工程师来说,布线也常常是个头疼的事情。</p><p>于是,出现了一个新技术,可以自动分析两台设备的接口模式,并决定是否要交叉 TX/RX。这个技术叫做“自动 MDI-X”。</p><p><strong>使用自动 MDI-X 技术,任意两台设备之间都可以通过直通线连接,并让两端动态确定是否需要交叉 TX 和 RX。</strong></p><p>自动 MDI-X 是 100BASE-T 实现中的一个可选功能,而在所有吉比特以太网设备中是必须的。</p><h4 id="自动-MDI-X-的工作原理"><a href="#自动-MDI-X-的工作原理" class="headerlink" title="自动 MDI-X 的工作原理"></a>自动 MDI-X 的工作原理</h4><p>那么,自动 MDI-X 是如何实现的?两端的设备如何确定哪对线是 TX 或 RX?如果有必要的话,哪一边的设备会交换 TX 和 RX?本节会介绍其内部工作原理。</p><p>记住,交叉线的目的是让一方的 TX 连接到另一方的 RX。也就是说,一方的 NIC 必须用 MDI 标准,另一方必须是 MDI-X 标准。自动 MDI-X 是这样实现这一功能的:</p><p>双方都先生成 1-2047 中的一个随机数,如果随机数是奇数,那么这一方会将自己的 NIC 配置为 MDI-X 模式;如果是偶数,则配置为 MDI 模式。而后双方就开始在其所选择的 TX 线上发送连接脉冲信号。</p><p>如果双方都能在自己的 RX 线上收到对方的连接脉冲,那么就代表协商完成,因为双方都能在 TX 线上发,在 RX 线上收。</p><p>如果双方都不能收到对方的连接脉冲,那么它们肯定都随机到了奇数或都随机到了偶数。因此,它们中的某一方必须将自身的 TX 和 RX 交换。</p><p>但是双方不能同时交换 TX 和 RX,因为这样一来依然是冲突的。因此,我们设计了一个系统,以随机的时间间隔切换 TX/RX 对,直到双方成功协商。</p><p>前文提到随机生成的数字(1-2047)会循环变化,以便双方能选择一个新的标准(MDI 或者 MDI-X)。但是这个数字不能每次加 1,因为这样的话,双方都会从奇数变为偶数,或者偶数变为奇数。换句话说,如果双方一开始都选择了 MDI 模式,如果同时加 1,它们都会切换为 MDI-X 模式,依然无法协商。</p><p>所以,这个随机数使用了叫“线性反馈移位寄存器”的设备以实现循环变化。</p><p>线性反馈移位寄存器(Linear-Feedback Shift Register)(LFSR)是一种算法,它会循环遍历某个范围内的所有数字,而且在每一个循环内不会重复。这些数字以一种可预测的、但随机的顺序循环出现(也就是说,它们不按照大小顺序依次出现,但出现的位置是确定的)。</p><p>举个例子,如果双方随机的初始值分别为 1000 和 2000,那么它们在 LFSR 序列中下一个数字的奇偶性是完全随机的。但如果双方随机到了同一个初始值,那么它们之后随机出来的数字依然是一样的。</p><p>这个过程会一直持续下去,直到双方成功协商。</p><p>现在问题来了,万一双方随机到了相同的数字,然后循环的时间间隔也一样呢?我们可以简单计算一下出现这种情况的几率:</p><p>双方随机到相同数字的几率是 1/2047,双方选择相同时间间隔的几率是 1/4,也就是说,双方同时切换 MDI/MDI-X 标准的几率是 1/8188。</p><p>循环每大概 62ms 运行一遍,也就是说,每秒有大概 16 个循环(每次循环开始时都会重新随机一次)。那么双方在 1 秒之内始终是相同的循环时间的几率是 1/4,294,967,296 (42 亿分之一,1/2^32)。因此,二者结合,双方在一秒内始终随机到相同的随机数、且时间间隔也一样的几率是 1/8,791,798,054,912 (8.7万亿),这种事情几乎不可能发生,就算发生了,你再等一秒就行了。</p><h2 id="为什么使用双绞线"><a href="#为什么使用双绞线" class="headerlink" title="为什么使用双绞线"></a>为什么使用双绞线</h2><p>在网络的物理连线上使用双绞线似乎毋庸置疑。但是,为什么呢?是什么源于让双绞线在网络布线选择中处于主导地位?</p><p>有两个主要的原因,且都与电磁干扰(Electromagnetic Interference)(EMI)相关:</p><ol><li>使用双绞线可以极大减少导线向外辐射电磁干扰;</li><li>使用双绞线可以减少外部电磁干扰对导线本身的影响。</li></ol><p>如果网线需要长距离与其他各种线缆捆绑在一起布置(比如数据中心或者配电箱),以上两个特性都是非常重要的。</p><h3 id="减少-EMI-向外辐射"><a href="#减少-EMI-向外辐射" class="headerlink" title="减少 EMI 向外辐射"></a>减少 EMI 向外辐射</h3><p>只要导线中有电流信号,那就一定会辐射 EMI,进而影响到周围的线缆——也就是通常所说的“串扰”。EMI 辐射可以通过额外的屏蔽装置补偿掉,但是大名鼎鼎的 <a href="https://zh.wikipedia.org/wiki/%E4%BA%9A%E5%8E%86%E5%B1%B1%E5%A4%A7%C2%B7%E6%A0%BC%E6%8B%89%E6%B1%89%E5%A7%86%C2%B7%E8%B4%9D%E5%B0%94">贝尔先生</a> 发明了抵消电磁干扰的绝妙方法。</p><p>他的想法是使用两根导线,其中一根发送原始信号,另一根发送与原始信号完全相反的信号。如此一来,两根线会辐射恰好反向的 EMI,也就互相抵消了。</p><p>简单解释一下,如果一根线发送 +10V 的电压,并辐射了 +0.01V 的 EMI;而另一根线同时发送 -10V 的电压,并辐射了 -0.01V 的 EMI。它们的 EMI 加起来就是 0。</p><p>在电气工程中,这两根线通常被称为“差分对”,可以用 TX+ 和 TX- 来表示。</p><p>这一发明可以实现不需要大量屏蔽的布线方案,也是当前非屏蔽线得以大量使用的原因之一。</p><p>但现在我们只回答了“双绞线”中的“双”,至于为什么还要“绞”,我们继续往下看:</p><h3 id="减少外部-EMI-的吸收"><a href="#减少外部-EMI-的吸收" class="headerlink" title="减少外部 EMI 的吸收"></a>减少外部 EMI 的吸收</h3><p>即使采用了上述的“差分线”,我们也无法避开所有外部的电磁干扰。无线网络、蓝牙、卫星通讯以及手机等都会成为空间中杂散的无线电波来源。</p><p>但幸好贝尔又出现了,并设计了一种非常简单却很有效的方案以屏蔽电磁干扰。</p><p>这一设计基于 EMI 的一个基本概念:离 EMI 辐射源越近,收到的干扰越强。如果两根线交替着靠近 EMI 辐射源,它们就能吸收同样多的辐射。如下图所示:</p><p><img src="/static/blog_images/Ethernet-Wiring/11.png"></p><p>蓝色线的初始电压是 +50V,绿线与之相反为 -50V。EMI 辐射源为图中的红圈,一圈圈向外辐射,离中心越远的圈层干扰电压越小。如果简单将图中每根线上绘制的点受到的干扰电压相加,会发现两根线都增加了 22V 的电压。</p><p>尽管上图导线右侧的电压与左侧的不同,但是两根导线之间的电压差却总是一致的,一直都是 100V。EMI 对两根导线的影响是等同的。经过简单的计算与变换,即可根据最终的 100V 电压差得到初始信号分别为 +50V 和 -50V,如下图所示:</p><p><img src="/static/blog_images/Ethernet-Wiring/12.png"></p><blockquote><p>提醒一下,以上 EMI 干扰相关电压数值被严重夸大了。实际上,正常情况下 EMI 带来的电压扰动是微伏(µV)级别的,即 1/1000,000 V。但原理依然是一样的。</p></blockquote><h3 id="发送比特位"><a href="#发送比特位" class="headerlink" title="发送比特位"></a>发送比特位</h3><p>上文讲到,网线中的数据是以数字信号的方式发送的,也就是一串 1 和 0 的数据流。但双绞线具体是如何发送数据的呢?我们接下来会用一个简化的模型来解释一下。</p><p>发送数据信号,本质上来说就是在某段时间内,给导线加上变化的电压。收发双方会先协商好一个时钟频率,以确定传输的每一单位的电压信号将维持多长时间。简便起见,我们称之为“位号”。在给定的时间点,每一个位号只能表示线上传输的 0 或者 1。</p><p>不同的标准会规定不同的电压等级,但由于我们简化了模型,所以不用管真正的电压是多少。但我们依然会使用 100BASE-TX 标准所规定的电压等级,即 +2.5V 和 -2.5V。</p><p>如果要在某个位号上发送比特 1,发送方会向 TX+ 线上施加 +2.5V 电压;如果要发送比特 0,就向 TX+ 线上发送 -2.5V 电压。</p><p>而 TX- 线则始终相反,比特 1 是 -2.5V,比特 0 是 +2.5V。</p><p>下表是发送 110010101110 二进制序列的相关情况:</p><p><img src="/static/blog_images/Ethernet-Wiring/13.png"></p><p>注意上图不是网线的实体布局,只代表 TX+ 和 TX- 线上交替变化的电压信号。双绞线实际是均匀缠绕的。</p><p>就像之前讲到的,每对中的两根线上的电压总是互为相反量,一切都很整齐,且在水平方向上是对称的。</p><p>现在假设网线附近有 EMI 辐射源,我们在上表中添加一行噪声数据,然后看看最终会变成什么样:</p><p><img src="/static/blog_images/Ethernet-Wiring/14.png"></p><p>注意到,现在这幅图已经不再对称了。两根线仍然发送相反的电压,但加了一个偏置量。</p><p>但是,接收端并不一定要完美的 +2.5V 和 -2.5V,它只需确定哪根线发送更高的电平。如果 TX+ 发送的是高电平,那么这个位号就表示 1,如果 TX- 是更高的电平,那么这个位号就表示 0。</p><p>或者更简单,如果上图中蓝线在上面,就代表 1,黄线在上面,就代表 0。</p><p>通过这种方式,接收端能一位一位地拼凑好整个数据,不管 EMI 对原始电平有怎样的干扰。可见,非屏蔽线不能消除电磁干扰,但能消除电磁干扰的影响。</p><h2 id="吉比特以太网"><a href="#吉比特以太网" class="headerlink" title="吉比特以太网"></a>吉比特以太网</h2><p>我们已经详细介绍了快速以太网(100Mbps),现在我们继续讨论一下吉比特以太网(千兆以太网,1000Mbps 或者 1Gbps)。</p><p>首要的区别就是,吉比特以太网标准需要用到全部 4 对 8 根线,不像百兆网只用到 2 对。因此,在制造吉比特以太网网线时,全部 4 对线都需要交叉。</p><p>前文讲到,RJ45 有两种不同的标准:T-568a 和 T-568b。下图描绘了 4 对线都交叉它们各自的样子:</p><p><img src="/static/blog_images/Ethernet-Wiring/15.png"></p><p>也就是说,吉比特以太网需要自动 MDI-X。所以,你可以直接在千兆网络中使用直通线,然后让网卡自动选择是否需要交叉。</p><p>吉比特以太网有两种布线标准:</p><h3 id="1000BASE-TX"><a href="#1000BASE-TX" class="headerlink" title="1000BASE-TX"></a>1000BASE-TX</h3><p>此标准使用了全部 4 对线,但规定了其中两对线为 TX,另外两对线为 RX。</p><p>理论上讲,这比 1000BASE-T 更简单,但是这需要更昂贵的 Cat6 网线,而不是常见的 Cat5 或 Cat5e 网线。因此,1000BASE-TX 在实际部署中并不常见。</p><h3 id="1000BASE-T"><a href="#1000BASE-T" class="headerlink" title="1000BASE-T"></a>1000BASE-T</h3><p>这是当前应用最广泛的吉比特以太网标准。它以全双工模式同时使用了全部 4 对线,也就是说每对线都可以<strong>同时</strong>用作 RX 和 TX。这是通过“回声消除”技术实现的,我们会在下一节详细阐述。</p><p>使用这种线序标准的最大优势是,你可以在现有的 Cat5e 网线上跑到千兆,而无需升级到更贵的 Cat6 网线。</p><blockquote><p>1000BASE-T 经常被错误地指代 1000BASE-TX。这可能是因为在快速以太网协议中,占主导地位的标准是 100BASE-TX。另外很多时候,线缆标准也经常合起来称作 10/100/1000 BASE-TX。实际上,各个不同速率下,占主导的以太网协议分别是 10BASE-T、100BASE-TX 以及 1000BASE-T。</p></blockquote><h2 id="在同一对线上实现全双工"><a href="#在同一对线上实现全双工" class="headerlink" title="在同一对线上实现全双工"></a>在同一对线上实现全双工</h2><p>上节说到,1000BASE-T 标准可以在同一对线上同时发送和接收数据。在本节我们将解释这是如何实现的。首先,我们来做一个简单的类比。</p><p>你应该有过这样的经历:在跟别人通电话时,如果对方开了免提,你就能在听筒中听到自己的声音。这是因为你的声音从对方的扬声器中发出,在空间中遇到障碍物反射,又被对方的麦克风接收。这就叫做回声。</p><p>高端的电话可以从麦克风收到的声波中剔除扬声器发出的声波——这个技术就叫做回声消除。</p><p>回声消除也是吉比特以太网能够在同一对线上同时发送和接收数据的基础。基本原理就是,如果你知道你发送了什么信号,那么你就能从你收到的信号中将其剔除。</p><p>前文讲到,发送信号本质上是往导线上施加电压。反之,接收信号就是读取导线上的电压值。</p><p>如果发送方往某根导线上施加了以下电压:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">+0.5V, +1V, -2V, -1V</span><br></pre></td></tr></table></figure><p>同时,也是发送方,它在同一个导线上读取到了以下电压值:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">+1.5V, 0V, -2.5V, +1V</span><br></pre></td></tr></table></figure><p>那么,发送方可做一个减法,用读取值减去其发送的值,这样就能得到对方往这根线上加了多高的电压:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">+1V, -1V, -0.5V, +2V</span><br></pre></td></tr></table></figure><p>如此一来,同一根线就能在同一时间,同时发送和接收数据了。</p><p>再次强调,上述电压值仅仅为了解释原理,实际情况下,电压值可能完全不同,还会包含 EMI 等。同时,我们刚刚只讨论了双绞线中的一根线,另一根线仍然会承载反向的电压。</p><p>使用这种技术,全部 4 对线都可被同时用作 TX 和 RX。另外与前面几节的讨论相同,由于采用了双绞线,它们都还会消除入方向和出方向的 EMI。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>读到这里,你应该对以太网和双绞线的知识点有一个宏观的理解了。这些年我们学习并整理出了这篇文章,原来看似简单的网线居然囊括了这么多技术点,现在感觉很对不起那些被我随便就扔掉的网线了。</p><p>以太网线充满了许多我们本以为理所当然的技术,但实际却很复杂。本文为了便于理解,也省略了很多细节,如果读者有兴趣可以继续研究。</p>]]></content>
<categories>
<category> 攻玉计划 </category>
</categories>
<tags>
<tag> 以太网 </tag>
</tags>
</entry>
<entry>
<title>FreeBSD vs Linux:哪个开源操作系统更强大</title>
<link href="/2022/05/25/freebsd-vs-linux-which-open-source-os-is-superior/"/>
<url>/2022/05/25/freebsd-vs-linux-which-open-source-os-is-superior/</url>
<content type="html"><![CDATA[<blockquote><p>本文是“攻玉计划”的一部分,翻译自 <a href="https://www.ateamsystems.com/tech-blog/freebsd-vs-linux-which-open-source-os-is-superior/">https://www.ateamsystems.com/tech-blog/freebsd-vs-linux-which-open-source-os-is-superior/</a></p></blockquote><p>FreeBSD 和 Linux,哪一个更强大?这个问题没那么简单。它们各有春秋,不能一概而论。</p><p>来自我们 A-Team Systems 的专家们有数十年这两个系统的使用经验,所以,我们将详细阐述这两个系统的优势和劣势,供你选择最适合的系统。</p><h2 id="FreeBSD-vs-Linux:功能对比"><a href="#FreeBSD-vs-Linux:功能对比" class="headerlink" title="FreeBSD vs Linux:功能对比"></a>FreeBSD vs Linux:功能对比</h2><p>让我们比较一下这两个 Unix 系统的关键几个方面:</p><h3 id="操作系统完整性"><a href="#操作系统完整性" class="headerlink" title="操作系统完整性"></a>操作系统完整性</h3><p>在这一点上,<a href="https://www.ateamsystems.com/tech-blog/intro-to-freebsd-learn-what-it-is-and-how-it-works/">FreeBSD</a> 更有优势。<br>这是因为 Linux 实际上并不是一个完整的操作系统,而只是一个内核。这是一个很常见的误解,因为很多用户经常把 Linux 看成是一个完整的操作系统。<br>各个 Linux 发行版通常会将必需的软件和库文件打包进系统,这些软件和库文件大多来自 GNU 项目,所以自由软件基金会才将 Linux 称作“GNU/Linux”。</p><p>以下是一些流行的 Linux 发行版:</p><ul><li>Ubuntu</li><li>CentOS</li><li>Fedora</li><li>Arch Linux</li><li>Linux Mint</li><li>Debian</li></ul><h3 id="价格"><a href="#价格" class="headerlink" title="价格"></a>价格</h3><p>关于价格,二者不分胜负。因为作为开源软件,FreeBSD 和 Linux 自然都是免费的。</p><blockquote><p>译者按:在译者看来,开源并不一定意味着免费,很多开源许可证并不允许商用。当然,Linux 和 FreeBSD 是允许免费商用的。</p></blockquote><p>你可能需要为某些额外功能付费,比如服务支持、硬件等。</p><p>任何人都可以免费使用、修改、分发、查阅 Linux 及 FreeBSD 的源代码。但是,任何对 Linux 所作的修改都必须公开源码。<br>而 FreeBSD 并不需要公开,因此,需要在产品中使用相关源码的公司,在这一点上可能更倾向于使用 FreeBSD。</p><h3 id="安全性"><a href="#安全性" class="headerlink" title="安全性"></a>安全性</h3><p>FreeBSD 比 Linux 略微更安全一点。FreeBSD 项目的核心支柱之一就是安全性,并且预先安装了顶级的安全功能,所以在这一点上,毫无疑问它更有优势。</p><p>但这也并不意味着 Linux 不安全。Linux 是高度可配置的,因此可以实现你想要的任何安全特性。但是从操作系统整体角度来看,FreeBSD 的安全性更高。</p><h3 id="硬件与架构支持"><a href="#硬件与架构支持" class="headerlink" title="硬件与架构支持"></a>硬件与架构支持</h3><p>如果比较硬件与架构支持度的话,Linux 绝对是占优势的。Linux 可以在许多不同的平台上运行,但是 FreeBSD 不行。所以,如果你很在乎兼容性和跨平台性,请选择 Linux。</p><p>但这也是一把双刃剑,为了能在大量不同的平台上运行,Linux 必须牺牲一部分性能以换取兼容性。而另一方面,FreeBSD 无需牺牲性能,因为它只需要在有限数量的平台上运行即可。</p><p>由于 Linux 是一个主流的系统,而 FreeBSD 不是,所以设备制造商更倾向于制造兼容 Linux 的软硬件。举个例子,如果你需要经常更新显卡驱动,Linux 会比 FreeBSD 更快获取相关更新支持。</p><p>FreeBSD 对硬件支持的短板大多集中在外设和显卡这种桌面级应用方面。但 FreeBSD 的目标场景是服务器应用,所以这并没有多大影响。</p><h3 id="稳定性"><a href="#稳定性" class="headerlink" title="稳定性"></a>稳定性</h3><p>Linux 和 FreeBSD 都相当稳定可靠。但如果必须得比个高下的话,FreeBSD 会更稳定一点。这又回到了一个事实:FreeBSD 更有组织性。Linux 的稳定性可能会被用户使用的额外组件而拖累。而与此同时,FreeBSD 是一个完整的操作系统,所以它的默认配置更加可靠。总而言之,二者都不缺乏稳定性。</p><h3 id="性能"><a href="#性能" class="headerlink" title="性能"></a>性能</h3><p>虽然业界没有确凿的证据证明 FreeBSD 比 Linux 的性能更优,但是大多数用过二者的用户都说 FreeBSD 在这方面更强一点。这同样归咎于 Linux 的高兼容性。FreeBSD 更精简,无需对环境做额外的判断,因此通常来说它的性能更好。</p><p>FreeBSD 的延迟比 Linux 更低。这里延迟指的是系统时钟中断发生后,到处理器开始运行代码的这段时间。但是大多数应用在 Linux 上跑得更快。</p><h3 id="许可证"><a href="#许可证" class="headerlink" title="许可证"></a>许可证</h3><p>FreeBSD 使用了它自己的 BSD 许可证。该许可证允许用户免费使用该操作系统,并随意修改源码。如果愿意的话,用户也可以发布修改后的源码,或者直接闭源,BSD 许可证允许他们这么做。</p><p>Linux 使用的是 GNU GPL 许可证(GNU通用公共许可协议)。用户可在遵循该许可证限制的情况下随意修改源码。主要区别是,如果你对 Linux 源码作了修改,那么法律意义上你<strong>必须</strong>公开你的代码。</p><blockquote><p>译者按:译者认为这是片面的,如果你修改代码并仅供自己研究使用,那么不需要公开代码。你只需要把源码公开给用户即可。</p></blockquote><p>这个许可证既有好处也有坏处,最大的劣势就是,用户不能用 Linux 开发闭源的系统。而优势是,所有用户都可互相贡献代码,推动整个项目前进。这也是 Linux 能有这么大社区的原因。</p><p>大多数用户无需关心本节的区别,因为大多数人根本不会修改源码。但如果你想使用一个开源的系统来开发闭源的系统,请选择 FreeBSD 而不是 Linux。</p><h3 id="Shell"><a href="#Shell" class="headerlink" title="Shell"></a>Shell</h3><p>从用户角度,大多数人可能认为 Linux 默认的 BASH 比 FreeBSD 的 tcsh 更强大,因为 tcsh 太落伍了。BASH 非常灵活,用户几乎可以在任何 Unix 兼容的系统上做任何事。但这也并不意味着 tcsh 一无是处,tcsh 只是学习路线更陡峭而已。当然,在 FreeBSD 上安装 BASH 也很简单。</p><h3 id="文件系统"><a href="#文件系统" class="headerlink" title="文件系统"></a>文件系统</h3><p>这一方面,二者也是平手。Linux 和 FreeBSD 都采用了非常高效的文件系统。</p><p>FreeBSD 默认使用 ZFS(泽字节文件系统),这绝对是长期存储数据的最佳文件系统之一。它内置了一个磁盘卷管理器,因此允许用户在同一个存储池上创建多个文件系统。因此在发生物理故障、操作失误或者数据损坏的情况下,仍能保证数据一定的可靠性。</p><p>ext4 是大多数 Linux 发行版的默认文件系统。它不如 ZFS 那么灵活,但相当可靠。</p><h3 id="制造商支持"><a href="#制造商支持" class="headerlink" title="制造商支持"></a>制造商支持</h3><p>这一轮 Linux 获胜。IBM、戴尔和惠普的服务器都直接支持运行 Linux。FreeBSD 也能在这些服务器上运行,并且有 A-Team Systems 团队可提供支持。你可以查阅 FreeBSD 的 <a href="https://www.freebsd.org/commercial/hardware/">硬件制造商</a> 以了解当前所支持的硬件。</p><h3 id="更新"><a href="#更新" class="headerlink" title="更新"></a>更新</h3><p>当考虑更新时,你需要关注两方面:更新的便捷度以及更新发布是否及时。</p><p>在便捷度方面,FreeBSD 更胜一筹。用户可以依其意愿选择更新某些组件,比如,你可以只更新某些核心组件,比如内核、源码等,或者只更新它们的子组件。当然也可以全部更新,操作非常简单。</p><p>而对于更新的及时度,Linux 表现得更好。开源公司通常有很强的动力去更新,因此,只要有需求,更新很快就能发布。FreeBSD 可能需要花更长的时间去开发、发布更新,但事实上,Linux 和 FreeBSD 经常可以同时获取相关更新,因为他们使用了同样的上游项目。</p><h3 id="包管理"><a href="#包管理" class="headerlink" title="包管理"></a>包管理</h3><p>在 FreeBSD 上安装软件包很简单。FreeBSD Ports 项目包含了将近 40000 个软件源,用户或管理员可以方便快捷地安装它们。每个软件源都有针对用户实际系统的相关补丁,以确保软件能在特定平台上正常运行。</p><p>而不同 Linux 发行版的包管理工具就参差不齐了,有些非常棒,有些就很一般。以下是一些做得比较好的包管理工具:</p><ul><li>DPKG - Debian</li><li>RPM - Red Hat</li><li>Pacman Package Manager</li><li>Pkgsrc</li><li>Portage</li></ul><h3 id="开发维护人员"><a href="#开发维护人员" class="headerlink" title="开发维护人员"></a>开发维护人员</h3><p>FreeBSD 核心团队有 9 名成员,并在世界范围内有大约 500 名代码贡献者。这个团队负责调试、开发并优化主线代码仓库。大多数贡献者都是不求回报的志愿者,核心团队成员由所有活跃的贡献者每两年一次投票选出。</p><p>而 Linux 内核由 Linus Torvals 先生管理维护,他也是 Linux 的缔造者。Linus 先生对 Linux 的新功能拥有最终决定权。</p><h2 id="FreeBSD-与-Linux-到底如何不同?"><a href="#FreeBSD-与-Linux-到底如何不同?" class="headerlink" title="FreeBSD 与 Linux 到底如何不同?"></a>FreeBSD 与 Linux 到底如何不同?</h2><p>FreeBSD 是一个完整的操作系统,拥有内核、驱动、文档以及各种工具。Linux 只有内核以及部分驱动,并且依赖第三方系统软件才能运行。FreeBSD 的源码使用 BSD 许可证,而 Linux 使用 GPL 许可证。</p><p>Linux 广泛支持各种硬件,而 FreeBSD 支持的硬件非常有限。Linux 也是当前市场上最流行的开源操作系统,所以不缺各种支持。FreeBSD 也有非常忠实的用户群,但远不能与 Linux 的用户群相提并论。</p><h2 id="FreeBSD-比-Linux-更安全吗?"><a href="#FreeBSD-比-Linux-更安全吗?" class="headerlink" title="FreeBSD 比 Linux 更安全吗?"></a>FreeBSD 比 Linux 更安全吗?</h2><p>FreeBSD 的安全问题通常比 Linux 更少,但是差距并不大。Linux 的用户比 FreeBSD 更多,所以也会发现更多的漏洞。由于 FreeBSD 提供了完整的操作系统,所以其默认配置非常安全。</p><p>Linux 系统的安全性取决于用户的配置。由于其高度的可定制化,Linux 用户可以让他们的系统变得几乎牢不可破。</p><h2 id="FreeBSD-可以运行-Linux-的程序吗?"><a href="#FreeBSD-可以运行-Linux-的程序吗?" class="headerlink" title="FreeBSD 可以运行 Linux 的程序吗?"></a>FreeBSD 可以运行 Linux 的程序吗?</h2><p>FreeBSD 提供了与 Linux 的 <a href="https://docs.freebsd.org/en/books/handbook/linuxemu/">二进制兼容性</a>。这允许用户在 FreeBSD 系统上安装并运行 Linux 的二进制程序。FreeBSD 上默认没有安装 Linux 的相关库文件,但可以从 FreeBSD Ports 上安装,或者手动安装。</p><h2 id="为什么-Linux-比-FreeBSD-更流行?"><a href="#为什么-Linux-比-FreeBSD-更流行?" class="headerlink" title="为什么 Linux 比 FreeBSD 更流行?"></a>为什么 Linux 比 FreeBSD 更流行?</h2><p>这其中有多个原因。一方面,FreeBSD 缺乏硬件支持,这就限制了用户使用它的场景。</p><p>另一个原因是 FreeBSD 缺乏商业支持。有如 Red Hat 这样的大公司能确保 Linux 及时获取更新支持,但对于 FreeBSD 而言这是不可能的。</p><p>最后,Linux 拥有数量众多的软件,允许其发挥最大的灵活性和可用性。FreeBSD 提供了一些预编译的软件包,但仍无法与 Linux 相比。</p><h2 id="FreeBSD-和-Linux-哪个用起来更简单?"><a href="#FreeBSD-和-Linux-哪个用起来更简单?" class="headerlink" title="FreeBSD 和 Linux 哪个用起来更简单?"></a>FreeBSD 和 Linux 哪个用起来更简单?</h2><p>FreeBSD 和 Linux 都需要一定的学习成本。但是,FreeBSD 相对而言更易学习使用,因为它没有那么多学习选项,例如发行版、包管理工具等等。</p><p>大多数开发者认为,比起 FreeBSD,Linux 太混乱了。对于同一个任务有无数种实现方案,并且不同的用户对应该如何选择方案有不同(且强烈)的意见。Linux 社区是一个快节奏的社区,经常经历变化。因此,很多用户更喜欢 FreeBSD 社区的一致性和条理性。</p><h2 id="哪个更快?"><a href="#哪个更快?" class="headerlink" title="哪个更快?"></a>哪个更快?</h2><p>总的来说,FreeBSD 通常比 Linux 更快。这主要是因为它是一个完整的系统。此外,FreeBSD 的延迟比 Linux 低,也就意味着它能更快处理输入。有如网飞、苹果和思科之类的公司会采用 FreeBSD 以获取这种处理速度优势。</p><p>Linux 也能获得类似的速度,但是,这取决于你的配置。还值得注意的是,大多数应用程序在 Linux 上运行得更快。因此大多数超级计算机会使用 Linux 而不是 FreeBSD。</p><h2 id="FreeBSD-vs-Linux:哪一个最适合你?"><a href="#FreeBSD-vs-Linux:哪一个最适合你?" class="headerlink" title="FreeBSD vs Linux:哪一个最适合你?"></a>FreeBSD vs Linux:哪一个最适合你?</h2><p>FreeBSD 和 Linux 都可作为开源用户的选择。最主要的区别就是,FreeBSD 更完整,更标准化,而 Linux 只提供了内核及驱动,需要第三方软件支持。</p><p>如果想要尽可能少地配置系统,FreeBSD 是更好的选择。但是,Linux 提供了更多的自定义选项,对于想要定制系统的人是个更好的选择。另外,如果你有硬件平台限制的话,Linux 的支持性可能更好点。</p><p>如果你喜欢紧跟技术潮流,Linux 的新技术、新特性和更新速度肯定会让你满意。如果稳定性、性能和安全性对你来说更重要,FreeBSD 也许更适合你。</p>]]></content>
<categories>
<category> 攻玉计划 </category>
</categories>
<tags>
<tag> FreeBSD </tag>
<tag> Linux </tag>
</tags>
</entry>
<entry>
<title>如何在 Markdown 中修改字体颜色</title>
<link href="/2022/05/06/How-to-apply-color-in-Markdown/"/>
<url>/2022/05/06/How-to-apply-color-in-Markdown/</url>
<content type="html"><![CDATA[<blockquote><p>本文是“攻玉计划”的一部分,翻译自 <a href="https://stackoverflow.com/questions/35465557/how-to-apply-color-in-markdown">https://stackoverflow.com/questions/35465557/how-to-apply-color-in-markdown</a></p></blockquote><h2 id="问题描述"><a href="#问题描述" class="headerlink" title="问题描述"></a>问题描述</h2><p>我想用 Markdown 记录文字信息,但我搜了一圈 Google,发现 Markdown 不支持修改字体颜色。而且 StackOverflow 和 GitHub 的 Markdown 编辑模式也不支持指定文字颜色。</p><p>有什么办法可以在 Markdown 里指定文字颜色吗?</p><h2 id="最佳答案"><a href="#最佳答案" class="headerlink" title="最佳答案"></a>最佳答案</h2><p><strong>太长不看系列:</strong></p><p>Markdown 自身并不支持色彩配置,但你可以在 Markdown 中添加 HTML 代码,例如:</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">span</span> <span class="attr">style</span>=<span class="string">"color:blue"</span>></span>这是**蓝色**的文字<span class="tag"></<span class="name">span</span>></span></span><br></pre></td></tr></table></figure><p><strong>以下是长回答:</strong></p><p>根据官方的 <a href="http://daringfireball.net/projects/markdown/syntax#html">语法规则</a>:</p><blockquote><p>Markdown 只有一个用途,就是作为编写网页的一种语法格式。<br>Markdown 不能取代 HTML,甚至不能实现 HTML 的大部分功能。它的语法很简单,只能覆盖很小一部分的 HTML 标签。Markdown 并不是为了让你更方便地插入 HTML 标签。我的观点是,HTML 标签已经很方便了,而 Markdown 是为了让人更容易读、写、改。HTML 是用于发布的格式,而 Markdown 是给人写的格式。因此,<strong>Markdown的语法格式只用来处理可以用纯文本表达的信息。</strong><br>对于任何 Markdown 未实现的功能,直接插入 HTML 代码即可。</p></blockquote><p>由于 Markdown 并不是用来发布的格式,修改字体的颜色已经超出了 Markdown 的处理范围。但你仍可以插入裸的 HTML 代码(因为 HTML 是发布级的格式),例如以下 Markdown 文本:</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">包含 <span class="tag"><<span class="name">span</span> <span class="attr">style</span>=<span class="string">"color:blue"</span>></span>*蓝色* 文字<span class="tag"></<span class="name">span</span>></span>的 Markdown 语句。</span><br></pre></td></tr></table></figure><p>将会转换为以下 HTML 代码:</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">p</span>></span>包含 <span class="tag"><<span class="name">span</span> <span class="attr">style</span>=<span class="string">"color:blue"</span>></span><span class="tag"><<span class="name">em</span>></span>蓝色<span class="tag"></<span class="name">em</span>></span> 文字<span class="tag"></<span class="name">span</span>></span>的 Markdown 语句。<span class="tag"></<span class="name">p</span>></span></span><br></pre></td></tr></table></figure><p>目前,StackOverflow (也许 GitHub 也是)会把 HTML 代码原原本本地显示出来(因为安全性考虑),因此你无法在这些地方实现文字颜色的功能,但你可以在 Markdown 的任何标准实现中使用。</p><p>另一种解决方案是,直接使用 <a href="http://maruku.rubyforge.org/proposal.html#attribute_lists">Markuru</a> 的非标准属性表。此标准后续被 <a href="https://pythonhosted.org/Markdown/extensions/attr_list.html">其它</a> <a href="https://pythonhosted.org/Markdown/extensions/attr_list.html">一些人</a>(可能还有其他类似理念的方案,比如 <a href="http://pandoc.org/MANUAL.html#divs-and-spans">pandoc 中的 div 和 span 参数</a>)继承开发。如果用了这种方案,你就可以为一段文字或者行内元素配置一个类,然后用 CSS 给这个类定义色彩属性。但显然,你必须使用支持这些非标准方案的编辑工具,并且这样写出来的文档也没法移植到其他系统上。</p><h2 id="其他回答-1"><a href="#其他回答-1" class="headerlink" title="其他回答 1"></a>其他回答 1</h2><p>如果你不想嵌入 HTML,只想用纯净的 Markdown 语句,可以尝试添加 emoji 以便强调指定语句。比如:⚠️警告⚠️,🔴重要❗🔴 或者 🔥新功能🔥。</p><h2 id="其他回答-2"><a href="#其他回答-2" class="headerlink" title="其他回答 2"></a>其他回答 2</h2><p>尽管 Markdown 不支持文字颜色属性,但你可以用 CSS 重定义一些格式标签,以便用它们来修改文字颜色。当然,你可以选择是否保留这些格式标签的原有属性。</p><p>例如:</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">// 重置标签属性</span><br><span class="line">s { <span class="attribute">text-decoration</span>: none; } // 删除线</span><br><span class="line"><span class="selector-tag">em</span> { <span class="attribute">font-style</span>: normal; <span class="attribute">font-weight</span>: bold; } // 斜体</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">// 增加颜色属性</span><br><span class="line">s { <span class="attribute">color</span>: green }</span><br><span class="line"><span class="selector-tag">em</span> { <span class="attribute">color</span>: blue }</span><br></pre></td></tr></table></figure><p>参见 <a href="https://stackoverflow.com/questions/25535836/how-to-restyle-em-tag-to-be-bold-instead-of-italic">如何使 em 标签标记粗体而不是斜体</a>。</p><p>然后,在 Markdown 文本中这样使用:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">~~这是绿色~~</span><br><span class="line">_这是蓝色_</span><br></pre></td></tr></table></figure><h2 id="其他回答-3"><a href="#其他回答-3" class="headerlink" title="其他回答 3"></a>其他回答 3</h2><p>可以换个思路,你可以用各种颜色的 Unicode 字符以满足相关需求,比如 🔴,U+1F534(大红圈)。</p><p>举个例子,在我 GitHub 里的硬件项目中,我会用以下的字符以注明接线的颜色:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">🔴 红色: +5V</span><br><span class="line">🟠 橙色: +3.3V</span><br><span class="line">⚫ 黑色: GND</span><br><span class="line">⚪ 白色: GND (拉低)</span><br><span class="line">🟣 紫色: I2C 信号线</span><br><span class="line">🟢 绿色: 时钟信号</span><br><span class="line">🟡 黄色: WS2812 信号</span><br><span class="line">🔵 蓝色: 电阻桥(模拟)输入</span><br></pre></td></tr></table></figure><p>这也许能帮到你,你可以直接复制粘贴上述字符到你的文档中,或者直接在网上搜例如“Unicode 紫色方块”之类。当然,它们也叫做 emoji。</p>]]></content>
<categories>
<category> 攻玉计划 </category>
</categories>
<tags>
<tag> Markdown </tag>
</tags>
</entry>
<entry>
<title>如何在 ESP8266 上选用合适的引脚</title>
<link href="/2022/05/05/esp8266-pinout-reference-gpios/"/>
<url>/2022/05/05/esp8266-pinout-reference-gpios/</url>
<content type="html"><![CDATA[<blockquote><p>本文是“攻玉计划”的一部分,翻译自 <a href="https://randomnerdtutorials.com/esp8266-pinout-reference-gpios/">https://randomnerdtutorials.com/esp8266-pinout-reference-gpios/</a></p></blockquote><p>本文旨在介绍 ESP8266 的引脚定义、引脚功能及如何使用它们。</p><p><img src="/static/blog_images/esp8266-pinout-reference-gpios/1.png"></p><p>ESP-12E 模块拥有 17 个 GPIO 引脚。但在各个开发板上,ESP8266 芯片的 GPIO 引脚并不一定全部引出,而且某些引脚不建议使用,某些引脚有非常特殊的功能。</p><p>本文将指导你如何正确使用 ESP8266 的各个 GPIO,避免用错引脚而浪费时间。</p><h2 id="ESP12-E-模块引脚定义"><a href="#ESP12-E-模块引脚定义" class="headerlink" title="ESP12-E 模块引脚定义"></a>ESP12-E 模块引脚定义</h2><p>下图阐述了 ESP-12E 模块的引脚定义。当你的项目使用裸 ESP-12E/F 模块的时候,可以参考此图。</p><p><img src="/static/blog_images/esp8266-pinout-reference-gpios/2.png"></p><blockquote><p>🔵注意:某些开发板可能不能使用全部的引脚,但相同的引脚在不同的开发板上,功能肯定是一样的。</p></blockquote><p>当前市场上有很多不同的 ESP8266 模块/开发板,它们的形状、大小、可用 GPIO 数目各不相同。但最常用的是 ESP-01(S)、ESP-12E/F、NodeMCU 开发板以及 Wemos D1 Mini 开发板。你可以自己搜索这些开发板模块的区别。</p><h2 id="ESP-01-S-引脚定义"><a href="#ESP-01-S-引脚定义" class="headerlink" title="ESP-01(S) 引脚定义"></a>ESP-01(S) 引脚定义</h2><p>如果你在用 ESP-01(S) 的板子,可以参考下图的 GPIO 引脚定义。</p><p><img src="/static/blog_images/esp8266-pinout-reference-gpios/3.png"></p><h2 id="ESP-12E-NodeMCU-开发板"><a href="#ESP-12E-NodeMCU-开发板" class="headerlink" title="ESP-12E NodeMCU 开发板"></a>ESP-12E NodeMCU 开发板</h2><p>ESP-12E NodeMCU 开发板的引脚定义如下图所示。</p><p><img src="/static/blog_images/esp8266-pinout-reference-gpios/4.png"></p><h2 id="Wemos-D1-Mini-开发板"><a href="#Wemos-D1-Mini-开发板" class="headerlink" title="Wemos D1 Mini 开发板"></a>Wemos D1 Mini 开发板</h2><p>Wemos D1 Mini 开发板的引脚定义如下图所示。</p><p><img src="/static/blog_images/esp8266-pinout-reference-gpios/5.png"></p><h2 id="ESP8266-的外设"><a href="#ESP8266-的外设" class="headerlink" title="ESP8266 的外设"></a>ESP8266 的外设</h2><p>ESP8266 的外设包括:</p><ul><li>17 个 GPIO</li><li>SPI</li><li>I2C(软件实现)</li><li>I2S(支持 DMA)</li><li>UART</li><li>10 位 ADC</li></ul><h2 id="推荐使用的引脚"><a href="#推荐使用的引脚" class="headerlink" title="推荐使用的引脚"></a>推荐使用的引脚</h2><p>需要注意的一点是,ESP8266 开发板上丝印的引脚号,并不是芯片真正的 GPIO 编号。比如,D0 是 GPIO16,D1 是 GPIO5。</p><p>下表说明了 ESP8266 开发板上丝印的引脚号与实际 GPIO 编号的对应关系,并提醒你哪些引脚在使用时需要注意。</p><p>绿色标记的引脚可以随意使用;黄色标记的引脚可以使用,但需要注意它们在芯片启动时的影响,可能带来意外的问题。红色标记的引脚不建议用作输入或输出功能。</p><table><thead><tr><th>丝印标签</th><th>GPIO</th><th>可作为输入</th><th>可作为输出</th><th>备注</th></tr></thead><tbody><tr><td>D0</td><td>GPIO16</td><td>不可用于中断</td><td>不可用于 PWM 或 I2C</td><td>🟠启动时为高电平<br>用于从深度睡眠中唤醒</td></tr><tr><td>D1</td><td>GPIO5</td><td>🟢是</td><td>🟢是</td><td>通常用作 <strong>SCL</strong> (I2C)</td></tr><tr><td>D2</td><td>GPIO4</td><td>🟢是</td><td>🟢是</td><td>通常用作 <strong>SDA</strong> (I2C)</td></tr><tr><td>D3</td><td>GPIO0</td><td>已被上拉</td><td>🟢是</td><td>与 FLASH 按键连接,如果拉低则会启动失败</td></tr><tr><td>D4</td><td>GPIO2</td><td>已被上拉</td><td>🟢是</td><td>🟠启动时为高电平<br>连接板载 LED,如果拉低则会启动失败</td></tr><tr><td>D5</td><td>GPIO14</td><td>🟢是</td><td>🟢是</td><td><strong>SPI</strong> (SCLK)</td></tr><tr><td>D6</td><td>GPIO12</td><td>🟢是</td><td>🟢是</td><td><strong>SPI</strong> (MISO)</td></tr><tr><td>D7</td><td>GPIO13</td><td>🟢是</td><td>🟢是</td><td><strong>SPI</strong> (MOSI)</td></tr><tr><td>D8</td><td>GPIO15</td><td>已被下拉至 GND</td><td>🟡是</td><td><strong>SPI</strong> (CS)<br>如果拉高则会启动失败</td></tr><tr><td>RX</td><td>GPIO3</td><td>🟡是</td><td>🔴RX 引脚</td><td>🟠启动时为高电平</td></tr><tr><td>TX</td><td>GPIO1</td><td>🔴TX 引脚</td><td>🟡是</td><td>🟠启动时为高电平<br>启动时的调试输出引脚,如果拉低会启动失败</td></tr><tr><td>A0</td><td>ADC0</td><td>🟢模拟输入</td><td>🔴禁用</td><td></td></tr></tbody></table><p>接下来的篇幅将更详细地介绍 ESP8266 GPIO 引脚的功能。</p><h3 id="连接-FLASH-芯片的引脚"><a href="#连接-FLASH-芯片的引脚" class="headerlink" title="连接 FLASH 芯片的引脚"></a>连接 FLASH 芯片的引脚</h3><p>GPIO6 到 GPIO11 通常用于连接 FLASH 芯片,所以,不推荐使用这几个引脚。</p><h3 id="启动过程中用到的引脚"><a href="#启动过程中用到的引脚" class="headerlink" title="启动过程中用到的引脚"></a>启动过程中用到的引脚</h3><p>如果某些引脚被拉高或者拉低,ESP8266 可能会启动失败。下表是部分引脚在启动时的状态:</p><ul><li><strong>GPIO16</strong>:启动时为高电平</li><li><strong>GPIO0</strong>:如果被拉低,则启动失败</li><li><strong>GPIO2</strong>:启动时为高电平,如果被拉低,则启动失败</li><li><strong>GPIO15</strong>:如果被拉高,则启动失败</li><li><strong>GPIO3</strong>:启动时为高电平</li><li><strong>GPIO1</strong>:启动时为高电平,如果被拉低,则启动失败</li><li><strong>GPIO10</strong>:启动时为高电平</li><li><strong>GPIO9</strong>:启动时为高电平</li></ul><h3 id="启动时为高电平的引脚"><a href="#启动时为高电平的引脚" class="headerlink" title="启动时为高电平的引脚"></a>启动时为高电平的引脚</h3><p>以下引脚在启动时会输出 3.3V 的高电平。如果你在这些引脚上接了继电器之类的外设,可能会带来一些问题:</p><ul><li>GPIO16</li><li>GPIO3</li><li>GPIO1</li><li>GPIO10</li><li>GPIO9</li></ul><p>此外,其他引脚(除了 GPIO5 和 GPIO4),在启动时会输出低电平信号,同样可能带来问题。你可以阅读 <a href="https://rabbithole.wwwdotorg.org/2017/03/28/esp8266-gpio.html">此文章</a> 以详细了解各个 GPIO 在启动时的状态。</p><blockquote><p>🟢如果需要控制继电器或功率管,GPIO4 和 GPIO5 是最安全的引脚。</p></blockquote><h3 id="模拟输入引脚"><a href="#模拟输入引脚" class="headerlink" title="模拟输入引脚"></a>模拟输入引脚</h3><p>ESP8266 只有一个引脚支持模拟输入,此引脚叫 ADC0,丝印上常标记为 A0。</p><p>如果使用 ESP8266 裸芯片(ESP-12E/F)的话,此引脚的电压输入范围为 0-1V。如果使用了 NodeMCU 之类的开发板,那么电压输入范围就是 0-3.3V,因为开发板上已经集成了分压器。</p><h3 id="板载-LED"><a href="#板载-LED" class="headerlink" title="板载 LED"></a>板载 LED</h3><p>大多数 ESP8266 模块均有一个内置的 LED,通常连在 GPIO2 上。LED 亮灭的逻辑是反向的,GPIO2 为高电平时,LED 熄灭;GPIO2 低电平时,LED 亮起。</p><p><img src="/static/blog_images/esp8266-pinout-reference-gpios/6.png"></p><h3 id="复位引脚"><a href="#复位引脚" class="headerlink" title="复位引脚"></a>复位引脚</h3><p>当 RST 引脚被拉低时,ESP8266 将被复位。按开发板上的 RESET 按键同理。</p><p><img src="/static/blog_images/esp8266-pinout-reference-gpios/7.png"></p><h3 id="GPIO0"><a href="#GPIO0" class="headerlink" title="GPIO0"></a>GPIO0</h3><p>当 GPIO0 被拉低时,复位 ESP8266,芯片将进入 bootloader 模式。按开发板上的 FLASH/BOOT 按钮同理。</p><p><img src="/static/blog_images/esp8266-pinout-reference-gpios/8.png"></p><h3 id="GPIO16"><a href="#GPIO16" class="headerlink" title="GPIO16"></a>GPIO16</h3><p>GPIO16 可被用于从深度睡眠中唤醒 ESP8266。要实现此功能,需要将 GPIO16 连接在 RST 引脚上。关于如何实现深度睡眠,请搜索并参考 Arduino 官网上的相关案例。</p><h3 id="I2C"><a href="#I2C" class="headerlink" title="I2C"></a>I2C</h3><p>ESP8266 没有硬件 I2C 引脚,但可以用软件模拟,所以你可以使用任意引脚实现 I2C。通常我们会使用以下引脚:</p><ul><li><strong>GPIO5</strong>:SCL</li><li><strong>GPIO4</strong>:SDA</li></ul><h3 id="SPI"><a href="#SPI" class="headerlink" title="SPI"></a>SPI</h3><p>ESP8266 上的 SPI 引脚如下:</p><ul><li><strong>GPIO12</strong>:MISO</li><li><strong>GPIO13</strong>:MOSI</li><li><strong>GPIO14</strong>:SCLK</li><li><strong>GPIO15</strong>:CS</li></ul><h3 id="PWM-引脚"><a href="#PWM-引脚" class="headerlink" title="PWM 引脚"></a>PWM 引脚</h3><p>我们可以在 ESP8266 的所有引脚(GPIO0 至 GPIO15)上软件实现 PWM 功能。ESP8266 上的 PWM 有 10 位精度。关于如何实现 PWM 功能,请搜索并参考 Arduino 官网上的相关案例。</p><h3 id="中断引脚"><a href="#中断引脚" class="headerlink" title="中断引脚"></a>中断引脚</h3><p>ESP8266 的所有 GPIO 引脚均支持中断,除了 GPIO16。相关案例请搜索并参考 Arduino 官网上的相关案例。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>希望本文能解决你对 ESP8266 GPIO 的相关疑惑,祝好!</p>]]></content>
<categories>
<category> 攻玉计划 </category>
</categories>
<tags>
<tag> Arduino </tag>
<tag> esp8266 </tag>
</tags>
</entry>
<entry>
<title>如何 DIY 一个苏康码与行程码“双码合一”的健康码 APP</title>
<link href="/2022/04/08/diy-to-combine-sukangma-and-xingchengma/"/>
<url>/2022/04/08/diy-to-combine-sukangma-and-xingchengma/</url>
<content type="html"><![CDATA[<h2 id="背景介绍"><a href="#背景介绍" class="headerlink" title="背景介绍"></a>背景介绍</h2><p>众所周知的背景:</p><ol><li>苏康码打开很慢,在支付宝中如果没有快捷键,需要以下步骤:点击打开支付宝 -> 点击健康码 -> 点击立即查看,如果设置了长按图标打开健康码,也得至少两步;其他 APP 比如“苏周到”,可以实现长按快捷键后一步访问,但是其中存在三个步骤:APP 启动 -> 健康码小程序启动 -> 加载网页,这种不可理喻的框架,在某些低端机上冷启动,可能需要长达数十秒的时间;</li><li>行程卡打开也很慢,在微信小程序中打开,不知道为什么每次都让我确认一下“同意并授权运营商查询”,严重影响效率;</li><li>很多地方两个码都要查,于是慢*2,话说我也不知道为什么有关部门不把这两个码合二为一……</li></ol><p>所以干脆 DIY 一个。</p><h2 id="开发流程"><a href="#开发流程" class="headerlink" title="开发流程"></a>开发流程</h2><p>我想做出这样的效果:打开 APP 后,直接显示苏康码,滑动屏幕可切换到行程码,不需要任何多余的点击动作。</p><h3 id="技术栈选择"><a href="#技术栈选择" class="headerlink" title="技术栈选择"></a>技术栈选择</h3><p>我没有任何 APP 开发经验,所以相当于新手。因为最近用 C# 和 .NET 框架比较多,<a href="https://v2ex.com/t/844404">经 V2EX 网友提醒</a>,我选择了 Xamarin 框架。据说如果新手想快速尝试跨平台 APP 开发,用 flutter 比较好,但是……whatever,支持一下微软,及其宇宙第一 IDE。</p><h3 id="获取苏康码直链"><a href="#获取苏康码直链" class="headerlink" title="获取苏康码直链"></a>获取苏康码直链</h3><p>既然苏康码本质上是网页,而且我在朋友圈了解到,可以获取到直链而且是不需要认证的,只要 token 对就行了,那就简单了。</p><p>于是我决定使用 <a href="https://www.telerik.com/fiddler/fiddler-classic">Fiddler</a> 抓包,大致步骤就是:配好 Fiddler 的监听端口,然后保证电脑和手机在同一个局域网内,手机在 WiFi 设置里配置好 Fiddler 的代理。</p><p><img src="/static/blog_images/diy-to-combine-sukangma-and-xingchengma/1.png"></p><p>测试的 APP 是苏周到,不出所料,苏康码链接是 HTTPS 的,想解密只能在手机上安装一个证书然后中间人了。</p><p><img src="/static/blog_images/diy-to-combine-sukangma-and-xingchengma/2.png"></p><p>导出 Fiddler 的证书并复制到手机上,MIUI 安装证书的步骤也很简单,记得在抓完包之后删除这个证书就好。</p><p><img src="/static/blog_images/diy-to-combine-sukangma-and-xingchengma/3.png"></p><p>然后就可以解密 HTTPS 流量。理论上,对于现在的 Android 版本,APP 可以选择不信任用户安装的证书,但还好,苏康码并没有采取这样的机制。</p><p>解析出来的苏康码的直链很简单,就是 <a href="https://jsstm.jszwfw.gov.cn/jkmIndex.html?token=xxxxxxxxxxxxxxxx&uuid=xxxxxxxxxxxxxxx">https://jsstm.jszwfw.gov.cn/jkmIndex.html?token=xxxxxxxxxxxxxxxx&uuid=xxxxxxxxxxxxxxx</a> 这样的格式,直接访问就可以看到自己的苏康码界面。</p><h3 id="获取行程码直链"><a href="#获取行程码直链" class="headerlink" title="获取行程码直链"></a>获取行程码直链</h3><p>行程码直链更简单,直接就可以搜到: <a href="https://xc.caict.ac.cn/">https://xc.caict.ac.cn/</a> 。有趣的是,行程码居然是用 Vue 框架做的,如果只是普通的 HTML 表单页面的话,我也许会做一个自动发验证码查询的功能。</p><h3 id="APP-开发"><a href="#APP-开发" class="headerlink" title="APP 开发"></a>APP 开发</h3><p>安装移动端开发相关 SDK 后,启动宇宙最强 IDE,新建一个空项目叫 <code>ShuangShuangMa</code> (随便起一个名字,双双码),然后面向 Google 编程……基本上只要查一下怎么使用 WebView 以及如何实现滑动切换页面就好了。</p><p>代码非常简单,xaml 页面如下:</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta"><?xml version=<span class="string">"1.0"</span> encoding=<span class="string">"utf-8"</span> ?></span></span><br><span class="line"><span class="tag"><<span class="name">CarouselPage</span> <span class="attr">xmlns</span>=<span class="string">"http://xamarin.com/schemas/2014/forms"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">xmlns:x</span>=<span class="string">"http://schemas.microsoft.com/winfx/2009/xaml"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">x:Class</span>=<span class="string">"ShuangShuangMa.MainPage"</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">ContentPage</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">StackLayout</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">WebView</span> <span class="attr">x:Name</span>=<span class="string">"WebView_SuKangMa"</span> <span class="attr">VerticalOptions</span>=<span class="string">"FillAndExpand"</span> /></span></span><br><span class="line"> <span class="tag"><<span class="name">Button</span> <span class="attr">Text</span>=<span class="string">"点击刷新"</span> <span class="attr">Margin</span>=<span class="string">"50,10"</span> <span class="attr">Clicked</span>=<span class="string">"Button_RefreshSuKangMa_Clicked"</span> /></span></span><br><span class="line"> <span class="tag"></<span class="name">StackLayout</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">ContentPage</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">ContentPage</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">StackLayout</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">WebView</span> <span class="attr">x:Name</span>=<span class="string">"WebView_XingChengMa"</span> <span class="attr">VerticalOptions</span>=<span class="string">"FillAndExpand"</span> /></span></span><br><span class="line"> <span class="tag"></<span class="name">StackLayout</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">ContentPage</span>></span></span><br><span class="line"></span><br><span class="line"><span class="tag"></<span class="name">CarouselPage</span>></span></span><br></pre></td></tr></table></figure><p>C# 逻辑如下:</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">using</span> System;</span><br><span class="line"><span class="keyword">using</span> Xamarin.Forms;</span><br><span class="line"></span><br><span class="line"><span class="keyword">namespace</span> <span class="title">ShuangShuangMa</span></span><br><span class="line">{</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">partial</span> <span class="keyword">class</span> <span class="title">MainPage</span> : <span class="title">CarouselPage</span></span><br><span class="line"> {</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">MainPage</span>()</span></span><br><span class="line"> {</span><br><span class="line"> InitializeComponent();</span><br><span class="line"> WebView_SuKangMa.Source = <span class="string">"https://jsstm.jszwfw.gov.cn/jkmIndex.html?token=xxxxxx&uuid=xxxxxx"</span>;</span><br><span class="line"> WebView_XingChengMa.Source = <span class="string">"https://xc.caict.ac.cn/"</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">Button_RefreshSuKangMa_Clicked</span>(<span class="params"><span class="built_in">object</span> sender, EventArgs e</span>)</span></span><br><span class="line"> {</span><br><span class="line"> WebView_SuKangMa.Reload();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>部分解释:</p><ol><li>添加刷新按钮是因为,WebView 在后台不会运行,而苏康码上的时间又是在前端运算的,所以防止再次打开应用后,苏康码的时间不对;</li><li>行程码网页在重新加载后就必须重新认证手机号,而 Android 应用在触发返回按钮后,会关闭所有的 WebView,所以需要在 <code>MainActivity.cs</code> 中添加以下代码以便把返回按钮当 Home 键用:</li></ol><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">override</span> <span class="keyword">void</span> <span class="title">OnBackPressed</span>()</span></span><br><span class="line">{</span><br><span class="line"> MoveTaskToBack(<span class="literal">true</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>当然,应用安装后得关闭电池优化、锁在后台,毕竟被清理掉之后又得重新认证行程码。</p><p>至此,APP 完成。</p><h2 id="效果展示"><a href="#效果展示" class="headerlink" title="效果展示"></a>效果展示</h2><p>效果见下面的视频:</p><video src='/static/blog_images/diy-to-combine-sukangma-and-xingchengma/4.mp4 ' type='video/mp4' controls='controls' width='320px'></video><p>话说,本来想着 Xamarin 是跨平台的,准备给玲玲的 iPhone 也整一个的,但无奈意识到自己没有 Mac,作罢。</p>]]></content>
<categories>
<category> TECHNOLOGY </category>
</categories>
<tags>
<tag> Xamarin </tag>
<tag> C# </tag>
<tag> WEB </tag>
</tags>
</entry>
<entry>
<title>我两周就写了三行代码 - ARM Cortex A9 中断与浮点数运算、FPU 问题</title>
<link href="/2022/03/08/arm-cortex-a9-interrupt-with-fpu/"/>
<url>/2022/03/08/arm-cortex-a9-interrupt-with-fpu/</url>
<content type="html"><![CDATA[<h2 id="问题出现"><a href="#问题出现" class="headerlink" title="问题出现"></a>问题出现</h2><p>公司产品采用了 Xilinx Zynq 7z010 芯片,用于运动控制以及网络通讯。两周前,测试过程中发现网络通信会小概率出错,TCP 收到的数据 CRC 校验失败,无法稳定复现。</p><p>设备平台概述:</p><ol><li>CPU: Cortex-A9 双核</li><li>RAM: 1GB DDR3</li><li>操作系统: FreeRTOS</li><li>网络协议栈: lwip211</li></ol><h2 id="定位过程"><a href="#定位过程" class="headerlink" title="定位过程"></a>定位过程</h2><h3 id="怀疑应用层数据处理问题"><a href="#怀疑应用层数据处理问题" class="headerlink" title="怀疑应用层数据处理问题"></a>怀疑应用层数据处理问题</h3><p>TCP 是二进制数据流,每个包的长度不固定,应用层也许会写错。于是我修改了应用层的处理方案,手动构造了定长的数据包,虽然会导致 TCP 流量大幅上涨,但是逻辑看起来更清晰。</p><p>然而,修改后,似乎由于流量变大了,原来小概率出现的错误,现在大概率会出现!这也给 Debug 带来了有利的一面。</p><h3 id="怀疑网络通讯链路电磁干扰问题"><a href="#怀疑网络通讯链路电磁干扰问题" class="headerlink" title="怀疑网络通讯链路电磁干扰问题"></a>怀疑网络通讯链路电磁干扰问题</h3><p>但是这个怀疑方向很快就被否定了,因为我用了 TCP 协议,理论上只可能超时,不可能出错。</p><h3 id="怀疑-lwip-接口调用问题"><a href="#怀疑-lwip-接口调用问题" class="headerlink" title="怀疑 lwip 接口调用问题"></a>怀疑 lwip 接口调用问题</h3><p>lwip 有多个 TCP API,之前用的 Socket API,我尝试换成了 RAW API,但是问题依旧。</p><p>在调试的过程中,我尝试在网络链路的每一层数据打印出来,惊奇地发现,在数据链路层,数据是正确的!然而 lwip 的代码冗杂且数 MB 数据中才会出现几个错误位,于是我暂时没有考虑一层层分析代码。</p><h3 id="怀疑与其他线程之间存在干扰,或者存在数组越界访问"><a href="#怀疑与其他线程之间存在干扰,或者存在数组越界访问" class="headerlink" title="怀疑与其他线程之间存在干扰,或者存在数组越界访问"></a>怀疑与其他线程之间存在干扰,或者存在数组越界访问</h3><p>这样 Debug 就很简单了。我关闭了所有其他的线程,不出所料,Bug 消失了。</p><p>一点点放开线程,发现是一个<strong>运动控制的硬中断</strong>造成的 Bug。</p><p>然后再“二分法”排除代码,结果排除到最后,仅仅是一行代码:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// b, c 也为 long long</span></span><br><span class="line"><span class="type">long</span> <span class="type">long</span> a = (<span class="type">long</span> <span class="type">long</span>)((<span class="type">double</span>)b * (<span class="type">double</span>)c);</span><br></pre></td></tr></table></figure><p>这让我大跌眼镜,因为实验证明,把这句话删了,TCP 通讯就正常了。</p><h3 id="怀疑是浮点运算的问题"><a href="#怀疑是浮点运算的问题" class="headerlink" title="怀疑是浮点运算的问题"></a>怀疑是浮点运算的问题</h3><p>更加让我迷惑的是,把上述语句改下,同样也没问题了:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">long</span> <span class="type">long</span> a = b * c;</span><br></pre></td></tr></table></figure><p>进一步定位:我在另一个线程中添加了浮点运算,并把这个有影响的中断关闭,TCP 通讯同样出问题了。</p><p>就此,几乎可以确定是浮点数运算造成的问题了。</p><h3 id="问题小结"><a href="#问题小结" class="headerlink" title="问题小结"></a>问题小结</h3><p>一句话描述问题:在中断或某个线程中进行浮点数操作,会导致另一个 TCP 通讯线程数据出错。</p><p>说实话,我当时也没法理解其中的联系。</p><p>只不过我们用的芯片自带双精度 FPU(浮点运算单元),也许是 FPU 的问题?</p><h2 id="解决过程"><a href="#解决过程" class="headerlink" title="解决过程"></a>解决过程</h2><h3 id="查找资料"><a href="#查找资料" class="headerlink" title="查找资料"></a>查找资料</h3><p>关键词 <code>lwip tcp receive wrong data</code>,<code>zynq float process corrupt memory</code> 等关键词,都没有找到有价值的解决方案。</p><h3 id="求助-Xilinx-技术支持"><a href="#求助-Xilinx-技术支持" class="headerlink" title="求助 Xilinx 技术支持"></a>求助 Xilinx 技术支持</h3><p>果然用微信联系的技术支持不靠谱,上午说帮忙复现,下午就没信了。</p><h3 id="求助朋友圈资深开发者"><a href="#求助朋友圈资深开发者" class="headerlink" title="求助朋友圈资深开发者"></a>求助朋友圈资深开发者</h3><p>只可惜他们都是互联网界的大佬,只有我在嵌入式开发领域摸爬滚打,他山之玉难以攻石。</p><h3 id="求助-V2EX-网友"><a href="#求助-V2EX-网友" class="headerlink" title="求助 V2EX 网友"></a>求助 V2EX 网友</h3><p>发了帖子 <a href="https://v2ex.com/t/838643">在这里</a>。</p><p>V 站网友给了非常有价值的线索:</p><ol><li>网友 A 称他们使用同样的平台出现过类似的问题。他们的解决方案是,进行浮点数操作之前,关闭所有的中断;</li><li>网友 B 分析可能 <code>正在计算浮点数的时候,刚好发生了 systick 线程切换,但是线程切换过程中,没有保存 /恢复浮点寄存器</code>;</li><li>网友 C 更是找到了相关文章:<blockquote><p>“Some GCC libraries optimise memory copy and memory set (and possibly other) functions by making use of the wide floating point registers. Therefore, by default, any task that uses functions such as memcpy(), memcmp() or memset(), or uses a FreeRTOS API function such as xQueueSend() which itself uses memcpy(), will inadvertently corrupt the floating point registers.”</p></blockquote></li></ol><p>真可谓一针见血,<strong>TCP 协议栈中大量使用了 memcpy,而 memcpy 又使用了 FPU 的寄存器,极有可能在 TCP 处理数据的过程中,另一个中断来了,进行了浮点运算并修改了 FPU 的寄存器,以致 TCP 数据出错。</strong></p><p>同样根据网友的指点,看了这篇文章 <a href="https://www.freertos.org/Using-FreeRTOS-on-Cortex-A-Embedded-Processors.html">Using FreeRTOS on ARM Cortex-A9 Embedded Processors</a>,原来 FreeRTOS 自身已经考虑了 FPU 与上下文切换相关的问题,只是要我们将 <code>configUSE_TASK_FPU_SUPPORT</code> 这个宏定义为 2 即可。</p><h2 id="问题解决"><a href="#问题解决" class="headerlink" title="问题解决"></a>问题解决</h2><p>花了些时间进行 FPU 寄存器相关的搜索,依照 <a href="https://stackoverflow.com/questions/38667425/how-to-push-and-pop-floating-point-registers-to-the-stack-on-armv7-32-bit">这篇文章</a> ,对 FPU 的寄存器做了相关处理,总结起来就三行代码:</p><h3 id="第一行代码"><a href="#第一行代码" class="headerlink" title="第一行代码"></a>第一行代码</h3><p>在中断响应函数开头添加以下代码:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">__asm(<span class="string">"VPUSH {d0-d15}"</span>); <span class="comment">// FPU 寄存器入栈</span></span><br></pre></td></tr></table></figure><h3 id="第二行代码"><a href="#第二行代码" class="headerlink" title="第二行代码"></a>第二行代码</h3><p>在中断响应函数末尾添加以下代码:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">__asm(<span class="string">"VPOP {d0-d15}"</span>); <span class="comment">// FPU 寄存器出栈</span></span><br></pre></td></tr></table></figure><h3 id="第三行代码"><a href="#第三行代码" class="headerlink" title="第三行代码"></a>第三行代码</h3><p>FreeRTOS 启用 FPU 支持相关宏:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">define</span> configUSE_TASK_FPU_SUPPORT 2</span></span><br></pre></td></tr></table></figure><p>至此,问题解决。</p>]]></content>
<categories>
<category> TECHNOLOGY </category>
</categories>
<tags>
<tag> ARM </tag>
<tag> FPU </tag>
</tags>
</entry>
<entry>
<title>随便聊聊最近在本站折腾的那些东西</title>
<link href="/2022/01/24/Casual-talk-of-current-site-management/"/>
<url>/2022/01/24/Casual-talk-of-current-site-management/</url>
<content type="html"><![CDATA[<blockquote><p>重新加上了 Disqus 评论系统,翻译了一个树莓派引脚定义网站,换了企业邮箱服务商,部署了一个站点监控系统,开始搞 nextcloud 中文论坛……随便聊聊最近在本站折腾的那些东西。</p></blockquote><h2 id="Disqus-评论系统"><a href="#Disqus-评论系统" class="headerlink" title="Disqus 评论系统"></a>Disqus 评论系统</h2><p>本站博客一直用的是 hexo,评论系统一开始也是用的 Disqus,后来不知道为啥就把评论系统给删了(可能是因为被墙了没意思?还是因为换了主题不支持啥的)。评论系统删了以后,又脑抽用 flarum 搭建了一个论坛,然后每篇博客前面都加一个对应的论坛讨论地址……这个方案被 PM 学叔给喷了,因为谁会为了评论,再去注册一个你的论坛账户呢?所以前几天把论坛给删了(话说也只有瑞辅一个人评论过一次,哈哈)。</p><p>Disqus 评论系统可能是在 hexo 上最好用的评论系统了,谁让 hexo 自己本身就是一个静态页面呢。但问题是 Disqus 被墙了,我得提醒读者,本站是有评论系统的。很久以前看到奶冰的博客,也是用的 Disqus,他是自己加了一个按钮,提醒用户点击加载 Disqus 评论组件。于是时隔几年我把这个想法抄过来了。</p><p>一开始感觉做这个按钮好复杂啊,因为自己几乎没有前端开发的经验。但是随便翻了翻我用的这个 aircloud 主题,发现源码改起来还是挺容易的,就强行加了一个按钮(话说改的方法我也上传到我 fork 的这个仓库里面了,<a href="https://github.com/Villivateur/hexo-theme-aircloud">https://github.com/Villivateur/hexo-theme-aircloud</a>。</p><h2 id="博客主题的魔改"><a href="#博客主题的魔改" class="headerlink" title="博客主题的魔改"></a>博客主题的魔改</h2><p>除了改了评论系统,我也魔改了其他小东西。我有点洁癖嘛,而且自从上次 jsdelivr 被拔网线的事情之后,我觉得静态资源用其他的站点托管都不太稳定,于是把 aircloud 里面用到的 js 和 CSS 都下载并重新托管在了自己的服务器上。</p><p>aircloud 还提供了浏览量计数功能,一看也是加载了第三方的 JS,算了不要了,有点洁癖。</p><h2 id="树莓派引脚定义网站-pinout-vvzero-com"><a href="#树莓派引脚定义网站-pinout-vvzero-com" class="headerlink" title="树莓派引脚定义网站 pinout.vvzero.com"></a>树莓派引脚定义网站 pinout.vvzero.com</h2><p>很早就了解到过一个做得很棒的树莓派引脚定义查询网站 pinout.xyz,而且已经有人翻译成了法语、德语、土耳其语啥的。我一直有很强烈的翻译欲望,现在终于找到了一个适合翻译的(之前没人翻译过、数据量不大),于是就开始了。</p><p>按照原作者给的翻译指引翻译了几个页面,但是感觉不过瘾,于是把本不该翻译的也翻译了(比如页面左下角的树莓派小图,还有生成脚本里面的一些细节),看来是没法全部提交 PR 了。故又 fork 了一个纯中文版的仓库,地址 <a href="https://git.vvzero.com/villivateur/pinout.vvzero.com">https://git.vvzero.com/villivateur/pinout.vvzero.com</a> ,用于部署 <a href="https://pinout.vvzero.com/">https://pinout.vvzero.com</a> 。按照翻译指引的仓库 <a href="https://github.com/Villivateur/Pinout.xyz">https://github.com/Villivateur/Pinout.xyz</a> 只用来提交 PR。</p><h2 id="zoho-企业邮箱"><a href="#zoho-企业邮箱" class="headerlink" title="zoho 企业邮箱"></a>zoho 企业邮箱</h2><p>腾讯企业邮箱是越来越恶心了,强行给你绑企业微信,还各种限制,不用微信登陆就强制改密码,算了,终于忍不了弃用了。V 站网友推荐了 zoho 邮箱,试了下感觉不错,于是给自己和玲玲买了两个账户,国区的,一年一共 100,完全可以接受。</p><p>目前发现 zoho 邮箱有两个问题:</p><ol><li>spam 有点严格,zoho 自家发的邮件都会进去,哈哈</li><li>我在 archive.org 的注册邮件,zoho 收不到</li></ol><h2 id="站点监控部署"><a href="#站点监控部署" class="headerlink" title="站点监控部署"></a>站点监控部署</h2><p>也是看到奶冰的网站,有个站点监控的玩意,用了 uptimerobot 的服务,试了下挺好玩,于是也部署在了 <a href="https://status.vvzero.com/">https://status.vvzero.com</a></p><h2 id="nextcloud-中文论坛"><a href="#nextcloud-中文论坛" class="headerlink" title="nextcloud 中文论坛"></a>nextcloud 中文论坛</h2><p>算是闲得蛋疼吧,感觉没人做 nextcloud 中文论坛,但我用 nextcloud 确实很多,就“抢先”搞了,虽然目前没什么内容。<a href="https://www.nextcloudcn.com/">https://www.nextcloudcn.com</a> 这个域名还是我抢注的,之前一个人刚好到期。没备案,所以只能托管在香港,套个 cloudflare。</p><p>至于运营就看情怀了,希望明年还想续费这个域名……</p>]]></content>
<categories>
<category> TECHNOLOGY </category>
</categories>
<tags>
<tag> 树莓派 </tag>
<tag> email </tag>
<tag> 站点监控 </tag>
<tag> 评论系统 </tag>
<tag> nextcloud </tag>
</tags>
</entry>
<entry>
<title>分享一下我的家庭网络布局</title>
<link href="/2021/11/04/Network-devices-at-my-home/"/>
<url>/2021/11/04/Network-devices-at-my-home/</url>
<content type="html"><![CDATA[<h2 id="我的家庭网络拓扑图"><a href="#我的家庭网络拓扑图" class="headerlink" title="我的家庭网络拓扑图"></a>我的家庭网络拓扑图</h2><p><img src="/static/blog_images/Network-devices-at-my-home/1.jpg"></p><h3 id="数据流部分"><a href="#数据流部分" class="headerlink" title="数据流部分"></a>数据流部分</h3><ol><li>网络核心部分是 Nano Pi R2S 这个软路由,经过一年调教,已经能适应我这里的一切网络需求了。主要运行了 Wireguard、流量监控、网路唤醒等服务。因为我们小区只有百兆网,所以性能暂时够用。</li><li>几乎所有网络设备都通过中间这个交换机与软路由通讯,虽然不算什么好的交换机,但是同理,够用。</li><li>数据中心是用 intel NUC8 搭建的家庭服务器,主要部署了 emby、Nextcloud、qbittorrent 等服务,满足了家庭观影、数据存储等需求。</li><li>存储采用多个硬盘,目前放弃了阵列模式,家庭用的话还是单块盘来用性价比最高,经常冷备、热备就好。</li><li>家庭服务器通过 FRP 服务把 Nextcloud 服务映射到公网,方便在外看家庭数据。</li><li>主力机是一台 E3-1231 V3 的机器,为什么还在用好几年前的平台?因为满足我的需求了。</li><li>我把一台小米路由器当 AP 用了,目前可以满足所有智能家居、所有手机、平板的稳定联网。</li><li>智能家居方面,完全采用了小米的方案,好看就是王道。</li></ol><h3 id="功率电部分"><a href="#功率电部分" class="headerlink" title="功率电部分"></a>功率电部分</h3><ol><li>图中除了主力 PC、移动设备和智能家居,其他均部署在一个机柜内,所以两个插排就搞定了。</li><li>UPS 还是很有必要的,帮我至少挡住了两次意外停电。</li></ol>]]></content>
<categories>
<category> TECHNOLOGY </category>
</categories>
<tags>
<tag> 智能家居 </tag>
<tag> 运维 </tag>
</tags>
</entry>
<entry>
<title>Mapuino - 一个硬件极客风的 WEB 访客地图显示摆件</title>
<link href="/2021/09/27/Mapuino-a-cute-knickknack-for-web-access-monitor/"/>
<url>/2021/09/27/Mapuino-a-cute-knickknack-for-web-access-monitor/</url>
<content type="html"><![CDATA[<h2 id="Mapuino-是什么"><a href="#Mapuino-是什么" class="headerlink" title="Mapuino 是什么"></a>Mapuino 是什么</h2><p>Mapuino 是一个简单的摆件,或者叫“玩具”。你可以在自己的个人博客、主页或者任何可以插入个性代码的社交网站(如 V2EX)上添加一行 URL,然后就可以在 Mapuino 上观赏全国哪些地方的人正在访问你的网站。</p><p><img src="/static/blog_images/Mapuino-a-cute-knickknack-for-web-access-monitor/1.jpg"></p><h2 id="Mapuino-不是什么"><a href="#Mapuino-不是什么" class="headerlink" title="Mapuino 不是什么"></a>Mapuino 不是什么</h2><p>Mapuino 不是生产力工具,它功能单一,仅供娱乐。但它真的可以给你的生活带来一些小乐趣。</p><h2 id="Mapuino-的历史故事"><a href="#Mapuino-的历史故事" class="headerlink" title="Mapuino 的历史故事"></a>Mapuino 的历史故事</h2><p>2017 年秋学季,我上大二,有幸加入学校的学生 IT 创新创业区,并认识了 suruifu 同学,当时我所在的部门叫“物联网创新区”。圣诞前夜,suruifu 同学在创新区内给我分享了一个外国小哥的 youtube 视频。视频中,外国小哥做了一个圣诞树,神奇之处是,只要有人 ping 他的电脑 ip,圣诞树上就会随机亮起一个 LED。很多人一起 ping 的时候,圣诞树就会闪闪发光。</p><p>suruifu 同学感慨:“这才是物联网!”</p><p>而后,到了今年,一个月以前,我做了第一个小摆件 <a href="https://blog.vvzero.com/2021/08/31/Topuino-the-wonderful-Knickknack-for-server-monitoring/">Topuino</a>。</p><p>用同样的技术栈,我又做了 Mapuino。</p><h2 id="Mapuino-的工作模式"><a href="#Mapuino-的工作模式" class="headerlink" title="Mapuino 的工作模式"></a>Mapuino 的工作模式</h2><p>Mapuino 与 Topuino 类似,在配置之后,会连接 Wi-Fi 并从服务器获取数据,在每个周期内(比如 1 分钟),所有在上一个周期访问过你网站的用户,其所在省级行政区的 LED 将会亮起。</p><h2 id="Mapuino-的工作原理"><a href="#Mapuino-的工作原理" class="headerlink" title="Mapuino 的工作原理"></a>Mapuino 的工作原理</h2><p>硬件部分与 Topuino 非常类似,采用 ESP8266 作为 MCU,TM1638 作为 LED 驱动。</p><p>Mapuino 会以 1 分钟为周期向服务器发起请求,服务器返回上一个周期哪些地区有用户访问了指定 URL。此 URL 可以嵌入在任何网页中,比如通过 JS 发起请求,或者假装是一个 img 标签,或者也可以用各类站长测速工具直接 DDOS 这个 URL……</p><p>服务端直接解析访问此 URL 的 IP 所在地(目前使用了高德的 API),并临时存储。</p><p><img src="/static/blog_images/Mapuino-a-cute-knickknack-for-web-access-monitor/2.jpg"></p><h2 id="与-Topuino-相比的改进"><a href="#与-Topuino-相比的改进" class="headerlink" title="与 Topuino 相比的改进"></a>与 Topuino 相比的改进</h2><ol><li>体积更小,<del>可以白嫖部分 PCB 打样厂的免费额度</del>;</li><li>调整了下面两个固定孔的位置,可以直接拧上两个螺柱,方便放在桌上;</li><li>隐藏了 Wi-Fi 天线;</li><li>成本更低。</li></ol><h2 id="代码与开源"><a href="#代码与开源" class="headerlink" title="代码与开源"></a>代码与开源</h2><p>硬件端: <a href="https://github.com/Villivateur/Mapuino">https://github.com/Villivateur/Mapuino</a></p><p>服务端: <a href="https://github.com/Villivateur/MapuinoServer">https://github.com/Villivateur/MapuinoServer</a></p><h2 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h2><p>我又有其他点子啦,下一个做啥呢?</p>]]></content>
<categories>
<category> TECHNOLOGY </category>
</categories>
<tags>
<tag> Arduino </tag>
<tag> ESP8266 </tag>
<tag> IoT </tag>
</tags>
</entry>
<entry>
<title>Topuino - 你愿意在办公桌上放一个监控服务器的小摆件吗?</title>
<link href="/2021/08/31/Topuino-the-wonderful-Knickknack-for-server-monitoring/"/>
<url>/2021/08/31/Topuino-the-wonderful-Knickknack-for-server-monitoring/</url>
<content type="html"><![CDATA[<blockquote><p>我做了一个用来监控服务器的桌面小摆件</p></blockquote><p><img src="/static/blog_images/Topuino-the-wonderful-Knickknack-for-server-monitoring/1.png"></p><h2 id="什么是-Topuino"><a href="#什么是-Topuino" class="headerlink" title="什么是 Topuino"></a>什么是 Topuino</h2><p>Topuino 是我 DIY 的一个桌面小摆件,可以实现通用服务器或计算机的数据监控,包括 CPU 占用、RAM 占用、两个硬盘的可用空间、硬盘读写速度、网络 IO 速率。</p><h2 id="为什么叫-Topuino"><a href="#为什么叫-Topuino" class="headerlink" title="为什么叫 Topuino"></a>为什么叫 Topuino</h2><p>在 Linux 系列服务器上,我们通常使用 top 命令查看 CPU 内存占用,我最初的设想也是将 top 命令实物化,这就是 Topuino 中 Top 的由来。</p><p>在选型的时候,为了兼顾开发效率和成本,我选用了大名鼎鼎的 ESP8266 单片机,配合了 Arduino 开发框架,Arduino 则是 Topuino 中 uino 的由来。</p><h2 id="Topuino-有哪些亮点"><a href="#Topuino-有哪些亮点" class="headerlink" title="Topuino 有哪些亮点"></a>Topuino 有哪些亮点</h2><p>先看图解:</p><p><img src="/static/blog_images/Topuino-the-wonderful-Knickknack-for-server-monitoring/2.png"></p><ol><li>我觉得它挺好看,哑光黑的 PCB 底板富有科技感,红绿蓝三色 LED 层次分明,指示性强;</li><li>显示的参数满足大部分的需求,刷新率为 1 秒,CPU、内存、磁盘占用以百分比表示在柱状图上,磁盘、网络 IO 各以四位数码管显示,配合 KB、MB 单位显示,可以表示 0KB - 9999MB /s 的速率;</li><li>配置、操作方便。在需要监控的服务器上只需要跑一个 python 脚本即可;Topuino 首次上电后支持用手机或任何支持 Wi-Fi 的设备连接,并通过浏览器配置。若需要重新配置,通过按键即可恢复;</li><li>使用了通用的 USB-TypeC 接口(后期会做带电池版本);</li><li>成本不高,谁都可以承担。</li></ol><p><img src="/static/blog_images/Topuino-the-wonderful-Knickknack-for-server-monitoring/3.jpg"></p><h2 id="Topuino-的工作原理"><a href="#Topuino-的工作原理" class="headerlink" title="Topuino 的工作原理"></a>Topuino 的工作原理</h2><ul><li>服务器部分很简单,主站使用了 Flask,维护一个数据库,保存着从站(被监控服务器)UUID 与运行参数的映射关系(实际上现在是用 python 的字典简单实现的)。主站接收从站的运行数据,并向 Topuino 回传数据;</li><li>服务器从站采用 python 的 psutil 库,获取所有的运行数据;</li><li>Topuino 硬件部分使用了 ESP-12F 作为 MCU,显示采用 LED 整列和数码管,显示驱动是 TM1638 芯片。</li></ul><p>附上原理图:</p><p><img src="/static/blog_images/Topuino-the-wonderful-Knickknack-for-server-monitoring/4.png"></p><p><img src="/static/blog_images/Topuino-the-wonderful-Knickknack-for-server-monitoring/5.png"></p><p><img src="/static/blog_images/Topuino-the-wonderful-Knickknack-for-server-monitoring/6.png"></p><p>PCB 打样交给专门的厂家,回来自己焊。</p><h2 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h2><p>你愿意在办公桌上放一个监控服务器的小摆件吗?至少,我做出来之后,很喜欢,就像看着一只猫一样。</p><p><img src="/static/blog_images/Topuino-the-wonderful-Knickknack-for-server-monitoring/7.png"></p><p>另:ESP8266 的代码初步开源在 <a href="https://github.com/Villivateur/Topuino">https://github.com/Villivateur/Topuino</a> ,供大家参考。服务器端代码因为太简单且写得太丑,以后再说吧~~</p>]]></content>
<categories>
<category> TECHNOLOGY </category>
</categories>
<tags>
<tag> Arduino </tag>
<tag> IoT </tag>
<tag> Topuino </tag>
</tags>
</entry>
<entry>
<title>对 PlatformIO 有点失望</title>
<link href="/2021/04/16/A-little-pitty-for-PlatformIO/"/>
<url>/2021/04/16/A-little-pitty-for-PlatformIO/</url>
<content type="html"><![CDATA[<blockquote><p>PlatformIO 目前只是玩具,单片机开发还得用 Keil</p></blockquote><p>好久不碰单片机,现在想搞个项目,选型 STM32xxxxxx,想找一套“现代化”的 IDE,于是找到了 PlatformIO。</p><p>刚开始很新奇很激动,VSCode 开发环境很友好,各种单片机型号、库很丰富,而且 STM32 可以直接用 Arduino 开发,各种一键式部署。最主要的是商用免费,差点就选用了。</p><p>但是问题很快就出现了,Arduino 框架对于底层的封装太完美,我甚至不能方便地修改 SPI 或者 I2C 的引脚,而且 GPIO 读写速度也相较使用 CMSIS 慢很多,STM32duino 虽然仍然在发展,但是,我认为还处在“玩具”的阶段。</p><p>如果抛弃 Arduino 框架,去使用 CMSIS ,那也太不方便了,而且 STM32 标准库在 PlatformIO 里面目前居然只支持很少几款芯片(F10x 系列全系不支持)。如果我要用 FreeRTOS,FreeRTOS 官方目前也没有适配 PlatformIO。</p><p>最终还是回到 Keil,花钱的才是最好的。</p>]]></content>
<categories>
<category> TECHNOLOGY </category>
</categories>
<tags>
<tag> Arduino </tag>
<tag> PlatformIO </tag>
<tag> STM32 </tag>
</tags>
</entry>
<entry>
<title>我们订婚了 We are engaged!</title>
<link href="/2021/02/14/we-are-engaged/"/>
<url>/2021/02/14/we-are-engaged/</url>
<content type="html"><![CDATA[<p>2021年2月14日,情人节,农历正月初三,玲玲与瑾瑾喜订良缘。</p><p>互联网是有记忆的,在此,我就以计算机为担保,依网络见证!</p>]]></content>
<categories>
<category> LIVES </category>
</categories>
<tags>
<tag> 玲玲 </tag>
<tag> 订婚 </tag>
</tags>
</entry>
<entry>
<title>如何成为 CA,并签发自己的证书</title>
<link href="/2021/01/24/Become-a-CA-and-generate-self-signed-certificate/"/>
<url>/2021/01/24/Become-a-CA-and-generate-self-signed-certificate/</url>
<content type="html"><![CDATA[<blockquote><p>要读懂此文章,你需要了解对称加密、非对称加密的基本概念,并了解证书签发的基本流程。</p></blockquote><h2 id="工具准备"><a href="#工具准备" class="headerlink" title="工具准备"></a>工具准备</h2><ol><li>一台 Linux 主机</li><li>openssl</li></ol><h2 id="创建-CA-的私钥"><a href="#创建-CA-的私钥" class="headerlink" title="创建 CA 的私钥"></a>创建 CA 的私钥</h2><p>很容易理解,CA 也有自己的公钥和私钥。</p><p><code>openssl genrsa -des3 -out CAPrivate.key 4096</code></p><p>这个命令会生成一个私钥 <code>CAPrivate.key</code>,并且必须要填写私钥的密码。不要奇怪这里只有一个私钥,其实公钥也保存在这个文件里了。</p><h2 id="创建根证书"><a href="#创建根证书" class="headerlink" title="创建根证书"></a>创建根证书</h2><p><code>openssl req -x509 -new -nodes -key CAPrivate.key -sha256 -days 3650 -out CAPrivate.pem</code></p><p>根证书,顾名思义,肯定是自签发的。这个证书待会需要安装到你的终端设备里面,不然靠这个根证书签发的其他证书不会被信任。</p><p>这个命令里面需要填写很多信息,按照实际填写就好。</p><p>至此,作为一个简单的 CA,所有的文件都已经齐全了。</p><h2 id="创建待签发证书的私钥"><a href="#创建待签发证书的私钥" class="headerlink" title="创建待签发证书的私钥"></a>创建待签发证书的私钥</h2><p>这个私钥与 CA 无关,是待签发的下一级证书。这一步和下一步可以在另一台机器上完成,然后把文件传给保存 CA 信息的机器就好。</p><p><code>openssl genrsa -out example.key 4096</code></p><h2 id="创建-CSR"><a href="#创建-CSR" class="headerlink" title="创建 CSR"></a>创建 CSR</h2><p>可以理解为一个待发送给 CA、为你签发证书的一个请求。</p><p><code>openssl req -new -key example.key -out example.csr</code></p><p>同样,这里需要填写很多信息,需要注意的是 <code>Common Name</code> 这个项目,如果你的证书是给 https 用的,这里就填你的域名。</p><h2 id="使用-CA-的私钥签发证书"><a href="#使用-CA-的私钥签发证书" class="headerlink" title="使用 CA 的私钥签发证书"></a>使用 CA 的私钥签发证书</h2><p><code>openssl x509 -req -in example.csr -CA CAPrivate.pem -CAkey CAPrivate.key -CAcreateserial -out example.crt -days 365 -sha256</code></p><h2 id="试一试刚刚签发的证书"><a href="#试一试刚刚签发的证书" class="headerlink" title="试一试刚刚签发的证书"></a>试一试刚刚签发的证书</h2><ol><li>在待测试终端设备上安装 CA 的根证书 <code>CAPrivate.pem</code>,比如 Windows、Android,某些浏览器必须单独安装证书。</li><li>把 <code>example.key</code> 和 <code>example.crt</code> 作为 HTTPS 服务(比如 Nginx)的私钥和证书,写好配置文件。</li><li>在浏览器里面试试看,应该可以显示小锁,且没有安全警告。</li></ol>]]></content>
<categories>
<category> TECHNOLOGY </category>
</categories>
<tags>
<tag> 网络安全 </tag>
<tag> 证书 </tag>
</tags>
</entry>
<entry>
<title>为什么要发明补码 - 我可能终于知道了补码的意义</title>
<link href="/2020/11/18/May-be-I-know-why-use-complement/"/>
<url>/2020/11/18/May-be-I-know-why-use-complement/</url>
<content type="html"><![CDATA[<blockquote><p>本文供初学者学习,供大佬批判。</p></blockquote><h2 id="补码背景"><a href="#补码背景" class="headerlink" title="补码背景"></a>补码背景</h2><p>很久很久以前,当我参加 NOIP(信息学奥林匹克竞赛)的时候,老师是这么讲补码的:</p><blockquote><p>int 型数据的最高位是符号位,符号位为 0 就代表这是正数,为 1 就是负数。但是,+0 和 -0 理应是同一个数,但在二进制中却表示成了两个不同的数(以 8 位为例,二进制表示): 00000000 和 10000000。所以,我们引入了补码,补码就是,对负数而言,符号位不变,其他位取反,然后再加 1。于是, -0 的补码就是 10000000 -> 11111111 -> 00000000 与 +0 一致了。</p></blockquote><h2 id="补码的意义"><a href="#补码的意义" class="headerlink" title="补码的意义"></a>补码的意义</h2><p>其实当时我就该意识到,仅仅为了 0 这一个数便创建了“补码”这个概念,未免太浪费了。我最近看了 <em>CS: APP</em> 这本书,才进一步理解了补码。</p><p>补码,就是为有符号整数而创建的概念。对于无符号整数,是不存在“补码”的概念的。我们先看一个无符号数 00100101,它是怎么转换成十进制的?加权求和,<code>0×2^7+0×2^6+1×2^5+0×2^4+0×2^3+1×2^2+0×2^1+1×2^0=37</code>;无符号数 10000011,<code>1×2^7+0×2^6+0×2^5+0×2^4+0×2^3+0×2^2+1×2^1+1×2^0=131</code>。</p><p>对于有符号数呢?假如我们不用补码,那么有符号数 10000011,就是 <code>-(0×2^6+0×2^5+0×2^4+0×2^3+0×2^2+1×2^1+1×2^0)=-3</code>。人这么算起来是挺舒服,但是计算机会很难受,凭什么我最高位的权值没了?只能做符号?</p><p>然后我们试试补码。10000011 的补码是 11111101,如何用补码求它的十进制呢?其实这跟无符号数是一样的,只不过,最高位的权值,不是 2^7,而是 -2^7。求值:<code>1×(-2^7)+1×2^6+1×2^5+1×2^4+1×2^3+1×2^2+0×2^1+1×2^0=-3</code>,是不是很神奇?</p><p>对于正数而言,就更简单了,与无符号数一致。</p><h2 id="补码再探"><a href="#补码再探" class="headerlink" title="补码再探"></a>补码再探</h2><p>计算机只能做加法(不是),所以,我们看看用补码做加法,有什么好处。</p><p>先计算 -13+10 吧,很简单的二进制竖式加法:</p><table><thead><tr><th>7</th><th>6</th><th>5</th><th>4</th><th>3</th><th>2</th><th>1</th><th>0</th></tr></thead><tbody><tr><td>1</td><td>1</td><td>1</td><td>1</td><td>0</td><td>0</td><td>1</td><td>1</td></tr><tr><td>0</td><td>0</td><td>0</td><td>0</td><td>1</td><td>0</td><td>1</td><td>0</td></tr><tr><td>1</td><td>1</td><td>1</td><td>1</td><td>1</td><td>1</td><td>0</td><td>1</td></tr></tbody></table><p>11111101 即为 -3 的补码。</p><p>再算一个,-8+9:</p><table><thead><tr><th>7</th><th>6</th><th>5</th><th>4</th><th>3</th><th>2</th><th>1</th><th>0</th></tr></thead><tbody><tr><td>1</td><td>1</td><td>1</td><td>1</td><td>1</td><td>0</td><td>0</td><td>0</td></tr><tr><td>0</td><td>0</td><td>0</td><td>0</td><td>1</td><td>0</td><td>0</td><td>1</td></tr><tr><td>0</td><td>0</td><td>0</td><td>0</td><td>0</td><td>0</td><td>0</td><td>1</td></tr></tbody></table><p>神奇吗?最高位的 0 不见了,变成正数 1 了。</p><h2 id="补码小结"><a href="#补码小结" class="headerlink" title="补码小结"></a>补码小结</h2><p>补码,就是计算机内部有符号数的存储方式,我们不要认为补码是由原码“转换”而来的,补码本来就代表了实际的数字,11111101 == -3,就这样。</p>]]></content>
<categories>
<category> TECHNOLOGY </category>
</categories>
<tags>
<tag> 补码 </tag>
<tag> 计算机原理 </tag>
</tags>
</entry>
<entry>
<title>使用虚拟机绕过 Windows Samba 客户端端口限制</title>
<link href="/2020/04/28/use-virtual-machine-to-bypass-windows-samba-port-limit/"/>
<url>/2020/04/28/use-virtual-machine-to-bypass-windows-samba-port-limit/</url>
<content type="html"><![CDATA[<h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>迫于不能浪费宿舍联通宽带的公网 IP,我用树莓派搭建了一个 NAS,使用 Samba 提供访问。但是,由于运营商限制,入站和出站的 139、445 端口都被封禁(对!你不能访问其他机器的 445 端口!),这样做只能让同一个局域网的服务访问。我尝试修改了端口号,把本地的 445 端口映射到公网的其它端口,算是可以给大部分 Samba 客户端使用了(比如安卓手机)。但是,Windows 的 Samba 客户端,并不支持设置端口号(如果在文件管理器地址栏输入 <code>\\a.b.c.d:xxx</code> 将会被认为是 WebDAV 协议)。</p><p>网上某些教程说,在 Windows 本地设置端口转发,把访问本机 445 端口的流量转发至你的 Samba 服务器端口。但是,这样做需要关闭 Windows 自己的 Samba 服务器,会导致网络共享、网络打印机等很多功能无法使用,导致莫名其妙的问题,所以不推荐。</p><p>但是可以换个思路。</p><h2 id="原理"><a href="#原理" class="headerlink" title="原理"></a>原理</h2><p>既然不能使用本机(127.0.0.1)做转发,那么为什么不使用本地网络中其他的机器进行数据转发呢?一开始我想在本地网络中加一个树莓派之类的小主机,但觉得这样太累赘了。虚拟机也是独立的机器,我觉得一样有效。</p><p>所以,只要在虚拟机里面装一个反向代理应用(Nginx),把发往虚拟机 445 端口的流量转发到 Samba 服务器,然后使用 Windows Samba 客户端访问这个虚拟机,就等于访问 Samba 服务器。</p><h2 id="实践"><a href="#实践" class="headerlink" title="实践"></a>实践</h2><p>这里,我使用了 Windows Hyper V,创建了 Ubuntu 20.04 Server 虚拟机(直接下载官方 ISO),然后使用默认交换机作为虚拟机的网络。</p><img src="/static/blog_images/ih/2020/04/28/2416a9e0b66ec.png" alt="Snipaste_2020-04-28_14-07-57.png" style="zoom:33%;" /><p>启动后,安装系统,然后除了更新镜像源、安装 Nginx,什么都不用做。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">sudo apt update</span><br><span class="line">sudo apt install nginx</span><br></pre></td></tr></table></figure><p>装好 Nginx 后,修改配置文件:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">vim /etc/nginx/nginx.conf</span><br></pre></td></tr></table></figure><p>添加一个 stream 模块,大致像下面:</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="section">http</span> {</span><br><span class="line"> ......</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="section">stream</span> {</span><br><span class="line"> <span class="section">server</span> {</span><br><span class="line"> <span class="attribute">listen</span> <span class="number">445</span>;</span><br><span class="line"> </span><br><span class="line"> // 你的 <span class="attribute">NAS</span> 地址</span><br><span class="line"> proxy_pass mynas.example.com:<span class="number">12345</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>然后重启 Nginx:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo nginx -s reload</span><br></pre></td></tr></table></figure><p>转发的模块就做好了。</p><p>然后,再在 Windows 中打开 <code>\\<虚拟机的ip>\<samba 路径(可选)></code> 就完成了。虚拟机 ip 可以在虚拟机里面用 ifconfig 命令查看。</p><h2 id="后续"><a href="#后续" class="headerlink" title="后续"></a>后续</h2><p>也许可以用 docker 实现,但我还没想好怎么搞。</p>]]></content>
<categories>
<category> TECHNOLOGY </category>
</categories>
<tags>
<tag> Windows </tag>
<tag> Samba </tag>
</tags>
</entry>
<entry>
<title>使用阿里云 ECS 搭建廉价的高性能云桌面</title>
<link href="/2020/02/08/build-powerful-remote-desktops-with-aliyun-ECS-at-low-price/"/>
<url>/2020/02/08/build-powerful-remote-desktops-with-aliyun-ECS-at-low-price/</url>
<content type="html"><![CDATA[<blockquote><p>要想读懂本文,你需要:</p><ol><li>了解阿里云等云服务的基本 WEB 界面操作;</li><li>了解 Windows 操作系统的中阶操作;</li><li>了解基础的软件开发术语。</li></ol></blockquote><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>本文面向的是需要使用高性能计算设备,但是身边只有低性能PC机的群体。最近由于 NCP 疫情,出不了门,返回不了工作地,想必不少人的高性能计算设备(好电脑)没带回家,但是,肯定也有人跟我一样,受不了笔记本电脑的龟速。</p><p>我的配置需求是,能够流畅运行安装多个插件的 VSCode、能够同时打开数十个 Firefox 标签页、能够快速完成 node 项目构建。但是我身边只有一台五年前的 intel NUC (i3-4010U, 4GB RAM)。</p><p>在详细了解了各大云服务商的云计算平台后,个人排除了华为云(弹性计算服务价格较高、云桌面售罄)、腾讯云(云计算服务类别过少)、天翼云(云桌面需要安装指定 APP 且性能不满足要求),决定使用阿里云的云计算服务。</p><h2 id="使用阿里云初步搭建云桌面"><a href="#使用阿里云初步搭建云桌面" class="headerlink" title="使用阿里云初步搭建云桌面"></a>使用阿里云初步搭建云桌面</h2><p>经过权衡,我决定使用阿里云 ESC “抢占式实例”付费模式下的“突发性能实例”。为什么这么选?</p><p>因为没钱……</p><p>开玩笑的。我觉得这是我需求下的合理选择方式。为什么选择“抢占式实例”?首先,我们复工的时间还是个未知数,而且可能随时就不想用这个云桌面了。“抢占式实例”是按量付费的一种,也就是,用多长时间给多少钱。并且,相对于按量付费,有相当大的折扣。<strong>“抢占式实例”的最大问题在于,这是竞价模式。也就是说,当别人出价比你高的时候,或者阿里云供给紧张而你又给钱给得不够的时候,它会自动帮你释放。我了解的是,在释放前5分钟,阿里云会有提醒。</strong>为了尽量避免实例被自动释放,我选择了“使用自动出价”,也就是说,系统会每小时自动判断当前时间该实例的价格,并选择一个高于市场均价的价格进行付费。<strong>但是,这并不是万无一失,阿里云在供给紧张的时候,仍然会将实例释放。</strong>所以,在处理重要数据时,应该随时备份。我的处理方法是,随时将代码提交到我的 Git 仓库。</p><p><img src="/static/blog_images/build-powerful-remote-desktops-with-aliyun-ECS-at-low-price/ea3d50f39b198.png" alt="Snipaste_2020-02-09_17-32-20.png"></p><p><img src="/static/blog_images/build-powerful-remote-desktops-with-aliyun-ECS-at-low-price/df1fabf212984.png" alt="Snipaste_2020-02-09_17-34-24.png"></p><p>如果要保证不会被释放,建议使用包年包月制。</p><p>为什么使用“突发性能实例”?因为个人电脑与服务器不同。个人电脑不会时时刻刻占用大量 CPU,CPU使用率是离散化的,所以 CPU 的使用积分,我觉得是用不完,25% 的使用基线,完全够用。</p><p><img src="/static/blog_images/build-powerful-remote-desktops-with-aliyun-ECS-at-low-price/d74c0622ec620.png" alt="Snipaste_2020-02-09_17-34-05.png"></p><p>哎,目前好像国内也只有阿里云能做出“抢占式实例”和“突发性能实例”。</p><p>其他的配置就简单了,区域选择靠近你的(不同地区的价格好像差别挺大),镜像选择 Windows Server,另外,SSD 是很关键的,我觉得现在已经没有多少人能忍受机械硬盘的龟速了。</p><p><img src="/static/blog_images/build-powerful-remote-desktops-with-aliyun-ECS-at-low-price/6ae75caa75c6a.png" alt="Snipaste_2020-02-09_18-03-26.png"></p><p><img src="/static/blog_images/build-powerful-remote-desktops-with-aliyun-ECS-at-low-price/cbe75edc3c797.png" alt="Snipaste_2020-02-09_17-36-25.png"></p><p>在下一个页面配置网络,为了保证远程桌面流畅,按使用流量计费,带宽拉满!</p><p><img src="/static/blog_images/build-powerful-remote-desktops-with-aliyun-ECS-at-low-price/2c112190ea447.png" alt="Snipaste_2020-02-09_17-45-57.png"></p><p>最终,我的配置如下:</p><ul><li>4核16GB,25% 平均CPU 计算性能;</li><li>100Mbps 按流量付费网络;</li><li>80GB SSD;</li><li>Windows Server 2019 数据中心版;</li></ul><p>这样的话,实例价格为 0.412 元每小时(我选的是上海区的,其他区的可能更便宜,在深夜、早上也会更便宜),流量费用为 0.8 元每 GB。算一算,一天大概要 10 元,一个月大概要 300 元,是不是还是有点贵?没关系,继续看。</p><p><img src="/static/blog_images/build-powerful-remote-desktops-with-aliyun-ECS-at-low-price/ecfef72872df3.png" alt="Snipaste_2020-02-09_17-36-50.png"></p><h2 id="价格的进一步优化"><a href="#价格的进一步优化" class="headerlink" title="价格的进一步优化"></a>价格的进一步优化</h2><p>现在我已经启动了这个实例:</p><p><img src="/static/blog_images/build-powerful-remote-desktops-with-aliyun-ECS-at-low-price/3ea24c1929367.png" alt="Snipaste_2020-02-09_18-21-04.png"></p><p>我还想更便宜。</p><h3 id="与基友合租一台机器"><a href="#与基友合租一台机器" class="headerlink" title="与基友合租一台机器"></a>与基友合租一台机器</h3><p>我想你一定不可能一天24小时都在用它,用它的时候也一定不会一直占满 CPU。关键是,我们这是 Windows Server,跟家庭版、专业版、企业版什么的都不一样,它支持多个用户同时登陆!所以,我觉得,如果有信任的人选,完全可以合租。</p><h3 id="关机时选择“停机不计费”"><a href="#关机时选择“停机不计费”" class="headerlink" title="关机时选择“停机不计费”"></a>关机时选择“停机不计费”</h3><p>阿里云真的是神奇的存在,在 ECS 管理界面,选择停机,竟然可以停机不收费!</p><p><img src="/static/blog_images/build-powerful-remote-desktops-with-aliyun-ECS-at-low-price/28437c5fae934.png" alt="图片1.png"></p><p>实际上也不是完全不收费,硬盘和弹性公网 IP 仍然是收费的。但是,与停机后仍然保留CPU、内存相比,价格大幅降低。也就是说,我们在每天晚上,完全可以关机并选择不收费,可以节约一大笔。如果想要一直使用同一个 IP,可以选择绑定弹性公网 IP。</p><h3 id="依据个人需求选择实例规格"><a href="#依据个人需求选择实例规格" class="headerlink" title="依据个人需求选择实例规格"></a>依据个人需求选择实例规格</h3><p>我觉得,可能 8GB 内存已经够我用了,另外,青岛区的实例好像每小时能更便宜一毛钱……</p><h2 id="云桌面体验"><a href="#云桌面体验" class="headerlink" title="云桌面体验"></a>云桌面体验</h2><p>在体验之前,再确保一下:该实例网络安全组,需要放通 3389 端口(TCP 和 UDP 都要放通)。</p><p>打开本地的远程桌面客户端试试吧!</p><p><img src="/static/blog_images/build-powerful-remote-desktops-with-aliyun-ECS-at-low-price/b4629c62b4373.png" alt="Snipaste_2020-02-09_18-38-25.png"></p><p>网络方面,我是江苏电信网络,连接上海区的 ECS,延迟很小,可以忽略,而且画质很清晰,暂时没有什么画面卡顿。至于下行网络,由于这是阿里云的专线,比家用运营商网络好太多,下载速度嗖嗖的,不管国内外。</p><p>计算性能方面,在我本地机器上,VSCode 的 Python 插件,进行一次文件错误扫描需要 5 秒左右,而这台云桌面,只要 1 秒。</p><p>操作系统方面,Windows Server 比 Windows PC 简洁很多,没有乱七八糟的服务,但也没有缺少日常开发所需的重要组件。可能会有点不一样,但是如果出问题,一般能很快解决。比如我在安装 Python 时出错,Google 一下便知要直接以管理员身份打开。</p><p>以下截图自我的云桌面。</p><p><img src="/static/blog_images/build-powerful-remote-desktops-with-aliyun-ECS-at-low-price/d3b52ba297968.png" alt="Screenshot _1_.png"></p><p><img src="/static/blog_images/build-powerful-remote-desktops-with-aliyun-ECS-at-low-price/e9ae18b879037.png" alt="Screenshot _3_.png"></p><p><img src="/static/blog_images/build-powerful-remote-desktops-with-aliyun-ECS-at-low-price/9f2c0741b0057.png" alt="Screenshot _4_.png"></p><p><img src="/static/blog_images/build-powerful-remote-desktops-with-aliyun-ECS-at-low-price/8109324fabb58.png" alt="Screenshot _2_.png"></p><h3 id="提高体验的可选项"><a href="#提高体验的可选项" class="headerlink" title="提高体验的可选项"></a>提高体验的可选项</h3><ol><li>关闭 Windows 的过渡动画,这在远程桌面上会导致卡顿,且会消耗大量的流量费;</li><li>安全起见,重新创建一个管理员用户并禁用 Administrator 用户;</li><li>用好各种云同步功能,比如 Git 仓库、浏览器云同步、云盘等。</li></ol><h2 id="实测价格"><a href="#实测价格" class="headerlink" title="实测价格"></a>实测价格</h2><p>我正常使用了一天,晚上停机不收费,消费如下:</p><p><img src="/static/blog_images/build-powerful-remote-desktops-with-aliyun-ECS-at-low-price/176c67e7015fa.png" alt="Snipaste_2020-02-09_18-54-30.png"></p><p><img src="/static/blog_images/build-powerful-remote-desktops-with-aliyun-ECS-at-low-price/140f26bd1d962.png" alt="Snipaste_2020-02-09_18-55-07.png"></p><p>可见,我一天大致消费5元。</p><h2 id="后记"><a href="#后记" class="headerlink" title="后记"></a>后记</h2><p>确实不是很贵,可行性也很高。假如有模型渲染需求,甚至可以绑定个显卡(</p><p>抢占式实例也不是很可怕吧?我的实例目前没被回收。</p><p>另外,有人想跟我合租吗?</p>]]></content>
<categories>
<category> TECHNOLOGY </category>
</categories>
<tags>
<tag> Windows </tag>
<tag> 阿里云 </tag>
<tag> 云桌面 </tag>
</tags>
</entry>
<entry>
<title>为 Windows PowerShell 设置 User Alias (命令别名)</title>
<link href="/2019/07/22/set-user-alias-for-windows-PowerShell/"/>
<url>/2019/07/22/set-user-alias-for-windows-PowerShell/</url>
<content type="html"><![CDATA[<blockquote><p>直接看步骤的话,在最下方。</p></blockquote><h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>Windows Terminal 虽然还处于预览阶段,但是也出来很长一段时间了。它的历史使命,也许就是让原生 Windows 也能有一个像样的命令行环境。以前我一直在用 Cmder,但是 Cmder 的启动速度确实不敢恭维,而 Windows Terminal 启动确实很快。相比 Cmder, Windows Terminal 还缺少很多功能,不过以后应该很快也会补上。</p><img src="/static/blog_images/set-user-alias-for-windows-PowerShell/5d35bef29b3f1.png" style="zoom:50%;" /><p>我想尝试使用 Windows Termimal 进行开发。Windows Terminal 默认可以使用 <code>PowerShell</code>、<code>cmd</code>、<code>wsl bash</code>作为脚本工具。既然是在 Windows 环境下嘛,还是得尊敬一下 Windows PoweShell 的。初次使用,我觉得 PowerShell 跟 Cmder 用起来,最不方便的一点就是,我在 Cmder 里面设了很多的 Alias,也就是通过修改 <code>config/user_aliases.cmd</code> 写一串 <code><alia>=<xxx> <xxx></code> 来实现的。</p><img src="/static/blog_images/set-user-alias-for-windows-PowerShell/5d35bef29ad52.png" style="zoom:50%;" /><p>但是,PowerShell 如何实现命令的别名设置呢?</p><h2 id="探索过程及原理概述"><a href="#探索过程及原理概述" class="headerlink" title="探索过程及原理概述"></a>探索过程及原理概述</h2><p>如果搜索关键词 <code>windows powershell set user alias</code>,通常谷歌会给出<a href="https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/set-alias?view=powershell-6">微软官方文档</a>,但是这个文档只是告诉我们如何在脚本里面设置临时的别名,如果要设置永久别名,该怎么办?实际上,“别名”这种东西,也就是 <code>alias</code>,几乎所有的脚本语言,都没有所谓的“永久别名”(Permanent alias),我们使用 Linux bash 、Cmder 之类的脚本工具,打开终端时,系统会默认执行一个脚本文件( bash 是用户目录下的 <code>.bashrc</code>,Cmder 是 <code>config/user_aliases.cmd</code> ),而这样的脚本文件里,就包含了别名的定义。这也是为什么,我们在 Linux 类系统中,修改 <code>.bashrc</code> 后,必须要重新登出登录、或者 <code>source .bashrc</code> 的原因了。</p><p>所以,我们只要修改 Windows Powershell 启动时执行的文件就行了。很多论坛里面说,默认执行的脚本是 <code>$Home\Documents\profile.ps1</code> ,也就是 <code>C:\Users\你的用户名\Documents\profile.ps1</code> ,但是这并不正确,最好的方式是,先启动 PowerShell ,再执行 <code>echo $profile</code>,这样得到的文件路径,才是 PowerShell 的默认执行文件路径。</p><p><img src="/static/blog_images/set-user-alias-for-windows-PowerShell/5d35bef29d447.png"></p><p>然后,创建这个文件就好啦。</p><p>在文件里面,写上别名设置的语句。再一次注意,假如你的别名指代的命令含有空格,就不可以使用 <code>New-Alias</code> 命令,因为它不能带空格,即使你把指代的命令用引号括起来也没用。那怎么办呢?继续谷歌,原来,正确姿势是用 <code>function</code> ,也就是,我们把自己要定义的指令,定义为一个函数,就行啦。</p><img src="/static/blog_images/set-user-alias-for-windows-PowerShell/5d35bef339443.png" style="zoom: 67%;" /><p>保存文件,重新启动 PowerShell 以后,不出意外,应该会报一个 <code>File xxxxxxx\Microsoft.PowerShell_profile.ps1 cannot be loaded because running scripts is disabled on this system.</code> 根据<a href="https://tecadmin.net/powershell-running-scripts-is-disabled-system/">此链接</a>,出现这种情况,是因为 Windows 系统为了防止恶意脚本自动执行,故默认不允许自动运行脚本。所以,在确定自己有能力把控的情况下,__以管理员身份__,在 PowerShell 中执行 <code>Set-ExecutionPolicy RemoteSigned </code>,即可。</p><p>再次重启 PowerShell,应该可以发现,自定义别名已经生效了。</p><h2 id="步骤整理"><a href="#步骤整理" class="headerlink" title="步骤整理"></a>步骤整理</h2><p>如果你想为自己的 Windows PowerShell 设置永久的命令别名 (Alias),可以遵循以下步骤:</p><ol><li><p>打开 PowerShell ,运行 <code>echo $profile</code>,会输出一个文件路径。创建这个文件。</p></li><li><p>打开刚创建的文件,按以下格式设置多条别名:</p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> 别名</span> { 需要替代的命令,可以包含空格 }</span><br></pre></td></tr></table></figure></li><li><p>以管理员身份打开 PowerShell,执行 <code>Set-ExecutionPolicy RemoteSigned</code>。</p></li><li><p>重新启动 PowerShell ,应该已经完成了。</p></li></ol>]]></content>
<categories>
<category> TECHNOLOGY </category>
</categories>
<tags>
<tag> Windows </tag>
<tag> PowerShell </tag>
</tags>
</entry>
<entry>
<title>免费使用 nuaa.portal 上网</title>
<link href="/2019/01/13/Use-the-Internet-via-nuaa-portal-for-free/"/>
<url>/2019/01/13/Use-the-Internet-via-nuaa-portal-for-free/</url>
<content type="html"><![CDATA[<h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>学校赐予我的 nuaa.portal 账号在一个月之前就到期了,这一个月内,虽然对我的生活没有太大的影响,但是还是感受到了某些不便(总觉得某些东西有了更好,没有却不能拉倒)。前些天跟创新区内的同学讨论了本校的ipv6部署情况,突然就有兴致来折腾一下。</p><p>没错,免费上网还是有可能的。</p><h2 id="前提概要"><a href="#前提概要" class="headerlink" title="前提概要"></a>前提概要</h2><ul><li>学校的 nuaa.portal 公共无线网,Windows 电脑只要连接上,就能获取一个 ipv4 内网地址和一个 ipv6 地址。如果没有在指定 Web 页面上认证,则无法使用 ipv4 连接互联网。但是可以通过 ipv6 联网。</li><li>学校默认不分配 ipv6 的 DNS 服务器。</li><li>学校教学办公区有线宽带,经直接拨号,同样可以获得一个 ipv4 内网地址和一个 ipv6 地址,均可以访问互联网。</li></ul><h2 id="一步步的尝试"><a href="#一步步的尝试" class="headerlink" title="一步步的尝试"></a>一步步的尝试</h2><h3 id="直接用-ipv6-上提供-ipv6-服务的网站"><a href="#直接用-ipv6-上提供-ipv6-服务的网站" class="headerlink" title="直接用 ipv6 上提供 ipv6 服务的网站"></a>直接用 ipv6 上提供 ipv6 服务的网站</h3><p>既然可以从 nuaa.portal 获取可以访问互联网的 ipv6 地址,那么最简单的思路,就是直接使用这条通道。但是,由于学校没有 ipv6 的 DNS(认证后,如果访问 ipv6 站点,其实仍然是使用 ipv4 的 DNS 服务器),故必须配置 ipv6 的 DNS。这一点很简单,直接修改 DNS 服务器即可:</p><p><img src="/static/blog_images/Use-the-Internet-via-nuaa-portal-for-free/2.png"></p><p>这里推荐两个 ipv6 的 DNS 服务器,一个是 Google 家的,一个是清华大学 TUNA 协会的:</p><ul><li><code>2001:4860:4860::8888</code></li><li><code>2001:da8::666</code></li></ul><p>然后,实测就可以访问提供 ipv6 服务的网站啦!</p><p>比如:</p><ul><li>google.com.hk</li><li><a href="http://www.youtube.com/">www.youtube.com</a></li><li>ipv6.baidu.com</li><li>ipv6.mirrors.ustc.edu.cn</li></ul><h3 id="使用-ipv6-代理服务器进行无限制的互联网访问"><a href="#使用-ipv6-代理服务器进行无限制的互联网访问" class="headerlink" title="使用 ipv6 代理服务器进行无限制的互联网访问"></a>使用 ipv6 代理服务器进行无限制的互联网访问</h3><p>上一条方法虽然简单,但是只能访问提供 ipv6 的网站,然而现在国内乃至国际上大多数网站并没有提供 ipv6 服务,至少,你目前还不能登微信。</p><p>但是,如果有一个同时具有有 ipv6 和 ipv4 的代理服务器,不就一切都解决了吗?把本地的所有流量都通过 ipv6 转发至代理服务器,然后就可以访问任意站点了!</p><p>这里,我借用了 Shadowsocks,配置了一台有 ipv6 和 ipv4 的服务器,然后本地开启全局代理,实际就可以访问任意站点了。</p><p>使用这个方法,本地 DNS 甚至都不需要配置。</p><h3 id="终极(硬核)蹭网方案"><a href="#终极(硬核)蹭网方案" class="headerlink" title="终极(硬核)蹭网方案"></a>终极(硬核)蹭网方案</h3><p>还记得一开始我说过,教学办公区也能获得 ipv6 地址吗?</p><p>对,我就是想用远程桌面!</p><p><img src="/static/blog_images/Use-the-Internet-via-nuaa-portal-for-free/1.jpg"></p><p>我在学校创新区日常开着一台机器,拨号联网,(性能还不错)。</p><p>emmm,其实这个方法真的很硬核很有效,而且,由于 nuaa.portal 和教学办公区实际属于同一个大的内网环境,学校的路由器还不错,实测 ping 延迟只有 2ms,完全满足了日常电脑使用(除了不能使用显卡玩游戏什么的)。微软家的远程桌面还是很强大的!</p><h2 id="最后"><a href="#最后" class="headerlink" title="最后"></a>最后</h2><p>我其实还是想再来一年的免费 nuaa.portal 账号的。</p>]]></content>
<categories>
<category> TECHNOLOGY </category>
</categories>
<tags>
<tag> NUAA </tag>
<tag> ipv6 </tag>
</tags>
</entry>
<entry>
<title>使用Nginx、nextcloud以及h5ai搭建下载平台</title>
<link href="/2018/11/22/build-download-platform-with-nginx-nextcloud-and-h5ai/"/>
<url>/2018/11/22/build-download-platform-with-nginx-nextcloud-and-h5ai/</url>
<content type="html"><![CDATA[<blockquote><p><strong>读懂这篇文章,你需要:</strong></p><ol><li>有过一定的Nginx或类似产品的维护经验。</li><li>使用过nextcloud或类似的私有网盘系统。如果没有,可以通过阅读下方的文档了解。</li><li>了解并使用过至少一个云服务供应商的CDN服务。</li></ol></blockquote><blockquote><p><strong>相关文档:</strong></p><p><a href="http://nginx.org/">Nginx</a></p><p><a href="https://nextcloud.com/">nextcloud</a></p><p><a href="https://www.alibabacloud.com/zh/product/cdn">阿里云CDN</a></p><p><a href="https://larsjung.de/h5ai/">h5ai</a></p><p><strong>部署后的站点展示:</strong></p><p><a href="https://downloads.vvzero.com/">VVDownloads</a></p></blockquote><h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>原先的下载站真的硬核到难以直视。由于我原先直接通过在Hexo上面添加链接以提供下载。这样不仅看起来丑陋,而且维护很不方便。</p><p>前一段时间接触到了h5ai这个好东西,之前也一直在维护自己的nextcloud,于是乎,想要改造下载站的欲望一发不可收拾。</p><h2 id="需求分析"><a href="#需求分析" class="headerlink" title="需求分析"></a>需求分析</h2><ol><li>可以使用已有的CDN服务对所提供的下载内容进行加速;</li><li>下载内容的维护应当很方便,并且可以在不同的终端进行;</li><li>下载页面应当保持美观。</li></ol><h2 id="关键思路"><a href="#关键思路" class="headerlink" title="关键思路"></a>关键思路</h2><p>把nextcloud的数据存储路径设置为h5ai的根目录。</p><h2 id="具体操作"><a href="#具体操作" class="headerlink" title="具体操作"></a>具体操作</h2><h3 id="初次尝试"><a href="#初次尝试" class="headerlink" title="初次尝试"></a>初次尝试</h3><p>我一开始尝试使用CDN对nextcloud直接加速,但是之后发现,这是一件极其愚蠢的事。因为nextcloud使用的是动态页面,对这样的页面进行加速可谓是难上加难。即使我只对公开分享的页面进行加速也不行。实测:使用CDN,哪怕全站都设置成立即回源,也会出现无法上传文件的情况。</p><h3 id="搭建nextcloud"><a href="#搭建nextcloud" class="headerlink" title="搭建nextcloud"></a>搭建nextcloud</h3><p>这一点我不再赘述,因为资料很多,或者直接点击我上面的链接看官方文档。</p><h3 id="配置h5ai"><a href="#配置h5ai" class="headerlink" title="配置h5ai"></a>配置h5ai</h3><p>h5ai这一web index展示系统的部署相当简单,只要将下载的_h5ai目录放在所需要展示的目录下即可,这个目录可以是你的nextcloud个人文件存储根目录(一般为<code><path to nextcloud>/data/<your user name>/files</code>),也可以是其一个子目录:</p><img src="/static/blog_images/build-download-platform-with-nginx-nextcloud-and-h5ai/1.JPG" style="zoom:50%;" /><h3 id="配置Nginx"><a href="#配置Nginx" class="headerlink" title="配置Nginx"></a>配置Nginx</h3><p>按照以下的套路来即可,注意index后面要加上<code>/_h5ai/public/index.php</code></p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="section">server</span> {</span><br><span class="line"> <span class="attribute">listen</span> <span class="number">443</span> ssl http2;</span><br><span class="line"></span><br><span class="line"> <span class="attribute">server_name</span> downloads.vvzero.com;</span><br><span class="line"></span><br><span class="line"> <span class="attribute">ssl</span> <span class="literal">on</span>;</span><br><span class="line"> <span class="attribute">ssl_certificate</span> ???; <span class="comment">#填写证书地址</span></span><br><span class="line"> <span class="attribute">ssl_certificate_key</span> ???; <span class="comment">#填写证书地址</span></span><br><span class="line"></span><br><span class="line"> <span class="attribute">root</span> ???;</span><br><span class="line"> <span class="attribute">index</span> index.html index.php /_h5ai/public/index.php; <span class="comment"># 关键</span></span><br><span class="line"></span><br><span class="line"> <span class="attribute">add_header</span> Strict-Transport-Security <span class="string">'max-age=31536000; includeSubDomains; preload'</span>;</span><br><span class="line"></span><br><span class="line"> <span class="section">location</span> / {</span><br><span class="line"> <span class="attribute">try_files</span> <span class="variable">$uri</span> <span class="variable">$uri</span>/ =<span class="number">404</span>;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment"># 由于h5ai需要php,所以请安装php-fpm</span></span><br><span class="line"> <span class="section">location</span> <span class="regexp">~ \.php$</span> {</span><br><span class="line"> <span class="attribute">include</span> snippets/fastcgi-php.conf;</span><br><span class="line"> <span class="attribute">fastcgi_pass</span> unix:/run/php/php7.1-fpm.sock;</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="section">server</span> {</span><br><span class="line"> <span class="attribute">listen</span> <span class="number">80</span>;</span><br><span class="line"></span><br><span class="line"> <span class="attribute">server_name</span> downloads.vvzero.com;</span><br><span class="line"></span><br><span class="line"> <span class="attribute">rewrite</span><span class="regexp"> ^(.*)</span> https://downloads.vvzero.com<span class="variable">$1</span> <span class="literal">permanent</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="配置CDN"><a href="#配置CDN" class="headerlink" title="配置CDN"></a>配置CDN</h3><p>这里以阿里云为例</p><ul><li><p>业务类型暂时先选“图片小文件”,因为自己分享的图片比较多。</p></li><li><p>缓存过期时间:默认所有直接回源(emmm我实在没想知道哪些跟动态页面有关),权值为1;然后<code>jpg, png, bmp, zip, rar, 7z, exe, msi, doc, docx, ppt, pptx, xls, xlsx, pdf, apk, rmvb, rm, avi, mov, mp3, mp4, flac, wav, tar, gz, iso, bin, js, css, txt, svg, ico, psd, md</code>这些,是我整理的一部分常用文件扩展名,设置成30天缓存,权值为10。</p></li><li><p>开启https并强制跳转</p></li><li><p>开启HTTP2</p></li><li><p>配置一下cname</p></li><li><p>ok</p></li></ul><h2 id="后记"><a href="#后记" class="headerlink" title="后记"></a>后记</h2><p>现在看着舒服多了。</p><p><a href="https://downloads.vvzero.com/">VVDownloads</a></p><p><img src="/static/blog_images/build-download-platform-with-nginx-nextcloud-and-h5ai/2.JPG"></p><p>然后还发现了,其实,这样的话,我可以在我对nextcloud上开一个公共账户,这样就可以让我的小伙伴们,使用我这套系统,假装有一个CDN啦!毕竟备案还是一件麻烦的事情呢。</p>]]></content>
<categories>
<category> TECHNOLOGY </category>
</categories>
<tags>
<tag> nextcloud </tag>
<tag> Nginx </tag>
<tag> h5ai </tag>
</tags>
</entry>
<entry>
<title>简易云监控服务器的搭建</title>
<link href="/2018/09/22/simple-hosts-monitor-system/"/>
<url>/2018/09/22/simple-hosts-monitor-system/</url>
<content type="html"><![CDATA[<h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>我有很多台主机,公网、内网的都有,形式各异。有的时候,因为他人误操作、停电、被攻击,或者系统崩溃等事故,会导致主机无法正常运转。往常,我并不能在第一时间得知消息并及时采取措施。所以,我希望能够有一套系统来监控我的主机是否正常。</p><p>市面上已经有这样的系统,但是我想了想觉得要实现我的需求并不是很困难,所以就决定自己做一套这样的系统。</p><h2 id="需求分析"><a href="#需求分析" class="headerlink" title="需求分析"></a>需求分析</h2><p>在指定被监控的主机(slave)无法向云监控服务器(master)发送请求时,使用短信通知用户处理。</p><h2 id="基本原理"><a href="#基本原理" class="headerlink" title="基本原理"></a>基本原理</h2><ul><li>slave每隔1分钟向master发送指定格式请求,参数包括<code>name</code>和<code>token</code>,<code>name</code>用于表明自己的身份(主机名),且只当<code>token</code>正确时,master才接受请求。</li><li>master在接收到请求后,会将数据文件<code>data.json</code>中<code>name</code>键所对应的值赋为当前的timestamp(如果<code>name</code>键不存在则新建一个)。</li><li>master上每隔1分钟检查一次<code>data.json</code>中每一个<code>name</code>的键值,并比较当前timestamp与各键值的差值。当某<code>name</code>差值大于300(超过5分钟未更新)则调用短信发送的API,提醒用户该主机出现问题。</li></ul><h2 id="代码示例"><a href="#代码示例" class="headerlink" title="代码示例"></a>代码示例</h2><h3 id="master中接收请求并修改数据文件的php脚本"><a href="#master中接收请求并修改数据文件的php脚本" class="headerlink" title="master中接收请求并修改数据文件的php脚本"></a>master中接收请求并修改数据文件的php脚本</h3><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta"><?php</span></span><br><span class="line"><span class="variable">$name</span> = <span class="variable">$_GET</span>[<span class="string">"name"</span>];</span><br><span class="line"><span class="variable">$token</span> = <span class="variable">$_GET</span>[<span class="string">"token"</span>];</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (<span class="variable">$name</span>==<span class="string">""</span> || <span class="variable">$token</span>!=<span class="string">"example_token"</span>)</span><br><span class="line">{</span><br><span class="line"> <span class="keyword">echo</span> <span class="string">"Error"</span>;</span><br><span class="line"> <span class="keyword">exit</span>(<span class="number">1</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="variable">$value</span> = <span class="title function_ invoke__">file_get_contents</span>(<span class="string">"data.json"</span>);</span><br><span class="line"><span class="variable">$data</span> = <span class="title function_ invoke__">json_decode</span>(<span class="variable">$value</span>,<span class="literal">true</span>);</span><br><span class="line"></span><br><span class="line"><span class="variable">$data</span>[<span class="variable">$name</span>]=<span class="title function_ invoke__">time</span>();</span><br><span class="line"></span><br><span class="line"><span class="title function_ invoke__">file_put_contents</span>(<span class="string">"data.json"</span>,<span class="title function_ invoke__">json_encode</span>(<span class="variable">$data</span>));</span><br><span class="line"></span><br><span class="line"><span class="keyword">echo</span> <span class="string">"Get"</span>;</span><br></pre></td></tr></table></figure><h3 id="master中检查数据文件并发送短信的python脚本"><a href="#master中检查数据文件并发送短信的python脚本" class="headerlink" title="master中检查数据文件并发送短信的python脚本"></a>master中检查数据文件并发送短信的python脚本</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#! /usr/bin/python3</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 每分钟运行一次,可在crontab中配置</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> json</span><br><span class="line"><span class="keyword">import</span> time</span><br><span class="line"><span class="keyword">from</span> qcloudsms_py <span class="keyword">import</span> SmsSingleSender</span><br><span class="line"><span class="keyword">from</span> qcloudsms_py.httpclient <span class="keyword">import</span> HTTPError</span><br><span class="line"><span class="keyword">import</span> datetime</span><br><span class="line"></span><br><span class="line"><span class="comment"># 腾讯云发短信的脚本,具体规则可参阅官方网站</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">sendSMS</span>(<span class="params"><span class="built_in">id</span></span>):</span><br><span class="line"> appid = <span class="number">1400000000</span></span><br><span class="line"> appkey = <span class="string">"exampleappkey"</span></span><br><span class="line"> phone_numbers = [<span class="string">"18000000000"</span>]</span><br><span class="line"> template_id = <span class="number">190000</span></span><br><span class="line"> sms_sign = <span class="string">"vvzero"</span></span><br><span class="line"></span><br><span class="line"> ssender = SmsSingleSender(appid, appkey)</span><br><span class="line"> params = [<span class="built_in">id</span>]</span><br><span class="line"></span><br><span class="line"> <span class="keyword">try</span>:</span><br><span class="line"> result = ssender.send_with_param(<span class="number">86</span>, phone_numbers[<span class="number">0</span>],</span><br><span class="line"> template_id, params, sign=sms_sign, extend=<span class="string">""</span>, ext=<span class="string">""</span>)</span><br><span class="line"> <span class="keyword">except</span> HTTPError <span class="keyword">as</span> e:</span><br><span class="line"> logFile.write(<span class="built_in">str</span>(e)+<span class="string">"\n"</span>)</span><br><span class="line"> <span class="keyword">except</span> Exception <span class="keyword">as</span> e:</span><br><span class="line"> logFile.write(<span class="built_in">str</span>(e)+<span class="string">"\n"</span>)</span><br><span class="line"></span><br><span class="line"> logFile.write(<span class="built_in">str</span>(result)+<span class="string">"\n"</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">logFile = <span class="built_in">open</span>(<span class="string">"hostsMonitor.log"</span>,<span class="string">'a'</span>)</span><br><span class="line">logFile.write(<span class="string">"\n"</span>+<span class="built_in">str</span>(datetime.datetime.now())+<span class="string">"\n"</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">try</span>:</span><br><span class="line"> inputFile = <span class="built_in">open</span>(<span class="string">"/var/www/iot/checkAlive/data.json"</span>,<span class="string">'r'</span>)</span><br><span class="line"><span class="keyword">except</span> Exception <span class="keyword">as</span> e:</span><br><span class="line"> logFile.write(<span class="built_in">str</span>(e)+<span class="string">"\n"</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">try</span>:</span><br><span class="line"> data = json.load(inputFile)</span><br><span class="line"><span class="keyword">except</span> Exception <span class="keyword">as</span> e:</span><br><span class="line"> logFile.write(<span class="built_in">str</span>(e)+<span class="string">"\n"</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">try</span>:</span><br><span class="line"> inputFile.close()</span><br><span class="line"><span class="keyword">except</span> Exception <span class="keyword">as</span> e:</span><br><span class="line"> logFile.write(<span class="built_in">str</span>(e)+<span class="string">"\n"</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> host <span class="keyword">in</span> data:</span><br><span class="line"> <span class="keyword">if</span> (<span class="built_in">int</span>(time.time()) - <span class="built_in">int</span>(data[host]) >= <span class="number">300</span>) <span class="keyword">and</span> (<span class="built_in">int</span>(time.time()) - <span class="built_in">int</span>(data[host]) <= <span class="number">360</span>): <span class="comment"># 超过5分钟没有心跳包就发短信</span></span><br><span class="line"> <span class="keyword">try</span>:</span><br><span class="line"> sendSMS(host)</span><br><span class="line"> logFile.write(<span class="string">"Problem found in "</span>+host+<span class="string">". SMS has been sent.\n"</span>)</span><br><span class="line"> <span class="keyword">except</span> Exception <span class="keyword">as</span> e:</span><br><span class="line"> logFile.write(<span class="built_in">str</span>(e)+<span class="string">"\n"</span>)</span><br><span class="line"></span><br><span class="line">logFile.close()</span><br></pre></td></tr></table></figure><h3 id="slave中的crontab配置,其他定时任务同理"><a href="#slave中的crontab配置,其他定时任务同理" class="headerlink" title="slave中的crontab配置,其他定时任务同理"></a>slave中的crontab配置,其他定时任务同理</h3><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">*/1 * * * * curl -X GET <span class="string">'https://monitor.example.com/?name=hostname&token=example_token'</span></span><br></pre></td></tr></table></figure><h2 id="实际效果"><a href="#实际效果" class="headerlink" title="实际效果"></a>实际效果</h2><p>这几天将这套系统运行了一下,没有什么问题。并且在我路由器掉线的时候及时提醒了我。</p><p><img src="/static/blog_images/simple-hosts-monitor-system/1.jpg"></p>]]></content>
<categories>
<category> TECHNOLOGY </category>
</categories>
<tags>
<tag> php </tag>
<tag> python </tag>
<tag> 云监控 </tag>
</tags>
</entry>
<entry>
<title>我的 2018 暑假</title>
<link href="/2018/09/01/summary-for-this-summer-vocation-2018/"/>
<url>/2018/09/01/summary-for-this-summer-vocation-2018/</url>
<content type="html"><![CDATA[<blockquote><p>暑假开始之前,我信誓旦旦地跟玲玲说,我这个暑假要赚一万块钱。结果,现在看来,一分钱也没赚到,倒是“赔”进去了不少钱。</p></blockquote><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>开学一周的此时,回顾一下之前两个月,也不算太晚。</p><h2 id="暑假概况"><a href="#暑假概况" class="headerlink" title="暑假概况"></a>暑假概况</h2><p>__时间:__要是从最后一门考试结束到第一次上课计算的话,应该是2018年7月5日至2018年9月4日,共61天。</p><p>__地点:__这是我第一次在假期里面“几乎不回家”。大部分时间是在南京航空航天大学将军路校区一号楼里面摸鱼,中途回家一个星期*2,在第二次回家期间,陪爸妈去扬州玩了一天,又到无锡,陪表弟过了一宿。</p><h2 id="大事小事"><a href="#大事小事" class="headerlink" title="大事小事"></a>大事小事</h2><h3 id="1-做了一个宿舍电费查询的小项目"><a href="#1-做了一个宿舍电费查询的小项目" class="headerlink" title="1. 做了一个宿舍电费查询的小项目"></a>1. 做了一个宿舍电费查询的小项目</h3><p>优秀的苏瑞辅同学,上学期在跟我共事的时候,曾跟我提过他想要做一个电费查询的项目,具体就是识别用户输入,向学校电费查询官网模拟请求,然后把电费数据抠出来,返回给用户。因为了解自己宿舍的空调电费余量、防止夜间突然断电,也算是刚需。何况电费查询网站必须要校园内网环境,而且查询页面并不是很友好,所以,这样的项目能够给同学们带来便利,应该具有挺高的实用价值。</p><p>但是,上学期由于种种原因,特别是查询网站的宿舍归类过于复杂,苏瑞辅并没有完成真正完成项目开发。我接过了他的想法,并在他提供的微信后端支持下,继续开发,目前完成了21-22栋、35-37栋的空调电费查询,并且后期将会跟据同学们的需求继续开发。</p><p>在这一次的项目开发过程中,我收获颇丰:</p><ol><li>__了解__了后端开发的基本概念;</li><li>__了解__了微信公众号开发的基本原理;</li><li>__知晓__了python的正则表达式用法、requests库的简单应用;</li><li>给自己的微信公众号涨了一点粉。</li></ol><h3 id="2-制作了一个恋爱打卡的小站点"><a href="#2-制作了一个恋爱打卡的小站点" class="headerlink" title="2. 制作了一个恋爱打卡的小站点"></a>2. 制作了一个恋爱打卡的小站点</h3><p>优秀的苏瑞辅同学,在暑期喜得一女友。我受不了他成天在我耳边念叨他的女票,于是自己也想办法秀自己一把。我和我的玲玲以前都是用“情侣空间”APP天天打卡,但是这个APP事实上挺臃肿,我们只需要“打卡”“记录恋爱天数”这两个功能,所以,不如就自己做一个。</p><p>我自学了前端的一些基本知识,欣赏了周围几个大佬的个人站点,强化了后端的一些概念,在10天左右的学习+实践后,我成功做了一个满足了以上两个需求的网页。玲玲也挺喜欢的,我们早就卸载了“情侣空间”APP,现在都在我做的页面上打卡。</p><p>在本次项目开发过程中,我:</p><ol><li>__了解__了前端开发的基本概念;</li><li>__学会了简单应用__bootstrap框架;</li></ol><h3 id="3-申请了很多云服务器"><a href="#3-申请了很多云服务器" class="headerlink" title="3. 申请了很多云服务器"></a>3. 申请了很多云服务器</h3><p>我也许有被迫害妄想症,总怕自己与世界的联系会断开。所以,我在周围大佬们的推荐下,尝试了很多的云主机提供商的服务。很多都有一年的免费,就算收费也挺便宜,何况很多时候都有学生优惠。</p><p>所以,我在阿里云、腾讯云、AWS、vultr等各大供应商,薅了一波羊毛,获得了屈指难数的云服务器和公网IP。总算给自己带来了一点安全感。</p><p>当然,这样的行为还是被周围的大佬们嘲笑了,其实吧,也真的没这个必要。</p><h3 id="4-搭建了自己的即时通讯平台和个人网盘"><a href="#4-搭建了自己的即时通讯平台和个人网盘" class="headerlink" title="4. 搭建了自己的即时通讯平台和个人网盘"></a>4. 搭建了自己的即时通讯平台和个人网盘</h3><p>同上,被迫害妄想症,总想什么都有自己的。</p><p>所以,咨询了赵祯真学长,他推荐我使用RocketChat这一款开源的即时通讯平台;咨询了优秀的苏瑞辅同学,他推荐我使用NextCloud这一款开源网盘系统。</p><p>我经过两三天的配置,基本完成了这两个系统的搭建,还好能用。我委屈了一下玲玲(毕竟我没有用户,只能找她),让我们每天互发日记的时候,都要在我搭建的通讯平台传输,她居然欣然同意了。我很感动,我唯一的用户。</p><h3 id="5-又一次感受了扬州的美"><a href="#5-又一次感受了扬州的美" class="headerlink" title="5. 又一次感受了扬州的美"></a>5. 又一次感受了扬州的美</h3><p>应该是我第三次去扬州了,扬州真的是个天堂。我去的地方不多,但是能让我真正感动的城市,只有扬州一个,每一次去都是一样的感觉。</p><p>扬州没有地铁,但是公交的便利程度丝毫不亚于地铁,如履平地。</p><p>瘦西湖景区是我见过的最舒心的景区,没有之一。</p><p>扬州人,让我感觉,说话好像都带着瘦西湖的水,很温和。</p><p>哎,回来之后就爱上了《扬州姑娘》这首歌。</p><h3 id="6-接触了一个上市公司"><a href="#6-接触了一个上市公司" class="headerlink" title="6. 接触了一个上市公司"></a>6. 接触了一个上市公司</h3><p>我在学生IT创新创业区里担任物联网区部长一职。这个暑假,老师让我们和一个校企合作的公司打打交道。我是秉着赚钱的目的去的,奈何自己的水平太菜,并不能给此公司带来价值,最终也就不了了之。</p><p>跟他们的技术总监(荷兰人)有过两次接触,一次是他们要做一个小API,我当时正在学习后端PHP,就顺手练习了一下,帮他们做了;第二次他要帮忙做一个摄像头盲区计算及最优解确定的问题,我实在是算法不精,无法胜任。</p><p>不过,至少也练习了英语口语。</p><h2 id="暑假里,那些人那些事"><a href="#暑假里,那些人那些事" class="headerlink" title="暑假里,那些人那些事"></a>暑假里,那些人那些事</h2><p>__我的玲玲:__真名张玲,是我的女朋友。这一个暑假,我们没有吵过架。而且,度过了第一个在一起的七夕。</p><p>__优秀的苏瑞辅同学:__谢谢他给我技术上和思维上的指导。以及,祝他和他的单单愉快!</p><p>__父母:__衣食父母。他们每年要在我这里投资4w+,这是风险最高的天使投资。</p><p>__张世博同学:__一位在暑假里参加电赛的大佬,他在的日子跟他玩得很愉快。以后如果我想赚钱的话,应该还是会找他的。</p><p>__赵祯真学长:__在创新区里认识的一位大佬。暑假前期和后期,他就坐在我旁边,给了我很多技术上的指导。另外,谢谢他的显示器。</p><p>__创新区的老师们:__假期的一切硬件资源都是学校提供的,老师们给了我很多的支持。</p><p>__工程训练中心的葛旺老师:__刚放暑假的时候,我和张世博帮他搬迁工盟的实验室。另外,得老师厚爱,现在担任工盟的主负责人。</p><p>__公寓管理中心的郑老师:__谢谢他给我查电费项目的支持,并帮我在公寓楼内宣传。</p><p>__表弟朱国盛:__很久不见了,上次去无锡,也就一天时间。不过,他陪我吃鸡的几个小时,真的好刺激。</p><p>__刘建廷老师、兄弟顾浩东、兄弟张子逸:__暑假里见过的高中哥们,也就他们仨了。听老刘讲了很久的人生经验,还被请去喝茶,妙。</p><p><strong>暑假里,还接触过很多人,就不一一赘述了。所有人都在影响着我,也许我也会有自己的影响力吧。</strong></p><h2 id="今天是"><a href="#今天是" class="headerlink" title="今天是"></a>今天是</h2><p>2018年9月8日。</p><p>开学一周。</p><blockquote><p>也许我真的一分钱都没赚到,赔进去了很多,但是,我想我这个暑假学到的,见识到的,遇到的,也许不止1w+吧?</p></blockquote>]]></content>
<categories>
<category> LIVES </category>
</categories>
<tags>
<tag> 心得 </tag>
<tag> 暑假 </tag>
</tags>
</entry>
<entry>
<title>近日浅尝网络开发的一点心得</title>
<link href="/2018/07/26/experience-gained-as-a-developer-these-day/"/>
<url>/2018/07/26/experience-gained-as-a-developer-these-day/</url>
<content type="html"><![CDATA[<blockquote><p>我不是CS科班的学生,此次也并没有接触完整的、系统的网络编程开发。在题目中用“网络开发”显得有点大,但是我也很难用一个词概括我做了什么,就用这个词代替吧。</p></blockquote><h2 id="我做了什么"><a href="#我做了什么" class="headerlink" title="我做了什么"></a>我做了什么</h2><ol><li>写了一个抠网页的脚本。我用python模拟访问电费查询网站,把电费数据抠了出来。</li><li>做了一个微信公众号的后端。在<a href="https://www.suruifu.com/">苏瑞辅大佬</a>的指导下,使用php完成了一个微信公众号后端的开发,可实现调用python脚本以及自动转发微信回复至个人邮箱。</li></ol><h2 id="几点心得"><a href="#几点心得" class="headerlink" title="几点心得"></a>几点心得</h2><p><em>CS大佬们就不要看了,我是讲给网络编程初学者以及非科班爱好者的[/滑稽]</em></p><h3 id="1-学会查找你想要的资料"><a href="#1-学会查找你想要的资料" class="headerlink" title="1. 学会查找你想要的资料"></a>1. 学会查找你想要的资料</h3><p>这是我觉得最重要的能力,没有之一。一个人所能记住的知识是有限的,哪怕只去学一种语言,也不可能放肆地说自己“精通”。但是,网络是无限的。比如,某个不常用函数的定义,我们完全没有必要记住。只要能用好各类搜索引擎,快速准确地找到它,就够了。另外,我觉得,能够自己获取的信息,就尽量不要问别人,这是礼仪。</p><p>我从来没有接触过php,但是我经历了以下的步骤:</p><ol><li>搜索“php快速入门”,花15分钟了解基本语法,例如,变量名前要加’$’</li><li>大佬给了我一个类库(此处向苏瑞辅致♂敬),我大体看了下语言结构</li><li>跟据自己的需求,在php官网查找相应函数调用方式</li><li>Debug的时候遇到错误提示,Google一下,一般在StackOverflow上就能找到对应解决方案</li></ol><p>所以,我可能根本没有入门php,但是我已经可以完成一些简单的后端操作了。</p><h3 id="2-英文水平很重要"><a href="#2-英文水平很重要" class="headerlink" title="2. 英文水平很重要"></a>2. 英文水平很重要</h3><p>这是接着第1点的。</p><p>毕竟,国内的计算机水平距离国际先进水平还有相当大的差距,汉化的资料总有不足。要想获取一手的开发资料,英文水平当然很重要。</p><h3 id="3-万物皆I-O"><a href="#3-万物皆I-O" class="headerlink" title="3. 万物皆I/O"></a>3. 万物皆I/O</h3><p>(此处再次向苏瑞辅致♂敬,就用原话了。)</p><p>这是一种思维方式,只可意会不可言传。</p><p>举些例子:</p><ol><li>调用函数,都是按类型传参(Input),然后获取一个返回值(Output)</li><li>我们浏览网页,都是先使用HTTP GET或POST一个请求(Input),然后就可以看到服务器传回的网页(Output)</li><li>硬件开发中,假如我要使用一个频率-电压转换集成块,一定是输入一个频率(Input),然后获得一个电压值(Output)</li></ol><h3 id="4-有一个大佬带着能让你避开很多坑"><a href="#4-有一个大佬带着能让你避开很多坑" class="headerlink" title="4. 有一个大佬带着能让你避开很多坑"></a>4. 有一个大佬带着能让你避开很多坑</h3><p>大佬一定是比你聪明的,或者比你起步早的,他能看到的,你不一定能看到。大佬也许会不停地嘲讽你,但是听着就好了,毕竟能学到东西,特别是能学到学习方法,这就够了。</p><p>当然,有一个能互相学习的大佬,那更是万幸。</p><p>此处又一次向苏瑞辅致♂敬。[/手动滑稽]</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>好啦,没什么好总结的,这几天挺开心。</p>]]></content>
<categories>
<category> TECHNOLOGY </category>
</categories>
<tags>
<tag> rvfu98 </tag>
<tag> php </tag>
<tag> 心得 </tag>
</tags>
</entry>
<entry>
<title>南京航空航天大学宿舍电费查询项目开发</title>
<link href="/2018/07/22/nuaa-ElectriFee-beta/"/>
<url>/2018/07/22/nuaa-ElectriFee-beta/</url>
<content type="html"><![CDATA[<h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>感觉自己应该做点什么……顺便学点什么……</p><p>于是,一边学习各种语言,一边想完成一个小项目:宿舍电费查询。</p><p>我们学校查电费是个很坑爹的网站,只能在校内访问,而且是.NET aspx页面,各种分类极其复杂,毫无规律性。@苏瑞辅同之前尝试做过,但是放弃了。我这次也没想要完成全校的电费查询,暂时只是想完成怡园21栋宿舍空调电费查询。</p><h2 id="项目地址"><a href="#项目地址" class="headerlink" title="项目地址"></a>项目地址</h2><p>项目已上传至GitHub,望大家能一同开发。</p><p><a href="https://github.com/NUAA-Open-Source/nuaa-ElectriFee">项目地址</a></p><h2 id="开发要点"><a href="#开发要点" class="headerlink" title="开发要点"></a>开发要点</h2><ul><li>由python开发的电费查询脚本,模拟访问学校页面,抠出电费输出</li><li>由php开发的微信公众号后端,向查询脚本传递用户输入并获取电费回传至微信</li><li>在校内设置代理,以保证以校园网访问</li></ul><h2 id="项目现状"><a href="#项目现状" class="headerlink" title="项目现状"></a>项目现状</h2><p>已经推出beta版,部署在本人公众号内。</p><p>扫描页面下方公众号二维码即可体验。</p><h2 id="特别鸣谢"><a href="#特别鸣谢" class="headerlink" title="特别鸣谢"></a>特别鸣谢</h2><p>感谢<a href="https://www.rvfu98.com/">@苏瑞辅</a>提供大量开发支持!</p>]]></content>
<categories>
<category> TECHNOLOGY </category>
</categories>
<tags>
<tag> wechat </tag>
<tag> python </tag>
</tags>
</entry>
<entry>
<title>Google Play出现DF-DFERH-01错误的分析与修复</title>
<link href="/2018/07/15/inspect-mix2s-google-play-error-DF-DFERH-01/"/>
<url>/2018/07/15/inspect-mix2s-google-play-error-DF-DFERH-01/</url>
<content type="html"><![CDATA[<p><code>#define 纸飞机 不可描述之神器</code></p><h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>换了陪伴了两年的MI5,换上了MIX2s。嗯……可是换机的数据迁移和初始化实在是工程量巨大。</p><p>当然,没有Google全家桶的安卓机是不完美的。</p><p>于是问题来了。</p><h2 id="错误描述"><a href="#错误描述" class="headerlink" title="错误描述"></a>错误描述</h2><p>与MI5不同的是,MIX2s自带谷歌框架。</p><img src="/static/blog_images/inspect-mix2s-google-play-error-DF-DFERH-01/1.png" style="zoom: 25%;" /><p>但是出厂并不标配Google Play,需要在应用商店里安装。(当然,小米官方是没有的,需要拉到最下面“在百度中搜索”或“在豌豆荚中搜索”)。</p><p>首次登陆的时候,并不会遇到什么问题(前提是配置好纸飞机)。但是,在重新启动Google Play,或者重启手机后,再次使用均会出现“DF-DFERH-01”的错误。</p><img src="/static/blog_images/inspect-mix2s-google-play-error-DF-DFERH-01/2.png" style="zoom:25%;" /><p>只有清除Google Play应用数据后,才能恢复原状。但是,一旦重启应用或机器,错误就会重现。</p><h2 id="错误分析"><a href="#错误分析" class="headerlink" title="错误分析"></a>错误分析</h2><p>错误信息是<code>Error retrieving information from server</code>,即从服务器获取信息失败。但是,显然我的纸飞机没有问题啊(别问我怎么知道的)。依靠错误代码在网络上搜索了半天,并不能找到有效的解决方法,清数据、重新登陆谷歌账号,都没有用。但是谷歌的其他服务可以使用(账户同步等)。甚是奇怪。</p><p>最终,还是本国的论坛更加了解国情。在MIUI论坛中,我找到了相关信息。</p><p>在MI5中,由于系统并不自带谷歌框架,所以我安装的是原生的谷歌服务,连接谷歌服务的域名均为<code>.com</code>后缀。但是,小米自带的谷歌服务,域名存在<code>.cn</code>后缀,问题就在于此。</p><p>使用抓包工具对Google Play的数据进行分析,发现了以下数据包:</p><p><img src="/static/blog_images/inspect-mix2s-google-play-error-DF-DFERH-01/6.jpg"></p><p>小米自带的谷歌服务,在Google Play启动的时候,会向<code>services.googleapis.cn</code>获取数据,而正常情况下(国际版),应是<code>services.googleapis.com</code>。在国内,由于众所周知的原因,<code>services.googleapis.cn</code>并不会指向谷歌服务器,而是指向了北京某公司的服务器。</p><p>以下是使用国内两台主机获取的host数据:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">$ host services.googleapis.cn</span><br><span class="line">services.googleapis.cn has address 203.208.39.239</span><br><span class="line">services.googleapis.cn has address 203.208.39.247</span><br><span class="line">services.googleapis.cn has address 203.208.39.255</span><br><span class="line">services.googleapis.cn has address 203.208.39.248</span><br><span class="line">services.googleapis.cn has IPv6 address 2401:3800:4002:805::100f</span><br></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">$ host services.googleapis.cn</span><br><span class="line">services.googleapis.cn has address 203.208.43.95</span><br><span class="line">services.googleapis.cn has address 203.208.43.79</span><br><span class="line">services.googleapis.cn has address 203.208.43.87</span><br><span class="line">services.googleapis.cn has address 203.208.43.88</span><br><span class="line">services.googleapis.cn has IPv6 address 2401:3800:4001:14::1018</span><br></pre></td></tr></table></figure><p>查询这类ip地址的归属地,发现均归属于北京:</p><img src="/static/blog_images/inspect-mix2s-google-play-error-DF-DFERH-01/3.jpg" style="zoom:67%;" /><blockquote><p>数据来源:ip138.com</p></blockquote><p>而使用国外的主机获取host数据,就截然不同。以下是使用新加坡以及加州的服务器获取的数据:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ host services.googleapis.cn</span><br><span class="line">services.googleapis.cn has address 74.125.24.94</span><br><span class="line">services.googleapis.cn has IPv6 address 2404:6800:4003:c03::5e</span><br></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ host services.googleapis.cn</span><br><span class="line">services.googleapis.cn has address 172.217.0.35</span><br><span class="line">services.googleapis.cn has IPv6 address 2607:f8b0:4005:802::2003</span><br></pre></td></tr></table></figure><p>查询ip归属,均属于谷歌公司:</p><img src="/static/blog_images/inspect-mix2s-google-play-error-DF-DFERH-01/4.jpg" style="zoom:67%;" /><img src="/static/blog_images/inspect-mix2s-google-play-error-DF-DFERH-01/5.jpg" style="zoom:67%;" /><blockquote><p> 数据来源:ip138.com</p></blockquote><p><strong>由此,只要将<code>services.googleapis.cn</code>换成国外的DNS服务器进行解析,即可。</strong></p><h2 id="修复方案"><a href="#修复方案" class="headerlink" title="修复方案"></a>修复方案</h2><p>最简单的方法就是在纸飞机中操作。</p><ul><li>如果想要省事,可以直接开启<code>DNS Forwarding</code>,即把所有的DNS请求发送到墙外进行处理。但是这样会导致访问国内的一些服务出现延迟。</li><li>所以,最好的方法还是把路由方式改成自定义规则,先导入GFW List,再添加如下一条规则:<code>googleapis.cn</code>,并设定为“域名及其所有子域名”。</li></ul><p><strong>最后,祝大家在互联网中,学习愉快!</strong></p>]]></content>
<categories>
<category> TECHNOLOGY </category>
</categories>
<tags>
<tag> 小米 </tag>
<tag> Google </tag>
</tags>
</entry>
<entry>
<title>用Arduino制作示波器之初尝试</title>
<link href="/2018/06/21/oscilloscope-by-arduino-1st-trial/"/>
<url>/2018/06/21/oscilloscope-by-arduino-1st-trial/</url>
<content type="html"><![CDATA[<h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>这学期快要结束了,要上交一个模拟电子技术基础的课程设计。调试的过程中需要用到示波器,但我又懒得去电工实验室借用示波器。在@张世博 的提醒下,尝试使用手头闲置已久的Arduino Uno做一个示波器。</p><h2 id="基本原理"><a href="#基本原理" class="headerlink" title="基本原理"></a>基本原理</h2><p>信号源 -> Arduino 电压模拟输入 -> 串口输出 -> PC端串口工具显示波形</p><h2 id="实验器材及软件"><a href="#实验器材及软件" class="headerlink" title="实验器材及软件"></a>实验器材及软件</h2><ul><li><p>恒压源</p></li><li><p>自己做的信号发生器(待测试)</p><p><img src="/static/blog_images/oscilloscope-by-arduino-1st-trial/1.jpg"></p></li><li><p>Arduino Uno</p><p><img src="/static/blog_images/oscilloscope-by-arduino-1st-trial/2.jpg"></p></li><li><p>Serial Debug Assistant (串口调试助手。我在Windows应用商店下载的UWP版,一个国人开发的软件,如果需要绘制图形,必须购买pro版。也不是很贵,几十块钱的样子)</p></li></ul><h2 id="测试视频"><a href="#测试视频" class="headerlink" title="测试视频"></a>测试视频</h2><video src='/static/blog_images/oscilloscope-by-arduino-1st-trial/3.mp4 ' type='video/mp4' controls='controls' width='100%' height='100%'></video><h2 id="测试说明"><a href="#测试说明" class="headerlink" title="测试说明"></a>测试说明</h2><ul><li><p>信号发生器理论上产生三角波信号</p></li><li><p>Arduino的主要代码如下:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">while</span> (<span class="number">1</span>)</span><br><span class="line">{</span><br><span class="line"> Serial.print(<span class="string">"Valtage = "</span>); <span class="comment">//此处配合Serial Debug Assistant的图形绘制接口</span></span><br><span class="line"> Serial.println(analogRead(PIN)*<span class="number">5.0</span>/<span class="number">1024.0</span>); <span class="comment">//换算成伏特</span></span><br><span class="line"> delay(<span class="number">0.1</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li><li><p>由于可能产生超过5V或低于0V的电压,故我将Arduino的GND接至待测电路的-12V电源,并将信号输出使用4个100kΩ电阻分压,测量输出电压1/4的量。</p></li><li><p>三角波的频率可调,实际上,只有在把调试软件的时间分度和三角波周期调整至同一数量级才可显示出清晰波形。</p></li></ul><h2 id="后记"><a href="#后记" class="headerlink" title="后记"></a>后记</h2><p>emmm,怎么说呢,这个示波器其实没什么卵用,不能很方便地调幅调频,而且对于高频信号完全无法胜任(测试2000Hz已经很吃力了)。</p><p>乖乖去实验室吧。</p>]]></content>
<categories>
<category> TECHNOLOGY </category>
</categories>
<tags>
<tag> Arduino </tag>
<tag> serial </tag>
</tags>
</entry>
<entry>
<title>My 20th birthday</title>
<link href="/2018/05/27/my-20th-birthday/"/>
<url>/2018/05/27/my-20th-birthday/</url>
<content type="html"><![CDATA[<p>这属于我的20岁生日。</p><p><img src="/static/blog_images/my-20th-birthday/1.png"></p>]]></content>
<categories>
<category> LIVES </category>
</categories>
<tags>
<tag> birthday </tag>
</tags>
</entry>
<entry>
<title>记几点 Windows 10 家庭基础版和专业版的区别</title>
<link href="/2018/05/12/windows-difference-between-pro-and-home/"/>
<url>/2018/05/12/windows-difference-between-pro-and-home/</url>