-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsearch.xml
More file actions
1243 lines (1243 loc) · 191 KB
/
search.xml
File metadata and controls
1243 lines (1243 loc) · 191 KB
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>Git笔记</title>
<url>/posts/git-notes/</url>
<content><![CDATA[<h2 id="Git简介">Git简介</h2>
<h3 id="Git的诞生">Git的诞生</h3>
<h3 id="集中式vs分布式">集中式vs分布式</h3>
<h2 id="安装Git">安装Git</h2>
<p><strong>在Linux上安装Git</strong></p>
<p>在Debian系上用<code>sudo apt-get install git</code>就可以。</p>
<p>安装完成后,还需要最后一步,在命令行中输入:</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">git config --global user.name <span class="string">"Your Name"</span></span><br><span class="line">git config --global user.email <span class="string">"email@example.com"</span></span><br></pre></td></tr></table></figure>
<h2 id="创建版本库">创建版本库</h2>
<p>初始化一个Git仓库,使用<code>git init</code>命令。<br>
添加文件到Git仓库,分为两步:</p>
<ul>
<li>第一步,使用<code>git add <file></code>,可反复使用多次,添加多个文件;</li>
<li>第二步,使用命令<code>git commit -m "注释"</code>,完成</li>
</ul>
<h2 id="时光穿梭机">时光穿梭机</h2>
<ul>
<li>要随时掌握工作区的状态,使用<code>git status</code>命令。</li>
<li>如果<code>git status</code>告诉你有文件被修改过,用<code>git diff</code>可以查看修改内容</li>
</ul>
<h3 id="版本回退">版本回退</h3>
<ul>
<li><code>HEAD</code>指向的版本就是当前版本,上一个版本是<code>HEAD^</code>,上上一个版本是<code>HEAD^^</code>。Git允许我们在版本的历史之间穿梭,使用命令<code>git reset --hard commit_id</code>。</li>
<li>穿梭前,用<code>git log</code>可以查看提交历史,以便确定要回退到哪个版本。如果嫌输出信息太多,可以试试<code>git log --pretty=oneline</code></li>
<li>要重返未来,用<code>git reflog</code>查看命令历史,以便确定要回到未来的哪个版本。</li>
</ul>
<h3 id="工作区和暂存区">工作区和暂存区</h3>
<p><code>git add</code>命令实际上就是把要提交的所有修改放到暂存区(Stage),然后,执行<code>git commit</code>就可以一次性把暂存区的所有修改提交到分支。例如<code>git commit -m "<修改的内容>"</code></p>
<h3 id="管理修改">管理修改</h3>
<p>用<code>git diff HEAD -- file</code>查看工作区的版本库里最新版的区别</p>
<h3 id="撤销修改">撤销修改</h3>
<p>场景1:当改乱了工作区某个文件的内容 想直接丢弃工作区的修改时,可以用<code>git checkout -- file</code>。</p>
<p>场景2:改乱的工作区文件已经添加到了暂存区,想丢弃,先用命令<code>git reset HEAD file</code>可以把暂存区的修改撤销掉(unstaged),重新放回工作区;然后用<code>git checkout -- file</code>。</p>
<p>场景3:已经提交了不合适的修改到版本库时,想要撤销本次提交,参考<strong>版本回退</strong>部分,前提时没有推送到远程仓库。</p>
<h3 id="删除文件">删除文件</h3>
<p>用<code>git rm file</code>来删除一个文件。</p>
<h2 id="远程仓库">远程仓库</h2>
<p>前提:已注册<a href="https://github.com/">Github</a></p>
<p>第1步:创建SSH Key。<code>ssh-keygen -t rsa -C "youremail@example.com"</code>,修建地址换为自己的邮箱地址,回车,yes。正常的话会看到用户主目录下的<code>.ssh</code>目录下找到<code>id_rsa</code>私钥,<code>id_rsa.pub</code>公钥。</p>
<p>第2步:打开Github的设置,增加SSH Key,填上<code>id_rsa.pub</code>文件里的内容。OK!</p>
<h3 id="添加远程仓库">添加远程仓库</h3>
<p>要关联一个远程库,使用命令<code>git remote add origin git@server-name:path/repo-name.git</code>;<br>
关联后,使用命令<code>git push -u origin master</code>第一次推送master分支所有内容;<br>
此后,每次提交后,只要有必要,就可以使用命令<code>git push origin master</code>推送最新修改;<br>
分布式版本控制系统的最大好处之一是有没有联网都可以正常工作,有网的时候推送一下就同步了。</p>
<h3 id="从远程仓库克隆">从远程仓库克隆</h3>
<p>要克隆一个仓库,首先必须只当仓库的地址,然后使用<code>git clone</code>命令克隆。</p>
<p>Git支持多种协议,包括<code>https</code>,但是通过<code>ssh</code>支持的原生<code>git</code>协议速度最快。</p>
<h2 id="分支管理">分支管理</h2>
<h3 id="创建与合并分支">创建与合并分支</h3>
<p>Git鼓励大量使用分支:</p>
<p>查看分支:<code>git branch</code><br>
创建分支:<code>git branch <name></code><br>
切换分支:<code>git checkout <name></code><br>
创建+切换分支:<code>git checkout -b <name></code><br>
合并某分支到当前分支:<code>git merge <name></code><br>
删除分支:<code>git branch -d <name></code></p>
<h3 id="解决冲突">解决冲突</h3>
<p>当Git无法自动合并分支时,就必须首先解决冲突。解决冲突后,再提交,合并完成。</p>
<p>用<code>git log --graph</code>命令可以看到分支合并图。</p>
<h3 id="分支管理策略">分支管理策略</h3>
<p>Git分支十分强大,在团队开发中应该充分应用。</p>
<p>合并分支时,加上<code>--no-ff</code>参数就可以用普通模式合并,合并后的历史有分支,能看出来曾经做过合并,而<code>fast forward</code>合并后就看不出来曾经做过合并。</p>
<h3 id="BUG分支">BUG分支</h3>
<p>修复bug时,我们会通过创建新的bug分支进行修复,然后合并,最后删除;</p>
<p>当手头工作没完成时,先把工作现场<code>git stash</code>一下,然后去修复bug,修复后再<code>git stash pop</code>,恢复现场,恢复的同时也把stash内容删了。</p>
<p>当多次stash,恢复的时候,先用<code>git stash list</code>查看,然后恢复制定的stash,用命令<code>git stash apply stash@{0}</code>,stash@{0}是查看stash list时那次stash的码。</p>
<h3 id="Feature分支">Feature分支</h3>
<p>开发一个新feature,最好新建一个分支;</p>
<p>如果要丢弃一个没有被合并过的分支,可以通过<code>git branch -D <name></code>强制删除。</p>
<h3 id="多人协作">多人协作</h3>
<p>多人协作的工作模式通常如下:</p>
<ol>
<li>首先,可以试图用<code>git push origin <branch-name></code>推送自己的修改;</li>
<li>如果推送失败,则因为远程分支比本地更新,需要先用<code>git pull</code>试图合并;</li>
<li>如果合并有冲突,则解决冲突,并在本地提交;</li>
<li>没有冲突或者解决冲突后,再用<code>git push origin <branch-name></code>推送就能成功!</li>
</ol>
<p>如果<code>git pull</code>提示“no tracking infomation”,则说明本地分支和远程分支的链接关系没有创建,用命令<code>git branch --set-upstream <branch-name> origin/<branch-name></code>。</p>
<ul>
<li>查看远程库信息,使用<code>git remote -v</code>;</li>
<li>本地分支如果不推送到远程,对其他人就是不可见的;</li>
</ul>
<h2 id="标签管理">标签管理</h2>
<h3 id="创建标签">创建标签</h3>
<ul>
<li>命令<code>git tag <name></code>用于新建一个标签,默认为<code>HEAD</code>,也可以指定一个commit id;</li>
<li><code>git tag -a <tagname> -m "balabala..."</code>可以指定标签信息;</li>
<li><code>git tag -s <tagname> -m "balabala..."</code>可以用PGP签名标签;</li>
<li>命令<code>git tag</code>可以查看所有标签。</li>
</ul>
<h3 id="操作标签">操作标签</h3>
<ul>
<li>命令<code>git push origin <tagname></code>可以推送一个本地标签;</li>
<li>命令<code>git push origin --tags</code>可以推送全部未推送过的标签;</li>
<li>命令<code>git tag -d <tagname></code>可以删除一个本地标签;</li>
<li>命令<code>git push origin :refs/tags/<tagname></code>可以删除一个远程标签;</li>
</ul>
<h2 id="使用Github">使用Github</h2>
<ul>
<li>在Github上,可以任意Fork开源仓库;</li>
<li>自己拥有Fork后的仓库的读写权限;</li>
<li>可以推送pull request给官方仓库来贡献代码。</li>
</ul>
<h2 id="使用码云">使用码云</h2>
<ul>
<li>码云和Github的使用方法差不多,具体参考上一节<strong>使用Github</strong>;</li>
<li>因为众所周知的原因Github访问速度比较感人,But码云不会,并且是国内的;</li>
<li>有免费的私有仓库,And支持中文,简直是我等英语渣渣的福音;</li>
</ul>
<p>本地库与多个远程库关联,嗯,不想说了,直接看廖雪峰<a href="https://www.liaoxuefeng.com/wiki/0013739516305929606dd18361248578c67b8067c8c017b000/00137583770360579bc4b458f044ce7afed3df579123eca000">Git教程</a>这一节吧。</p>
<h2 id="自定义Git">自定义Git</h2>
<h3 id="忽略特殊文件">忽略特殊文件</h3>
<ul>
<li>编写<code>.gitignore</code>文件,把要忽略的文件名填进去,Git就会自动忽略这些文件。<a href="https://github.com/github/gitignore">https://github.com/github/gitignore</a> 上的稍微改一下就可以用了</li>
<li><code>.gitignore</code>文件本身要放到版本库里,并且可以对<code>.gitignore</code>做版本管理</li>
</ul>
<p>忽略文件的原则:</p>
<ol>
<li>忽略操作系统自动生成的文件,比如缩略图等;</li>
<li>忽略编译生成的中间文件、可执行文件等;</li>
<li>忽略带有自己敏感信息的配置文件;</li>
</ol>
<h3 id="配置别名">配置别名</h3>
<ul>
<li><code>git config --global alias.st status</code>表示用<code>st</code>作为<code>status</code>的别名,以后敲<code>git st</code>就可以了</li>
</ul>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">git config --global alias.co checkout</span><br><span class="line">git config --global alias.ci commit</span><br><span class="line">git config --global alias.br brach</span><br><span class="line">git config --global alias.last <span class="string">'log -1'</span> //用`git last`显示最后一次提的交</span><br><span class="line">git config --global alias.lg <span class="string">"log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit"</span> //查看提交历史,丧心病狂版</span><br></pre></td></tr></table></figure>
<h2 id="搭建Git服务器">搭建Git服务器</h2>
<ul>
<li>
<p>搭建Git服务器,参考<a href="https://github.com/sitaramc/gitolite">教程原文</a>;</p>
</li>
<li>
<p>要方便管理公钥,用<a href="https://github.com/sitaramc/gitolite">Gitosis</a>;</p>
</li>
<li>
<p>要像SVN那样变态控制权限,用 Gitolite;</p>
</li>
</ul>
]]></content>
<categories>
<category>技术</category>
</categories>
<tags>
<tag>Git</tag>
</tags>
</entry>
<entry>
<title>Java问题排查</title>
<url>/posts/java-troubleshooting/</url>
<content><![CDATA[<h2 id="排查业务问题">排查业务问题</h2>
<ol>
<li>采用<code>tail -fn 200 log_file </code>实时查询线上日志</li>
<li>找准日志搜索关键字 keyWord,例如 orderid、mobileId、reqId 等</li>
<li>采用<code>grep keyWord log_file</code>查询关键字所在行的日志</li>
<li>采用 <code>grep -C n keyWord log_file</code>匹配关键字所在行的上下 n 行</li>
<li>采用<code>grep keyWord log_file | wc -l</code>匹配关键字的行数有多少</li>
<li>根据实际排查日志场景进行搜索 tail、grep 用的最多</li>
</ol>
<h2 id="线上应用-CPU-占用过高">线上应用 CPU 占用过高</h2>
<ol>
<li>采用<code>top</code>命令,找出 CPU 占用最高的 PID</li>
<li>通过<code>ps -ef | grep PID</code>查看对应的应用</li>
<li>采用<code>jstack -l PID >> PID.log</code>获取进程额堆栈信息</li>
<li>采用<code>ps -mp PID -o THREAD,tid,time</code>拿到占用 CPU 最高的 tid</li>
<li>采用<code>printf “%x\n” tid</code>获取 16 进制的线程 TID</li>
<li>采用<code>grep TID -A20 PID.log</code>确定是线程哪里除了问题</li>
<li>腿疼医腿,辩证施治,对症下药,找准代码位置,进行调整代码</li>
</ol>
<h2 id="线上应用内存溢出">线上应用内存溢出</h2>
<ol>
<li>
<p>采用<code>top</code>命令,找出对应的 PID</p>
</li>
<li>
<p>采用<code>jmap -heap PID</code>确认一下分配的内存少不少</p>
</li>
<li>
<p>采用<code>jmap -histo:live PID | more</code>找出分析最耗内存的对象</p>
</li>
<li>
<p>采用<code>ps -efL | grep PID | wc -l</code>查看进程创建的线程数</p>
</li>
<li>
<p>采用<code>ls -l /proc/PID/task | wc -l</code>也可以查看进程创建的线程数</p>
</li>
<li>
<p>采用<code>netstat -apn | grep 4532 | wc -l</code>查看进程网络连接数</p>
</li>
<li>
<p>腿疼医腿,辩证施治,对症下药</p>
<p>a. 内存分配确实小,适当调整内存;</p>
<p>b. 对象被频繁创建,且不释放,优化代码;</p>
<p>c. 不断创建线程或者不断进行网络连接,优化代码。</p>
</li>
</ol>
<h2 id="工具">工具</h2>
<h3 id="JDK-自带工具">JDK 自带工具</h3>
<table>
<thead>
<tr>
<th>命令</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>jps</td>
<td>查看 java 进程概述</td>
</tr>
<tr>
<td>jconsole</td>
<td>图形化查看内存线程 VM 参数等信息</td>
</tr>
<tr>
<td>jstat</td>
<td>查看内存使用状况</td>
</tr>
<tr>
<td>jstack</td>
<td>分析线程</td>
</tr>
<tr>
<td>jmap</td>
<td>查看内存信息或 dump 下内存详情</td>
</tr>
<tr>
<td>jvisualvm</td>
<td>图形化工具,功能在 jconsole 之上</td>
</tr>
</tbody>
</table>
<h3 id="第三方工具">第三方工具</h3>
<p>Arthas,官网 <a href="https://arthas.aliyun.com">https://arthas.aliyun.com</a>,功能强大,常用命令:</p>
<table>
<thead>
<tr>
<th>命令</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>dump</td>
<td>将已加载的字节码文件保存到特定的目录中</td>
</tr>
<tr>
<td>classloader</td>
<td>获取类加载器的信息</td>
</tr>
<tr>
<td>monitor</td>
<td>监控指定类种方法的执行情况</td>
</tr>
<tr>
<td>watch</td>
<td>观察指定方法的调用情况</td>
</tr>
<tr>
<td>trace</td>
<td>对方法内部调用路径进行追踪,并输出方法路径上每个节点上耗时</td>
</tr>
<tr>
<td>stack</td>
<td>输出当前方法被调用的路径</td>
</tr>
<tr>
<td>tt</td>
<td>记录指定方法每次调用的入参和返回的信息</td>
</tr>
<tr>
<td>options</td>
<td>全局开关</td>
</tr>
<tr>
<td>profiler</td>
<td>生成火焰图</td>
</tr>
</tbody>
</table>
<h2 id="性能问题排查思路">性能问题排查思路</h2>
<ol>
<li>设计调优:一个良好的系统设计可以规避很多钱在的性能问题。必须熟悉常用的软件设计方法、设计模式、基本性能组件和常用优化思想。</li>
<li>代码调优:掌握编码技巧、对算法、数据结构的灵活使用。</li>
<li>JVM 调优:JVM 的各项参数会直接影响 Java 程序的性能。</li>
<li>数据库调优:在应用层对 SQL 语句进行优化;对数据库进行优化。</li>
<li>操作系统调优:操作系统的性能对应用系统也有较大影响。</li>
<li>腿疼医腿,辩证施治,对症下药。</li>
</ol>
]]></content>
<categories>
<category>技术</category>
</categories>
<tags>
<tag>Java</tag>
</tags>
</entry>
<entry>
<title>Linux上的OpenVPN搭建</title>
<url>/posts/OpenVPN-setup/</url>
<content><![CDATA[<h2 id="1-环境">1. 环境</h2>
<h3 id="1-1-硬件和网络拓扑">1.1 硬件和网络拓扑</h3>
<p><img src="https://blog-1258671016.cos.ap-chengdu.myqcloud.com/image/1586794884591.png" alt="网络拓扑"></p>
<ul>
<li>
<p>OpenVPN Server:双网卡机器,同时连接大小网,提供 VPN 拨号服务。</p>
</li>
<li>
<p>OpenVPN Client:位于大网,通过拨号,获得小网地址后,可以和小网 PC 互相访问。</p>
</li>
<li>
<p>小网 PC:位于小网(如 192.168 网段)</p>
<blockquote>
<p><strong>注意</strong>:以下 OpenVPN Server 网络配置相关信息仅供参考,并非真实信息。</p>
<p>IP1:10.185.80.57 子网掩码1:255.255.254.0<br>
IP2:192.168.18.6 子网掩码2:255.255.240.0</p>
</blockquote>
</li>
</ul>
<h3 id="1-2-软件要求">1.2 软件要求</h3>
<p>操作系统:Centos 7<br>
第三方包:所有软件包均通过 yum 在线安装</p>
<h2 id="2-安装配置-OpenVPN-服务端">2.安装配置 OpenVPN 服务端</h2>
<h3 id="2-1-配置-yum-源">2.1 配置 yum 源</h3>
<p>由于 rpm 包安装需要的依赖关系可能比较繁杂,这里选用 yum 在线安装。</p>
<h4 id="2-1-1-配置-Centos-源">2.1.1 配置 Centos 源</h4>
<p>打开 <a href="https://mirrors.tuna.tsinghua.edu.cn/help/centos/">Centos 清华镜像源</a> ,选择 Centos 7 配置文件,直接使用如下内容覆盖掉<code>/etc/yum.repos.d/CentOS-Base.repo</code>文件:</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">[base]</span><br><span class="line">name=CentOS-$releasever - Base</span><br><span class="line">baseurl=https://mirrors.tuna.tsinghua.edu.cn/centos/$releasever/os/$basearch/</span><br><span class="line">#mirrorlist=http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=os</span><br><span class="line">enabled=1</span><br><span class="line">gpgcheck=1</span><br><span class="line">gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-7</span><br><span class="line"></span><br><span class="line">#released updates</span><br><span class="line">[updates]</span><br><span class="line">name=CentOS-$releasever - Updates</span><br><span class="line">baseurl=https://mirrors.tuna.tsinghua.edu.cn/centos/$releasever/updates/$basearch/</span><br><span class="line">#mirrorlist=http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=updates</span><br><span class="line">enabled=1</span><br><span class="line">gpgcheck=1</span><br><span class="line">gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-7</span><br><span class="line"></span><br><span class="line">#additional packages that may be useful</span><br><span class="line">[extras]</span><br><span class="line">name=CentOS-$releasever - Extras</span><br><span class="line">baseurl=https://mirrors.tuna.tsinghua.edu.cn/centos/$releasever/extras/$basearch/</span><br><span class="line">#mirrorlist=http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=extras</span><br><span class="line">enabled=1</span><br><span class="line">gpgcheck=1</span><br><span class="line">gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-7</span><br><span class="line"></span><br><span class="line">#additional packages that extend functionality of existing packages</span><br><span class="line">[centosplus]</span><br><span class="line">name=CentOS-$releasever - Plus</span><br><span class="line">baseurl=https://mirrors.tuna.tsinghua.edu.cn/centos/$releasever/centosplus/$basearch/</span><br><span class="line">#mirrorlist=http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=centosplus</span><br><span class="line">gpgcheck=1</span><br><span class="line">enabled=0</span><br><span class="line">gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-7</span><br></pre></td></tr></table></figure>
<p>然后缓存元数据。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">yum makecache</span><br></pre></td></tr></table></figure>
<h4 id="2-1-2-配置-EPEL-源">2.1.2 配置 EPEL 源</h4>
<p>openvpn 在 EPEL 仓库中,所以需要配置 EPEL 源。这里参考<a href="https://mirrors.tuna.tsinghua.edu.cn/help/epel/">清华镜像源epel帮助</a>,在<code>/etc/yum.repos.d</code>目录下增加“epel.repo”文件,内容如下:</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">[epel]</span><br><span class="line">name=Extra Packages for Enterprise Linux 7 - $basearch</span><br><span class="line">baseurl=https://mirrors.tuna.tsinghua.edu.cn/epel/7/$basearch</span><br><span class="line">#mirrorlist=https://mirrors.fedoraproject.org/metalink?repo=epel-7&arch=$basearch</span><br><span class="line">failovermethod=priority</span><br><span class="line">enabled=1</span><br><span class="line">gpgcheck=1</span><br><span class="line">gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-7</span><br><span class="line"></span><br><span class="line">[epel-debuginfo]</span><br><span class="line">name=Extra Packages for Enterprise Linux 7 - $basearch - Debug</span><br><span class="line">baseurl=https://mirrors.tuna.tsinghua.edu.cn/epel/7/$basearch/debug</span><br><span class="line">#mirrorlist=https://mirrors.fedoraproject.org/metalink?repo=epel-debug-7&arch=$basearch</span><br><span class="line">failovermethod=priority</span><br><span class="line">enabled=0</span><br><span class="line">gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-7</span><br><span class="line">gpgcheck=1</span><br><span class="line"></span><br><span class="line">[epel-source]</span><br><span class="line">name=Extra Packages for Enterprise Linux 7 - $basearch - Source</span><br><span class="line">baseurl=https://mirrors.tuna.tsinghua.edu.cn/epel/7/SRPMS</span><br><span class="line">#mirrorlist=https://mirrors.fedoraproject.org/metalink?repo=epel-source-7&arch=$basearch</span><br><span class="line">failovermethod=priority</span><br><span class="line">enabled=0</span><br><span class="line">gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-7</span><br><span class="line">gpgcheck=1</span><br></pre></td></tr></table></figure>
<p>然后执行下面的命令,缓存元数据。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">yum makecache</span><br></pre></td></tr></table></figure>
<h3 id="2-2-安装相关软件包">2.2 安装相关软件包</h3>
<p>执行如下命令,一次性完成安装</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">yum -y install lzo openssl easy-rsa openvpn</span><br></pre></td></tr></table></figure>
<p>安装后,easy-rsa 的相关文件存放于<code>/usr/share/easy-rsa/3.0.3</code>和<code>/usr/share/doc/easy-rsa-3.0.3</code>两个路径下,需要把 easy-rsa 的相关文件放到一个目录,以便后面的操作, 这里我放到<code>/root/EasyRSA-3.0.3/</code>目录。操作如下</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">mkdir /root/EasyRSA-3.0.3</span><br><span class="line">cp -a /usr/share/easy-rsa/3.0.3/* /root/EasyRSA-3.0.3</span><br><span class="line">cp -a /usr/share/doc/easy-rsa-3.0.3/* /root/EasyRSA-3.0.3</span><br></pre></td></tr></table></figure>
<p>操作完成后,目录中的内容如下</p>
<p><img src="https://blog-1258671016.cos.ap-chengdu.myqcloud.com/image/1586795038260.png" alt="easy-rsa的相关文件"></p>
<h3 id="2-3-制作-server-端证书">2.3 制作 server 端证书</h3>
<p>制作证书需要一个目录,(我这里以<code>/etc/openvpn/</code>目录为 server 端根目录),执行</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">cp /root/EasyRSA-3.0.3 /etc/openvpn/ # 复制一份给server端,用于生成客户端证书</span><br><span class="line">cd /etc/openvpn/EasyRSA-3.0.3</span><br><span class="line">cp vars.example vars # 复制一份样例,准备修改</span><br><span class="line">vim vars # 修改样例</span><br></pre></td></tr></table></figure>
<p>修改下 EasyRSA-3.0.3 目录下的 vars 这个文件,配置下环境变量,这里配置了主要是方便在后面制作证书的过程中不用输入很多信息,比较方便,我这里的配置如下图,业务可根据需求自行配置。</p>
<p><img src="https://blog-1258671016.cos.ap-chengdu.myqcloud.com/image/1586795146203.png" alt="vars编辑内容"></p>
<p>vars 这个文件修改完毕之后,需要 source 一下,再把证书系统初始化,操作如下</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">source vars # 把var里面的值写入到环境变量中去</span><br><span class="line">./easyrsa init-pki # 初始化证书生成环境</span><br></pre></td></tr></table></figure>
<p><img src="https://blog-1258671016.cos.ap-chengdu.myqcloud.com/image/1586795181625.png" alt="初始化证书系统"></p>
<p>下一步就是制作 CA 证书了,如下:</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">./easyrsa build-ca # 制作ca证书</span><br></pre></td></tr></table></figure>
<p><img src="https://blog-1258671016.cos.ap-chengdu.myqcloud.com/image/1586795209619.png" alt="CA证书制作"></p>
<p>这里会要求出入一个 CA 证书的密码,可以不用输入,我这里输入的 CA 密码为 123456,同时设置 CA 的 Common Name 为 puma。</p>
<p><strong>注意:这里CA的这个密码 123456 要记住,因为后续进行 server 证书和 client 证书认证的时候都需要输入这个密码的。如果忘记,需用重新制作根证书。</strong></p>
<p>下一步就是生成server端的证书了,执行:</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">./easyrsa gen-req server nopass # 生成server端证书,需要输入server端的CommonName,可以自行设置</span><br></pre></td></tr></table></figure>
<p><img src="https://blog-1258671016.cos.ap-chengdu.myqcloud.com/image/1586799570954.png" alt="生成server端证书"></p>
<p>我这里配置的服务器的名字为 server,服务器证书的 Common Name 为 puma_server</p>
<p>接下来需要对服务器端的证书进行签名认证</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">./easyrsa sign server server # 认证server端证书,需要前面设置的 CA 密码</span><br></pre></td></tr></table></figure>
<p><strong>注意:认证证书时需要输入CA的密码</strong>,我这里输入 123456,可以看到,认证成功了。</p>
<p><img src="https://blog-1258671016.cos.ap-chengdu.myqcloud.com/image/1586795264163.png" alt="签名认证server端证书"></p>
<p>最后一步就是生成 Diffie-Hellman 文件了,如下:</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">./easyrsa gen-dh # 生成 dh 证书</span><br></pre></td></tr></table></figure>
<p><img src="https://blog-1258671016.cos.ap-chengdu.myqcloud.com/image/1586795296654.png" alt="生成dh证书"></p>
<p><img src="https://blog-1258671016.cos.ap-chengdu.myqcloud.com/image/1586795322693.png" alt="dh证书生成成功"></p>
<p>完成后如上图。到这里,server 端的证书和根证书就都生成了,我们把它们拷贝到一个目录下,方便后续 openvpn 的配置文件进行配置。这里把需要用到的证书都拷贝到 <code>/etc/openvpn/server</code> 目录下,操作如下:</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">cd /etc/openvpn/EasyRSA-3.0.3</span><br><span class="line">cp pki/ca.crt ../server/</span><br><span class="line">cp pki/issued/server.crt ../server/</span><br><span class="line">cp pki/private/server.key ../server/</span><br><span class="line">cp pki/dh.pem ../server/</span><br><span class="line">mv /etc/openvpn/server/dh.pem ../server/dh2048.pem # 根据vars文件中的设置,改名为dh2048.pem</span><br></pre></td></tr></table></figure>
<p>执行完成后,<code>/etc/openvpn/server</code>目录下应该有4个文件</p>
<p><img src="https://blog-1258671016.cos.ap-chengdu.myqcloud.com/image/1586795375667.png" alt="server目录下的文件"></p>
<h3 id="2-4-配置-server-端配置文件">2.4 配置 server 端配置文件</h3>
<p>执行如下命令写配置文件</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">cd /etc/openvpn/server/</span><br><span class="line">vim server.conf # 创建 server 端配置文件并编辑,文件内容如下</span><br></pre></td></tr></table></figure>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># 强烈建议配置文件中用绝对路径</span><br><span class="line">port 1194 # 监听1194端口</span><br><span class="line">proto udp # 使用udp协议</span><br><span class="line">dev tun # 通信隧道类型为路由IP隧道</span><br><span class="line">ca /etc/openvpn/server/ca.crt</span><br><span class="line">cert /etc/openvpn/server/server.crt</span><br><span class="line">key /etc/openvpn/server/server.key</span><br><span class="line">dh /etc/openvpn/server/dh2048.pem # 2048位密钥</span><br><span class="line">server 10.9.0.0 255.255.255.0 # 分配的ip地址区间</span><br><span class="line">ifconfig-pool-persist /etc/openvpn/server/ipp.txt</span><br><span class="line">push "route 192.168.16.0 255.255.240.0" # 给客户端推送路由信息</span><br><span class="line">client-to-client # 客户端之间可以互相发现</span><br><span class="line">duplicate-cn # 开启证书复用</span><br><span class="line">keepalive 10 120</span><br><span class="line">comp-lzo # 启用lzo压缩算法</span><br><span class="line">persist-key</span><br><span class="line">persist-tun</span><br><span class="line">status /etc/openvpn/server/openvpn-status.log</span><br><span class="line">verb 3 # 设置日志文件的冗余级别</span><br><span class="line">explicit-exit-notify 1 # 设置服务器重启时通知客户端,自动重新连接</span><br><span class="line">auth-user-pass-verify /etc/openvpn/server/checkpsw.sh via-env # 服务端认证</span><br><span class="line">;client-cert-not-required # 不要求客户端证书</span><br><span class="line">verify-client-cert # 要求客户端证书</span><br><span class="line">username-as-common-name</span><br><span class="line">script-security 3 # 安全等级 3</span><br></pre></td></tr></table></figure>
<p>上面的配置中我们把用户名密码校验交给了<code>checkpsw.sh</code>,现在写一个校验脚本</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">cd /etc/openvpn/server/</span><br><span class="line">vim checkpsw.sh # 创建校验脚本,文件内容如下</span><br></pre></td></tr></table></figure>
<figure class="highlight sh"><table><tr><td class="code"><pre><span class="line"><span class="meta">#!/bin/sh</span></span><br><span class="line"><span class="comment">###########################################################</span></span><br><span class="line">PASSFILE=<span class="string">"/etc/openvpn/server/psw-file"</span></span><br><span class="line">LOG_FILE=<span class="string">"/etc/openvpn/server/openvpn-password.log"</span></span><br><span class="line">TIME_STAMP=`<span class="built_in">date</span> <span class="string">"+%Y-%m-%d %T"</span>`</span><br><span class="line"><span class="comment">###########################################################</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> [ ! -r <span class="string">"<span class="variable">${PASSFILE}</span>"</span> ]; <span class="keyword">then</span></span><br><span class="line"> <span class="built_in">echo</span> <span class="string">"<span class="variable">${TIME_STAMP}</span>: Could not open password file \"<span class="variable">${PASSFILE}</span>\" for reading."</span> >> <span class="variable">${LOG_FILE}</span></span><br><span class="line"> <span class="built_in">exit</span> 1</span><br><span class="line"><span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line">CORRECT_PASSWORD=`awk <span class="string">'!/^;/&&!/^#/&&$1=="'</span><span class="variable">${username}</span><span class="string">'"{print $2;exit}'</span> <span class="variable">${PASSFILE}</span>`</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> [ <span class="string">"<span class="variable">${CORRECT_PASSWORD}</span>"</span> = <span class="string">""</span> ]; <span class="keyword">then</span></span><br><span class="line"> <span class="built_in">echo</span> <span class="string">"<span class="variable">${TIME_STAMP}</span>: User does not exist: username=\"<span class="variable">${username}</span>\", password=\"<span class="variable">${password}</span>\"."</span> >> <span class="variable">${LOG_FILE}</span></span><br><span class="line"> <span class="built_in">exit</span> 1</span><br><span class="line"><span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> [ <span class="string">"<span class="variable">${password}</span>"</span> = <span class="string">"<span class="variable">${CORRECT_PASSWORD}</span>"</span> ]; <span class="keyword">then</span></span><br><span class="line"> <span class="built_in">echo</span> <span class="string">"<span class="variable">${TIME_STAMP}</span>: Successful authentication: username=\"<span class="variable">${username}</span>\"."</span> >> <span class="variable">${LOG_FILE}</span></span><br><span class="line"> <span class="built_in">exit</span> 0</span><br><span class="line"><span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">echo</span> <span class="string">"<span class="variable">${TIME_STAMP}</span>: Incorrect password: username=\"<span class="variable">${username}</span>\", password=\"<span class="variable">${password}</span>\"."</span> >> <span class="variable">${LOG_FILE}</span></span><br><span class="line"><span class="built_in">exit</span> 1</span><br></pre></td></tr></table></figure>
<p>保存后,给脚本加上可执行权限。接着编写 psw-file 文件,其中保存着已授权的用户名和密码。</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">chmod +x checkpsw.sh</span><br><span class="line">vim psw-file # 编写psw-file文件,内容如下</span><br></pre></td></tr></table></figure>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">用户名1 密码1</span><br><span class="line">用户名2 密码2</span><br></pre></td></tr></table></figure>
<p>执行如下命令配置 iptables 的 NAT 转发</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">iptables -t nat -A POSTROUTING -j MASQUERADE</span><br></pre></td></tr></table></figure>
<p>在 openvpn-server 机器上,编辑<code>/etc/sysctl.conf</code>文件,加入<code>net.ipv4.ip_forward=1</code>到该文件中并保存,开启操作系统的网关转发功能,操作如下:</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">vim /etc/sysctl.conf</span><br></pre></td></tr></table></figure>
<p><img src="https://blog-1258671016.cos.ap-chengdu.myqcloud.com/image/1586795411956.png" alt="启用系统的网关转发功能"></p>
<p>修改完成并保存后就运行如下命令:</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">sysctl -p # 使 /etc/sysctl.conf 配置文件里面的配置生效</span><br></pre></td></tr></table></figure>
<h3 id="2-5-启动-server-端">2.5 启动 server 端</h3>
<p>执行如下命令启动 openvpn 服务端,如图,表示启动成功。</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">openvpn --config /etc/openvpn/server/server.conf</span><br></pre></td></tr></table></figure>
<p><img src="https://blog-1258671016.cos.ap-chengdu.myqcloud.com/image/1586795443042.png" alt="server端启动成功"></p>
<blockquote>
<p>注意:OpenVPN 启动后会占用终端,按<code>Ctrl+C</code>停止运行。如果想启动后不占用终端,可加上<code>--daemon</code>参数,如此,启动命令则为:<code>openvpn --daemon --config /etc/openvpn/server/server.conf</code></p>
</blockquote>
<h3 id="2-6-设置-OpenVPN-开机自启(可选)">2.6 设置 OpenVPN 开机自启(可选)</h3>
<p>服务端的安装配置到上一节就完了,为了避免服务器重启后需要手动开启 OpenVPN,这里将其设置为开机自启动,请根据需要选择。</p>
<p>进入到 <code>/etc/rc.d/</code>目录下,编辑 rc.local 文件,操作命令如下:</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">cd /etc/rc.d/</span><br><span class="line">vim rc.local</span><br></pre></td></tr></table></figure>
<p>在 rc.local 文件中新增下列内容</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">OpenVPN start</span></span><br><span class="line">openvpn --daemon --config /etc/openvpn/server/server.conf > /dev/null</span><br><span class="line">if [ $? -ne 0 ]</span><br><span class="line">then</span><br><span class="line"> echo "`date "+%Y-%m-%d %H:%M:%S"` start vpn failed in /etc/rc.d/rc.local" >> /etc/openvpn/server/openvpn-status.log</span><br><span class="line">fi</span><br></pre></td></tr></table></figure>
<p>保存后,修改系统时区和时间,使打印到日志中的时间和本地计算机的时间同步,执行操作:</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">timedatectl set-timezone Asia/Shanghai # 设置时区为上海</span><br><span class="line">date -s 10:23:30 # 设置为自己本机时间</span><br></pre></td></tr></table></figure>
<h2 id="3-安装配置-OpenVPN-的客户端">3. 安装配置 OpenVPN 的客户端</h2>
<h3 id="3-1-制作-client-端证书">3.1 制作 client 端证书</h3>
<p>client 端证书的制作过程和 server 端的相似,要注意 client 端制作证书的目录和 server 端并非同一个目录。我用来给client 端制作证书的目录:<code>/root/EasyRSA-3.0.3</code>,给server 端则是:<code>/etc/openvpn/EasyRSA-3.0.3</code>。</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">cd /root/EasyRSA-3.0.3/ # 进入客户端证书制作目录</span><br><span class="line">./easyrsa init-pki # 初始化客户端证书制作目录</span><br></pre></td></tr></table></figure>
<p><img src="https://blog-1258671016.cos.ap-chengdu.myqcloud.com/image/1586795473382.png" alt="client端证书制作目录初始化"></p>
<p>创建客户端证书,下面是创建成功的截图,client 的名字为 client_xushan,CommonName 为 puma_client_xushan</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">./easyrsa gen-req client_xushan nopass # 生成client端证书,需要输入client端的CommonName,可自行设置,可设置密码</span><br></pre></td></tr></table></figure>
<p><img src="https://blog-1258671016.cos.ap-chengdu.myqcloud.com/image/1586795510577.png" alt="生成client端证书"></p>
<p>创建成功后,会在<code>pki/reqs/</code>目录下生成一个名为 client_xushan.req 的文件。</p>
<p>下一步就是在 server 端所在的目录里把上一步生成的 client_xushan.req 这个证书进行签约。</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">cd /etc/openvpn/EasyRSA-3.0.3</span><br><span class="line">./easyrsa import-req /root/EasyRSA-3.0.3/pki/reqs/client_xushan.req client_xushan #导入客户端证书</span><br></pre></td></tr></table></figure>
<p><img src="https://blog-1258671016.cos.ap-chengdu.myqcloud.com/image/1586795536171.png" alt="导入client端证书"></p>
<p>客户端证书签约完成后,还需要进行最后一步的认证,认证之后就可以使用了,过程如下:</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">./easyrsa sign client client_xushan # 认证客户端证书</span><br></pre></td></tr></table></figure>
<p><img src="https://blog-1258671016.cos.ap-chengdu.myqcloud.com/image/1586795578907.png" alt="认证client端证书"></p>
<p>到这里,客户端的证书就生成完成了,我们需要把他们拷贝到一个文件夹下,方便后续的使用,如下:</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">把客户端需要的文件统一放到 /etc/openvpn/client 下</span></span><br><span class="line">cp pki/ca.crt ../client</span><br><span class="line">cp pki/issued/client_xushan.crt ../client</span><br><span class="line">cp /root/EasyRSA-3.0.3/pki/private/client_xushan.key ../client</span><br></pre></td></tr></table></figure>
<p>至此,client 目录下有3个文件<code>ca.crt</code>、<code>client_xushan.crt</code>、<code>client_xushan.key</code>,将 client 目录下载到本地(windows)。</p>
<h3 id="3-2-client-端安装">3.2 client 端安装</h3>
<p>客户端一般是 windows,组件全选安装。安装后桌面会有<code>OpenVPN GUI</code>的快捷方式。<img src="https://blog-1258671016.cos.ap-chengdu.myqcloud.com/image/1586795604321.png" alt="icon"></p>
<h3 id="3-3-制作-client-端配置文件">3.3 制作 client 端配置文件</h3>
<p>在本地(windows)创建 .ovpn的配置文件,名字随便起,这里我的是<code>10.185.80.57.ovpn</code>,配置信息:</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">client # 表示当前是客户端</span><br><span class="line">dev tun # tun 模式(路由模式)</span><br><span class="line">proto udp # 使用 udp 协议</span><br><span class="line">remote 10.185.80.57 1194 # 服务器端的ip和端口</span><br><span class="line">resolv-retry infinite # 断后尝试重连</span><br><span class="line">nobind</span><br><span class="line">persist-key</span><br><span class="line">persist-tun</span><br><span class="line">ca ca.crt</span><br><span class="line">cert client.crt</span><br><span class="line">key client.key</span><br><span class="line">auth-user-pass # 密码认证</span><br><span class="line">remote-cert-tls server</span><br><span class="line">comp-lzo # 启用lzo压缩算法,与server端保持一致</span><br><span class="line">verb 3</span><br></pre></td></tr></table></figure>
<p>根据配置文件,修改<code>client_xushan.crt</code>文件名为<code>client.crt</code>、修改<code>client_xushan.key</code>文件名为<code>client.key</code>保存后,放到刚下载的 client 文件夹下,把 client 文件夹改名成<code>10.185.80.57</code>。将整个文件夹放在 OpenVPN的安装目录下的 config 文件夹下,启动客户端,选择当前配置,输入用户名和密码后会连接上(必须在 server 端中已授权),如图,连接成功</p>
<p><img src="https://blog-1258671016.cos.ap-chengdu.myqcloud.com/image/1586795642344.png" alt="客户端连接"></p>
<p><img src="https://blog-1258671016.cos.ap-chengdu.myqcloud.com/image/1586795685665.png" alt="客户端分配的ip"></p>
<p>【可选配置】</p>
<p>如不想经常输入用户名和密码,可打开客户端ovpn配置文件,将<code>auth-user-pass</code>改成<code>auth-user-pass pass.txt</code>。同时在相同路径下新增一个名为<code>pass.txt</code>的文本文件。<br>
内容为两行,第一行为 VPN 用户名,第二行为密码。<br>
重新运行 OpenVPN 客户端,即可不输入密码登录 VPN 了!</p>
<hr>
<blockquote>
<p>参考:</p>
<ol>
<li>完整CentOS搭建OpenVPN服务环境图文教程.老左博客</li>
</ol>
</blockquote>
]]></content>
<categories>
<category>技术</category>
</categories>
<tags>
<tag>OpenVPN</tag>
</tags>
</entry>
<entry>
<title>冯玉军:俄国历史文化与当代发展</title>
<url>/posts/russian-history-culture-and-contemporary-development/</url>
<content><![CDATA[<p>俄罗斯是中国的一面镜子,20世纪20年代以来,俄罗斯对中国产生了深入骨髓的全方位影响。深入研究俄国历史,对于理解中国自己也至关重要。</p>
<h2 id="中俄关系">中俄关系</h2>
<p>了解俄罗斯,不妨从和大家切身相关的中俄关系入手。</p>
<p>1850年,林则徐就感叹俄国“将来必为大患”。据不完全统计,自1860年至1945年,俄国在85年间让中国丧失了325万平方公里的土地。近代以来,中国在国家安全方面所遭受的最大威胁也来源于俄罗斯。</p>
<p>中俄历史上三次结盟,每一次都以中国付出惨重代价,俄国获得天大好处而结束。第一次是甲午战争后,李鸿章前往俄国祝贺末代沙皇尼古拉二世登基,签订了《中俄密约》。俄国表面上承诺联合中国,共修中东铁路以抗衡日本,实际上是借修建铁路之名,将俄国势力在整个东北如洪水一般铺开。第二次结盟是抗战结束后,民国政府和苏联签订《中苏友好同盟条约》,让刚刚以八年浴血抗战赶走日本人的中国,被迫承认了苏联在外蒙古策划的“独立公投”,从而丧失了近160万平方公里的国土。第三次就是1949年以后,中苏同盟“一边倒”。“一边倒”虽然让中国获得了苏联的支持,但也对中国产生了两个至关重要的影响。第一,“一边倒”“打扫干净屋子再请客”把欧美势力完全驱除出中国,让中国丧失了一个与世界体系融合在一起的机会,直至1979年,中国都与主流国际社会相脱离。第二,苏联把中国推进了三年朝鲜战争。这不仅让中国失去了众多生命,还让中美关系极度恶化,使中国在几十年间都难以繁荣。</p>
<p>十月革命之后,新生的苏维埃政权处于内外交困的境地。为缓解自身困境,俄国人在1919年成立了共产国际,并借其不断推进“世界革命”。现代中国也由此与俄国有了两处重要交集。第一个是国民党的改组。孙中山先生确定了联俄方针,国民党改组基本上是在俄国人的指导之下进行的。第二个是1921年中国共产党的建立在共产国际的直接指导下完成。中共作为共产国际的一个支部,其对中国革命制定的所有方针曾一度基本上听从共产国际的指示。在上世纪20年代到50年代的整整30年间,中国政治舞台上的一切重大事件无一没有莫斯科的影响。建国后,我们的政治体制、经济体制、军事文化、意识形态等等,完全按照苏联的模式建立。这一直影响着中国的现代化进程。当然从改革开放以后,我们在一定程度上走出了苏联模式。但是历史并非直线性发展,而是有可能第二次跨入同一条河流,普京执政以来,中俄之间的相互影响再次加深。</p>
<p>至于中俄之间的文化交流,基本上是俄国单方面向中国全方位的单向输入。有学者总结:“中国文学成了俄罗斯文学的学生,一度丧失了自尊、自信,拜服在北国巨大的石榴裙下。”更为重要的是,百多年来,俄国除了在制度、文化上对中国产生影响之外,还在相当大的程度上改变了中国人的思维方式,让中国文化当中的那些优秀的中庸和谐、天人合一的思想,变成了极端化、二元化的思维。</p>
<p>有人认为如今中俄关系处于历史上最好的时期,如何理解?我认为主要有两点:第一,经过40多年的改革开放,中国的综合国力已远超俄罗斯,使俄国人难以用原来那种高高在上的优势对中国进行各种各样的影响;第二,2001年签署的《中俄睦邻友好合作条约》中确定了“不结盟,不对抗,不针对第三方”的原则,成为中俄关系的“黄金定律”。守住了这条“黄金定律”,中俄关系就不会出现大的偏差。</p>
<p>今天的中俄关系面临着诸多挑战。第一,很多国人甚至精英都还没有清醒地意识到中俄综合国力出现了历史性的反转。尽管我们的国力已十倍于俄罗斯,但是很多人的思想仍臣服于它,这是最大的挑战。而俄国人对这种落差非常敏感,作为一个具有300年帝国外交传统的国家,俄罗斯怎么可能心甘情愿地接受身边的中国强势崛起?第二,外交谋略能力不对称。在外交层面上,我们基本上处于被俄国牵着鼻子走的状态,在很大程度上仍是一个言听计从的小兄弟。面对如此复杂的国际关系,跟俄罗斯的亲近,其实带来了很多意想不到的负面影响。我们一定要意识到俄国人对华的信息战、心理战打得如火如荼。第三,成本和收益不平衡。无论是经济上还是政治上,中国付出了太多,却没有收获预期结果,即没有改善中国的国际环境,也没有减轻美对华施加的战略压力。中美俄三角关系,永远对最弱的一方最有利。当年我们最弱,拉住美国抗衡苏联,对我们有利;今天俄国最弱,中美俄关系对俄有利,俄国成功调动中美矛盾,从中获利。我们既要和俄罗斯保持一个长期睦邻友好的合作伙伴关系,更要和美国保持一个建设性的伙伴关系,因为和美国的关系决定了中国未来的整体国际环境。</p>
<h2 id="俄罗斯的当代发展">俄罗斯的当代发展</h2>
<p>1999年12月31日,病夫治国的叶利钦把普京请到克里姆林宫,把俄罗斯的权杖交到了他的手上并对他说“珍重俄罗斯”。如今22年过去,普京不仅没有还给俄罗斯人一个奇迹般兴旺发达的俄罗斯,反而带给他们一个奇迹般衰败的俄罗斯。</p>
<p>普京执政20多年主要做了三件事。第一,政治上,重新建立垂直权力体系,即恢复中央集权制。俄罗斯1993年宪法中的多党制、联邦制等基本上已形同虚设。第二,经济上,实行寡头资本主义,贫富分化严重,中小企业发展困难。第三,外交上,试图重整帝俄河山,再当世界大国。苏联解体对俄国人而言是切肤之痛,普京渴望恢复小俄罗斯帝国。</p>
<p>俄罗斯在苏联解体后的30多年间,经历着非常可怕的去工业化。由于投资不足、设备老化、人员流失、既有供应链断裂等一系列问题,苏联曾经的工业体系已残破不堪。俄国基本上成了一个靠出卖自然资源过活的国家,在当前世界经济体系当中,仅能扮演卖油郎、卖炭翁的角色。随着俄乌战争暴露出俄军的诸多弱点,也让俄国军火难有市场。在这种情境下,俄罗斯的经济已经跌出了世界经济十强,在很大程度上成为了一个二流国家。</p>
<p>更为重要的是,俄乌战争对俄产生的负面影响远不止于此。目前乌克兰发起反攻,战争步入了消耗、僵持阶段。政治上,俄罗斯受到了大面积孤立,联合国大会三次投票以压倒性多数通过了强烈谴责俄国发动侵略战争的决定。经济上,俄罗斯受到了前所未有的经济制裁,3000多亿美元的海外资产被冻结,海外金融融资被限制,进出口被严格管制,1000多家跨国公司离开俄罗斯市场…历史上,对外战争的失败常常引起俄国国内颠覆性的变化。1856年克里米亚战争失败导致了1861年农奴制改革;1905年日俄战争的失败引发了1905年革命;第一次世界大战的失利又引发了二月革命和十月革命;阿富汗战争的失利也成为苏联解体的重要刺激性因素。今天这场战争也极可能成为俄罗斯历史发展的重大转折点。</p>
<p>中美之间的战略竞争,更多关注的是科技创新、金融能力,及对全球治理的影响,而俄罗斯人直到今天,内心最关切的仍然是抢夺地盘、占据领土、控制资源。这场战争不是普京一个人的战争,也是俄罗斯民族的战争,是俄罗斯思想当中的帝国“执念”建构出的战争。绝大多数俄罗斯人的观念里仍是陈旧思想,正是这些思想在当代条件下的重新发酵才导致了俄乌战争。当然,这场战争所带来的后果,也终将由俄罗斯整个国家和民族共同背负。</p>
<h2 id="历史进程与文明演化">历史进程与文明演化</h2>
<p>公元862年,俄罗斯民族出现了最早的国家——基辅罗斯。从基辅罗斯建立开始,俄国就受到了北部的维京文化、南边的拜占庭文化以及周边亚洲游牧民族的游牧文化的共同影响。</p>
<p>988年,基辅大公弗拉基米尔从拜占庭帝国接受了东正教,实施了罗斯受洗。这一方面让基辅罗斯成为了基督教世界的一部分,但更为主要的是让基辅罗斯接受了拜占庭帝国的政治文化——专制主义制度。</p>
<p>从1240年到1480年的240年间,俄国人都臣服于蒙古人的铁蹄之下。蒙古人的生活习惯、政治制度、宗教信仰都对俄罗斯产生了潜移默化的影响。“剥开一个俄国人,你就会发现一个鞑靼人。”20世纪初的俄国欧亚主义学派认为,俄国把大陆联合成整体的国家思想来源于蒙古人,真正的俄国历史是俄罗斯人和图兰人比邻而居的历史。如别尔嘉耶夫所说:“在俄罗斯的灵魂中,永远有东方和西方的两种因素在相互搏斗。”</p>
<p>综上所述,可以说拜占庭文化和蒙古文化是俄国文化两个最重要的根源。</p>
<p>俄国文化是独特的,俄国人在近代以来采取了一种非常有效的话术,将自己的形象塑造得神秘。丘吉尔就曾言:“俄国是一个秘密。”恩格斯也曾说:“俄国外交巧妙地蒙骗了欧洲的两大资产阶级党派。俄国外交,也只有这种外交,被容许同时既是正统派又是革命派,既是保守派又是自由派,既是传统派又是开明派。”普京这些年来也一直采用这种保守主义思想在国际上赢得支持。</p>
<p>上世纪20年代,美国历史学家玛丽·普拉特·帕米利便在《俄国简史》中指出俄国需要做的三件事:“一个拥有如此幅员的国家,一个拥有无以为计的国民财富,拥有最忠诚、最质朴和最勤勉的农民阶层,拥有深深的民族自豪感和强烈的爱国主义的民族,俄罗斯还需要什么呢?只有三件事情——摒弃残忍;塑造民族同质化;建立一个能够公正执法的政府。”然而一百年过去,俄国仍未完成这三个重要任务。</p>
<h2 id="政治文化">政治文化</h2>
<p>1832年,俄国国民教育大臣乌瓦罗夫总结了俄国官方意识形态的三个支柱——东正教、专制制度和人民性。东正教是俄国社会道德与文化发展的基础,是俄国自认为在精神上优于西欧的一个重要条件;专制制度是保证俄国政治稳定的基础;人民性是宗教性和专制性的结合,主要表现为人民对东正教的虔诚与对沙皇的忠诚。</p>
<p>俄国政治文化里有两个核心内涵。第一,国家主义。在俄国的国民意识深处,否认私有财产的神圣性,认为国家利益高于一切。第二,专制主义,这是其政治文化的核心。政治上,俄罗斯自莫斯科公国以后,完全确立了中央集权制度。经济上,国家君主操控国家经济命脉,没有贵族经济。社会层面,漠视个人权利。</p>
<p>从莫斯科公国开始,一直到后来的俄罗斯帝国,再到苏联,乃至当代俄罗斯,其政治文化本质上就是长期稳定的专制制度。16世纪,伊凡雷帝将传统的分封贵族变为服役贵族,让他们只能为国家服务。彼得大帝时,制定“官秩表”,官吏人数大增,形成了强大的官僚阶层,促使俄国的君主专制和官僚体系进一步结合。到了尼古拉一世时期,成立了第三办公厅政治警察机构,还让俄国成为欧洲专制制度的堡垒,建立了俄、普、奥三皇同盟,共同压制欧洲革命。即便经过亚历山大二世和尼古拉二世时期的改革,俄国在向西方学习的过程中,调整了行政体制、司法制度,但其沙皇完全专权的核心从未更张。</p>
<p>托克维尔在《旧制度与大革命》曾说,“对于一个坏政府来说,最危险的时刻通常就是它开始改革的时刻。”那么反过来讲,尽量延缓和压制改革就是掌权者最关心的问题。所以,俄国历史上每次现代化运动都被其后更进一步的反动所掩盖。彼得一世、叶卡捷琳娜二世改革后是一波反动。亚历山大二世的改革之后,又是尼古拉一世的一波反动。到了苏联时期,赫鲁晓夫的改革之后,又是勃列日涅夫的反动及其17年统治的长期停滞。这正是历史的复杂吊诡之处。</p>
<p>那么为什么中国在五四运动以后,最终没有选择欧美模式而是选择了俄国模式?我认为,这是因为中国历史上的皇权专制、官本位社会、小农经济、市场经济的不发达,及对于个人权利的漠视,对集体主义的强调、对大一统的执念等等,和俄国政治文化有诸多相似之处。在主动选择和外部输入的共同作用下,中国最终接受了一条俄国式的发展道路。</p>
<h2 id="经济模式">经济模式</h2>
<p>俄罗斯经济的基本特征有五个方面。</p>
<p>第一,垄断性。从莫斯科公国开始,俄国的大商业资本便占据经济主导地位。到了彼得一世,俄国开启工业现代化进程,政府采取人头税等措施,扶持大型工商业主。19世纪末20世纪初,工业迅速发展,俄国各大工业部门都诞生了大型垄断组织,重工业尤甚。这一是阻碍生产技术革新;二是破坏市场秩序;三是压榨工人剩余价值,使阶级矛盾逐步激化;四是官商之间剪不断理还乱的关系,导致利益集团绑架国家决策,很大程度上影响了俄国的近现代走向。政治问题有其经济根源,二者不可分割。</p>
<p>俄国经济当中另一个非常重要的特点便是重商主义,关税壁垒严重。18世纪开始,俄国和欧洲国家之间的贸易摩擦时常发生,导致贸易战。尽管今天俄国也加入了WTO,但仍然维持很高的关税。同时,俄国吸引外国投资的力度强,但对于外国资本的保护却非常脆弱。外国资本在发展后往往会成为被打击,破坏,甚至被征收的对象。</p>
<p>第二,强制性。国家机器对于经济压榨严重。基辅罗斯时期,俄国就实行“索贡巡行”。每年秋末冬初,大公率领亲兵到所属居民中挨家挨户征收毛皮、蜂蜜、蜂蜡等贡物。索贡的队伍所到之处,横征暴敛,肆无忌惮,抢劫财物,还虏掠人口,贩卖为奴。15至16世纪,在欧洲农奴制已趋于解体的背景下,俄罗斯反而发生农奴制的重新高涨,强制农奴劳动,成为俄罗斯经济最突出的特色。到了苏联时期,又有古拉格劳改营。从1929年到1953年,有1500万人被关押在全苏各地的古拉格劳改营中从事苦力劳动。而彼时的苏联人口也才1.6亿人,相当于总人口的十分之一。</p>
<p>第三,资源依赖性。从沙俄到苏联再到今天,俄国的农业仍是靠天吃饭的农业。除依赖土地外,16世纪至17世纪,俄国毛皮生意繁荣,成为俄国经济的重要支柱,也成为其不断向外扩张的刺激性因素。到了19世纪末和20世纪,随着工业对石油依赖度的不断上升,俄国又将油气作为赚取外汇的重要来源。</p>
<p>第四,结构单一性。这是苏联经济一个难以解决的长期问题。重工业过重,轻工业过轻。</p>
<p>第五,超越型发展。由于相较于欧洲,俄国是后发型国家,因此它总是试图跨越“卡夫丁峡谷”,实现赶超型发展。从彼得大帝改革开始,俄国始终用皮鞭和棍棒强迫民众努力去追赶先进的欧洲。俄国的现代化进程由国家主导,为了实现特定目标,集中全部资源投入特定领域,带来了消极影响。一方面,造成经济的畸形发展。通过压榨农民,以工农业之间的剪刀差,实现资本的原始积累。另一方面,压制具有自由思想的企业家与知识分子。现代化的目标应该锁定在最根本的人的思想的现代化。然而,后发国家往往把人作为工具,而不是实现国家现代化最根本的归宿点和出发点。</p>
<h2 id="社会思潮">社会思潮</h2>
<p>从988年罗斯受洗开始,东正教一直是影响俄国社会最深的思想力量。东正教有两个最重要的特点:一是教权对皇权的依附;二是教义的神秘主义和保守主义。</p>
<p>在西欧派看来,正是俄国接受了东正教这种基督教的东方形式,才使俄国与西方大家庭脱离。十二月党人起义后,俄国知识界的西欧派和斯拉夫派之间还围绕俄国发展道路展开了激烈的思想论战。争论的核心议题即俄国是要完全按照西方模式来实现整个社会的更新和再造,还是根据本国的国情,部分借鉴欧洲的经验,把俄国的东正教、村社等加以改造,成为俄罗斯未来发展的基础。西欧派主要代表人物恰达耶夫在其名著《哲学书简》中指出了俄国在人类文明中的边缘状态和孤立境地,对俄国与西方的差距进行了深刻反思。斯拉夫派则认为俄国自古以来就拥有优秀的文化传统,如果背弃了自身的文化传统走西方道路,将会带来一场灾难。如果说斯拉夫主义具有狭隘民族主义的倾向,那么泛斯拉夫主义就是纯粹的民族沙文主义。俄国人始终认为他们是整个斯拉夫民族的最优秀代表,有能力有义务来保护整个的斯拉夫民族。这一套思想在很大程度上诱发了第一次世界大战。正如恩格斯所说:“泛斯拉夫主义的统一,不是纯粹的幻想,就是俄国的鞭子。”</p>
<p>俄国的社会思潮中,还有一个非常重要的派别——民粹派。从赫尔岑,到车尔尼雪夫斯基,再到巴枯宁,民粹主义逐渐演变成激进主义。19世纪70年代,民粹派分为三个派别:一派以巴枯宁为代表,巴枯宁是俄国无政府主义运动活动家和民粹派思想家,宣扬泛斯拉夫主义。他认为私有制、国家和教会为三大敌人,号召人民起义,追求自由;一派以拉甫罗夫为代表,拉甫罗夫曾参加第一国际会议和巴黎公社活动,主张依靠人民进行社会变革,做好革命宣传工作,也被称为“宣传派”;另一派以特卡乔夫为代表,他认为革命活动具有双重性,即革命的破坏性和革命的建设性。虽然三者观点有所不同,但主要内容是农民民主主义和空想社会主义。俄国的社会主义和原本意义上的马克思主义有着根本性的差异,在很大程度上和俄国的东正教传统、集权主义、村社制度,包括民粹派极端主义思想的结合,最后还发展出了斯大林主义。</p>
<p>俄国的社会思潮当中还有非常强烈的“弥赛亚意识”。俄国人认为东正教最正宗,而天主教和新教都已经背离了基督教的原始教义,俄国肩负着拯救基督教世界和整个人类的伟大“使命”。强烈的使命感在为俄罗斯国家走向强大提供内在动力的同时,也助长了俄罗斯人觊觎别国领土,甚至欺负弱小民族的沙文主义习气,推动了俄罗斯大规模向外扩张。俄国往往打着解救其他民族的旗号,来压制其他民族。而其殖民主义同英法殖民是为了经济利益不同,是对土地的无限贪婪,是杀戮和占领。</p>
<p>扩张的冲动也源自于俄国人强烈的不安全感,从老百姓到其最高统治者,都认为外部世界对他们充满敌意。这也促使俄国形成了让别人产生恐惧的战略文化。其国际影响力的主要来源,不在于它的文化软实力或经济力量,而在于它的破坏力。</p>
<p>近年来,亚历山大·杜金宣扬的地缘政治观成为了俄国主流社会思想。但是过度地强调地缘政治,最终会成为一种自我实现的预言,在很大程度上让俄国社会陷入一种危险境地。越是关心地缘政治,越是投入资源,越是难以避免地和其他国家发生冲突。同时,帝国历史观直到今天也仍然影响着俄国。从2008年的俄罗斯格鲁吉亚战争,到2014年的克里米亚危机,再到此次全面的俄乌冲突,都是俄国帝国观念不断作祟的结果。</p>
<h2 id="民族心理">民族心理</h2>
<p>俄国的民族心理有六个主要特征。</p>
<p>第一,是矛盾性和极端化。别尔嘉耶夫指出俄国“经常从一个极端走向另一个极端”。这种心理特征一是推翻本国稳定的文化和历史,二是作用到国际体系当中,形成了一种对立意识。</p>
<p>第二,是宗教化的俄式“人道主义”。但是它的人道主义和西方的人道主义有着巨大区别。西欧的人道主义从文艺复兴勃发而来,主要矛头指向宗教和神学,强调用理性来反对迷信,用人的能力抗衡神的无所不在。而俄国既没有文艺复兴,也没有宗教改革,它的人道主义是有神论的人道主义,是把东正教的思想拿到尘世间,希望有一个上帝来解救人类远离苦难,保证人类的人道化。</p>
<p>第三,这也导致了俄罗斯另一个重要的心理特征——非理性,其思维类型的特殊性在于它建立在直觉的基础上。非理性心理在社会政治中日益蔓延,就形成了唯意志论倾向、法律虚无主义和道德优先原则。</p>
<p>第四,是共同性,源自于俄国自古以来的村社共同生活和东正教的“聚合性”。这导致了俄国崇尚平均主义,不思进取,依赖别人,反资产阶级等,与马克思·韦伯所说的新教伦理和资本主义精神完全相对立的特征。</p>
<p>第五,是苦难意识。对于俄国人而言,苦难往往是人走向神、个体走向上帝,获得上帝拯救的唯一途径。俄国历史上的众多苦难,当然有各种自然因素、制度文化因素的影响,但这种受虐心理也是重要原因。</p>
<p>第六,这还孕育出了一种受害者心理和排他性,引发了上文所谈及的不安全感和让别人产生恐惧的战略文化。</p>
<p>上述俄罗斯民族心理特征在很大程度上反映在俄罗斯的酒文化中。俄国人喝酒不用劝,自己喝,目的就是要把自己喝倒。在喝倒之后,平时非常冷漠的俄国人,甚至会痛哭流涕,体现出俄国人既豪爽又脆弱的矛盾气质。</p>
<p>俄国的力量不在于它的强大,而在于它的虚弱。俄国的外交不善于守城,却往往会出其不意,反败为胜。当其顺风顺水之时,会犯战略性错误,但是当俄国陷入困境,又会采取意想不到的手段,重振旗鼓,东山再起。</p>
<p>俄国诗人丘特切夫有诗云:“理性理解不了俄罗斯,普通的尺子无法度量俄罗斯。俄罗斯有自己的气质。你只能去 相信俄罗斯。”但我想,对于俄罗斯如此复杂的发展和国家而言,中国人还是不能简单的盲从、相信,而是要在历史发展、世界比较、中国利益和全人类共同价值共同构成的立体空间里,努力地去研究俄罗斯、理解俄罗斯、洞察俄罗斯。</p>
<p>(本文根据冯玉军教授《俄国历史文化与当代发展》讲座发言整理,经教授本人审阅。)</p>
]]></content>
<categories>
<category>转载</category>
</categories>
<tags>
<tag>历史</tag>
<tag>文化</tag>
</tags>
</entry>
<entry>
<title>博客迁移说明</title>
<url>/posts/blog-migration-notes/</url>
<content><![CDATA[<p>最早接触的是 8 年前的 Cmd Markdown,十分优秀的产品。也是看着它的语法手册,学会了 Markdown 语法。后来作者没怎么更新,以为放弃了。用过 Hexo,也用过 Gridea,感觉 yilia 之类的主题差点意思便上手去改,没写多少东西,倒是学了学 EJS 和 Less。去年 Cmd Markdown 被以有违规内容为由停掉,笔记博客类的后起之秀层出不群但大多要被审查。虽然不会有什么出格言论,癞蛤蟆趴脚面,不咬人、膈应人,遂重新搭建。</p>
<h2 id="思路">思路</h2>
<ul>
<li>静态生成器/博客框架:Hexo</li>
<li>CI/CD:Github 私有仓库(源码) + 公开仓库(成品) + action,本地/在线编写,提交到远端自动构建部署</li>
<li>托管:Github Pages</li>
<li>优化:使用 Imgbot 自动压缩图片,还访问慢的话就 CDN + 部署到 Netlify(以后再说)</li>
</ul>
<p>本着能白嫖就白嫖的原则,选用上述方案。</p>
<h2 id="过程">过程</h2>
<p><strong>本篇只是记录过程中的关键点,并不会手把手教搭建部署。</strong></p>
<p>初始化博客仓库,将主题作为 git 子模块,更新 Hexo 框架时同步更新主题,涉及的命令:</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">添加主题作为子模块</span></span><br><span class="line">git submodule add -b 指定分支 主题的仓库地址 主题存放目录</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">出错的话使用下面命令,删除对主题目录下文件的追踪</span></span><br><span class="line">git rm --cached 主题目录</span><br></pre></td></tr></table></figure>
<p>利用 action CI/CD,需要在博客仓库下建一个<code>.github/workflows/gh-page.yml</code>,内容如下</p>
<figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">name:</span> <span class="string">Deploy</span> <span class="string">GitHub</span> <span class="string">Pages</span></span><br><span class="line"></span><br><span class="line"><span class="attr">on:</span></span><br><span class="line"> <span class="attr">push:</span></span><br><span class="line"> <span class="attr">branches:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">main</span> <span class="comment"># 推送到main分支时触发</span></span><br><span class="line"></span><br><span class="line"><span class="attr">jobs:</span></span><br><span class="line"> <span class="attr">deploy-gh-pages:</span></span><br><span class="line"> <span class="attr">runs-on:</span> <span class="string">ubuntu-latest</span> <span class="comment"># 运行环境</span></span><br><span class="line"> <span class="attr">permissions:</span></span><br><span class="line"> <span class="attr">contents:</span> <span class="string">write</span> <span class="comment"># 写权限</span></span><br><span class="line"> <span class="attr">concurrency:</span></span><br><span class="line"> <span class="attr">group:</span> <span class="string">${{</span> <span class="string">github.workflow</span> <span class="string">}}-${{</span> <span class="string">github.ref</span> <span class="string">}}</span> <span class="comment"># 并发组控制,保证同时只能有一个job跑</span></span><br><span class="line"> <span class="attr">steps:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">uses:</span> <span class="string">actions/checkout@v4</span></span><br><span class="line"> <span class="attr">with:</span></span><br><span class="line"> <span class="attr">submodules:</span> <span class="string">recursive</span> <span class="comment"># 获取子模块最新主题,true/recursive</span></span><br><span class="line"></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Setup</span> <span class="string">Node</span></span><br><span class="line"> <span class="attr">uses:</span> <span class="string">actions/setup-node@v4</span></span><br><span class="line"> <span class="attr">with:</span></span><br><span class="line"> <span class="attr">node-version:</span> <span class="number">22</span></span><br><span class="line"></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Cache</span> <span class="string">NPM</span> <span class="string">dependencies</span></span><br><span class="line"> <span class="attr">uses:</span> <span class="string">actions/cache@v4</span></span><br><span class="line"> <span class="attr">with:</span></span><br><span class="line"> <span class="attr">path:</span> <span class="string">~/.npm</span></span><br><span class="line"> <span class="attr">key:</span> <span class="string">${{</span> <span class="string">runner.os</span> <span class="string">}}-node-${{</span> <span class="string">hashFiles('**/package-lock.json')</span> <span class="string">}}</span></span><br><span class="line"> <span class="attr">restore-keys:</span> <span class="string">|</span></span><br><span class="line"><span class="string"> ${{ runner.os }}-node-</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Install</span> <span class="string">dependencies</span></span><br><span class="line"> <span class="attr">run:</span> <span class="string">npm</span> <span class="string">ci</span></span><br><span class="line"></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Build</span></span><br><span class="line"> <span class="attr">run:</span> <span class="string">npm</span> <span class="string">run</span> <span class="string">build</span></span><br><span class="line"></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Deploy</span></span><br><span class="line"> <span class="comment"># 用法 https://github.com/marketplace/actions/github-pages-action</span></span><br><span class="line"> <span class="attr">uses:</span> <span class="string">peaceiris/actions-gh-pages@v4</span></span><br><span class="line"> <span class="attr">if:</span> <span class="string">github.ref</span> <span class="string">==</span> <span class="string">'refs/heads/main'</span></span><br><span class="line"> <span class="attr">with:</span></span><br><span class="line"> <span class="comment"># 部署密钥,需要手动生成密钥对,私有库用私钥,公开库用公钥,具体操作见上面网址</span></span><br><span class="line"> <span class="attr">deploy_key:</span> <span class="string">${{</span> <span class="string">secrets.ACTIONS_DEPLOY_KEY</span> <span class="string">}}</span></span><br><span class="line"> <span class="attr">external_repository:</span> <span class="string">username/external-repository</span> <span class="comment"># 部署到的外部仓库</span></span><br><span class="line"> <span class="attr">publish_branch:</span> <span class="string">main</span> <span class="comment"># 部署分支</span></span><br><span class="line"> <span class="attr">publish_dir:</span> <span class="string">./public</span> <span class="comment"># 发布目录</span></span><br><span class="line"> <span class="attr">user_name:</span> <span class="string">'github-actions[bot]'</span> <span class="comment"># 指定部署用户名</span></span><br><span class="line"> <span class="attr">user_email:</span> <span class="string">'github-actions[bot]@users.noreply.github.com'</span> <span class="comment"># 指定部署邮箱</span></span><br><span class="line"> <span class="attr">commit_message:</span> <span class="string">${{</span> <span class="string">github.event.head_commit.message</span> <span class="string">}}</span> <span class="comment"># 部署提交消息使用推送时的信息</span></span><br></pre></td></tr></table></figure>
<p>Hexo 搭建博客的细节没有讲,参考下面两篇文章或者网上类似文章,大差不差:</p>
<ul>
<li>
<p><a href="https://krislinzhao.github.io/docs/create-a-wesite-using-github-pages-and-hugo/">如何用 GitHub Pages + Hugo 搭建个人博客</a></p>
</li>
<li>
<p><a href="https://chiujun.github.io/posts/6f0712da.html">一键部署 Hexo 博客到 GitHub Pages</a></p>
</li>
</ul>
<p>Imgbot 是一个 github app,可以用来优化仓库中的图片尺寸,具体参考<a href="https://liangyuanpeng.com/post/add-imgbot-for-your-blog-image/">为你的博客添加imgbot优化图片</a>。</p>
<h2 id="待更新">待更新</h2>
<ul>
<li>Imgbot 优化后会生成一个 PR,考虑<a href="https://githubdocs.cn/en/pull-requests/collaborating-with-pull-requests/incorporating-changes-from-a-pull-request/automatically-merging-a-pull-request">自动合并拉取请求</a>,原理也是 action。</li>
<li>优化访问速度,CDN + 部署到 Netlify。</li>
</ul>
]]></content>
<categories>
<category>技术</category>
</categories>
<tags>
<tag>折腾</tag>
</tags>
</entry>
<entry>
<title>如何看待父母</title>
<url>/posts/how-should-we-perceive-our-parents/</url>
<content><![CDATA[<p><img src="https://blog-1258671016.cos.ap-chengdu.myqcloud.com/image/%E5%A6%82%E4%BD%95%E7%9C%8B%E5%BE%85%E7%88%B6%E6%AF%8D.jpg" alt="雄叔视界-如何看待父母"></p>
<p>网上看到一个截图,关于如何看待父母。我反对要求我们理解、感恩、感谢、感激父母的付出和牺牲的态度。道德是用来要求自己的。</p>
<p>父母辛苦、付出和牺牲,也有无知、迂腐和愚昧。试图用一方面去掩盖另一方面,也是徒劳。他们的价值观是时代的烙印,原生家庭、性格缺陷、情感障碍,是父母家庭和时代在我们身上的烙印。同样都是烙印,都是挣扎着活,没有谁比谁更牺牲、谁对谁更苛责。一代人有一代人要面对的问题和困境。</p>
<p>有位自媒体人说的好“如何看待父母,要先看待那个时代;如何理解父母,要先理解父母所处的年代”。不仅要理解年代,还得理解他们的经历。理解一个人很难,甚至终其一生也只能理解一小部分。能理解理解,理解不了或许没到时候。理解不代表认同,理解但不要盲从。以前习以为常的,或许现在就有些不合时宜、格格不入。我曾试图改变他们的某些观念,但后来发现是徒劳。时代在变,人也得跟着变。那些好的、适宜的一代代传承下来,其他的就到此为止吧。能做的就是让他们过他们想过的生活,我们过我们的,互不打扰,顺其自然。</p>
<p>意识到这些也是在警醒,“父母”,我们该如何做。踩了这么多坑,下一代能不踩就不踩吧。</p>
]]></content>
<categories>
<category>思考</category>
</categories>
</entry>
<entry>
<title>对山西大同订婚强奸案的思考</title>
<url>/posts/Thoughts-on-the-Engagement-Rape-Case-in-Datong-Shanxi-Province/</url>
<content><![CDATA[<p>二审 2025 年 4 月 16 号作出<a href="https://www.news.cn/legal/20250416/e480221dfe524379bee1b9b83169f61b/c.html">判决</a>并<a href="https://www.news.cn/legal/20250416/c266cb71bb764a248d6810e99751b0e8/c.html">答疑</a>,但讨论还在继续,足以说明此案件影响之大。这次才知道,<strong>原来强奸罪不保护男性</strong>!同一个事可从很多角度去看,这几天的报道总的来说是:<strong>传统婚俗观念与法治社会的冲突</strong>、<strong>常识还原事件过程的角度</strong>,<strong>双方博弈的角度</strong>。</p>
<p>总感觉哪里缺了,那到底缺了哪些?缺的是<strong>这种场景下强奸罪的认定</strong>、<strong>女方报警后各环节程序和司法公正</strong>,<strong>社会思潮变迁的角度(常识往往变得局限)</strong>,没有看到相关报道。官方答疑中或避重就轻,或不予回应,而这几种恰恰是热议最多的。</p>
<p>昨晚看到一篇<a href="https://news.china.com/socialgd/10000169/20250417/48224664_all.html">“张捷谈处女膜完整与强奸罪疑罪从无,司法逻辑与民间共识的碰撞”</a>,可能是借张捷说事,姑且一看,兼听则明。 具体细节和疑点上面文章已经说了很多,就不赘述了。看到个最高检发布的典型案例<a href="https://www.spp.gov.cn/spp/xwfbh/wsfbh/202402/t20240226_644761.shtml">“河南省安阳市文峰区检察院防冤止错监督办理王某甲等三人诬告陷害、敲诈勒索案”</a></p>
<blockquote>
<p>2021年6月至11月,王某甲、王某乙、李某某(女,2004年6月出生)共谋,由王某甲、王某乙冒充李某某的成年亲属,由李某某通过社交软件或到酒吧等场所结识男性,以假装醉酒、无处可去等借口引诱男方与其发生性关系,故意在对方身上留下抓痕,后向公安机关报案称被强奸,再由王某甲、王某乙以此为要挟向男方索要财物。王某甲三人采用此种犯罪方式共诬告陷害孙某某等8名被害人,导致其中3人被立案后采取拘留、逮捕强制措施,并对4名被害人敲诈勒索23.7万元,实际非法获取赃款8.7万元。</p>
<p>2023年1月17日,安阳市文峰区法院以诬告陷害罪、敲诈勒索罪分别判处王某甲有期徒刑七年三个月,并处罚金两万元;王某乙有期徒刑七年,并处罚金两万元;李某某有期徒刑六年,并处罚金一万五千元。</p>
</blockquote>
<p>没有强奸那 3 人怎么被采取措施了、那 4 人为何认栽花钱了事?女方诬陷被对方强奸,作为达成目的的手段。有的案件中强奸罪被重重拿起,诬陷罪却被轻轻放下——批评教育罚点钱,甚至被掐灭在萌芽阶段——立案前和解。花钱了事恰恰是大多数男性的选择,反映他们对当前司法现状的认识。疑罪从无是刑事司法的重要原则,但实务中强奸罪往往做有罪推定,这是强奸罪上最大的“常识”,被网友讽刺“以传统社会秩序下的贞洁烈女形象的常识来推定现代社会的强奸罪”。当你发现屋里有一直蟑螂的时候,你家可能已经被蟑螂占领了。这只是发现了的,那些没发现的呢?</p>
<p>以前不理解为何有人要把性生活过程公开拍或偷偷记录?或许有其合理性。4月16号判决一出瞬间懂了,作为男性下意识反应是在这种场景下该如何自我保护。于是抛出一条“暴论”:</p>
<blockquote>
<p>发生关系之前各自录像,双方互相告知且出镜,录到结束,一人一份。无事各自安好,有事起码有证据来保障双方权益。如果女方不愿录像和出镜,除了终止还能怎么办?只能偷拍。</p>
</blockquote>
<p>是不是很离谱?偷拍侵犯对方隐私权,但与强奸罪相比可小太多了。有人会说“南昌锜振东案”你怎么讲?我会说仍然有用。法院判强奸罪是它的权力,起码男方有证据向社会证明事实真相,不会社死。</p>
<blockquote>
<p>南昌锜振东案:女方以男方拒绝建立男女朋友关系为由起诉强奸,学校监控证明事前事后双方举止亲昵,未有强迫之嫌,事中录音证明是男方说太困了想睡觉女方不让,并且女上位可证明不可能是强奸(注:女强男不叫强奸),但法院以女方发生关系是以建立男女朋友关系为前提,男方偷录手段非法证据无效,仍判三缓三。</p>
</blockquote>
<p>近些年消费主义广泛传播、精致利己主义肆意横行、女权主义逐渐兴起、LGBTQ等被关注、民族主义民粹主义抬头。小红书上有专门发教程教怎么从男性身上获取“米”(黑话,钱/财产)骗婚,在实际中却定不了诈骗,最多定“借婚姻索取财物”,返还部分彩礼,被称为“0 成本创业”“致富的办法在刑法和婚家编里”。豆瓣《仙剑奇侠传》讨论区碰到过女权主义对剧中价值观的抨击。且不说那只是艺术作品,单单用现在的尺子衡量20年前的电视剧,捡个锤子就觉得满世界是钉子到处敲,就已经十分可笑。媒体自媒体常常或布下逻辑陷阱,或选择性的以部分事实掩盖全部事实。甚至辟谣辟到最后发现谣言是遥遥领先的预言,官方也经常被打脸。现实情况是网上吃瓜群众大多没有独立思考能力,心智处于8~15岁,容易被煽动而高举“爱国主义”“女权主义”等大旗网暴这个网暴那个。而这些在某种程度上已经成为一种政治正确,政治正确过头也有问题。</p>
<p>网上有一种声音说如果他是订婚强奸案的男方但确实被冤枉,出狱要杀女方和法官全家(法官大多是女性)。跟女权横行有没有关系,他们是在开玩笑吗?去年的湖南省财政厅厅长坠楼,珠海体育馆恶性事件也摆在眼前。用法律的途径来解决,是给法律一个机会,是对法律的一种尊重。不是我必须要求你。是我尊重你,让你来办,你办不好,我有力量自己解决。法律不是万能的,法制只是一种制度,法治也只是一个目标和理念。<strong>现实不是在好苹果和烂苹果之间选,现实往往是在一个烂苹果和另一个更烂的苹果之间选,这就是法治的代价,而这代价,需要所有人来承担</strong>。为什么要“民主”“法治”这种上价值的话,不是要多么好,只是不要太差。“我们之所以一路溃败,是因为过于恐惧”。对于个人来说,如果有选择去港澳台,如果能力足够,去更文明的社会。</p>
<p>二审完毕已经进入执行阶段,离刑期结束只剩一百多天。从博弈的角度看,认罪认罚是最好的选择。男方明确表示要上诉申请再审,实际上只有很低的比例能重启再审,作为一名局外人表示支持。既是为了双方的权益,也为了案件之外的所有人,和司法的公平正义。<strong>能保护自己的,只有法治。司法公正的底线一旦失守,无人能幸免。</strong></p>
]]></content>
<categories>
<category>思考</category>
</categories>
<tags>
<tag>法律</tag>
<tag>社会学</tag>
</tags>
</entry>
<entry>
<title>对精神分析的理解</title>
<url>/posts/my-view-of-psychoanalysis/</url>
<content><![CDATA[<p>经典精神分析的晦涩名词和“胡说八道”的理论,虽然看了两遍,现在已经忘得差不多了,哈哈哈,不重要。</p>
<p>在我看来精神分析给来访指出一条路:<strong>你的种种感觉、种种异常不是没由来的。如果你想,借助精神分析框架下各种方向的理论,会找到你要的答案</strong>。仅此一点,就足以给来访巨大的帮助了。</p>
<p>精神分析的本质是将思维解构然后重构。直接操作可能接受不了,需要借神秘性的东西挡在前面,间接起作用。类似于通过求神拜佛来自我暗示。</p>
<p>事实上在来访尝试理解理论、理解自己的过程中,人格就被润物无声般的改变。这就是与其他技术流派最大的不同,更曲折也更坚定。</p>
]]></content>
<categories>
<category>思考</category>
</categories>
<tags>
<tag>精神分析</tag>
<tag>心理学</tag>
</tags>
</entry>
<entry>
<title>成都地铁误会偷拍案的反思</title>
<url>/posts/20250912/</url>
<content><![CDATA[<p><a href="https://weibo.com/1699432410/Q45mHbokE">成都地铁误会偷拍案二审宣判</a>,官方出了对此案的说明。</p>
<p>第一部分:依据现有证据能证明的事实(部分存疑)。</p>
<p>第二部分:二人给原告道过歉,对方不接受。</p>
<p>第三部分:二人行为不构成法律意义上的“诬陷”。</p>
<p>第四部分:从一般人格权侵权的构成要件解释为何这么判。</p>
<p>疑问:一审中成都地铁是如何把自己摘干净的?车厢内的监控找到了?这部分“事实”是如何证明的。原告无法证明自己无过,而被告成都地铁却因“无证据证明其有过”而免责。</p>
<p>现在尝试从朴素价值观的角度看看。<a href="https://www.thecover.cn/news/3RwWGNKWOG%2BH90qSdq8Jkw==">《事件情况说明》</a> 看标题是在说什么?不懂。看内容是对舆论的回应,而非正式道歉,侧重点不同。建议学学法院怎么写,比如“关于……事件的道歉”。至于说二人已经道歉三次,借用网络热梗“那能一样吗”。</p>
<p>个人的朴素认知有一条:<strong>权责对等</strong>。地铁上质疑被男生偷拍,维护自己权益的同时在公共场合下借社会对女性的偏袒和同情给对方施压。拿了权就得担,事情查清了,那就在地铁这种公众场合道歉呗,对方接不接受那是他的权利。再不行,网上道歉和涉事地铁站张贴多张道歉声明。男生的核心诉求不就是“赔礼道歉,恢复名誉,消除影响”十二个字,其它诉求以当前司法现状也不会支持。</p>
<p>第二条:<strong>法律不是万能的</strong>。这种“小题大做”的案子,为何历经数次调解一审二审?案子可以结,事却未了。求助于法律,结果是“抱歉,法律帮不了你”。凸显现行法律体系在处理此类复杂社会冲突时的局限,导致部分当事人难以获得有效救济。什么法院多么忙,法官多么辛苦有什么可讲的,不过是司法系统在道德破产与法律边界模糊下的被动承载。在误判情况下,是否应承担道义上的责任而非法律责任?而这个案子就如同那句“不是你撞的你为何要扶?”它所引发的社会规范重塑,将悄然显现。</p>
<p>第三条:<strong>社会各方力量得互相制衡</strong>。在当前舆论环境下,女生一方几乎无需承担成本,而男生若要维权,则需付出极高代价。5万元的赔偿诉求,实则是试图通过法律手段提高对方的行为成本,以求制衡。之前长沙公交车上<a href="https://weibo.com/1644114654/Q14xEDbFf">女子怀疑遭性骚扰起冲突后被砸伤</a>,并在网络上散布自己被80岁老头性骚扰的相关言论。评论区开玩笑“老人:谁怕谁,信不信我躺这儿”。事实上老人的解决思路是对的,提高对方的成本——能动手就不争辩。在未达成制衡前,类似的事还会不断上演,拭目以待。</p>
<p>第四条:<strong>不支持网络暴力,但支持对等反击</strong>。学法懂法,保护自己。遇到这种事先取证。了解对方惯用套路,有时候以彼之道还施彼身,也不失为一种解决办法。两手都要抓,两手都要硬。不少三观还未确立便被网络女权浸染过深,陷入受害叙事的群体最终反噬自身,可怜又可悲。</p>
<p>历数最近引起广泛讨论的案子,大多跟男女对立、公平正义有关。比如广铁法院的女子持刀伤人被法官共情轻判并包装成知心姐姐拯救无知少女的“正能量”故事,山西大同订婚强奸案、武大杨某媛“被性骚扰”事件……传统社会女性地位低,看看红楼梦就知道了。现代社会对女性赋予特权和照顾,司法上的社会上的,辅以各种明目和解释。初衷是好的为了平权,结果却造成对男性群体的“逆向歧视”和更大的不平等。</p>
<p>一方面是武大杨某媛借社会对女性的照顾和同情煽动舆论对肖某某网暴、不少人借婚姻之名行诈骗之实;一方面是不时就有地铁偷拍侵害权益的报道、农村外嫁女的权益得不到保障等问题。而在许多引起广泛讨论的案子中男性往往陷入“有劲使不上”的被动局面。或许这就是我们不得不面对的复杂现实。</p>
]]></content>
<categories>
<category>思考</category>
</categories>
<tags>
<tag>法律</tag>
<tag>社会学</tag>
</tags>
</entry>
<entry>
<title>提前写给俄罗斯的碑文</title>
<url>/posts/epitaph-for-russia-in-advance/</url>
<content><![CDATA[<blockquote>
<p>转载自<a href="https://www.youtube.com/post/UgkxLohZrZlUYEEIUjVNB5vhvu6Cz14yRS-K">@蒲曲波</a>。</p>
</blockquote>
<p>昨天有人在知乎问我:“俄罗斯的领土为什么这么大?”</p>
<p>我的回答是:这事说来话长。</p>
<p>张三游手好闲,还偷看妇女洗澡,被村里赶到村西头的破屋栖身,饥一顿饱一顿。一天鬼子来了,要粮、要壮丁、要花姑娘,村里人躲得干干净净,皇军一无所获。夜里,张三摸黑进了鬼子驻地告密,第二天,村里的藏粮、藏人都被搜出,张三被封为村长,带着鬼子的刀枪去收拾自己的同胞。</p>
<p>这可以解释莫斯科公国的发家史。伊凡一世的爵位、封号、土地,靠的不是罗斯正统,而是充当蒙古人的鹰犬,替金帐汗国收税、镇压、告密、榨干自己的同胞,换取主子的封赏。至于后来吹嘘的什么弗拉基米尔大公血统?别扯了,以苏联“真理部”之强大都没能把这事儿整成真理,可见这谎言有多站不住脚。说白了,祖上其实是个坏种。不好意思!读书人不该说脏话,但看看陈琳的《讨曹操檄》:“操赘阉遗丑,本无懿德”,再看骆宾王《讨武曌檄》:“伪临朝武氏者,性非喝顺,地实寒微。”看来,我只是受到了古代文人风范的影响。</p>
<p>唐朝时,文成公主远嫁吐蕃,带去了种子、工匠、书籍、儒生,推动吐蕃文明进步。而彼时,伊凡三世迎娶了拜占庭亡国公主索菲亚,说是带来了宗教典籍、工匠,还有那只“双头鹰”徽章(但很可能是俄罗斯人自己做的高仿)。于是,莫斯科公国摇身一变,自称“第三罗马”,仿佛娶了个流亡公主,就能祖宗升级,登基称帝了。</p>
<p>吐蕃未敢自称“中华”,而莫斯科却敢自封“第三罗马”。他们对文化挪用的熟练度,恐怕是具有无耻遗传的天赋(华夷之辩的本意是,文明认同重于血统认同,关键在于文化的熏陶)。</p>
<p>挣脱金帐汗国的枷锁后,莫斯科公国正式踏上了扩张之路。先是摧毁诺夫哥罗德,把罗斯地区唯一可能走向民主的城邦共和国屠个干净,彻底堵死了罗斯人的另一条发展道路。接着南下攻陷喀山、阿斯特拉罕,掌控伏尔加贸易动脉;随后一路扩张克里米亚、高加索、西伯利亚、中亚,直至远东……扩张的代价是什么?人命,且优先消耗非俄罗斯族裔的人命。</p>
<p>这也解释了为什么今日的俄乌战争,死得最多的是布里亚特人、达吉斯坦人、车臣人,而莫斯科和圣彼得堡的年轻人依然在咖啡馆里讨论“普京多么英明”。战争打得越久,俄罗斯的主体民族就越“纯净”,如果赢了还能开疆扩土。真是典型的“双赢”啊!</p>
<p>贯穿俄罗斯的历史,沙俄如此,苏联亦然。依靠征服而非发展,依靠镇压而非治理,依靠虚构的历史正统而非真正的国家认同。可问题是,21世纪的世界已经不是13世纪的蒙古帝国,甚至不是19世纪的欧洲殖民体系。昔日的扩张机器,如今已锈迹斑斑,能源经济被制裁掏空,战争机器被腐败掣肘,而所谓的“正统继承”更是个笑话。</p>
<p>俄罗斯疆域的扩张,并非文明传播的结果,而是依靠暴力与欺骗的长期积累。然而,历史的负债终将面临清算。本来今天正当其时!奈何出了位“格拉斯诺夫”(此梗源自互联网调侃,称特朗普90年代在莫斯科被克格勃“看中”,以解释他为何对普京唯命是从)。对泽连斯基强硬施压,但在普京面前却唯唯诺诺。</p>
<p>这块大地上,冬天虽长总会过去,但春天未必属于俄罗斯。</p>
]]></content>
<categories>
<category>转载</category>
</categories>
<tags>
<tag>历史</tag>
</tags>
</entry>
<entry>
<title>源码阅读-HashMap</title>
<url>/posts/read-source-HashMap/</url>
<content><![CDATA[<p>HashMap的原理就是用一个数组存储一系列 K-V 结构的 Node 实体,存储位置通过对 key 的 hashCode 计算得到,Node 对象有 next 指针,作为单链表的一个节点。如果发生 hash 冲突,就将新的 Node 插入链表里。链表过长超过阈值的话为了提高查询效率会转为红黑树(JDK 1.8以后)。</p>
<h2 id="成员变量">成员变量</h2>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">//默认初始化容量</span></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">DEFAULT_INITIAL_CAPACITY</span> <span class="operator">=</span> <span class="number">1</span> << <span class="number">4</span>; <span class="comment">// aka 16</span></span><br><span class="line"><span class="comment">//最大容量</span></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">MAXIMUM_CAPACITY</span> <span class="operator">=</span> <span class="number">1</span> << <span class="number">30</span>;</span><br><span class="line"><span class="comment">//默认负载因子 0.75</span></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> <span class="type">float</span> <span class="variable">DEFAULT_LOAD_FACTOR</span> <span class="operator">=</span> <span class="number">0.75f</span>;</span><br><span class="line"><span class="comment">//桶中链表树化的阈值 8</span></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">TREEIFY_THRESHOLD</span> <span class="operator">=</span> <span class="number">8</span>;</span><br><span class="line"><span class="comment">//树转为链表的阈值 6</span></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">UNTREEIFY_THRESHOLD</span> <span class="operator">=</span> <span class="number">6</span>;</span><br><span class="line"><span class="comment">//table最小树化容量 64</span></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">MIN_TREEIFY_CAPACITY</span> <span class="operator">=</span> <span class="number">64</span>;</span><br><span class="line"><span class="comment">//hashmap 中键值对的个数</span></span><br><span class="line"><span class="keyword">transient</span> <span class="type">int</span> size;</span><br><span class="line"><span class="comment">//扩容阈值(容量×载因子)</span></span><br><span class="line"><span class="type">int</span> threshold;</span><br><span class="line"><span class="comment">//真正存放数据的地方</span></span><br><span class="line"><span class="keyword">transient</span> Node<K,V>[] table;</span><br></pre></td></tr></table></figure>
<h3 id="hashMap默认初始化长度是多少?为什么是这么多?">hashMap默认初始化长度是多少?为什么是这么多?</h3>
<blockquote>
<p>默认初始化长度是 1<<< 4,之所以不直接写16,是因为位运算比算术运算效率高,之所以选择16,是为了服务与 index 的计算公式 index = hash & (length - 1)(这里采用与运算和取模的效果一样,但性能更好),初始化长度减1后为15,二进制位1111,和 hash 值运算后的结果等同于 hash 后几位的值,只要 hash 值本身分布均匀,那 hash 算法的结果就是均匀的,从而实现均匀分布。</p>
</blockquote>
<h3 id="为什么树化标准是8个?">为什么树化标准是8个?</h3>
<p>在源码注释中有提到:如果 hashCode 分布良好的话,很少出现链表比较长的情况。理想情况下,链表的长度符合<a href="http://www.ruanyifeng.com/blog/2015/06/poisson-distribution.html">泊松分布</a>。各长度的概率如下</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">/* 0: 0.60653066</span></span><br><span class="line"><span class="comment"> * 1: 0.30326533</span></span><br><span class="line"><span class="comment"> * 2: 0.07581633</span></span><br><span class="line"><span class="comment"> * 3: 0.01263606</span></span><br><span class="line"><span class="comment"> * 4: 0.00157952</span></span><br><span class="line"><span class="comment"> * 5: 0.00015795</span></span><br><span class="line"><span class="comment"> * 6: 0.00001316</span></span><br><span class="line"><span class="comment"> * 7: 0.00000094</span></span><br><span class="line"><span class="comment"> * 8: 0.00000006</span></span><br><span class="line"><span class="comment"> */</span></span><br></pre></td></tr></table></figure>
<p>当链表长度为8时,概率为0.00000006,概率非常小,红黑树的转换基本不会发生。当然也会有用到:用户使用自己的 hash 算法,导致 hashCode 分布离散很差,链表很长的情况。</p>
<h3 id="为什么树退化为链表的阈值是6个?">为什么树退化为链表的阈值是6个?</h3>
<p>增加一个过渡,防止在临界值时增加/删除一个元素时,在树和链表之间频繁的转换,降低性能。</p>
<h2 id="初始化方法">初始化方法</h2>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 最常用的一个,负载因子0.75</span></span><br><span class="line"><span class="keyword">public</span> <span class="title function_">HashMap</span><span class="params">()</span> {</span><br><span class="line"> <span class="built_in">this</span>.loadFactor = DEFAULT_LOAD_FACTOR; <span class="comment">// all other fields defaulted</span></span><br><span class="line">}</span><br><span class="line"><span class="comment">// 指定初始化容量,负载因子0.75</span></span><br><span class="line"><span class="keyword">public</span> <span class="title function_">HashMap</span><span class="params">(<span class="type">int</span> initialCapacity)</span> {</span><br><span class="line"> <span class="built_in">this</span>(initialCapacity, DEFAULT_LOAD_FACTOR);</span><br><span class="line">}</span><br><span class="line"><span class="comment">// 指定初始化容量和负载因子</span></span><br><span class="line"><span class="keyword">public</span> <span class="title function_">HashMap</span><span class="params">(<span class="type">int</span> initialCapacity, <span class="type">float</span> loadFactor)</span> {</span><br><span class="line"> <span class="keyword">if</span> (initialCapacity < <span class="number">0</span>)</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(<span class="string">"Illegal initial capacity: "</span> +</span><br><span class="line"> initialCapacity);</span><br><span class="line"> <span class="keyword">if</span> (initialCapacity > MAXIMUM_CAPACITY)</span><br><span class="line"> initialCapacity = MAXIMUM_CAPACITY;</span><br><span class="line"> <span class="keyword">if</span> (loadFactor <= <span class="number">0</span> || Float.isNaN(loadFactor))</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(<span class="string">"Illegal load factor: "</span> +</span><br><span class="line"> loadFactor);</span><br><span class="line"> <span class="built_in">this</span>.loadFactor = loadFactor;</span><br><span class="line"> <span class="comment">// 取二次幂里比指定容量大且最接近的一个,比如指定容量10,则输出16</span></span><br><span class="line"> <span class="built_in">this</span>.threshold = tableSizeFor(initialCapacity);</span><br><span class="line">}</span><br><span class="line"><span class="comment">// 将传入的map的键值对复制一份放hashmap里,不常用</span></span><br><span class="line"><span class="keyword">public</span> <span class="title function_">HashMap</span><span class="params">(Map<? extends K, ? extends V> m)</span> {</span><br><span class="line"> <span class="built_in">this</span>.loadFactor = DEFAULT_LOAD_FACTOR;</span><br><span class="line"> putMapEntries(m, <span class="literal">false</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h2 id="hash方法">hash方法</h2>
<p>hash() 方法是 HashMap 的核心方法,算出 key 的 hashCode 后,将算出的结果右移16位,与原 hashCode 按位异或得到 hash 后的值,</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="title function_">hash</span><span class="params">(Object key)</span> {</span><br><span class="line"> <span class="type">int</span> h;</span><br><span class="line"> <span class="keyword">return</span> (key == <span class="literal">null</span>) ? <span class="number">0</span> : (h = key.hashCode()) ^ (h >>> <span class="number">16</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h3 id="为什么要右移16位?">为什么要右移16位?</h3>
<p>让 hashCode 的高16位也参与运算,增加扰动,减少碰撞冲突,因为大部分元素的 hashCode 在低位是相同的。</p>
<h3 id="相关问题:String的hashCode的实现?">相关问题:String的hashCode的实现?</h3>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">hashCode</span><span class="params">()</span> {</span><br><span class="line"> <span class="type">int</span> <span class="variable">h</span> <span class="operator">=</span> hash;</span><br><span class="line"> <span class="keyword">if</span> (h == <span class="number">0</span> && value.length > <span class="number">0</span>) {</span><br><span class="line"> <span class="type">char</span> val[] = value;</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i < value.length; i++) {</span><br><span class="line"> h = <span class="number">31</span> * h + val[i];</span><br><span class="line"> }</span><br><span class="line"> hash = h;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> h;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>简单来说就是给字符串的每一位,按位乘31的n次幂,再相加,用自然溢出来等效取模。选择31因为这个质数不大不小,且可以被虚拟机优化,运算效率更高。<code>i*31 = i*(32-1) = i<<5-1</code></p>
<h2 id="put方法">put方法</h2>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> V <span class="title function_">put</span><span class="params">(K key, V value)</span> {</span><br><span class="line"> <span class="keyword">return</span> putVal(hash(key), key, value, <span class="literal">false</span>, <span class="literal">true</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">final</span> V <span class="title function_">putVal</span><span class="params">(<span class="type">int</span> hash, K key, V value, <span class="type">boolean</span> onlyIfAbsent,</span></span><br><span class="line"><span class="params"> <span class="type">boolean</span> evict)</span> {</span><br><span class="line"> <span class="comment">// tab:引用当前HashMap的散列表</span></span><br><span class="line"> <span class="comment">// p:表示当前散列表的元素</span></span><br><span class="line"> <span class="comment">// n:当前散列表数组长度</span></span><br><span class="line"> <span class="comment">// i:表示路由寻址结果</span></span><br><span class="line"> Node<K,V>[] tab; Node<K,V> p; <span class="type">int</span> n, i;</span><br><span class="line"> <span class="comment">// 延迟初始化逻辑,第一次调用putVal方法时初始化</span></span><br><span class="line"> <span class="keyword">if</span> ((tab = table) == <span class="literal">null</span> || (n = tab.length) == <span class="number">0</span>)</span><br><span class="line"> n = (tab = resize()).length;</span><br><span class="line"> <span class="comment">// 找到的桶位为null,将key,valuef封装成node对象,放进去就ok</span></span><br><span class="line"> <span class="keyword">if</span> ((p = tab[i = (n - <span class="number">1</span>) & hash]) == <span class="literal">null</span>)</span><br><span class="line"> tab[i] = newNode(hash, key, value, <span class="literal">null</span>);</span><br><span class="line"> <span class="keyword">else</span> {</span><br><span class="line"> Node<K,V> e; K k;</span><br><span class="line"> <span class="comment">// 桶位中的元素与当前插入元素的key一致,后续会进行value的替换操作</span></span><br><span class="line"> <span class="keyword">if</span> (p.hash == hash &&</span><br><span class="line"> ((k = p.key) == key || (key != <span class="literal">null</span> && key.equals(k))))</span><br><span class="line"> e = p;</span><br><span class="line"> <span class="comment">// 如果桶位中的元素是红黑树</span></span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> (p <span class="keyword">instanceof</span> TreeNode)</span><br><span class="line"> e = ((TreeNode<K,V>)p).putTreeVal(<span class="built_in">this</span>, tab, hash, key, value);</span><br><span class="line"> <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// 如果桶位中的元素是链表,且链表的头元素与要插入的之不一致</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">binCount</span> <span class="operator">=</span> <span class="number">0</span>; ; ++binCount) {</span><br><span class="line"> <span class="keyword">if</span> ((e = p.next) == <span class="literal">null</span>) {</span><br><span class="line"> p.next = newNode(hash, key, value, <span class="literal">null</span>);</span><br><span class="line"> <span class="comment">// 如果当前桶的元素计数大于等于树化阈值,则进行树化</span></span><br><span class="line"> <span class="keyword">if</span> (binCount >= TREEIFY_THRESHOLD - <span class="number">1</span>) <span class="comment">// -1 for 1st</span></span><br><span class="line"> treeifyBin(tab, hash);</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 找到一个元素与当前要插入的元素的key一致</span></span><br><span class="line"> <span class="keyword">if</span> (e.hash == hash &&</span><br><span class="line"> ((k = e.key) == key || (key != <span class="literal">null</span> && key.equals(k))))</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> p = e;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 找到了一致的元素,用新的value替换原来的value</span></span><br><span class="line"> <span class="keyword">if</span> (e != <span class="literal">null</span>) { <span class="comment">// existing mapping for key</span></span><br><span class="line"> <span class="type">V</span> <span class="variable">oldValue</span> <span class="operator">=</span> e.value;</span><br><span class="line"> <span class="keyword">if</span> (!onlyIfAbsent || oldValue == <span class="literal">null</span>)</span><br><span class="line"> e.value = value;</span><br><span class="line"> afterNodeAccess(e);</span><br><span class="line"> <span class="keyword">return</span> oldValue;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> ++modCount;</span><br><span class="line"> <span class="keyword">if</span> (++size > threshold)</span><br><span class="line"> resize();</span><br><span class="line"> afterNodeInsertion(evict);</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>在 putVal 方法中为什么要用 <code>i = (n-1) & hash</code> 作为索引运算呢?</p>
<blockquote>
<p>这其实是种优化手段,由于数组的大小永远是一个2次幂,在扩容后,一个元素的新索引要么在原位置,要么在原位置+扩容前的容量。这个方法的巧妙处全在于&运算,&运算只会关注 n-1(n=数组长度)的有效位,当扩容后,n的有效位相比之前会多增加一位(n会变成之前的两倍,所以确保数组长度永远是2次幂很重要),然后只需要判断 hash 在新增的有效位的位置是 0 还是 1 就可以算出新的索引位置,如果是 0,如果是 2,索引就是原索引+扩容前的容量。</p>
</blockquote>
<h2 id="resize方法">resize方法</h2>
<p>resize()方法主要是用来扩容,具体操作是新建一个hash表,然后将旧表中的内容重新散列复制到新表中。源码主要分为2部分:</p>
<ul>
<li>第一部分主要用于判断当前操作的类型(初始化or扩容)并且计算出新生成的表的容量和阈值。</li>
<li>第二部分只用于扩容操作时,将旧表中的元素重新散列放入新表。</li>
</ul>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">//扩容方法</span></span><br><span class="line"><span class="keyword">final</span> Node<K,V>[] resize() {</span><br><span class="line"> Node<K,V>[] oldTab = table; <span class="comment">// 引用扩容前的哈希表</span></span><br><span class="line"> <span class="type">int</span> <span class="variable">oldCap</span> <span class="operator">=</span> (oldTab == <span class="literal">null</span>) ? <span class="number">0</span> : oldTab.length; <span class="comment">// 扩容前table的容量</span></span><br><span class="line"> <span class="type">int</span> <span class="variable">oldThr</span> <span class="operator">=</span> threshold; <span class="comment">// 扩容前的阈值</span></span><br><span class="line"> <span class="type">int</span> newCap, newThr = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">if</span> (oldCap > <span class="number">0</span>) {</span><br><span class="line"> <span class="comment">// 扩容前table数组大小已达到最大值,则不扩容,且扩容条件设置为int最大值</span></span><br><span class="line"> <span class="keyword">if</span> (oldCap >= MAXIMUM_CAPACITY) {</span><br><span class="line"> threshold = Integer.MAX_VALUE;</span><br><span class="line"> <span class="keyword">return</span> oldTab;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 正常情况,新容量在老容量的基础上翻倍,则在阈值上也翻倍</span></span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> ((newCap = oldCap << <span class="number">1</span>) < MAXIMUM_CAPACITY &&</span><br><span class="line"> oldCap >= DEFAULT_INITIAL_CAPACITY)</span><br><span class="line"> newThr = oldThr << <span class="number">1</span>; <span class="comment">// double threshold</span></span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// oldCap==0,oldThr>0 说明哈希表中的散列表还未初始化,在使用带参构造的时候会发生</span></span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> (oldThr > <span class="number">0</span>) <span class="comment">// initial capacity was placed in threshold</span></span><br><span class="line"> newCap = oldThr;</span><br><span class="line"> <span class="comment">//oldCap==0,oldThr>0 使用无参构造的时候</span></span><br><span class="line"> <span class="keyword">else</span> { <span class="comment">// zero initial threshold signifies using defaults</span></span><br><span class="line"> newCap = DEFAULT_INITIAL_CAPACITY;</span><br><span class="line"> newThr = (<span class="type">int</span>)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (newThr == <span class="number">0</span>) {</span><br><span class="line"> <span class="type">float</span> <span class="variable">ft</span> <span class="operator">=</span> (<span class="type">float</span>)newCap * loadFactor;</span><br><span class="line"> newThr = (newCap < MAXIMUM_CAPACITY && ft < (<span class="type">float</span>)MAXIMUM_CAPACITY ?</span><br><span class="line"> (<span class="type">int</span>)ft : Integer.MAX_VALUE);</span><br><span class="line"> }</span><br><span class="line"> threshold = newThr;</span><br><span class="line"> <span class="meta">@SuppressWarnings({"rawtypes","unchecked"})</span></span><br><span class="line"> Node<K,V>[] newTab = (Node<K,V>[])<span class="keyword">new</span> <span class="title class_">Node</span>[newCap];</span><br><span class="line"> table = newTab;</span><br><span class="line"> <span class="keyword">if</span> (oldTab != <span class="literal">null</span>) {</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">0</span>; j < oldCap; ++j) {</span><br><span class="line"> Node<K,V> e;</span><br><span class="line"> <span class="comment">// 说明当前桶位有数据</span></span><br><span class="line"> <span class="keyword">if</span> ((e = oldTab[j]) != <span class="literal">null</span>) {</span><br><span class="line"> <span class="comment">// 方便JVM回收内存</span></span><br><span class="line"> oldTab[j] = <span class="literal">null</span>;</span><br><span class="line"> <span class="comment">// 当前桶位有一个元素,计算当前元素扩容后的位置</span></span><br><span class="line"> <span class="keyword">if</span> (e.next == <span class="literal">null</span>)</span><br><span class="line"> newTab[e.hash & (newCap - <span class="number">1</span>)] = e;</span><br><span class="line"> <span class="comment">// 判断当前节点已经树化</span></span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> (e <span class="keyword">instanceof</span> TreeNode)</span><br><span class="line"> ((TreeNode<K,V>)e).split(<span class="built_in">this</span>, newTab, j, oldCap);</span><br><span class="line"> <span class="comment">// 当前是链表</span></span><br><span class="line"> <span class="keyword">else</span> { <span class="comment">// preserve order</span></span><br><span class="line"> <span class="comment">// 低位链表:扩容之后数组的下标位置与当前数组下标位置一致</span></span><br><span class="line"> Node<K,V> loHead = <span class="literal">null</span>, loTail = <span class="literal">null</span>;</span><br><span class="line"> <span class="comment">// 高位链表:扩容之后数组的下标位置 = 当前数组下标 + 扩容前数组长度</span></span><br><span class="line"> Node<K,V> hiHead = <span class="literal">null</span>, hiTail = <span class="literal">null</span>;</span><br><span class="line"> Node<K,V> next;</span><br><span class="line"> <span class="keyword">do</span> {</span><br><span class="line"> next = e.next;</span><br><span class="line"> <span class="comment">// 通过将hash值与 oldCap 相与,将原来的链表拆分为高位链、低位链</span></span><br><span class="line"> <span class="keyword">if</span> ((e.hash & oldCap) == <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">if</span> (loTail == <span class="literal">null</span>)</span><br><span class="line"> loHead = e;</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> loTail.next = e;</span><br><span class="line"> loTail = e;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">if</span> (hiTail == <span class="literal">null</span>)</span><br><span class="line"> hiHead = e;</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> hiTail.next = e;</span><br><span class="line"> hiTail = e;</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">while</span> ((e = next) != <span class="literal">null</span>);</span><br><span class="line"> <span class="keyword">if</span> (loTail != <span class="literal">null</span>) {</span><br><span class="line"> loTail.next = <span class="literal">null</span>;</span><br><span class="line"> newTab[j] = loHead;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (hiTail != <span class="literal">null</span>) {</span><br><span class="line"> hiTail.next = <span class="literal">null</span>; <span class="comment">//修改高位链表结尾</span></span><br><span class="line"> newTab[j + oldCap] = hiHead; <span class="comment">//放进新table</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> newTab;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h2 id="get方法">get方法</h2>
<p>get 方法逻辑比较简单,直接看注释</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> V <span class="title function_">get</span><span class="params">(Object key)</span> {</span><br><span class="line"> Node<K,V> e;</span><br><span class="line"> <span class="keyword">return</span> (e = getNode(hash(key), key)) == <span class="literal">null</span> ? <span class="literal">null</span> : e.value;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">final</span> Node<K,V> <span class="title function_">getNode</span><span class="params">(<span class="type">int</span> hash, Object key)</span> {</span><br><span class="line"> Node<K,V>[] tab; Node<K,V> first, e; <span class="type">int</span> n; K k;</span><br><span class="line"> <span class="keyword">if</span> ((tab = table) != <span class="literal">null</span> && (n = tab.length) > <span class="number">0</span> &&</span><br><span class="line"> (first = tab[(n - <span class="number">1</span>) & hash]) != <span class="literal">null</span>) {</span><br><span class="line"> <span class="comment">// 第一种情况:定位出的桶元素即为要get的数据</span></span><br><span class="line"> <span class="keyword">if</span> (first.hash == hash && <span class="comment">// always check first node</span></span><br><span class="line"> ((k = first.key) == key || (key != <span class="literal">null</span> && key.equals(k))))</span><br><span class="line"> <span class="keyword">return</span> first;</span><br><span class="line"> <span class="comment">// 第二种情况:当前桶位不只一个元素</span></span><br><span class="line"> <span class="keyword">if</span> ((e = first.next) != <span class="literal">null</span>) {</span><br><span class="line"> <span class="comment">// 当前桶位上的为红黑树</span></span><br><span class="line"> <span class="keyword">if</span> (first <span class="keyword">instanceof</span> TreeNode)</span><br><span class="line"> <span class="keyword">return</span> ((TreeNode<K,V>)first).getTreeNode(hash, key);</span><br><span class="line"> <span class="comment">// 当前桶位上是链表,依次匹配</span></span><br><span class="line"> <span class="keyword">do</span> {</span><br><span class="line"> <span class="keyword">if</span> (e.hash == hash &&</span><br><span class="line"> ((k = e.key) == key || (key != <span class="literal">null</span> && key.equals(k))))</span><br><span class="line"> <span class="keyword">return</span> e;</span><br><span class="line"> } <span class="keyword">while</span> ((e = e.next) != <span class="literal">null</span>);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h2 id="remove方法">remove方法</h2>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> V <span class="title function_">remove</span><span class="params">(Object key)</span> {</span><br><span class="line"> Node<K,V> e;</span><br><span class="line"> <span class="keyword">return</span> (e = removeNode(hash(key), key, <span class="literal">null</span>, <span class="literal">false</span>, <span class="literal">true</span>)) == <span class="literal">null</span> ?</span><br><span class="line"> <span class="literal">null</span> : e.value;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">final</span> Node<K,V> <span class="title function_">removeNode</span><span class="params">(<span class="type">int</span> hash, Object key, Object value,</span></span><br><span class="line"><span class="params"> <span class="type">boolean</span> matchValue, <span class="type">boolean</span> movable)</span> {</span><br><span class="line"> Node<K,V>[] tab; Node<K,V> p; <span class="type">int</span> n, index;</span><br><span class="line"> <span class="keyword">if</span> ((tab = table) != <span class="literal">null</span> && (n = tab.length) > <span class="number">0</span> &&</span><br><span class="line"> (p = tab[index = (n - <span class="number">1</span>) & hash]) != <span class="literal">null</span>) {</span><br><span class="line"> Node<K,V> node = <span class="literal">null</span>, e; K k; V v;</span><br><span class="line"> <span class="comment">// 当前桶位上的节点匹配一致</span></span><br><span class="line"> <span class="keyword">if</span> (p.hash == hash &&</span><br><span class="line"> ((k = p.key) == key || (key != <span class="literal">null</span> && key.equals(k))))</span><br><span class="line"> node = p;</span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> ((e = p.next) != <span class="literal">null</span>) {</span><br><span class="line"> <span class="comment">// 是树节点的情况</span></span><br><span class="line"> <span class="keyword">if</span> (p <span class="keyword">instanceof</span> TreeNode)</span><br><span class="line"> node = ((TreeNode<K,V>)p).getTreeNode(hash, key);</span><br><span class="line"> <span class="comment">// 是链表的情况,遍历链表匹配</span></span><br><span class="line"> <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">do</span> {</span><br><span class="line"> <span class="keyword">if</span> (e.hash == hash &&</span><br><span class="line"> ((k = e.key) == key ||</span><br><span class="line"> (key != <span class="literal">null</span> && key.equals(k)))) {</span><br><span class="line"> node = e;</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> p = e;</span><br><span class="line"> } <span class="keyword">while</span> ((e = e.next) != <span class="literal">null</span>);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (node != <span class="literal">null</span> && (!matchValue || (v = node.value) == value ||</span><br><span class="line"> (value != <span class="literal">null</span> && value.equals(v)))) {</span><br><span class="line"> <span class="comment">// 匹配到的节点是树,用树的方法移除</span></span><br><span class="line"> <span class="keyword">if</span> (node <span class="keyword">instanceof</span> TreeNode)</span><br><span class="line"> ((TreeNode<K,V>)node).removeTreeNode(<span class="built_in">this</span>, tab, movable);</span><br><span class="line"> <span class="comment">// 如果就是当前桶位上的节点,用下一个节点直接覆盖</span></span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> (node == p)</span><br><span class="line"> tab[index] = node.next;</span><br><span class="line"> <span class="comment">// 匹配到的节点是链表,用链表的方法移除</span></span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> p.next = node.next;</span><br><span class="line"> ++modCount;</span><br><span class="line"> --size;</span><br><span class="line"> afterNodeRemoval(node);</span><br><span class="line"> <span class="keyword">return</span> node;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<categories>
<category>技术</category>
</categories>
<tags>
<tag>Java</tag>
<tag>源码阅读</tag>
</tags>
</entry>
<entry>
<title>用数组或栈实现阻塞队列</title>
<url>/posts/implement-blocking-queue-with-array-or-stack/</url>
<content><![CDATA[<h2 id="用数组实现">用数组实现</h2>
<p>参考 JDK 中的实现<code>java.util.concurrent.ArrayBlockingQueue</code>。</p>
<ol>
<li>考虑使用环形数组。用两个指针 <code>putIndex</code>(下一个入队位置)、<code>takeIndex</code>(下一个出队位置),入队/出队到数组末尾时都从零开始,<code>count</code>统计元素数量,count=0说明队列为空,count=数组容量说明队列已满。</li>
<li>考虑用 lock + Condition.await 实现阻塞和保证线程安全。</li>
</ol>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> java.util.concurrent.locks.Condition;</span><br><span class="line"><span class="keyword">import</span> java.util.concurrent.locks.ReentrantLock;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ArrayBlockingQueue</span><E> {</span><br><span class="line"> <span class="keyword">final</span> Object[] items; <span class="comment">// 存放元素的数组</span></span><br><span class="line"> <span class="type">int</span> putIndex; <span class="comment">// 下一个入队位置</span></span><br><span class="line"> <span class="type">int</span> takeIndex; <span class="comment">// 下一个出队位置</span></span><br><span class="line"> <span class="type">int</span> count; <span class="comment">// 元素数量</span></span><br><span class="line"> <span class="keyword">final</span> ReentrantLock lock;</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> Condition notEmpty; <span class="comment">// 不空,可以出</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> Condition notFull; <span class="comment">// 不满,可以进</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="title function_">ArrayBlockingQueue1</span><span class="params">(<span class="type">int</span> capacity)</span> {</span><br><span class="line"> <span class="keyword">if</span> (capacity <= <span class="number">0</span>)</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>();</span><br><span class="line"></span><br><span class="line"> items = <span class="keyword">new</span> <span class="title class_">Object</span>[capacity];</span><br><span class="line"> lock = <span class="keyword">new</span> <span class="title class_">ReentrantLock</span>();</span><br><span class="line"> notEmpty = lock.newCondition();</span><br><span class="line"> notFull = lock.newCondition();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 往队列放元素</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">put</span><span class="params">(E e)</span> <span class="keyword">throws</span> InterruptedException {</span><br><span class="line"> <span class="keyword">final</span> <span class="type">ReentrantLock</span> <span class="variable">lock</span> <span class="operator">=</span> <span class="built_in">this</span>.lock;</span><br><span class="line"> lock.lockInterruptibly();<span class="comment">// 加锁</span></span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">// 如果已满,释放锁并阻塞</span></span><br><span class="line"> <span class="keyword">while</span> (count == items.length)</span><br><span class="line"> notFull.await();</span><br><span class="line"> <span class="comment">// 如果未满,入队</span></span><br><span class="line"> enqueue(e);</span><br><span class="line"> <span class="comment">// 唤醒关注未空的线程</span></span><br><span class="line"> notEmpty.signal();</span><br><span class="line"> } <span class="keyword">finally</span> {</span><br><span class="line"> lock.unlock();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 从队列取元素</span></span><br><span class="line"> <span class="keyword">public</span> E <span class="title function_">take</span><span class="params">()</span> <span class="keyword">throws</span> InterruptedException {</span><br><span class="line"> <span class="keyword">final</span> <span class="type">ReentrantLock</span> <span class="variable">lock</span> <span class="operator">=</span> <span class="built_in">this</span>.lock;</span><br><span class="line"> lock.lockInterruptibly();</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">// 如果已空,阻塞释放锁</span></span><br><span class="line"> <span class="keyword">while</span> (count == <span class="number">0</span>)</span><br><span class="line"> notEmpty.await();</span><br><span class="line"> <span class="comment">// 如果未空,出队</span></span><br><span class="line"> <span class="type">E</span> <span class="variable">e</span> <span class="operator">=</span> dequeue();</span><br><span class="line"> <span class="comment">// 唤醒关注未满的线程</span></span><br><span class="line"> notFull.signal();</span><br><span class="line"> <span class="keyword">return</span> e;</span><br><span class="line"> } <span class="keyword">finally</span> {</span><br><span class="line"> lock.unlock();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 入队</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">enqueue</span><span class="params">(E e)</span> {</span><br><span class="line"> Object[] items = <span class="built_in">this</span>.items;</span><br><span class="line"> items[putIndex] = e;</span><br><span class="line"> <span class="keyword">if</span> (++putIndex == items.length)</span><br><span class="line"> putIndex = <span class="number">0</span>;</span><br><span class="line"> count++;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 出队</span></span><br><span class="line"> <span class="keyword">private</span> E <span class="title function_">dequeue</span><span class="params">()</span> {</span><br><span class="line"> Object[] items = <span class="built_in">this</span>.items;</span><br><span class="line"> <span class="type">E</span> <span class="variable">e</span> <span class="operator">=</span> (E) items[takeIndex];</span><br><span class="line"> items[takeIndex] = <span class="literal">null</span>;</span><br><span class="line"> <span class="keyword">if</span> (++takeIndex == items.length)</span><br><span class="line"> takeIndex = <span class="number">0</span>;</span><br><span class="line"> count--;</span><br><span class="line"> <span class="keyword">return</span> e;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h2 id="用栈实现">用栈实现</h2>
<p>栈的特点先进后出(FILO),阻塞队列的特点先进先出(FIFO),比较别扭不太合适。</p>
<p>硬要做也能做,思路是使用两个栈,一个入栈栈,一个出栈栈。</p>
<ul>
<li>入队操作:将元素入栈栈</li>
<li>出队操作:如果出栈栈不空,直接弹出;如果出栈栈为空,将入栈栈中所有元素依次弹出并进出栈栈,然后出栈栈弹出。</li>
<li>同样用 lock + Condition.await 实现阻塞和保证线程安全。</li>
</ul>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> java.util.Stack;</span><br><span class="line"><span class="keyword">import</span> java.util.concurrent.locks.Condition;</span><br><span class="line"><span class="keyword">import</span> java.util.concurrent.locks.ReentrantLock;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">StackBlockingQueue</span><E> {</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> Stack<E> inStack;</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> Stack<E> outStack;</span><br><span class="line"></span><br><span class="line"> <span class="type">int</span> capacity;</span><br><span class="line"> <span class="keyword">final</span> ReentrantLock lock;</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> Condition notEmpty;<span class="comment">// 不空,可以出</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> Condition notFull;<span class="comment">// 不满,可以进</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="title function_">StackBlockingQueue</span><span class="params">(<span class="type">int</span> capacity)</span> {</span><br><span class="line"> <span class="keyword">if</span> (capacity <= <span class="number">0</span>)</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>();</span><br><span class="line"></span><br><span class="line"> <span class="built_in">this</span>.inStack = <span class="keyword">new</span> <span class="title class_">Stack</span><>();</span><br><span class="line"> <span class="built_in">this</span>.outStack = <span class="keyword">new</span> <span class="title class_">Stack</span><>();</span><br><span class="line"> <span class="built_in">this</span>.capacity = capacity;</span><br><span class="line"> <span class="built_in">this</span>.lock = <span class="keyword">new</span> <span class="title class_">ReentrantLock</span>();</span><br><span class="line"> <span class="built_in">this</span>.notEmpty = lock.newCondition();</span><br><span class="line"> <span class="built_in">this</span>.notFull = lock.newCondition();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 往队列放元素</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">put</span><span class="params">(E e)</span> <span class="keyword">throws</span> InterruptedException {</span><br><span class="line"> <span class="keyword">if</span> (e == <span class="literal">null</span>)</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">NullPointerException</span>();</span><br><span class="line"></span><br><span class="line"> <span class="keyword">final</span> <span class="type">ReentrantLock</span> <span class="variable">lock</span> <span class="operator">=</span> <span class="built_in">this</span>.lock;</span><br><span class="line"> lock.lockInterruptibly();<span class="comment">// 加锁</span></span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">// 如果已满,阻塞并释放锁</span></span><br><span class="line"> <span class="keyword">while</span> (inStack.size() + outStack.size() >= capacity)</span><br><span class="line"> notFull.await();</span><br><span class="line"> <span class="comment">// 如果未满,入队</span></span><br><span class="line"> enqueue(e);</span><br><span class="line"> <span class="comment">// 唤醒挂在未空条件上的线程</span></span><br><span class="line"> notEmpty.signal();</span><br><span class="line"> } <span class="keyword">finally</span> {</span><br><span class="line"> lock.unlock();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 从队列取元素</span></span><br><span class="line"> <span class="keyword">public</span> E <span class="title function_">take</span><span class="params">()</span> <span class="keyword">throws</span> InterruptedException {</span><br><span class="line"> <span class="keyword">final</span> <span class="type">ReentrantLock</span> <span class="variable">lock</span> <span class="operator">=</span> <span class="built_in">this</span>.lock;</span><br><span class="line"> lock.lockInterruptibly();</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">// 如果已空,阻塞并释放锁</span></span><br><span class="line"> <span class="keyword">while</span> (outStack.isEmpty() && inStack.isEmpty())</span><br><span class="line"> notEmpty.await();</span><br><span class="line"> <span class="comment">// 如果未空,出队</span></span><br><span class="line"> <span class="type">E</span> <span class="variable">e</span> <span class="operator">=</span> dequeue();</span><br><span class="line"> <span class="comment">// 唤醒挂在未空条件上的线程</span></span><br><span class="line"> notFull.signal();</span><br><span class="line"> <span class="keyword">return</span> e;</span><br><span class="line"> } <span class="keyword">finally</span> {</span><br><span class="line"> lock.unlock();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 入队</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">enqueue</span><span class="params">(E e)</span> {</span><br><span class="line"> inStack.push(e);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 出队</span></span><br><span class="line"> <span class="keyword">private</span> E <span class="title function_">dequeue</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">if</span> (outStack.isEmpty()) {</span><br><span class="line"> <span class="keyword">while</span> (!inStack.isEmpty()) {</span><br><span class="line"> outStack.push(inStack.pop());</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> outStack.pop();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
]]></content>
<categories>
<category>技术</category>
</categories>
<tags>
<tag>Java</tag>
</tags>
</entry>
<entry>
<title>电信机顶盒无线改造</title>
<url>/posts/wireless-retrofit-for-telecom-STB/</url>
<content><![CDATA[<h2 id="场景">场景</h2>
<p>原本电信光猫 iTV 口接盒子,在盒子里认证输出 IPTV 信号给客厅电视,光猫的 LAN 口接无线路由器在路由器中拨号用来上网,如下图。现<strong>需要在卧室看电视</strong>,但是卧室没布线,考虑使用无线。</p>
<p><img src="https://blog-1258671016.cos.ap-chengdu.myqcloud.com/image/20210221095537.png" alt="原网络拓扑"></p>
<h2 id="设备清单">设备清单</h2>
<ul>
<li>电信天翼E家光纤猫(有WIFI模块,GPON天翼网关,华为定制,型号:EchoLife HS8145C)</li>
<li>电信机顶盒(IPTV盒子,有WIFI模块,烽火定制,型号HG680,个人猜测相似的型号 HG680-J 等只是固件不同,硬件条件都一样)</li>
<li>无线路由器</li>
<li>客厅电视(WIFI或者网线连接均可)</li>
<li>卧室电视(使用之前的到手的显示器 + 小音箱作为电视)</li>
</ul>
<h2 id="改造过程">改造过程</h2>
<h3 id="获取必要信息">获取必要信息</h3>
<ul>
<li>获取光猫配置地址、光猫WIFI名和密码(或者直接网线接 Lan 口)</li>
<li>获取光猫超级管理员账号密码,网络搜索可得,用户名:telecomadmin 密码:nE7jA%5m(仅适用于华为定制的电信光纤猫部分型号)</li>
<li>获取机顶盒设置密码:10000(如果不对,尝试6321、8288、10086、10010)</li>
</ul>
<h3 id="IPTV走无线网络">IPTV走无线网络</h3>
<p>参考<a href="https://zhuanlan.zhihu.com/p/27008904">改造“电信天翼E家”有线IPTV为无线</a>一文,访问光猫配置地址,使用超级管理员账号密码登录,可以查看到 iTV 端口桥接(连接名称:3_OTHER_B_VID_43),其他端口包括无线 WIFI 桥接上网,改造的思路:将无线网络也绑定到 iTV 业务。</p>
<p>在“<strong>网络 - 网络设置</strong>”中找到 iTV 的连接,修改“<strong>SSID 端口绑定</strong> ”,勾选“<strong>SSID1(无线网络)</strong>”保存,这样 iTV 端口和 光猫的无线网络都可以使用 IPTV 了。</p>
<p>在“<strong>应用 - IGMP设置</strong>”中启用“<strong>IGMP Snooping</strong>” 选项,这一步非常重要,否则网络中会有 IGMP 泛洪,导致观看 IPTV 时卡成 PPT😭,同时别的设备无法上网。</p>
<p><img src="https://blog-1258671016.cos.ap-chengdu.myqcloud.com/image/20210221095729.png" alt="新网络拓扑"></p>
<p>按照上图改造网络拓扑,输入维护密码进入设置界面,在“<strong>网络设置</strong>”中选择光猫的无线网络,勾选“<strong>高级选项 - PPPOE</strong>”。正常来说机顶盒激活后,配置信息会写入机器,无需填写 IPTV 的账号密码(如果没有,请在办理电信天翼E家网络及IPTV服务协议上找相关账号及密码认证)。连接后会获取一个 10.*.*.* 的电信的组播 IP,此时卧室电视就可以看了。</p>
<p><img src="https://blog-1258671016.cos.ap-chengdu.myqcloud.com/image/20210221114958.jpg" alt="设置界面"></p>
<p>由于显示器没有功放,考虑用蓝牙连接家里的小度音箱,尝试后发现蓝牙失败。网上搜索得知这款烽火 HG680 无蓝牙功能,小度音箱也无法接线输入音频,于是老老实实从家里翻出一个带音频输入线的小音箱,接显示器实现电视功能。</p>
<h3 id="盒子弃用IPTV">盒子弃用IPTV</h3>
<p>在设置网络的时候发现也可以连接路由器的无线网络,但是看不了IPTV,搜索得知 IPTV 只能使用组播网络,但是烽火 HG680 可以刷机或者通过其他手段安装 app,一下子就打开了新世界的大门。于是有了此方案,机顶盒连接路由器的无线网络,弃用 IPTV。</p>
<p><img src="https://blog-1258671016.cos.ap-chengdu.myqcloud.com/image/20210221124526.jpg" alt="电信机顶盒"></p>
<p>各地区类似型号的烽火机顶盒由于固件不同,可能需要不同的方式安装 app</p>
<ul>
<li>电脑和机顶盒处于同一网络下,直接 adb 安装</li>
<li>adb 连接不上,说明 USB 调试模式没打开,参考<a href="https://www.znds.com/forum.php?mod=viewthread&ordertype=1&tid=1174073">电信机顶盒开启ADB方法汇总</a>,如果还不行,则需要通过刷机打开 USB 模式/安装新 app,有风险</li>
<li>通过 <a href="https://www.znds.com/tv-644159-1-1.html">samba 共享</a>的方式将要安装的 apk 包塞到机顶盒中,通过文件浏览安装</li>
</ul>
<p>由于电脑是 deepin,安装 adb 后可以连接,windows 电脑安装 adb 请百度搜索此不赘述</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 安装adb工具</span></span><br><span class="line"><span class="built_in">sudo</span> apt install adb -y</span><br><span class="line"><span class="comment"># ip是盒子的ip,成功后显示Success</span></span><br><span class="line">adb connect 192.168.11.238</span><br><span class="line"><span class="comment"># xxx.apk表示要安装的app,最好使用绝对路径,一般先安装当贝桌面</span></span><br><span class="line">adb install xxx.apk</span><br></pre></td></tr></table></figure>
<p>安装成功后重启机顶盒,开机后一般默认进入当贝桌面。如果还是 IPTV 界面,可以从“应用商城”中找到当贝桌面,打开后设置开机自启。然后安装需要的应用市场/直播/其他 app 即可。毕竟规格不大,不要安装太多,会卡。从当贝桌面也可以进 IPTV,但是使用 IPTV 需要切换为光猫的无线网络。</p>
<h3 id="方案对比">方案对比</h3>
<table>
<thead>
<tr>
<th></th>
<th>方案一:IPTV走无线网络</th>
<th>方案二:弃用IPTV直接使用盒子</th>
</tr>
</thead>
<tbody>
<tr>
<td>优点</td>
<td>高清直播,换频道不卡</td>
<td>新世界,自由度高</td>
</tr>
<tr>
<td>缺点</td>
<td>只能使用运营商上提供的资源,很多都要收费</td>
<td>会占用部分上网带宽,直播体验没有IPTV好,可以切换回IPTV,需要切换回光猫的无线网络</td>
</tr>
</tbody>
</table>
<p>经过权衡,使用了方案二,找了个直播软件,差不多可以替换 IPTV,体验不错。</p>
]]></content>
<categories>
<category>生活</category>
</categories>
<tags>
<tag>折腾</tag>
</tags>
</entry>
<entry>
<title>读《盐铁论》</title>
<url>/posts/read-discourses-on-salt-and-iron/</url>
<content><![CDATA[<h2 id="前言">前言</h2>
<p>《盐铁论》读的是<a href="https://book.douban.com/subject/26417290/">中华书局的版本</a>,书中记录的是西汉汉昭帝时的一场盐铁政策辩论会,辩论一方是御史大夫桑弘羊,文学贤良是另一方。桑弘羊在汉武帝时就主推的盐铁官营、酒类专卖、均输、平准、算缗、告缗、铸五铢钱等政策被文学贤良一方针锋相对。政治、经济、文化、军事、外交也都有涉及,从每章节标题就可以看得出来。最初是作为经济学入门来读,现在这种辩论已经很难看到了。</p>
<h2 id="摘录">摘录</h2>
<blockquote>
<p>夫导民以德,则民归厚;示民以利,则民俗薄。</p>
</blockquote>
<p>这是文学说的,有道理,但实际情况是君主往往根据需要调整,台面上说的和台面下做的并不一致,实用主义。比如以儒家来教化民众,因为儒家思想有利于统治。也有所谓的“外儒内法”一说。</p>
<blockquote>
<p>开委府于京师,以笼货物。贱即买,贵则卖。是以县官不失实,商贾无所贸利,故曰平准。</p>
</blockquote>
<p>高卖低买来平抑物价。</p>
<blockquote>
<p>富在术数,不在劳身;利在势居,不在力耕也。</p>
</blockquote>
<p>振聋发聩!</p>
<blockquote>
<p>如今世俗败坏,竞相奢侈浪费,妇女手工制品要求极其精细,工匠制品要求极其奇巧,雕琢素朴的东西,崇尚珍奇怪物,开山凿石来寻找金银,潜入深水来寻求珠玑,设置机关陷阱来捕捉犀牛大象,张设网罗来猎捕翡翠,寻求边远蛮荒地区的宝物来迷乱中国,转运邛、筰的货物,到达东边沿海地区,交流万里之外的财物,耽误时日,耗费功效,无益于实用。所以平民夫妇,忙得精疲力竭,却衣食不足。</p>
</blockquote>
<p>以现在的眼光看有点迂腐。</p>
<blockquote>
<p>故山泽无征,则君臣同利。</p>
</blockquote>
<p>有点勉强,文学的天真。</p>
<blockquote>
<p>币数变而民滋伪。</p>
</blockquote>
<p>通过发行新币来攫取民间财富,民众愈穷苦,则“滋伪”,饥寒起盗心。</p>
<blockquote>
<p>上好礼则民闇饰,上好货则下死利也。</p>
</blockquote>
<p>楚王好细腰,宫中多饿死。</p>
<blockquote>
<p>算不及蛮、夷则不行。</p>
</blockquote>
<p>把政策想清楚影响和后果,再发布和施行。</p>
<blockquote>
<p>权利深者,不在山海,在朝廷;一家害百家,在萧墙,而不在朐邴也。</p>
</blockquote>
<blockquote>
<p>当世之工匠,不能调其凿枘,则改规矩,不能协声音,则变旧律。</p>
</blockquote>
<p>用来看现在一点不为过,A股为了到达3000点修改指数、统计局为了失业率好看修改统计口径……比比皆是。</p>
<blockquote>
<p>原文:故任能者责成而不劳,任己者事废而无功。</p>
<p>译文:因此任用贤能的人,责其完成任务,自己不会劳苦;只信任自己的人导致事业荒废,没有功效。</p>
</blockquote>
<p>李作为一个温和改革派中后期被架空,搞经济的专业人士被架空,不胜唏嘘。</p>
<blockquote>
<p>如今在位的公卿,既不能像燕昭王那样礼贤下士,像《诗经·小雅·鹿鸣》那样娱乐贤人,又亲身施行臧文仲、子椒的做法,嫉贤妒能,抬高自己的才智,诋毁他人的才能,自满自得,不肯向他人请教,轻视贤士,不愿与贤士做朋友,倚仗自己的地位,凌驾于贤人之上,以自己优厚的俸禄在贤士面前骄傲,却要求士人为己所用,这当然是很难的了!</p>
</blockquote>
<blockquote>
<p>原文:大抵逋流,皆在大家,吏正畏惮,不敢笃责,刻急细民,细民不堪,流亡远去;</p>
<p>译文:大抵逃税的都是豪强大户,地方官员害怕他们,不敢深责他们交税,于是就更加苛刻地急催小民,小民忍受不了,于是流亡到远方。</p>
</blockquote>
<p>土地兼并,生产资料流转到豪强和有权势的人手中。</p>
<blockquote>
<p>陛下富于春秋,委任大臣,公卿辅政,政教未均,故庶人议也。</p>
</blockquote>
<p>这个指摘可以的,现在谁还敢这么说,分分钟禁言。</p>
<blockquote>
<p>非人主用心,好事之臣为县官计过也。</p>
</blockquote>
<p>前面大夫以君主为盾,文学以史为鉴迂回战术直言好事之臣的锅,这辩论技巧。</p>
<blockquote>
<p>原文:大夫曰:“挟管仲之智者,非为厮役之使也。怀陶硃之虑者,不居贫困之处。文学能言而不能行,居下而讪上,处贫而非富,大言而不从,高厉而行卑,诽誉訾议,以要名采善于当世。夫禄不过秉握者,不足以言治,家不满檐石者,不足以计事。儒皆贫羸,衣冠不完,安知国家之政,县官之事乎?何斗辟造阳也!”</p>
<p>译文:大夫说:“拥有管仲智慧的人,不会担任奴仆的职务。怀藏陶朱公思虑的人,不会居住在贫困的处所。文学能说而不能行,居下位而诽谤上司,身处贫困而非难富有,说大话而做不到,外表清高却行为卑鄙,诽谤他人声誉,妄发议论,以此向当世求取好名声。俸禄不过一束禾一把米的人,不足以谈论国家治理,家产还不到一石粮的人,不足以筹划朝廷大事。儒生们都很贫苦,衣冠尚且不完备,怎么知道国家大政、朝廷之事呢?还侈谈什么绝险僻远、舍弃造阳的事!”</p>
</blockquote>
<p>大夫的言辞犀利态度傲慢。从现实角度看,能言不能行,解决不了问题白搭。</p>
<blockquote>
<p>古之君子,守道以立名,修身以俟时,不为穷变节,不为贱易志,惟仁之处,惟义之行。临财苟得,见利反义,不义而富,无名而贵,仁者不为也。</p>
</blockquote>
<p>不为也,说的没毛病。从另一个角度看,也可以说文学在抱残守缺,时代在变,人也在变。</p>
<blockquote>
<p>未有不能自足而能足人者也。未有不能自治而能治人者也。</p>
</blockquote>
<blockquote>
<p>大夫曰:“所谓文学高第者,智略能明先王之术,而姿质足以履行其道。故居则为人师,用则为世法。今文学言治则称尧、舜,道行则言孔、墨,授之政则不达,怀古道而不能行,言直而行枉,道是而情非,衣冠有以殊于乡曲,而实无以异于凡人。诸生所谓中直者,遭时蒙幸,备数适然耳,殆非明举所谓,固未可与论治也。”</p>
</blockquote>
<p>大夫批驳对方解决不了实际问题,只用孔孟之道高谈阔论。</p>
<blockquote>
<p>文学曰:“天设三光以照记,天子立公卿以明治。故曰:公卿者,四海之表仪,神化之丹青也。上有辅明主之任,下有遂圣化之事,和阴阳,调四时,安众庶,育群生,使百姓辑睦,无怨思之色,四夷顺德,无叛逆之忧,此公卿之职,而贤者之所务也。</p>
</blockquote>
<p>文学要求公卿怎样怎样,上劝谏君王,下教化百姓,要所有人做圣人,其实是做不到的。</p>
<blockquote>
<p>原文:塞士之涂,壅人之口,道谀日进而上不闻其过,此秦所以失天下而殒社稷也。</p>
<p>译文:堵塞士人进身道路,堵住民众的嘴巴,说奉承话的人步步高升,皇上听不见过错,这就是秦王朝失去天下、毁灭社稷的原因啊。</p>
</blockquote>
<p>阶级固化,给民众发口罩眼罩,张维为之流被搬上台面,是丧失信心的原因之一。</p>
<blockquote>
<p>饭蔬粝者不可以言孝,妻子饥寒者不可以言慈,绪业不修者不可以言理。</p>
</blockquote>
<p>别整天讲那些大道理,过好自己的生活是根本。</p>
<blockquote>
<p>言者不必有德,何者?言之易而行之难。</p>
</blockquote>
<blockquote>
<p>况且《诗经·小雅》批评他人,一定会拿出办法来替代弊政。</p>
</blockquote>
<blockquote>
<p>百姓或短褐不完,而犬马衣文绣,黎民或糟糠不接,而禽兽食粱肉。</p>
</blockquote>
<p>文学直言贫富差距悬殊。</p>
<blockquote>
<p>贤良认为导致国家财政匮乏的根本原因是权贵阶层的豪奢之风,要求公卿大夫子孙做到节制车马,衣着适当,亲身节俭,以敦厚朴实作为民众表率,罢弃园林池苑,减少良田豪宅。</p>
</blockquote>
<p>敲在了鼓边。儒家那一套,要求每个人怎样怎样。浪漫的理想主义,做不到就是空想。</p>
<blockquote>
<p>在朝政腐败形势下要想百官廉洁,是不可能的。要想影子正,先端正标杆,要想下级廉洁,长官先要自身做表率。因此贪污粗鄙根源在长官而不在下级,教诲训示的重点在朝政而不在民众。</p>
</blockquote>
<p>“其身正,不令而行;其身不正,虽令不从”。说不上有多大效果,但做比不做好。</p>
<blockquote>
<p>大夫首先指出,部分民众贫穷是由于他们懒惰造成的,朝廷不应该同情此类懒惰之人。</p>
</blockquote>
<p>与“农民没有交过社保,老时自然没有多少养老金”、"不能发钱,发钱养懒汉"的论调何其相似。</p>
<blockquote>
<p>贤良认为,鼓励农耕关键在于不误农时,而不是搞一些悬挂青旗、鞭策土牛的劝农形式。</p>
</blockquote>
<blockquote>
<p>《老子》曰:‘上无欲而民朴,上无事而民自富。’</p>
</blockquote>
<p>上面不瞎折腾,民众努力创造财富自然会富起来。整天形式主义各种开会出政策,虚头巴脑,毫无用处。也不全是,唯一的用处大概是宣传我们也是做了很多事情的。</p>
<blockquote>
<p>原文:不轨之民,困桡公利,而欲擅山泽。从文学、贤良之意,则利归于下,而县官无可为者。上之所行则非之,上之所言则讥之,专欲损上徇下,亏主而适臣。</p>
<p>译文:一些不守法的庶民,困扰削弱国家利益,想占有山泽资源。如果顺从文学、贤良的意见,那么山泽资源利益就会归于下民,朝廷就没有什么作为了。朝廷所推行的政策你们就非难,朝廷官员说话你们就反唇相讥,一心想损害朝廷,谋取下层利益,亏损君主,迎合臣民。</p>
</blockquote>
<p>有所作为?政府喜欢凯恩斯那一套,从结果看是加强管控,民生凋敝。</p>
<blockquote>
<p>公曰:‘天寒哉?我何不寒哉?’人之言曰:‘安者不能恤危,饱者不能食饥。’故余粱肉者难为言隐约,处佚乐者难为言勤苦。</p>
</blockquote>
<p>何不食肉糜。专家的“房子租出来就有收入”“稳中向好”……把这类专家的津贴减半,精简体制人员,减轻纳税人负担。</p>
<blockquote>
<p>夫高堂邃宇、广厦洞房者,不知专屋狭庐、上漏下湿者之癐也。系马百驷、货财充内、储陈纳新者,不知有旦无暮、称贷者之急也。广第唐园、良田连比者,不知无运踵之业、窜头宅者之役也。原马被山,牛羊满谷者,不知无孤豚瘠犊者之窭也。高枕谈卧、无叫号者,不知忧私责与吏正戚者之愁也。被纨蹑韦、搏粱啮肥者,不知短褐之寒、糠謪之苦也。从容房闱之间、垂拱持案食者,不知蹠耒躬耕者之勤也。乘坚驱良、列骑成行者,不知负檐步行者之劳也。匡床旃席、侍御满侧者,不知负辂挽舩、登高绝流者之难也。衣轻暖、被美裘、处温室、载安车者,不知乘边城、飘胡、代、乡清风者之危寒也。妻子好合、子孙保之者,不知老母之憔悴、匹妇之悲恨也。耳听五音、目视弄优者,不知蒙流矢、距敌方外者之死也。东向伏几、振笔如调文者,不知木索之急、棰楚者之痛也。坐旃茵之上,安图籍之言若易然,亦不知步涉者之难也。</p>
</blockquote>
<blockquote>
<p>双方论争的实质是王霸之争,即究竟是以仁德感化还是以武力征伐的方式来处理夷夏矛盾。</p>
</blockquote>
<p>站当时情形看,当恩威并施。理解为对少数民族的政策,亦如是。</p>
<blockquote>
<p>辔衔者,御之具也,得良工而调。法势者,治之具也,得贤人而化。执辔非其人,则马奔驰。执轴非其人,则船覆伤。</p>
</blockquote>
<blockquote>
<p>故世不患无法,而患无必行之法也。</p>
</blockquote>
<p>法律得不到落实和执行依然没有用。</p>
<blockquote>
<p>民之仰法,犹鱼之仰水,水清则静,浊则扰;扰则不安其居,静则乐其业;乐其业则富,富则仁生,赡则争止。</p>
</blockquote>
<h2 id="提问">提问</h2>
<h3 id="为何要读?">为何要读?</h3>
<p>前年读了几本经济学的书,很受启发。有网友推荐《盐铁论》作为经济学入门,看着挺有趣,便加到待看书单。</p>
<h3 id="读一年之久?书该怎么读?">读一年之久?书该怎么读?</h3>
<p>开始读还挺带劲,而后用典慢慢增多,看的晕头转向。上学时被文言文支配的感觉上来了,两章之内必睡着,堪称催眠入睡神器。这种情况读了几个月,五百多页的书没多少进展,决定换一种方法。读这本书的目的,于我而言是经济学入门,了解讲的内容就行,没必要深究。看标题题解,通读原文,放弃各种注释,直接看译文。</p>
<p>书该怎么读?</p>
<ul>
<li>选自己感兴趣的书。</li>
<li>读不进去不要硬读,或许只是当前阶段不太匹配这本书。</li>
<li>确定读这本书的目的。</li>
<li>最好读完提几个问题,试着回答。</li>
</ul>
<h3 id="有什么感受?">有什么感受?</h3>
<p>很多地方感觉文学说的在理,可能因为传统教育就是以儒家思想为底子。什么三纲五常,本质上是要求每个人:执政者要怎样、官吏要怎样、家庭中要怎样、平民百姓要怎样。太理想主义,其实是做不到的。相比之下桑弘羊一方更实际,能解决问题。看看自己身上理想化的部分,等到实际去解决问题,发现完全不是那么回事。而且因为长久以来接受的是不完整的儒家思想,往往会被这些条条框框束缚限制自身发展。</p>
<h3 id="没有变的是什么?">没有变的是什么?</h3>
<p>文学贤良来自民间,对盐铁政策下出现的各种问题看的清楚,诸如流民失地、贫富差距悬殊、腐败,但没有能力改变。主导盐铁政策的桑弘羊重点在解决具体问题,但态度傲慢并不了解底层之苦。这不是跟现在一模一样嘛。</p>
<p>看似是文学贤良一方取得暂时的胜利,但朝廷仅接受文学贤良部分建议废除酒类专卖,其他政策依旧施行,甚至就是文学贤良的支持者霍光施行的。盐铁辩论之后,民众的生活并没有什么改善。</p>
<p>从经济学角度来看,桑弘羊主张的是大政府,资源管制,集中力量办大事。文学贤良一方倡导自由主义经济,放松资源管制,藏富于民。暴露的问题和其中的经济学规律,现在看也并未过时。</p>
<p>我们能做什么?了解其中规律,在此之下做出正确决策,过好自己的生活。</p>
]]></content>
<categories>
<category>思考</category>
</categories>
<tags>
<tag>经济学</tag>
</tags>
</entry>