-
Notifications
You must be signed in to change notification settings - Fork 0
/
local-search.xml
983 lines (471 loc) · 466 KB
/
local-search.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>Rust Analyzer 小技巧——自动创建 module 文件</title>
<link href="/2024/06/02/Rust%20Analyzer%20%E5%B0%8F%E6%8A%80%E5%B7%A7%E2%80%94%E2%80%94%E8%87%AA%E5%8A%A8%E5%88%9B%E5%BB%BA%20module%20%E6%96%87%E4%BB%B6/"/>
<url>/2024/06/02/Rust%20Analyzer%20%E5%B0%8F%E6%8A%80%E5%B7%A7%E2%80%94%E2%80%94%E8%87%AA%E5%8A%A8%E5%88%9B%E5%BB%BA%20module%20%E6%96%87%E4%BB%B6/</url>
<content type="html"><![CDATA[<p>在 Rust 编程过程中,当我们想要新增一个模块时,往往需要手动去创建相应的 .rs 文件,这一过程可能会稍显繁琐。然而,有了 rust-analyzer 的这个 quickfix 之后,情况就大不一样了。</p><p><img src="/img/blog_pic/2024/1.gif" alt="" /></p><p>现在,我们只需要在父模块中用 <code>mod xxx;</code> 声明这个新模块,然后应用 rust-analyzer 对应的智能修复,它就会自动帮我们创建对应的 .rs 文件。</p><p>如果是直接手动创建 .rs 文件,那么这个 .rs 文件是不会被 rust-analyzer “分析”的,因为不在 module tree 中,这时候可以点击文件最上方的小灯泡,手动应用 quickfix 将这个文件加入到 parent module 中</p><p><img src="/img/blog_pic/2024/2.gif" alt="" /></p><p>我了解到这个技巧是在看 rust-analyzer 作者的视频的时候,他在开发的时候也用到这个 quickfix,这是一个推荐的方式,不必自己手动创建 .rs 文件。</p>]]></content>
<categories>
<category>Rust Analyzer</category>
</categories>
</entry>
<entry>
<title>Rust Analyzer 的 IDE 功能实现 Series 1 —— LSP</title>
<link href="/2024/01/02/Rust%20Analyzer%20%E7%9A%84%20IDE%20%E5%8A%9F%E8%83%BD%E5%AE%9E%E7%8E%B0%20Series%201%20%E2%80%94%E2%80%94%20LSP/"/>
<url>/2024/01/02/Rust%20Analyzer%20%E7%9A%84%20IDE%20%E5%8A%9F%E8%83%BD%E5%AE%9E%E7%8E%B0%20Series%201%20%E2%80%94%E2%80%94%20LSP/</url>
<content type="html"><![CDATA[<h2 id="前言"><a class="markdownIt-Anchor" href="#前言"></a> 前言</h2><p>最近给 Rust Analyzer(RA) 贡献了<a href="https://github.com/rust-lang/rust-analyzer/pulls?q=is%3Apr+author%3AYoung-Flash+is%3Aclosed">一些 PR</a>,在这过程中对于它的 IDE 功能实现挺感兴趣的,于是有了新年的这第一篇博客,记录一下我在探索 Rust Analyzer 的 IDE 功能实现的过程。首先想先从 LSP(Language Server Protocol) 在 Rust Analyzer 中的实现开始,后面可能会介绍一些其他内容,比如 RA 服务端的 AST 解析、智能 assist 和 diagnostic quickfix 实现、HIR lowering … (有可能会鸽哈哈哈哈)</p><h2 id="rust-analyzer-介绍"><a class="markdownIt-Anchor" href="#rust-analyzer-介绍"></a> Rust Analyzer 介绍</h2><p><a href="https://github.com/rust-lang/rust-analyzer">Rust Analyzer</a> 是由 Rust 社区维护的 Rust 语言 IDE 功能实现,Rust Analyzer 插件搭配 VSCode 可以得到一个现代化的智能 Rust IDE,这或许是目前最流行的 Rust 开发环境。我觉得对于一门新的编程语言,在 VSCode 的强大的编辑器功能上构建 language-specific 的 IDE 功能是兼顾开发成本和用户体验的最佳选择,毕竟从零开发一个 IDE 所需的成本可想而知,而且就算开发出来了短时间内用户体验也比不上经过千锤百炼的 VSCode。</p><p>从用户的角度而言,Rust Analyzer 只是 VSCode 插件市场上的一个插件,安装完 Rust 工具链之后再安装这个插件就能得到开箱即用的 IDE 功能。为了达到这个目的,RA 背后做了不少工作。在它的 GitHub 主页 上的 About 介绍上是这么写的:<strong>A Rust compiler front-end for IDEs</strong>;是的,RA 首先是一个编译器前端,准确的说,它是一个旨在实现 IDE 功能的编译器前端。</p><p>这篇文章主要会聚焦在Rust Analyzer 的 IDE 功能实现中的 LSP 部分,LSP 由微软主导,将语言服务器(一个代码分析工具)和语言客户端(VSCode、Sublime Text…)之间的通信进行了标准化,这样一来插件开发者就可以只写一次代码分析程序,然后在多个编辑器中重用。关于 LSP 的更多内容可以查看<a href="https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/">微软的官方主页</a>。</p><p>Rust Analyzer 的客户端(VSCode 插件)代码和语言服务器代码都在同一个仓库中,editors/code 目录下是 VSCode 客户端插件代码,其余均为服务端代码;本文写作时参考的 Rust Analyzer 代码版本为 <a href="https://github.com/rust-lang/rust-analyzer/tree/e1e4626f">e1e4626f</a>。</p><h2 id="client-和-server-的协作方式"><a class="markdownIt-Anchor" href="#client-和-server-的协作方式"></a> Client 和 Server 的协作方式</h2><p>Rust Analyzer 客户端和服务端在同一台机器的不同进程中运行。客户端插件激活后会 <a href="https://github.com/microsoft/vscode-languageserver-node/blob/3f725d8368ec0c75f390a37e4e159d78bc1527ac/client/src/node/main.ts#L353">spawn 一个进程运行服务端</a>。RA 是 C/S 工作模式,不过却是一个 Server 服务一个 Client,这也是我看了代码之后才知道。于是想着验证一下,我在 VSCode 中打开一个新的 Rust Workspace,用 ps 命令查看活动进程后会发现多出一个新的名为 rust-analyzer 的进程。这点倒是出乎我的意料,我还以为以为是一对多呢🤔。不过一对一也合理,这样其中一个 crash 了也不会影响到其他 Client Workspace。</p><p>Client 和 Server 这两个进程使用 stdio 进行通信,客户端进程向自己的 stdout 发出的请求服务端会在自己的 stdin 接收到,服务端进程向自己的 stdout 返回响应则会被客户端进程从 stdin 接收。当然,RA 服务端还实现了基于 socket 的 TCP 通信方式,未来不排除有本地 client 连接远程 server 的可能。<br /><img src="/img/blog_pic/2024/conn.png" alt="" /></p><h2 id="rust-analyzer-client"><a class="markdownIt-Anchor" href="#rust-analyzer-client"></a> Rust Analyzer Client</h2><p>首先直观地对比一下 Rust Analyzer 客户端和服务端的代码量,客户端 TypeScript 代码不过 5k 行,而服务端 Rust 代码达到了 36w 行。<br /><img src="/img/blog_pic/2024/RA-client-code.png" alt="" /> <img src="/img/blog_pic/2024/RA-server-code.png" alt="" /></p><p>客户端代码量能做到这么少,是因为 Rust Analyzer 在客户端使用了 <a href="https://github.com/microsoft/vscode-languageserver-node/tree/main/client">vscode-languageclient</a> 这个库。<code>vscode-languageclient</code> 是一个 Node.js 库,它提供了一种在 VSCode 插件中实现 LSP 客户端的方式。这个库提供了一些高级的抽象,使得插件可以十分容易地与 LSP 服务器进行交互。客户端代码中只要注册某些处理特定 LSP 事件回调函数,<code>vscode-languageclient</code> 会自动在特定的事件发生后发出对应的 LSP 请求并且调用对应的回调函数处理来自服务端的响应。</p><p>比如,当用户在 VS Code 中打开一个 .rs 文件时,这个操作对应一个文件打开事件,<code>vscode-languageclient</code> 则会自动发送 LSP 中的 <a href="https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_didOpen">textDocument/didOpen</a> 请求到服务端,服务端收到会分析打开的这个文件,然后通过一个 <a href="https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_publishDiagnostics">textDocument.publishDiagnostics</a> 响应发送给客户端这个文件上的代码诊断,客户端收到这个响应后 <code>vscode-languageclient</code> 会使用默认的处理逻辑处理这个响应(当然也可以 override 为其他逻辑),也就是把这些代码诊断在编辑器中源代码对应的位置展示出来提示用户;最终用户会看到这样的效果:<br /><img src="/img/blog_pic/2024/unused_diag.png" alt="" /></p><p>可视化客户端和服务端的通信过程,大概是这样的效果:</p><center><img src="/img/blog_pic/2024/client-server.png" width="70%"></center><p>对于 LSP 规范中定义的方法,<code>vscode-languageclient</code> 会自动发送请求和处理响应。对于 LSP 规范中没有定义的方法,插件需要使用 <code>LanguageClient.sendRequest</code> 函数来显式地向服务器发送请求。比如,Rust Analyzer 定义了一些<a href="https://github.com/Young-Flash/rust-analyzer/blob/master/docs/dev/lsp-extensions.md">扩展方法</a>(<code>Show Syntax Tree</code> 查看当前文件的抽象语法树,<code>Expand macro</code> 查看宏展开后的代码)来提供一些 LSP 规范中没有的功能。<br /><img src="/img/blog_pic/2024/ext.png" alt="" /></p><h2 id="rust-analyzer-server"><a class="markdownIt-Anchor" href="#rust-analyzer-server"></a> Rust Analyzer Server</h2><p>Rust Analyzer 的所有语言服务功能都是在服务端中实现的,为了支持诸如跳转到定义、引用查找、代码诊断等 IDE 功能它实现了一个囊括了 <strong>lexering</strong>、<strong>parsing</strong>、<strong>IR lowering</strong>、<strong>type inferencing</strong> 等阶段的完整的编译器前端,这也是为什么 RA 服务端代码量达到 36w 行的原因。</p><p>服务端的入口点在 <a href="https://github.com/rust-lang/rust-analyzer/blob/cf52c4b2b3367ae7355ef23393e2eae1d37de723/crates/rust-analyzer/src/bin/main.rs#L170">run_server</a>,在这里创建一对 crossbeam_channel 的 Sender 和 Receiver 用于收发消息,spawn 出两个线程分别监听 stdin 和 stdout,来自客户端的请求会通过 stdin 传达到 Receiver 中,服务端处理完请求后会将响应通过 Sender 输出到 stdout 从而被客户端接接收。</p><p>完成收发消息的准备工作后这一对 crossbeam_channel 会被传递到 <a href="https://github.com/rust-lang/rust-analyzer/blob/cf52c4b2b3367ae7355ef23393e2eae1d37de723/crates/rust-analyzer/src/main_loop.rs#L31">main_loop</a> 中,这也是整个服务端的核心。LSP 请求到达 stdin 后,控制流会来到 main_loop 中,根据不同的请求类型(比如要求跳转到定义、提供 inlay hint 等)分派到不同的处理逻辑中。</p><h2 id="一个具体的例子"><a class="markdownIt-Anchor" href="#一个具体的例子"></a> 一个具体的例子</h2><p>以请求当前光标所在源代码位置的可用 CodeAction 为例,假设当前光标所在位置如下(<code>|</code> 所在位置):</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">fn</span> <span class="hljs-title function_">test</span>() {<br> <span class="hljs-keyword">let</span> <span class="hljs-variable">var</span> = <span class="hljs-title function_ invoke__">Some</span>(<span class="hljs-number">1</span>);<br> <span class="hljs-keyword">if</span> var.<span class="hljs-title function_ invoke__">is_some</span>()| {<br><br> }<br>}<br></code></pre></td></tr></table></figure><p>此时客户端会发送一个 <a href="https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_codeAction">textDocument/codeAction</a> 类型的 LSP 请求,携带了当前文件以及光标所在具体位置等信息,具体如下:</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><code class="hljs json"><span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"textDocument"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"uri"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"file:///home/flash/projects/ra/src/temp.rs"</span><br> <span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"range"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"start"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"line"</span><span class="hljs-punctuation">:</span> <span class="hljs-number">3</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"character"</span><span class="hljs-punctuation">:</span> <span class="hljs-number">10</span><br> <span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"end"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"line"</span><span class="hljs-punctuation">:</span> <span class="hljs-number">3</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"character"</span><span class="hljs-punctuation">:</span> <span class="hljs-number">10</span><br> <span class="hljs-punctuation">}</span><br> <span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span><br> ...<br><span class="hljs-punctuation">}</span><br></code></pre></td></tr></table></figure><p>服务端收到后请求并将其序列化为一个内部表示的 lsp_types::CodeActionParams 对象,接着将这个对象代表的请求分派到 <a href="https://github.com/rust-lang/rust-analyzer/blob/cf52c4b2b3367ae7355ef23393e2eae1d37de723/crates/rust-analyzer/src/handlers/request.rs#L1117">handle_code_action</a> 中进行处理,处理过程中发现有两个 CodeAction 适用于光标所在的上下文(为减少篇幅这里我们假设只有两个),一个是将 <code>is_some</code> 写法改为 <code>if let Some</code>,另一个是将 <code>if</code> 写法改为 <code>match</code> 的形式。这两项 CodeAction 分别是在 <a href="https://github.com/rust-lang/rust-analyzer/tree/cf52c4b2b3367ae7355ef23393e2eae1d37de723/crates/ide-assists/src/handlers">crates/ide-assists/src/handlers</a> 目录下的 <a href="https://github.com/rust-lang/rust-analyzer/blob/cf52c4b2b3367ae7355ef23393e2eae1d37de723/crates/ide-assists/src/handlers/replace_is_method_with_if_let_method.rs#L4">replace_is_method_with_if_let_method.rs</a>(这是我给 RA 提的<a href="https://github.com/rust-lang/rust-analyzer/pull/15743">第一个 PR</a>) 和 <a href="https://github.com/rust-lang/rust-analyzer/blob/cf52c4b2b3367ae7355ef23393e2eae1d37de723/crates/ide-assists/src/handlers/replace_if_let_with_match.rs">replace_if_let_with_match.rs</a> 中基于 AST 进行分析得到的,这个目录下还有许多类似的 .rs 文件,每一个都代表了一个特定上下文中可用的 CodeAction 的分析过程,在处理一个 CodeAction 请求时,其中的每一个文件中的逻辑都会被执行,如果这个 CodeAction 适用于当前上下文则会返回 Some 否则则是 None。</p><p><img src="/img/blog_pic/2024/demo.gif" alt="" /> 对于前面举的例子中的两个可用 CodeAction,服务端会返回这样的响应,其中的 <code>title</code> 字段也就是我们点击了💡后所看到的内容。</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><code class="hljs json"><span class="hljs-punctuation">[</span><br> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"title"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"Replace `is_some` with `if let Some`"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"kind"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"refactor.rewrite"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"data"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"codeActionParams"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"textDocument"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"uri"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"file:///home/flash/projects/ra/src/temp.rs"</span><br> <span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"range"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"start"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"line"</span><span class="hljs-punctuation">:</span> <span class="hljs-number">3</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"character"</span><span class="hljs-punctuation">:</span> <span class="hljs-number">10</span><br> <span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"end"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"line"</span><span class="hljs-punctuation">:</span> <span class="hljs-number">3</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"character"</span><span class="hljs-punctuation">:</span> <span class="hljs-number">10</span><br> <span class="hljs-punctuation">}</span><br> <span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span><br> ...<br> <span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"id"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"replace_is_some_with_if_let_some:RefactorRewrite:2"</span><br> <span class="hljs-punctuation">}</span><br> <span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span><br> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"title"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"Replace if with match"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"kind"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"refactor.rewrite"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"data"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"codeActionParams"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"textDocument"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"uri"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"file:///home/flash/projects/ra/src/temp.rs"</span><br> <span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"range"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"start"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"line"</span><span class="hljs-punctuation">:</span> <span class="hljs-number">3</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"character"</span><span class="hljs-punctuation">:</span> <span class="hljs-number">10</span><br> <span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"end"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"line"</span><span class="hljs-punctuation">:</span> <span class="hljs-number">3</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"character"</span><span class="hljs-punctuation">:</span> <span class="hljs-number">10</span><br> <span class="hljs-punctuation">}</span><br> <span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span><br> ...<br> <span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"id"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"replace_if_let_with_match:RefactorRewrite:4"</span><br> <span class="hljs-punctuation">}</span><br> <span class="hljs-punctuation">}</span><br><span class="hljs-punctuation">]</span><br></code></pre></td></tr></table></figure><p>当我们决定应用其中一项时(假设是 Replace <code>is_some</code> with <code>if let Some</code>),点击对应的文字,此时客户端会发出一个 <a href="https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#codeAction_resolve">codeAction/resolve</a> 请求,向服务端请求应用这个 CodeAction 后源代码会如何被改写,</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><code class="hljs json"><span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"title"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"Replace `is_some` with `if let Some`"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"kind"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"refactor.rewrite"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"data"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"codeActionParams"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"textDocument"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"uri"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"file:///home/flash/projects/ra/src/temp.rs"</span><br> <span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"range"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"start"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"line"</span><span class="hljs-punctuation">:</span> <span class="hljs-number">3</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"character"</span><span class="hljs-punctuation">:</span> <span class="hljs-number">10</span><br> <span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"end"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"line"</span><span class="hljs-punctuation">:</span> <span class="hljs-number">3</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"character"</span><span class="hljs-punctuation">:</span> <span class="hljs-number">10</span><br> <span class="hljs-punctuation">}</span><br> <span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"context"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"diagnostics"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><span class="hljs-punctuation">]</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"triggerKind"</span><span class="hljs-punctuation">:</span> <span class="hljs-number">2</span><br> <span class="hljs-punctuation">}</span><br> <span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"id"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"replace_is_some_with_if_let_some:RefactorRewrite:2"</span><br> <span class="hljs-punctuation">}</span><br><span class="hljs-punctuation">}</span><br></code></pre></td></tr></table></figure><p>服务端根据请求中的信息进行计算分析后,返回给客户端具体的响应,其中的 <code>newText</code> 和 <code>range</code> 字段告诉客户端把 <code>range</code> 范围中的旧代码替换为 <code>newText</code> 表示的内容。</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><code class="hljs json"><span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"title"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"Replace `is_some` with `if let Some`"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"kind"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"refactor.rewrite"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"edit"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"documentChanges"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><br> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"textDocument"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"uri"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"file:///home/flash/projects/ra/src/temp.rs"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"version"</span><span class="hljs-punctuation">:</span> <span class="hljs-number">63</span><br> <span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"edits"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><br> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"range"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"start"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"line"</span><span class="hljs-punctuation">:</span> <span class="hljs-number">3</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"character"</span><span class="hljs-punctuation">:</span> <span class="hljs-number">7</span><br> <span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"end"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"line"</span><span class="hljs-punctuation">:</span> <span class="hljs-number">3</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"character"</span><span class="hljs-punctuation">:</span> <span class="hljs-number">18</span><br> <span class="hljs-punctuation">}</span><br> <span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"newText"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"let Some(${0:a}) = a"</span><br> <span class="hljs-punctuation">}</span><br> <span class="hljs-punctuation">]</span><br> <span class="hljs-punctuation">}</span><br> <span class="hljs-punctuation">]</span><span class="hljs-punctuation">,</span><br> ...<br> <span class="hljs-punctuation">}</span><br><span class="hljs-punctuation">}</span><br></code></pre></td></tr></table></figure><p>一个完整的请求响应过程至此结束,类似的过程在用户在编辑器中写代码,移动鼠标悬浮,各种点击查看等的过程中时刻发生着。</p><h1 id="参考"><a class="markdownIt-Anchor" href="#参考"></a> 参考</h1><ul><li><a href="https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/">Language Server Protocol Specification - 3.17</a></li><li><a href="https://liiked.github.io/VS-Code-Extension-Doc-ZH/#/language-extensions/programmatic-language-features">VSCode 插件中文文档</a></li><li><a href="https://www.youtube.com/watch?v=I3RXottNwk0&list=PLhb66M_x9UmrqXhQuIpWC5VgTdrGxMx3y&ab_channel=AlekseyKladov">Explaining Rust Analyzer</a></li></ul>]]></content>
<categories>
<category>开源</category>
<category>Rust Analyzer</category>
</categories>
</entry>
<entry>
<title>排查一个 Rust Analyzer 发版后出现的 bug</title>
<link href="/2023/11/27/%E6%8E%92%E6%9F%A5%E4%B8%80%E4%B8%AA%20Rust%20Analyzer%20%E5%8F%91%E7%89%88%E5%90%8E%E5%87%BA%E7%8E%B0%E7%9A%84%20bug/"/>
<url>/2023/11/27/%E6%8E%92%E6%9F%A5%E4%B8%80%E4%B8%AA%20Rust%20Analyzer%20%E5%8F%91%E7%89%88%E5%90%8E%E5%87%BA%E7%8E%B0%E7%9A%84%20bug/</url>
<content type="html"><![CDATA[<h2 id="发现问题"><a class="markdownIt-Anchor" href="#发现问题"></a> 发现问题</h2><p>前些天有用户反馈了一个 Rust Analyzer 的 <a href="https://github.com/rust-lang/rust-analyzer/issues/15909">issue</a>,说的是在升级到最新版的 RA 后出现了之前没出现的 bug:当一个 trait 的 associate item 提供了默认实现时,struct impl 这个 trait 时如果不提供实现则 RA 会报错如下,而实际上这在 Rust 中是合法的。</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">trait</span> <span class="hljs-title class_">Marker</span> {<br><span class="hljs-keyword">const</span> FLAG: <span class="hljs-type">bool</span> = <span class="hljs-literal">false</span>;<br>}<br><span class="hljs-keyword">struct</span> <span class="hljs-title class_">Foo</span>;<br><span class="hljs-keyword">impl</span> <span class="hljs-title class_">Marker</span> <span class="hljs-keyword">for</span> <span class="hljs-title class_">Foo</span> {} <span class="hljs-comment">// <--- error: not all trait items implemented, missing: `const FLAG`</span><br></code></pre></td></tr></table></figure><h2 id="定位问题"><a class="markdownIt-Anchor" href="#定位问题"></a> 定位问题</h2><p>这是一个在最新版中才出现的错误,说明是在这一版中才引入的而不是陈年老 bug。于是我在 GitHub 上 compare 这一版的 commit 和上一版本的 commit,这中间有 <a href="https://github.com/rust-lang/rust-analyzer/compare/416e9c856a792ce2214c449677ca0a1f38965248...58de0b130a763f3a2d373f508ac0c18a8e7d0acd?diff=unified">84 个 Files changed</a>, 不算多。<br /><img src="/img/blog_pic/2023/diff.png" alt="" /><br />一番浏览下来,发现了这个新增的 crates/ide-diagnostics/src/handlers/trait_impl_missing_assoc_item.rs,从名字上看就很可疑,然后仔细浏览这个文件,发现正是他的问题。这个文件所在的 <a href="https://github.com/rust-lang/rust-analyzer/pull/15895">PR</a> 增加了一个新的 diagnostic,使得 RA 可以检测出用户在为 struct impl trait 的时候哪些 associate item 没有实现。<br /><img src="/img/blog_pic/2023/missing_assoc_item.png" alt="" /></p><h2 id="解决问题"><a class="markdownIt-Anchor" href="#解决问题"></a> 解决问题</h2><p>这个 PR 是这样<a href="https://github.com/rust-lang/rust-analyzer/pull/15895/files#diff-9ded74602cbdce03cc0b20f094e733658a7b43d14ffe0f9167bc79a0851e7e53R665-R704">检测未在 impl 体中实现的 associate item 的</a>,对于 <code>AssocItemId::ConstId(_) => true,</code> 的处理是直接返回 true,也就是默认为必须在 impl 体中实现而漏掉了在 trait 的定义中可能有为这个 const item 设置默认值的情况。知道了问题所在,剩下的就比较好解决了,将针对 const item 的处理修改为 <code>Const::from(id).value(db).is_none()</code>,也就是判断这一项有没有设置了默认值,如果有的话就不必作为 required_items 从而引发错误的 diagnostic</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">let</span> <span class="hljs-variable">required_items</span> = items.<span class="hljs-title function_ invoke__">iter</span>().<span class="hljs-title function_ invoke__">filter</span>(|&(_, assoc)| <span class="hljs-keyword">match</span> *assoc {<br> AssocItemId::<span class="hljs-title function_ invoke__">FunctionId</span>(it) => !db.<span class="hljs-title function_ invoke__">function_data</span>(it).<span class="hljs-title function_ invoke__">has_body</span>(),<br> AssocItemId::<span class="hljs-title function_ invoke__">ConstId</span>(_) => <span class="hljs-literal">true</span>,<br> AssocItemId::<span class="hljs-title function_ invoke__">TypeAliasId</span>(it) => db.<span class="hljs-title function_ invoke__">type_alias_data</span>(it).type_ref.<span class="hljs-title function_ invoke__">is_none</span>(),<br>});<br></code></pre></td></tr></table></figure><p>接着再为这个小 fix 增加一个验证的 test,跑一遍 cargo test 确保所有测试都没问题之后就可以提 <a href="https://github.com/rust-lang/rust-analyzer/pull/15911">PR</a> 修复了</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><code class="hljs rust"> <span class="hljs-meta">#[test]</span><br><span class="hljs-keyword">fn</span> <span class="hljs-title function_">trait_with_default_value</span>() {<br> <span class="hljs-title function_ invoke__">check_diagnostics</span>(<br> <span class="hljs-string">r#"</span><br><span class="hljs-string">trait Marker {</span><br><span class="hljs-string"> const FLAG: bool = false;</span><br><span class="hljs-string">}</span><br><span class="hljs-string">struct Foo;</span><br><span class="hljs-string">impl Marker for Foo {}</span><br><span class="hljs-string"> "#</span>,<br> )<br>}<br></code></pre></td></tr></table></figure><h2 id="最后"><a class="markdownIt-Anchor" href="#最后"></a> 最后</h2><p>Rust Analyzer 会自动发 nightly releases,这样看来频繁发版还是有好处的,相邻版本之间的 diff 不会太大,发版后引发的 bug 才比较好定位,要是版本之间的 diff 上千个文件我可能就不会一个个去看了哈哈哈</p><h2 id="参考"><a class="markdownIt-Anchor" href="#参考"></a> 参考</h2><ul><li>原 issue <a href="https://github.com/rust-lang/rust-analyzer/issues/15909">RA reports missing trait item implementation for associated constant with default value: possibly a regression</a></li><li>引入 bug 的 PR <a href="(https://github.com/rust-lang/rust-analyzer/pull/15895)">feat: Diagnose missing assoc items in trait impls</a></li><li>修复 PR <a href="https://github.com/rust-lang/rust-analyzer/pull/15911">fix: handle default constant values in trait_impl_missing_assoc_item diagnostic</a></li></ul>]]></content>
<categories>
<category>开源</category>
<category>Rust Analyzer</category>
</categories>
</entry>
<entry>
<title>OSPP 2023 结项</title>
<link href="/2023/11/20/OSPP%202023%20%E7%BB%93%E9%A1%B9/"/>
<url>/2023/11/20/OSPP%202023%20%E7%BB%93%E9%A1%B9/</url>
<content type="html"><![CDATA[<blockquote><p>感觉自己变得老油条了,OSPP 2023 的申请报告和结项报告都写得很简短 hhh</p></blockquote><h2 id="相关信息"><a class="markdownIt-Anchor" href="#相关信息"></a> 相关信息</h2><ul><li>项目名称:Implementing a Webdav Compatibility Layer for Oay</li><li>项目编号:231110459</li><li>项目导师:PsiACE</li></ul><h2 id="开发规划"><a class="markdownIt-Anchor" href="#开发规划"></a> 开发规划</h2><ul><li>第一阶段:熟悉项目,理解项目目标,与社区和导师沟通,确定技术/开发路线,做好实际开发之前的设计工作</li><li>第二阶段:实际开发编码</li><li>第三阶段:编写测试、项目文档、准备项目验收材料</li></ul><h2 id="项目交流"><a class="markdownIt-Anchor" href="#项目交流"></a> 项目交流</h2><ul><li><a href="https://discord.com/channels/1081052318650339399/1109814626046197770">Discord:套磁交流</a></li><li><a href="https://discord.com/channels/1081052318650339399/1122784753393881179">Discord:开发交流</a></li><li><a href="https://github.com/apache/incubator-opendal/issues/2586">GitHub Issue:开发过程追踪</a></li></ul><h2 id="完成情况"><a class="markdownIt-Anchor" href="#完成情况"></a> 完成情况</h2><ul><li><p>在 Oay <code>frontends-webdav</code> feature 下增加一个结构体 <code>WebDAVFs</code>,实现 dav-server-rs 中的 <code>fs::DavDirEntry</code>、<code>fs::DavFile</code>、<code>fs::DavFileSystem</code>、<code>fs::DavMetaData</code> 等 trait;利用 OpenDAL operator 中的各种数据操作算子(<code>read</code>、<code>write</code>、<code>create_dir</code>、<code>stat</code> 等)实现这些 trait 中的各个接口</p></li><li><p>OpenDAL Oay WebDAV 服务通过 <a href="http://www.webdav.org/neon/litmus/">litmus</a>(WebDAV 服务器协议遵从性测试套件)行为测试</p> <figure class="highlight txt"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><code class="hljs txt">-> running `http':<br>0. init.................. pass<br>1. begin................. pass<br>2. expect100............. pass<br>3. finish................ pass<br><- summary for `http': of 4 tests run: 4 passed, 0 failed. 100.0%<br>-> running `basic':<br>0. init.................. pass<br>1. begin................. pass<br>2. options............... pass<br>3. put_get............... pass<br>4. put_get_utf8_segment.. pass<br>5. put_no_parent......... pass<br>6. mkcol_over_plain...... pass<br>7. delete................ pass<br>8. delete_null........... pass<br>9. delete_fragment....... WARNING: DELETE removed collection resource with Request-URI including fragment; unsafe<br> ...................... pass (with 1 warning)<br>10. mkcol................. pass<br>11. mkcol_again........... pass<br>12. delete_coll........... pass<br>13. mkcol_no_parent....... pass<br>14. mkcol_with_body....... pass<br>15. finish................ pass<br><- summary for `basic': of 16 tests run: 16 passed, 0 failed. 100.0%<br>-> running `copymove':<br>0. init.................. pass<br>1. begin................. pass<br>2. copy_init............. pass<br>3. copy_simple........... pass<br>4. copy_overwrite........ pass<br>5. copy_nodestcoll....... pass<br>6. copy_cleanup.......... pass<br>7. copy_coll............. pass<br>8. copy_shallow.......... pass<br>9. move.................. pass<br>10. move_coll............. pass<br>11. move_cleanup.......... pass<br>12. finish................ pass<br><- summary for `copymove': of 13 tests run: 13 passed, 0 failed. 100.0%<br></code></pre></td></tr></table></figure></li><li><p>PR 列表</p><ul><li><a href="https://github.com/apache/incubator-opendal/pull/2658">#2658 feat(oay): Add webdav basic read impl</a></li><li><a href="https://github.com/apache/incubator-opendal/pull/2736">#2736 feat(oay): Add read_dir</a></li><li><a href="https://github.com/apache/incubator-opendal/pull/2769">#2769 feat(oay): add write for oay webdav</a></li><li><a href="https://github.com/apache/incubator-opendal/pull/2832">#2832 feat: Add create_dir, remove, copy and rename API for oay-webdav</a></li><li><a href="https://github.com/apache/incubator-opendal/pull/2857">#2857 feat(oay): impl some method for WebdavMetaData</a></li><li><a href="https://github.com/apache/incubator-opendal/pull/2879">#2879 fix(oay): add some error handle</a></li><li><a href="https://github.com/apache/incubator-opendal/pull/2944">#2944 fix(oay): pass litmus copymove test</a></li><li><a href="https://github.com/apache/incubator-opendal/pull/2957">#2957 CI(oay): add CI for oay-webdav</a></li><li><a href="https://github.com/apache/incubator-opendal/pull/2971">#2971 CI(oay): Polish oay webdav test</a></li></ul></li></ul><h2 id="致谢"><a class="markdownIt-Anchor" href="#致谢"></a> 致谢</h2><p>第二次参加 OSPP,既偶然,也幸运。感谢 OSPP 主办方提供的活动平台,以及感谢 OpenDAL 社区在此期间的帮助,最后还要感谢我的导师 @PsiACE 以及 @xuanwo 予以我的指导以及快速且耐心的 code review,谢谢你们 💖</p>]]></content>
<categories>
<category>开源</category>
</categories>
</entry>
<entry>
<title>Contribute to Rust Analyzer</title>
<link href="/2023/10/21/My%20first%20contribution%20to%20Rust%20Analyzer/"/>
<url>/2023/10/21/My%20first%20contribution%20to%20Rust%20Analyzer/</url>
<content type="html"><![CDATA[<h2 id="前言"><a class="markdownIt-Anchor" href="#前言"></a> 前言</h2><p>我写 Rust 用的 IDE 是 VSCode 搭配 Rust Analyzer 插件,二者搭配起来使用体验还是相当不错的。Rust Analyzer 虽然只是 VSCode 生态下的一个插件,但实际上可以看成 Rust 语言的一个 LSP(Language Server Protocol) 完整实现,提供了诸如补全提示、代码诊断、引用查找、跳转到定义等 IDE 常用功能。Rust Analyzer 插件可以分为前端和后端,前端是<a href="https://github.com/rust-lang/rust-analyzer">代码仓库</a>中 editors/code 路径下的 TypeScript 项目,其余部分属于后端服务实现代码,我在这次贡献中涉及的是后端服务部分,下文中如无说明默认讨论的是 Rust Analyzer 后端服务。</p><p>LSP 只是一个协议,并没有规定实现的形式。Rust Analyzer 由 Rust 语言开发,开发团队为此做了许多工作,比如用 Rust 实现了 Parser、<a href="https://docs.rs/lsp-server/0.7.4/lsp_server/">lsp_server</a> 等工具。此外,Rust Analyzer 还有详细的<a href="https://github.com/rust-lang/rust-analyzer/tree/master/docs/dev">开发文档</a>,为 new contributor 提供了许多指引说明,,是我见过的开源项目中开发文档最详细的项目之一,甚至还有一个 Rust Analyzer 核心开发者主讲的<a href="https://www.youtube.com/playlist?list=PLhb66M_x9UmrqXhQuIpWC5VgTdrGxMx3y">系列视频</a>(虽然我看不太懂听不太懂 🙃)介绍 Rust Analyzer 的工作流程。如果没有这些资料,从上手理解这么一个庞大的项目到能提 PR 解决某个小问题对我来说应该要花费更多的时间精力,再次感慨详细的开发文档对于 new contributor 加入社区的重要性。</p><h2 id="入坑"><a class="markdownIt-Anchor" href="#入坑"></a> 入坑</h2><p>每天都在使用 IDE,我对于 IDE 是如何实现这些功能是比较好奇的,某天心血来潮去 Rust Analyzer 的项目仓库逛逛,浏览了一遍 issue,发现 <a href="https://github.com/rust-lang/rust-analyzer/issues/12977">Feature request: if x.is_some() -> if let Some(_tmp) = x</a> 这个我能看懂的 issue,于是打算从这个 issue 入手,通过 learning by doing 的方式去学习一下如何开发 Rust Analyzer。这个 issue 描述的是希望在 Rust Analyzer 中增加一个 feature,使得 IDE 能够智能感知并为 <code>Option</code> 以及 <code>Result</code> 类型的某些方法提供自动改写功能,如以下代码所示。其中 ┃ 表示光标的位置,意思是当光标停留在某个特定的 context 位置的时候 IDE 能提供一个自动改写功能。</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">// before</span><br><span class="hljs-keyword">if</span> x.is_┃<span class="hljs-title function_ invoke__">some</span>() {<br> ...<br>}<br><br><span class="hljs-comment">// after</span><br><span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> <span class="hljs-variable">Some</span>(┃_tmp) = x {<br> ...<br>}<br></code></pre></td></tr></table></figure><h2 id="过程"><a class="markdownIt-Anchor" href="#过程"></a> 过程</h2><p>我第一版实现的效果是下面这样,当光标停留在 <code>Option</code> 或者 <code>Result</code> 对象的 is_some 或者 is_ok 方法的时候,通过点击小灯泡💡展示当下可选的功能,用户单击选择某个选项即可应用某项功能(在 Rust Analyzer 中这些点击💡后展示的功能称为 <code>assist</code>)。<br /><img src="/img/blog_pic/2023/if_is_some_assist.gif" alt="" /></p><p><img src="/img/blog_pic/2023/if_is_ok_assist.gif" alt="" /></p><p>一开始我完全不知道从哪里入手,粗略过了一遍文档之后也没有什么头绪。<strong>于是我转变了思路,在已经合并的 PR 里面找是否有完成了类似的事情的 PR</strong>,终于找到了 <a href="https://github.com/rust-lang/rust-analyzer/pull/13825">feat: Add unqualify_method_call assist</a> 这个 PR,它同样是提供了当光标在特定 context 位置时的一项可选的 <code>assist</code>。<code>assist</code> 是 Rust Analyzer 的后端服务通过分析当前光标所处的 context 位置所提供的一些“智能”功能,关于小灯泡💡<code>assist</code> Rust Analyzer 的官方博客中有<a href="https://rust-analyzer.github.io/blog/2020/09/28/how-to-make-a-light-bulb.html">一篇文章</a>做了介绍,我也是在看了这篇博客之后才悟到原来这些 <code>assist</code> 是由服务端提供的而不是预先定义在客户端中的。</p><p>从这个参考 PR 中的 Files changed 中我知道了要增加一项 <code>assist</code> 需要在 <code>crates/ide-assists/src/handlers</code> 下增加一个对应的处理方法,然后注册到 <code>/crates/ide-assists/src/lib.rs</code> 中的 <code>pub(crate) fn all() -> &'static [Handler]</code> 方法,调用链如下。当光标停留的时候,Rust Analyzer 会检查所有在 <code>all()</code> 中注册的 <code>assist</code> 是否可用于当前的 context,如果可以则会将这项 <code>assist</code> 返回给客户端,对应的就是我们点击💡时看到的那些选项。<br /><img src="/img/blog_pic/2023/call_stack.png" alt="" /></p><h2 id="实现"><a class="markdownIt-Anchor" href="#实现"></a> 实现</h2><p>Rust Analyzer 实现了一系列数据结构用于映射用户在 VSCode 中写的代码,参考其他的一些 <code>assist</code>,我在实现 <code>replace_is_method_with_if_let_method</code> 这个 <code>assist</code> 的思路如下:</p><ol><li>首先检查光标当前所处的 context 位置是否是一个 if 表达式,因为我要实现的 <code>assist</code> 是改写 <code>if x.is_some() {}</code>,所以要确保光标所处的位置是一个 if 表达式,这样才有必要进行后续的操作;</li><li>读取 if 表达式中的 condition 部分,检查该 condition 是否是一个方法调用(<code>x.is_some()</code>),如果不是那这就不是我要的操作目标,直接 return 即可;</li><li>如果是一个方法调用的话,这个方法调用的名字是否是 <code>is_some</code> 或者 <code>is_ok</code>,若不是则直接 return;</li><li>确保光标位置的 context 是我要操作的目标代码后,构造出替代后的代码语句实现代码替换。</li></ol><p>得益于 Rust Analyzer 所做的一些抽象,只用 30 多行代码我就完成了 replace_is_method_with_if_let_method 这项 <code>assist</code>。思路其实很简单,而且 Rust Analyzer 也有许多 api 用于代码 context 检查以及信息提取(比如读取某个方法调用的发起对象名以及方法名等),完成的代码如下:</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><code class="hljs rust"><span class="hljs-title function_ invoke__">pub</span>(<span class="hljs-keyword">crate</span>) <span class="hljs-keyword">fn</span> <span class="hljs-title function_">replace_is_method_with_if_let_method</span>(<br> acc: &<span class="hljs-keyword">mut</span> Assists,<br> ctx: &AssistContext<<span class="hljs-symbol">'_</span>>,<br>) <span class="hljs-punctuation">-></span> <span class="hljs-type">Option</span><()> {<br> <span class="hljs-keyword">let</span> <span class="hljs-variable">if_expr</span> = ctx.find_node_at_offset::<ast::IfExpr>()?;<br><br> <span class="hljs-keyword">let</span> <span class="hljs-variable">cond</span> = if_expr.<span class="hljs-title function_ invoke__">condition</span>()?;<br> <span class="hljs-keyword">let</span> <span class="hljs-variable">call_expr</span> = <span class="hljs-keyword">match</span> cond {<br> ast::Expr::<span class="hljs-title function_ invoke__">MethodCallExpr</span>(call) => call,<br> _ => <span class="hljs-keyword">return</span> <span class="hljs-literal">None</span>,<br> };<br><br> <span class="hljs-keyword">let</span> <span class="hljs-variable">name_ref</span> = call_expr.<span class="hljs-title function_ invoke__">name_ref</span>()?;<br> <span class="hljs-keyword">match</span> name_ref.<span class="hljs-title function_ invoke__">text</span>().<span class="hljs-title function_ invoke__">as_str</span>() {<br> <span class="hljs-string">"is_some"</span> | <span class="hljs-string">"is_ok"</span> => {<br> <span class="hljs-keyword">let</span> <span class="hljs-variable">receiver</span> = call_expr.<span class="hljs-title function_ invoke__">receiver</span>()?;<br> <span class="hljs-keyword">let</span> <span class="hljs-variable">target</span> = call_expr.<span class="hljs-title function_ invoke__">syntax</span>().<span class="hljs-title function_ invoke__">text_range</span>();<br><br> <span class="hljs-keyword">let</span> (assist_id, message, text) = <span class="hljs-keyword">if</span> name_ref.<span class="hljs-title function_ invoke__">text</span>() == <span class="hljs-string">"is_some"</span> {<br> (<span class="hljs-string">"replace_is_some_with_if_let_some"</span>, <span class="hljs-string">"Replace `is_some` with `if let Some`"</span>, <span class="hljs-string">"Some"</span>)<br> } <span class="hljs-keyword">else</span> {<br> (<span class="hljs-string">"replace_is_ok_with_if_let_ok"</span>, <span class="hljs-string">"Replace `is_ok` with `if let Ok`"</span>, <span class="hljs-string">"Ok"</span>)<br> };<br><br> acc.<span class="hljs-title function_ invoke__">add</span>(<span class="hljs-title function_ invoke__">AssistId</span>(assist_id, AssistKind::RefactorRewrite), message, target, |edit| {<br> <span class="hljs-keyword">let</span> <span class="hljs-variable">replacement</span> = <span class="hljs-built_in">format!</span>(<span class="hljs-string">"let {}({}) = {}"</span>, text, <span class="hljs-string">"${0:_tmp}"</span>, receiver);<br> edit.<span class="hljs-title function_ invoke__">replace</span>(target, replacement);<br> })<br> }<br> _ => <span class="hljs-keyword">return</span> <span class="hljs-literal">None</span>,<br> }<br>}<br></code></pre></td></tr></table></figure><p>我认为这次的难点不在于实现思路,而是在于理解项目,知道要在哪里做出什么样的修改,理解 Rust Analyzer 的哪些数据结构对应了用户侧写的代码。关于这一点可以参考 Rust Analyzer 的 Syntax tree 结构,在 VSCode 中写下 rust 代码,按下 ctrl + shift + p 之后在搜索栏中输入 Syntax tree,按下回车之后就能在编辑器右侧得到代码的语法树结构。比如 <code>let x: i32 = 1;</code> 这行代码对应的语法树结果是下面这样的。其中 <code>PATH_TYPE</code>、<code>PATH_SEGMENT</code>、<code>NAME_REF</code> 这些关键字对应了 <code>crates/syntax/src/ast/generated/nodes.rs</code> 中对应的 <code>struct</code>。</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><code class="hljs text">[email protected]<br> [email protected] "let"<br> [email protected] " "<br> [email protected]<br> [email protected]<br> [email protected] "x"<br> [email protected] ":"<br> [email protected] " "<br> [email protected]<br> [email protected]<br> [email protected]<br> [email protected]<br> [email protected] "i32"<br> [email protected] " "<br> [email protected] "="<br> [email protected] " "<br> [email protected]<br> [email protected] "1"<br> [email protected] ";"<br></code></pre></td></tr></table></figure><h2 id="细节"><a class="markdownIt-Anchor" href="#细节"></a> 细节</h2><p>在 replace_is_method_with_if_let_method 的初版实现中,完成代码改写后光标并不会自动选中 <code>_tmp</code> 变量。我希望它可以,这样用户在选择应用这项 <code>assist</code> 之后不需要任何鼠标和键盘的操作就可以直接编辑这个变量名。同样的,一开始我不知道如何实现,翻看了其他 <code>assist</code> 的实现似乎也没找到这样的功能,于是问了 ChatGPT,得到了一个能 work 的方法,利用 VSCode 中的 <code>${0:_tmp}</code> 表达式实现自动范围选择。不过我这里的实现是用粗糙的手动构造方式,或者 Rust Analyzer 里有封装好某个对应的 api 而我还没找到…<br /><img src="/img/blog_pic/2023/ask_gpt.png" alt="" /></p><p><img src="/img/blog_pic/2023/cursor_selection.gif" alt="" /></p><h2 id="参考"><a class="markdownIt-Anchor" href="#参考"></a> 参考</h2><ul><li><a href="https://github.com/rust-lang/rust-analyzer/pull/15743">feat: add replace_is_some_with_if_let_some assist</a></li><li><a href="https://github.com/rust-lang/rust-analyzer/pull/15752">feat: add replace_is_ok_with_if_let_ok assist</a></li><li><a href="https://github.com/rust-lang/rust-analyzer/pull/15755">fix: make cursor select at _tmp</a></li><li><a href="https://rust-analyzer.github.io/blog/2020/09/28/how-to-make-a-light-bulb.html">How to Make a 💡</a></li><li><a href="https://www.youtube.com/playlist?list=PLhb66M_x9UmrqXhQuIpWC5VgTdrGxMx3y">Explaining Rust Analyzer</a></li></ul>]]></content>
<categories>
<category>开源</category>
<category>Rust Analyzer</category>
</categories>
</entry>
<entry>
<title>成为 Apache Committer</title>
<link href="/2023/09/09/%E6%88%90%E4%B8%BA%20Apache%20Committer/"/>
<url>/2023/09/09/%E6%88%90%E4%B8%BA%20Apache%20Committer/</url>
<content type="html"><![CDATA[<p>8 月 30 号晚上,突然收到一封来自 <a href="https://github.com/apache/incubator-opendal">OpenDAL</a> PPMC 的 Committer 邀请邮件,这对于我来说完全是意外之喜。在我眼中 Apache 是一个很高大上的组织,Apache Committer 是一个很厉害的程序员群体,没想到有一天我也能加入其中;以至于我在收到邀请时兴奋地手舞足蹈,当时本来还打算自己做饭吃的我当即叫了个外卖犒劳一下自己哈哈哈哈哈。</p><p><img src="/img/blog_pic/2023/opendal_welcome.png" alt="" /></p><p>今天设置好了我的 Apache 账号,link 了我的 GitHub 账号,正式加入了 ASF org,还通过 Apache 邮箱获取到了 JetBrains 全家桶的 license,正好续上了我毕业即到期的学生包 license 😄<br /><img src="/img/blog_pic/2023/ASF_invitation.png" alt="" /><br /><img src="/img/blog_pic/2023/jetbrains_all_products_pack.png" alt="" /></p><p>我在 <a href="https://young-flash.github.io/2023/05/03/OpenDAL%20%E7%A4%BE%E5%8C%BA%E5%8F%82%E4%B8%8E%E8%AE%B0%E5%BD%95/">OpenDAL 社区参与记录</a> 中提到我给 OpenDAL 贡献的初衷是为了在实际项目中学习 Rust,当时对于开源的看法是“用爱发电,就图一乐”,也没想着能有成为 Apache Committer 之类的收获。那时候 OpenDAL 还只有 500+ star,也还没正式进入 Apache 孵化器,后来在 OpenDAL 各位贡献者的努力下它进入了 Apache 孵化器。</p><p>接着“<strong>命运的齿轮开始悄然转动</strong>”,我通过 @xuanwo 的推特得知 OpenDAL 在 OSPP 2023 中有两个 slot,我之前参加过 OSPP 2021,所以得知这个消息的时候觉得挺巧合的,后面就开始关注这两个 slot 会是什么样的项目;当时心里想的是刚好我对于 OpenDAL 有一些了解,如果它发布到 OSPP 2023 上的项目比较合适的话那么我就申请,要是不合适的话就不打算参与了,毕竟当时临近毕业,在毕业论文的压力我也不想花费很多时间精力去从零了解陌生的社区和新的项目,在<a href="https://mp.weixin.qq.com/s/o5w03sM22pX9_Lo0JUOf-Q">开源之夏的专访</a>中我提到参与 OSPP 2023 有些偶然就是这个原因。结果是我很幸运地中选了 OpenDAL 的 Implementing a Webdav Compatibility Layer for Oay 这个项目,实现了硕士三年 OSPP 2021 & GSoC 2022 & OSPP 2023 的“大满贯” 😄</p><p>参与 OSPP 2021 的时候我连在 GitHub 上提 PR 的流程都不清楚,如今两年过去了,我已经能够在开源社区里熟练地和其他开发者协作,这些经历让我提高了代码能力也认识了一些很优秀的程序员,知道了这个世界上有一些很 cool 的事情正在发生着,更 cool 的是我也参与到了这些事情中。成为 Apache Committer 对我来说是一份荣誉,是对我参与开源这件事情的肯定,如果很多年后我还能够继续行走在开源的道路上的话,一定是这些肯定给我的动力。</p>]]></content>
<categories>
<category>开源</category>
</categories>
</entry>
<entry>
<title>OSPP 2021 & GSoC 2022 参与经历</title>
<link href="/2023/06/29/OSPP%202021%20&%20GSoC%202022%20%E5%8F%82%E4%B8%8E%E7%BB%8F%E5%8E%86/"/>
<url>/2023/06/29/OSPP%202021%20&%20GSoC%202022%20%E5%8F%82%E4%B8%8E%E7%BB%8F%E5%8E%86/</url>
<content type="html"><![CDATA[<h1 id="前言"><a class="markdownIt-Anchor" href="#前言"></a> 前言</h1><p>在参与到 GSoC-like 这类活动之前,我对开源的看法简单直接且现实。当时的我只是开源的索取者,开源对我来说就是免费获取(高质量、安全)应用程序和源代码的一种方式。但是当我参与到开源后,在索取者这个身份之外我还是一个贡献者,我参与了项目的开发,社区的活动以及和其他人的交流,这不仅开阔了我的技术视野也开阔了我的认知视野,原来世界上还有这些这么酷的事情正在发生着。</p><p>理所当然地,我把我参与的 OSPP 和 GSoC 的项目写到了简历里,在找实习和秋招的时候都收到了一些大厂的 offer。这些项目经历是不是帮助我获得了面试官的青睐这一点无法考究,不过我觉得也不重要了。这些经历让我对于开源有了新的看法,我相信它带给我的某些东西能在一定程度上影响我的职业生涯。我赞同 wjsvec 所言:</p><blockquote><p>参加开源之夏对很多同学只是打开了一扇开源的大门,而这扇大门背后,是绚丽多彩的宝藏。</p></blockquote><h1 id="ospp-2021"><a class="markdownIt-Anchor" href="#ospp-2021"></a> OSPP 2021</h1><h2 id="缘起"><a class="markdownIt-Anchor" href="#缘起"></a> 缘起</h2><p>初次参与开源之夏是在我研一的时候,当时是我实验室的同学(我称他为 k 老师)告诉我的。最初的想法是这里的项目我都看不懂,肯定很难申请上吧?在 k 老师的鼓励和我的一番思想斗争下我转变了想法——那就试试呗,申请一下又不亏,万一让我成功了,能拿钱还能放上简历,何乐而不为呢?</p><h2 id="过程"><a class="markdownIt-Anchor" href="#过程"></a> 过程</h2><p>我申请的是 Nebula Graph 社区的一个 Java 项目:为图数据库 Nebula Graph 实现 JDBC 协议。当时确定选这个项目是因为我在实验室也写一些 Java,并且这个项目的描述里面的一些名词相比其他项目来说我更加能“看懂”。当时我的申请策略是锁定一个项目之后就 all in 进去,后来我在申请 GSoC 2022 的项目时也是如此。</p><h3 id="与社区获得联系"><a class="markdownIt-Anchor" href="#与社区获得联系"></a> 与社区获得联系</h3><p>我首先做的是去了解 Nebula Graph 这个图数据库,有些磕磕绊绊地在我的 Windows 机器上安装 Nebula Graph,我发现官方的论坛里好像没有关于 Windows 下的安装教程贴,于是我就写了<a href="https://discuss.nebula-graph.com.cn/t/topic/4382">一篇文章</a>发表在了论坛,随后社区运营清蒸找到了我,还送了我社区的纪念品作为我在论坛发表文章的鼓励。我们交流了关于开源之夏的一些问题,跟我介绍了我的项目导师等等。</p><p>和社区套磁是申请过程中很重要的一步,以上是我和社区取得联系的经历。靠 PR 代码和导师搭上线当然是一种直接有效的方式,但不一定适用于所有社区所有项目,比如说有些项目的描述不是很清晰或者自己理解不是很到位,没有和导师充分地沟通就写代码是很困难的。所以我个人觉得这个过程不一定是要非得靠代码,也不一定是非得直接和项目导师搭上线,如果社区有像清蒸这么靠谱且有趣的运营,首先和她们取得联系也是不错的选择哈哈哈😄</p><p>之后我便尝试就项目本身和导师进行沟通。我在 Nebula Studio 中发现了 Nebula Graph 的 Server 端有暴露 HTTP 接口,于是我便想借助 OkHttp 框架在 Java 中实现与本地 Nebula Graph 的通信,在跑通了基本的 CRUD 流程后我便和导师说了我的想法,后来导师告诉我可以用基于现有的 nebula-java 去封装出 JDBC 的接口。是的,我的想法没有得到认可,但是我一点都不觉得遗憾或者难过,反而有些庆幸我跟导师沟通了,要是没沟通好自己就开始闷头做的话那就白费了更多功夫了。我在今年的 OSPP 2023 申请的过程中同样没有准确地把握技术路线,还好在 Discord 中和社区沟通过后在 proposal 中修正了过来。</p><p>充分的沟通很重要,它可以确保你始终把功夫花在正确的地方;同时这也是和导师互相了解的一个过程,如果发现和导师沟通无法取得一直或者导师本身对于项目的指导不太上心(是的,可能有些人作为项目导师纯粹是因为公司的安排,本身意愿就不高;但我相信大部分导师都是很认真负责的),那么也可以及时止损,寻找其他项目或者其他社区的机会。或许开源之夏这个活动以及某些社区某些人并不是百分百美好,但只要用心挖掘总还是有机会的。</p><h3 id="coding"><a class="markdownIt-Anchor" href="#coding"></a> coding</h3><p>我的 nebula-jdbc 项目在申请时被标注为一个中等难度的项目,本身并不难,特别在是导师说可以基于现有的 nebula-java 封装之后。接下来我找到了 JDBC Specification,仔细读了一遍尽量不让自己的理解出错;而后就是在 JDBC 的各个接口中调用 nebula-java 的 API 去实现对应的功能了,这一部分有<a href="https://mp.weixin.qq.com/s/u1iTb_K0EoMboxTrvb3P3Q">一篇当时发表在 OSPP 公众号的文章</a>。</p><p>时隔两年之后回望当时做项目写代码的过程,很多细节都已经模糊了。但我还清晰地记得那个暑假里和 k 老师(他也参加了 OSPP 的活动)在实验室互相讨论,彼此鼓励(<s>商业互吹</s>),一起吃饭游泳的日子。</p><h2 id="后来"><a class="markdownIt-Anchor" href="#后来"></a> 后来</h2><p>在导师的指导下我的项目顺利结项了,不仅收获了来自清蒸的一系列的社区周边,后面导师还送了我一个键盘作为礼物哈哈哈哈。此后在清蒸的邀请(<s>坑蒙拐骗</s>)下在社区出了<a href="https://discuss.nebula-graph.com.cn/t/topic/5656">一期访问</a>,还在 Nebula Graph 母公司的邀请下去到杭州参加社区举办的开源之夏线下总结会,认识了其他参加 Nebula 社区项目的同学,也是借此机会和 Wey 他们一起吃了个饭😄<br /><img src="/img/blog_pic/2023/meeting.jpg" alt="" /><br /><img src="/img/blog_pic/2023/gift_from_mentor.jpg" alt="" /></p><h1 id="gsoc-2022"><a class="markdownIt-Anchor" href="#gsoc-2022"></a> GSoC 2022</h1><h2 id="缘起-2"><a class="markdownIt-Anchor" href="#缘起-2"></a> 缘起</h2><p>我是在参与 OSPP 之后才知道的 GSoC——比起 OSPP 历史更悠久面向范围更广泛。在我得知要申请上面的项目是要和全球范围内的开发者竞争时我内心又是打起了退堂鼓的。和之前参加 OSPP 不一样的是当时的我已经不是一个纯小白了,怎么说也是有过 OSPP 经验的,这给了我很大的信心,所以还是试试呗,申请一下又不亏哈哈哈。</p><p>GSoC 的项目真是又多又杂啊,甚至还有保护鲸鱼的组织和项目,这就是 diversity 吗哈哈哈。一开始并没有想好尝试哪个项目,想着从自己的技术栈出发找一些项目看看,我对 Linux C++ 比较有兴趣所以想找一些这方面的项目,一番浏览下来发现了 <a href="https://wiki.videolan.org/SoC_2022/">Develop a MPD server inside VLC</a>、<a href="https://github.com/casbin/SummerOfCode2022#casbin-for-cc">Casbin for C/C++</a>、HAIKU 以及 <a href="https://ccextractor.org/public/gsoc/rtorrent-modern-rpc/">Introduce WebSockets into rTorrent</a>,其中 VLC 和 HAIKU 初看上去不太契合我,Casbin 的有尝试过跑一下项目不过也没啥思路,最后发现了 rTorrent 的这个项目,主要是为 rTorrent 引入 Websockets 协议,涉及到网络编程和 Modern C++,二者我都有一点基础并且也都挺感兴趣的所以就锁定这个项目了。并没有想过同时尝试多个项目,因为不想花很多时间精力去多线程,认定好一个然后 all in 就好了,尽人事听天命,这是我在参加这类活动的一贯做法。</p><h2 id="套磁"><a class="markdownIt-Anchor" href="#套磁"></a> 套磁</h2><p>项目了解的差不多了就在 slack 上联系导师,首先介绍了自己的基本情况、个人技术栈以及去年开源之夏的经历之类的背景等。第二天 mentor 就回复我了,说我的 profile looks great,和我讲了一下项目的目标,以及接下来会发布 qualification tasks to identify the best candidate。接下来我认真地完成了 mentor 发布的 qualification tasks,在这个过程中也让我对完成项目有了更大的把握;于是便着手写 proposal,参考了这个 <a href="https://github.com/saketkc/fos-proposals">repo</a>,后来我在项目顺利结项之后也把我自己的 proposal 通过 <a href="https://github.com/saketkc/fos-proposals/pull/25">PR</a> 回馈给了这个 repo。</p><p>我对自己的前期工作以及和 mentor 的交流过程感觉还是挺不错的,所以认为自己中选的概率还是比较大的。GSoC 从截止提交 proposal 到公布中选中间隔了一个月,还是让人等得够久的,中间我给 rTorrent 提交了两个 commit,初步引入了 websocket,给 mentor 看后他说 looks good at the first glance。至此我就觉得中选应该没问题了,意料之内地收到了中选邮件哈哈哈哈。</p><p><img src="/img/blog_pic/2023/accept_letter.png" alt="" /></p><h2 id="过程-2"><a class="markdownIt-Anchor" href="#过程-2"></a> 过程</h2><p>印象深刻的是关于 websocket 库的选择,我们希望有一个支持在 unix domain socket 上运行 websocket 服务的 lib。一开始我的计划是使用 <a href="https://github.com/uNetworking/uWebSockets">uWebsockets</a>,这是一个 C++ 实现的 websocket 库,底层依赖 <a href="https://github.com/uNetworking/uSockets">uSockets</a> 进行网络通信,性能优越,API 很简洁优雅,github 上有 15 k+ stars,但遗憾的是当时的它并不支持 unix domain socket;另一个选择是 libwebsocket,有 unix domain socket 支持,不过是 C 写的,接口比较 old-style 我很难看懂。</p><p>在 mentor 的建议下我尝试给 uWebsockets 加上 unix domain socket 支持。一开始没有任何头绪,在 uWebsockets 和 uSockets 中翻遍了所有跟 unix domain socket 有关的 issue,一番了解下来知道了可以从 uSockets 的 bsd.c 这个文件中的创建 socket 部分入手,接着还在 uWebsockets 中提了一个 <a href="https://github.com/uNetworking/uWebSockets/discussions/1438">discussion</a>,@ 了好多人都没人理我。。。实际动手操作后竟然真的让我做出来了,比想象中的简单许多,这也得益于这个库本身的设计和编码都很优秀。在 uSockets 中提了 <a href="https://github.com/uNetworking/uSockets/pull/178">PR</a> 回馈给社区,经历了大半年后 PR 终于 merged 了,当时我是很激动的,因为这是一个我从实际需求出发所做的并且还被作者认可的一项工作,给一个一万多 stars 的 repo 提了一个实际的 PR 是一件很让我自豪的事情。(在 OSPP 2021 中除了项目外我几乎没什么贡献了,但在 GSoC 2022 中除了项目本身外我还回馈了一篇 proposal 和给其他社区的 PR,我觉得这是我的进步)</p><p>关于我在 GSoC 2022 中的详细经历我写在了我的<a href="https://young-flash.github.io/2022/05/21/GSoC%202022%20Series%201/">博客</a>中,就不在此赘述了。</p><h1 id="最后"><a class="markdownIt-Anchor" href="#最后"></a> 最后</h1><p>最后我想以我在 GSoC 2022 的 <a href="https://github.com/saketkc/fos-proposals/blob/master/GSoC-2022/Accepted/CCExtractor-DongyangZheng-Introduce-WebSocket-into-rTorrent/DongyangZheng-Introduce%20WebSocket%20into%20rTorrent.md">proposal</a> 中的一句话作为结尾:</p><blockquote><p>Although GSoC 2022 may be over, my enthusiasm for open source isn’t.</p></blockquote><h1 id="参考"><a class="markdownIt-Anchor" href="#参考"></a> 参考</h1><ul><li><a href="https://github.com/nebula-contrib/nebula-jdbc">nebula-jdbc</a></li><li><a href="https://discord.com/channels/1081052318650339399/1109814626046197770">在 Discord 中和社区沟通项目</a></li><li><a href="https://blog.aflybird.cn/2023/06/please-stop-fucking-open-source-activities-in-china/">请还国内开源活动一片净土</a></li><li><a href="https://young-flash.github.io/2022/05/21/GSoC%202022%20Series%201/">我的 GSoC 2022 系列 Blog</a></li></ul>]]></content>
<categories>
<category>开源</category>
</categories>
<tags>
<tag>开源之夏</tag>
</tags>
</entry>
<entry>
<title>OpenDAL 社区参与记录</title>
<link href="/2023/05/03/OpenDAL%20%E7%A4%BE%E5%8C%BA%E5%8F%82%E4%B8%8E%E8%AE%B0%E5%BD%95/"/>
<url>/2023/05/03/OpenDAL%20%E7%A4%BE%E5%8C%BA%E5%8F%82%E4%B8%8E%E8%AE%B0%E5%BD%95/</url>
<content type="html"><![CDATA[<!-- 我第一次学习 Rust 是去年在微软实习期间。当时我的同事把他没有看的《Rust 编程之道》送给了我,但由于准备秋招,我并没有花太多时间学习它。实习结束后回到学校继续准备秋招,也就忘记了这件事情。后来再次捡起来的时候,我觉得通过实践学习可能更好一些。因此,我想尝试找一个开源项目,并为其做出贡献。恰巧,在推特上看到 Xuanwo 天天在吆喝 OpenDAL 项目,于是便开始参与其中了。 --><h1 id="缘起"><a class="markdownIt-Anchor" href="#缘起"></a> 缘起</h1><p>第一次入门学 Rust 还是去年在微软实习那会儿,同事把他不看的《Rust 编程之道》送给我了,当时在准备秋招也就没多花心思学,实习结束后回学校准备秋招了也就不了了之。后来再次捡起来的时候觉得还是 learning by doing 的方式比较好,想着能不能找个开源项目贡献代码试试,正好在推特上看到 <a href="https://twitter.com/OnlyXuanwo">Xuanwo</a> 天天在吆喝 <a href="https://github.com/apache/incubator-opendal">OpenDAL</a>,于是“就决定是你了”。<br /><img src="/img/blog_pic/2023/%E7%9A%AE%E5%8D%A1%E4%B8%98.jpg" alt="" /><br />我相信肯定还有其他 contributor 是通过 Xuanwo 的推特才认识 OpenDAL 进而参与其中的,这对于开源社区吸引新 contributor 来说也是一种成功。</p><h1 id="初试"><a class="markdownIt-Anchor" href="#初试"></a> 初试</h1><p>把项目 clone 下来后第一想法是试着用起来,也是选择了其中的 WebDAV Service,在打算连接坚果云 WebDAV 时发现 OpenDAL 并没有提供 username & password 验证的方式,觉得很奇怪所以在 <a href="https://github.com/apache/incubator-opendal/issues/1319">issue</a> 中问了问,发现这是 OpenDAL WebDAV Service 还没完善的地方,Xuanwo 顺势问我要不要尝试给它加上,我想机会这不就来了嘛,于是便接了下来,在 <a href="https://github.com/apache/incubator-opendal/pull/1323">feat: Add username and password support for WebDAV</a> 这个 PR 中完成了 WebDAV username & password 验证。完成这项支持后发现还有个差不多的活儿,给 <code>HttpBackend</code> 加上 Basic 和 Bearer 的授权方式。整体上来说很简单,代码理解起来也并不复杂。</p><h1 id="后来"><a class="markdownIt-Anchor" href="#后来"></a> 后来</h1><p>有了前面的两个 PR,我想能不能为 OpenDAL 做更多的事情,于是翻了翻 issue,看到 <a href="https://github.com/apache/incubator-opendal/issues/5">Tracking issue of services support</a> 中列出了一系列还不支持的存储服务,觉得这应该是一个不错的方向。不同于前面只是在已经实现的 service 中做缝缝补补的工作,新增一个 service 涉及的工作量会更多一些,也能从中学习到 OpenDAL 为了支持不同存储服务所做的一些抽象。于是我便盯上了 Google Drive,恰好已经有一个 <a href="https://github.com/apache/incubator-opendal/issues/654">issue</a> 与之对应,接着便开始动手。</p><p>第一想法是先找找有没有现成的 Google Drive Rust client 可以集成到 OpenDAL 中,我找到了 <a href="https://crates.io/crates/google-drive3">google-drive3</a>,跑通了 oauth authorization 和 <code>file_path -> id</code> 之后在 issue 中说我打算引入这个依赖从而实现 Google Drive Service for OpenDAL。意外的是这个思路并没有得到支持,对此,Xuanwo 的观点的这样的:希望用原生的 HTTP 请求实现 OpenDAL 的各种操作而不是引入新的 SDK,一方面是上手新 SDK 的成本并不比手写 HTTP 请求低多少,另一方面是避免引入新依赖后的 license 和版本管理问题。嗯,make sense.<br /><img src="/img/blog_pic/2023/xuanwo.png" alt="" /></p><p>接着我并没有马上动手做下去,因为当时毕业论文还没写完,就想着后面再说吧。直到前些天 <a href="https://github.com/imWildCat">imWildCat</a> 问我做得怎么样了我才想起来这回事,这时候毕业论文刚提交送审,手上也没其他事情,终于有心思来做这项工作了。趁着五一假期,在 <a href="https://github.com/apache/incubator-opendal/pull/2184">feat(services/gdrive): Add read & write & delete support for GoogleDrive</a> 这个 PR 中初步实现了 Google Drive Service for OpenDAL。接下来还有一些工作要做,比如其他 op 的支持、<code>file_path -> id</code> 的内部缓存等等。</p><p>暂时先到这里,后面有新进展再接着更新吧~~~</p>]]></content>
<categories>
<category>Rust</category>
</categories>
<tags>
<tag>开源</tag>
</tags>
</entry>
<entry>
<title>深入浅出 Rust笔记 Series 4</title>
<link href="/2023/03/09/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BA%20Rust%E7%AC%94%E8%AE%B0%20Series%204/"/>
<url>/2023/03/09/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BA%20Rust%E7%AC%94%E8%AE%B0%20Series%204/</url>
<content type="html"><![CDATA[<h1 id="系列导航"><a class="markdownIt-Anchor" href="#系列导航"></a> 系列导航</h1><ul><li><a href="https://young-flash.github.io/2023/01/30/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BA%20Rust%E7%AC%94%E8%AE%B0%20Series%201/">深入浅出 Rust笔记 Series 1</a></li><li><a href="https://young-flash.github.io/2023/02/06/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BA%20Rust%E7%AC%94%E8%AE%B0%20Series%202/">深入浅出 Rust笔记 Series 2</a></li><li><a href="https://young-flash.github.io/2023/03/09/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BA%20Rust%E7%AC%94%E8%AE%B0%20Series%203/">深入浅出 Rust笔记 Series 3</a></li><li><a href="https://young-flash.github.io/2023/03/09/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BA%20Rust%E7%AC%94%E8%AE%B0%20Series%204/">深入浅出 Rust笔记 Series 4</a></li></ul><p>这是我在阅读范长春的《深入浅出 Rust》时做的笔记,绝大部分内容源自此书,另有小部分内容源自 <a href="https://course.rs/about-book.html">Rust 圣经</a>。这两份资料是我入门 Rust 的主要材料。</p><h1 id="第四部分-线程安全"><a class="markdownIt-Anchor" href="#第四部分-线程安全"></a> 第四部分 线程安全</h1><h2 id="chap-27-线程安全"><a class="markdownIt-Anchor" href="#chap-27-线程安全"></a> chap 27 线程安全</h2><ul><li><p>std::thread API</p><ul><li><p>创建线程。默认情况下,新创建的子线程与原来的父线程是分离的关系。子线程可以在父线程结束后继续存在,除非父线程是主线程。如果一个进程的主线程也退出了,这个进程就会终止,其他所有的线程也会随之结束。</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">// child 的类型是 JoinHandle<T>,这个 T 是闭包的返回类型</span><br><span class="hljs-keyword">let</span> <span class="hljs-variable">child</span> = thread::<span class="hljs-title function_ invoke__">spawn</span>(<span class="hljs-keyword">move</span> || {<br> <span class="hljs-comment">// 这里是新建线程的执行逻辑</span><br>});<br></code></pre></td></tr></table></figure></li><li><p>等待子线程执行结束 <code>.join()</code></p></li><li><p>Builder 模式可为子线程指定更多的参数信息</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs rust">thread::Builder::<span class="hljs-title function_ invoke__">new</span>().<span class="hljs-title function_ invoke__">name</span>(<span class="hljs-string">"child1"</span>.<span class="hljs-title function_ invoke__">to_string</span>()).<span class="hljs-title function_ invoke__">spawn</span>(<span class="hljs-keyword">move</span> || {<br> <span class="hljs-built_in">println!</span>(<span class="hljs-string">"Hello, world!"</span>);<br>});<br></code></pre></td></tr></table></figure></li><li><p><code>thread::sleep(dur: Duration)</code>使得当前线程等待一段时间继续执行。在等待的时间内,线程调度器会调度其他的线程来执行。</p></li><li><p><code>thread::current()</code> 获得当前的线程。</p></li><li><p><code>thread::yield_now()</code> 放弃当前线程的执行,要求线程调度器执行线程切换。</p></li><li><p><code>thread::park()</code> 暂停当前线程,进入等待状态。当 <code>thread::Thread::unpark(&self:: Thread</code> 方法被调用的时候,这个线程可以被恢复执行。</p></li><li><p><code>thread::Thread::unpark(&self::Thread)</code> 恢复一个线程的执行</p></li></ul></li><li><p>Rust 规定不能在多线程中直接读写普通的共享变量,除非使用 Rust 提供的线程安全相关的设施。</p></li><li><p>“data race”即数据竞争,意思是在多线程程序中,不同线程在没有使用同步的条件下并行访问同一块数据,且其中至少有一个是写操作的情况。</p><ul><li><p>数据竞争的发生需要三个条件:数据共享——有多个线程同时访问一份数据;数据修改——至少存在一个线程对数据做修改;没有同步——至少存在一个线程对数据的访问没有使用同步措施。</p></li><li><p>只要让这三个条件无法同时发生即可避免竞态条件</p><ul><li>可以禁止数据共享,比如 actor-based concurrency,多线程之间的通信仅靠发送消息来实现,而不是通过共享数据来实现;</li><li>可以禁止数据修改,比如 functional programming,许多函数式编程语言严格限制了数据的可变性,而对共享性没有限制。</li></ul></li><li><p>Rust 允许存在可变变量,允许存在状态共享,同时也做到了完整无遗漏的线程安全检查。因为 Rust 设计的一个核心思想就是“<strong>共享不可变,可变不共享</strong>”,然后再加上类型系统和合理的 API 设计,就可以保证共享数据在访问时一定使用了同步措施。Rust 既可以支持多线程数据共享的风格,也可以支持消息通信的风格。无论选择哪种方案,编译器都能保证没有数据竞争。</p></li></ul></li><li><p>Rust 线程安全背后的功臣是两个特殊的 trait:<code>std::marker::Sync</code>(如果类型 T 实现了 Sync 类型,那说明在不同的线程中使用&T 访问同一个变量是安全的);<code>std::marker::Send</code>(如果类型 T 实现了 Send 类型,那说明这个类型的变量在不同的线程中传递所有权是安全的)。<strong>Rust 中所有跟多线程有关的 API,会根据情况,要求类型必须满足 Sync 或者 Send 的约束。</strong></p></li></ul><h2 id="chap-28-详解-send-和-sync"><a class="markdownIt-Anchor" href="#chap-28-详解-send-和-sync"></a> chap 28 详解 Send 和 Sync</h2><h2 id="chap-29-状态共享"><a class="markdownIt-Anchor" href="#chap-29-状态共享"></a> chap 29 状态共享</h2><h2 id="chap-30-管道"><a class="markdownIt-Anchor" href="#chap-30-管道"></a> chap 30 管道</h2><ul><li><p>异步管道 <code>std::sync::mpsc::channel</code>:发送端和接收端之间存在一个缓冲区(不限长度的缓冲区,可以一直往里面填充数据,直至内存资源耗尽。),发送端发送数据的时候,是先将这个数据扔到缓冲区,再由接收端自己去取。因此,每次发送,立马就返回了,发送端不用管数据什么时候被接收端处理。接口:<code>pub fn channel<T>() -> (Sender<T>, Receiver<T>)</code></p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">use</span> std::sync::mpsc::channel;<br><span class="hljs-keyword">use</span> std::thread;<br><span class="hljs-keyword">fn</span> <span class="hljs-title function_">main</span>() {<br> <span class="hljs-keyword">let</span> (tx, rx) = <span class="hljs-title function_ invoke__">channel</span>();<br> <span class="hljs-keyword">for</span> <span class="hljs-variable">i</span> <span class="hljs-keyword">in</span> <span class="hljs-number">0</span>..<span class="hljs-number">10</span> {<br> <span class="hljs-keyword">let</span> <span class="hljs-variable">tx</span> = tx.<span class="hljs-title function_ invoke__">clone</span>(); <span class="hljs-comment">// 复制一个新的 tx,将这个复制的变量 move 进入子线程</span><br> thread::<span class="hljs-title function_ invoke__">spawn</span>(<span class="hljs-keyword">move</span> || {<br> tx.<span class="hljs-title function_ invoke__">send</span>(i).<span class="hljs-title function_ invoke__">unwrap</span>();<br> });<br> }<br> <span class="hljs-title function_ invoke__">drop</span>(tx);<br> <span class="hljs-keyword">while</span> <span class="hljs-keyword">let</span> <span class="hljs-variable">Ok</span>(r) = rx.<span class="hljs-title function_ invoke__">recv</span>() {<br> <span class="hljs-built_in">println!</span>(<span class="hljs-string">"received {}"</span>, r);<br> }<br>}<br></code></pre></td></tr></table></figure><ul><li><p>Sender 和 Receiver 的泛型参数必须满足 T: Send 约束。这个条件是显而易见的:被发送的消息会从一个线程转移到另外一个线程,这个约束是为了满足线程安全。如果用户指定的泛型参数没有满足条件,在编译的时候会发生错误。</p></li><li><p>发送者调用 send 方法,接收者调用 recv 方法,返回类型都是 Result 类型,用于错误处理,因为它们都有可能调用失败。<strong>当发送者已经被销毁的时候,接收者调用 recv 则会返回错误;同样,当接收者已经销毁的时候,发送者调用 send 也会返回错误。</strong></p></li><li><p>管道还可以是多发送端单接收端。做法很简单,只需将发送端 Sender 复制多份即可。复制方式是调用 Sender 类型的 clone() 方法。这个库不支持多接收端的设计,因此 Receiver 类型没有 clone() 方法。</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">use</span> std::thread;<br><span class="hljs-keyword">use</span> std::sync::mpsc::channel;<br><span class="hljs-keyword">fn</span> <span class="hljs-title function_">main</span>() {<br> <span class="hljs-keyword">let</span> (tx, rx) = <span class="hljs-title function_ invoke__">channel</span>();<br> <span class="hljs-keyword">for</span> <span class="hljs-variable">i</span> <span class="hljs-keyword">in</span> <span class="hljs-number">0</span>..<span class="hljs-number">10</span> {<br> <span class="hljs-comment">// 复制一个新的 tx,将这个复制的变量 move 进入子线程</span><br> <span class="hljs-keyword">let</span> <span class="hljs-variable">tx</span> = tx.<span class="hljs-title function_ invoke__">clone</span>();<br> thread::<span class="hljs-title function_ invoke__">spawn</span>(<span class="hljs-keyword">move</span>|| {<br> tx.<span class="hljs-title function_ invoke__">send</span>(i).<span class="hljs-title function_ invoke__">unwrap</span>();<br> });<br> }<br> <span class="hljs-comment">// 如果没有手动 drop 掉 sender 则程序永远不会停止</span><br> <span class="hljs-title function_ invoke__">drop</span>(tx);<br> <span class="hljs-keyword">while</span> <span class="hljs-keyword">let</span> <span class="hljs-variable">Ok</span>(r) = rx.<span class="hljs-title function_ invoke__">recv</span>() {<br> <span class="hljs-built_in">println!</span>(<span class="hljs-string">"received {}"</span>, r);<br> }<br>}<br></code></pre></td></tr></table></figure></li></ul></li><li><p>同步管道 <code>std::sync::mpsc::sync_channel</code>:其内部有一个固定大小的缓冲区,用来缓存消息。如果缓冲区被填满了,继续调用 send 方法的时候会发生阻塞,等待接收端把缓冲区内的消息拿走才能继续发送。缓冲区的长度可以在建立管道的时候设置,而且 0 是有效数值。如果缓冲区的长度设置为 0,那就意味着每次的发送操作都会进入等待状态,直到这个消息被接收端取走才能返回。</p></li></ul><p></p>]]></content>
<categories>
<category>Rust</category>
</categories>
</entry>
<entry>
<title>深入浅出 Rust笔记 Series 3</title>
<link href="/2023/03/09/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BA%20Rust%E7%AC%94%E8%AE%B0%20Series%203/"/>
<url>/2023/03/09/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BA%20Rust%E7%AC%94%E8%AE%B0%20Series%203/</url>
<content type="html"><![CDATA[<h1 id="系列导航"><a class="markdownIt-Anchor" href="#系列导航"></a> 系列导航</h1><ul><li><a href="https://young-flash.github.io/2023/01/30/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BA%20Rust%E7%AC%94%E8%AE%B0%20Series%201/">深入浅出 Rust笔记 Series 1</a></li><li><a href="https://young-flash.github.io/2023/02/06/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BA%20Rust%E7%AC%94%E8%AE%B0%20Series%202/">深入浅出 Rust笔记 Series 2</a></li><li><a href="https://young-flash.github.io/2023/03/09/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BA%20Rust%E7%AC%94%E8%AE%B0%20Series%203/">深入浅出 Rust笔记 Series 3</a></li><li><a href="https://young-flash.github.io/2023/03/09/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BA%20Rust%E7%AC%94%E8%AE%B0%20Series%204/">深入浅出 Rust笔记 Series 4</a></li></ul><p>这是我在阅读范长春的《深入浅出 Rust》时做的笔记,绝大部分内容源自此书,另有小部分内容源自 <a href="https://course.rs/about-book.html">Rust 圣经</a>。这两份资料是我入门 Rust 的主要材料。</p><h1 id="第三部分-高级抽象"><a class="markdownIt-Anchor" href="#第三部分-高级抽象"></a> 第三部分 高级抽象</h1><h2 id="chap-22-闭包"><a class="markdownIt-Anchor" href="#chap-22-闭包"></a> chap 22 闭包</h2><ul><li><p>闭包(closure)是一种匿名函数,具有“捕获”外部变量的能力。闭包有时候也被称作 lambda 表达式。它有两个特点:(1)可以像函数一样被调用;(2)可以捕获当前环境中的变量。</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">fn</span> <span class="hljs-title function_">main</span>() {<br> <span class="hljs-keyword">let</span> <span class="hljs-variable">add</span> = | a :<span class="hljs-type">i32</span>, b:<span class="hljs-type">i32</span> | <span class="hljs-punctuation">-></span> <span class="hljs-type">i32</span> { <span class="hljs-keyword">return</span> a + b; } ;<br> <span class="hljs-comment">// 闭包的参数和返回值类型都是可以省略的</span><br> <span class="hljs-comment">// let add = |a, b| { a + b };</span><br> <span class="hljs-keyword">let</span> <span class="hljs-variable">x</span> = <span class="hljs-title function_ invoke__">add</span>(<span class="hljs-number">1</span>,<span class="hljs-number">2</span>);<br> <span class="hljs-built_in">println!</span>(<span class="hljs-string">"result is {}"</span>, x);<br>}<br></code></pre></td></tr></table></figure></li><li><p>变量捕获:closure 的原理与 C++11 的 lambda 非常相似。当一个 closure 创建的时候,编译器帮我们生成了一个匿名 struct 类型,通过自动分析 closure 的内部逻辑,来决定该结构体包括哪些数据,以及这些数据该如何初始化。</p><ul><li>在保证能编译通过的情况下,编译器会自动选择一种对外部影响最小的类型存储。对于被捕获的类型为 T 的外部变量,在匿名结构体中的存储方式选择为:尽可能先选择&T 类型,其次选择&mut T 类型,最后选择 T 类型。</li></ul></li><li><p>move 关键字:闭包前加上 move 关键字,所有的变量捕获全部使用 by value 的方式,所有被捕获的外部变量所有权一律转移进闭包。一般用于闭包需要传递到函数外部(escaping closure)的情况。</p></li><li><p>Fn/FnMut/FnOnce:闭包被调用的时候,不需要执行某个成员函数,而是采用类似函数调用的语法来执行。这是因为它自动实现了编译器提供的几个特殊的 trait,Fn 或者 FnMut 或者 FnOnce。</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">pub</span> <span class="hljs-keyword">trait</span> <span class="hljs-title class_">FnOnce</span><Args> {<br> <span class="hljs-keyword">type</span> <span class="hljs-title class_">Output</span>;<br> <span class="hljs-keyword">extern</span> <span class="hljs-string">"rust-call"</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">call_once</span>(<span class="hljs-keyword">self</span>, args: Args) <span class="hljs-punctuation">-></span> <span class="hljs-keyword">Self</span>::Output;<br>}<br><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">trait</span> <span class="hljs-title class_">FnMut</span><Args> : <span class="hljs-built_in">FnOnce</span><Args> {<br> <span class="hljs-keyword">extern</span> <span class="hljs-string">"rust-call"</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">call_mut</span>(&<span class="hljs-keyword">mut</span> <span class="hljs-keyword">self</span>, args: Args) <span class="hljs-punctuation">-></span> <span class="hljs-keyword">Self</span>::Output;<br>}<br><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">trait</span> <span class="hljs-title class_">Fn</span><Args> : <span class="hljs-built_in">FnMut</span><Args> {<br> <span class="hljs-keyword">extern</span> <span class="hljs-string">"rust-call"</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">call</span>(&<span class="hljs-keyword">self</span>, args: Args) <span class="hljs-punctuation">-></span> <span class="hljs-keyword">Self</span>::Output;<br>}<br></code></pre></td></tr></table></figure><p>这几个 trait 的主要区别在于,被调用的时候 self 参数的类型。FnOnce 被调用的时候,self 是通过 move 的方式传递的,因此它被调用之后,这个闭包的生命周期就已经结束了,它只能被调用一次;FnMut 被调用的时候,self 是&mut Self 类型,有能力修改当前闭包本身的成员,甚至可能通过成员中的引用,修改外部的环境变量;Fn 被调用的时候,self 是&Self 类型,只有读取环境变量的能力。</p><ul><li>对于一个闭包,编译器是如何选择 impl 哪个 trait 呢?答案是,编译器会都尝试一遍,实现能让程序编译通过的那几个。闭包调用的时候,会尽可能先选择调用 <code>fn call(&self,args:Args)</code>函数,其次尝试选择 <code>fn call_mut(&self,args:Args)</code> 函数,最后尝试使用 <code>fn call_once(self,args:Args) </code>函数。</li><li><a href="https://stackoverflow.com/questions/42859330/how-do-i-make-a-struct-callable">为自定义类型实现 Fn trait</a> 要使用 nightly 版本的 Rust</li></ul></li><li><p>每个闭包,编译器都会为它生成一个匿名结构体类型;即使两个闭包的参数和返回值一致,它们也是完全不同的两个类型,只是都实现了同一个 trait 而已。</p></li><li><p>闭包与泛型约束</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">fn</span> <span class="hljs-title function_">call_with_closure</span><F>(some_closure: F) <span class="hljs-punctuation">-></span> <span class="hljs-type">i32</span><br> <span class="hljs-keyword">where</span> F : <span class="hljs-title function_ invoke__">Fn</span>(<span class="hljs-type">i32</span>) <span class="hljs-punctuation">-></span> <span class="hljs-type">i32</span> {<br> <span class="hljs-title function_ invoke__">some_closure</span>(<span class="hljs-number">1</span>)<br>}<br></code></pre></td></tr></table></figure><ul><li>其中泛型参数 F 的约束条件是 F:Fn(i32)->i32。这里 Fn(i32)-> i32 是针对闭包设计的专门的语法,而不是像普通 trait 那样使用 Fn<i32,i32> 来写。这样设计为了让它们看起来跟普通函数类型 fn(i32)->i32 更相似。除了语法之外,Fn FnMut FnOnce 其他方面都跟普通的泛型一致。</li></ul></li><li><p>向函数中传递闭包的两种方式(闭包作为函数参数)</p><ul><li><p>通过泛型的方式。这种方式会为不同的闭包参数类型生成不同版本的函数,实现静态分派。</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">// 这里是泛型参数。对于每个不同类型的参数,编译器将会生成不同版本的函数</span><br><span class="hljs-keyword">fn</span> <span class="hljs-title function_">static_dispatch</span><F>(closure: &F)<br> <span class="hljs-keyword">where</span> F: <span class="hljs-title function_ invoke__">Fn</span>(<span class="hljs-type">i32</span>) <span class="hljs-punctuation">-></span> <span class="hljs-type">i32</span><br>{<br> <span class="hljs-built_in">println!</span>(<span class="hljs-string">"static dispatch {}"</span>, <span class="hljs-title function_ invoke__">closure</span>(<span class="hljs-number">42</span>));<br>}<br></code></pre></td></tr></table></figure></li><li><p>通过 trait object 的方式。这种方式会将闭包装箱进入堆内存中,向函数传递一个胖指针,实现运行期动态分派。</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">// 这里是 `trait object``Box<Fn(i32)->i32>`也算`trait object`</span><br><span class="hljs-keyword">fn</span> <span class="hljs-title function_">dynamic_dispatch</span>(closure: &<span class="hljs-title function_ invoke__">Fn</span>(<span class="hljs-type">i32</span>)<span class="hljs-punctuation">-></span><span class="hljs-type">i32</span>)<br>{<br><span class="hljs-built_in">println!</span>(<span class="hljs-string">"dynamic dispatch {}"</span>, <span class="hljs-title function_ invoke__">closure</span>(<span class="hljs-number">42</span>));<br>}<br></code></pre></td></tr></table></figure></li></ul></li><li><p>闭包作为函数的返回值</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><code class="hljs rust"><br><span class="hljs-comment">// 如果我们希望一个闭包作为函数的返回值,那么就不能使用泛型的方式</span><br><span class="hljs-comment">// 了。因为如果泛型类型不在参数中出现,而仅在返回类型中出现的话,会要</span><br><span class="hljs-comment">// 求在调用的时候显式指定类型,编译器才能完成类型推导。可是调用方根本</span><br><span class="hljs-comment">// 无法指定具体类型,因为闭包类型是匿名类型,用户无法显式指定。所以下</span><br><span class="hljs-comment">// 面这样的写法是编译不过的:</span><br><br><span class="hljs-keyword">fn</span> <span class="hljs-title function_">test</span><F>() <span class="hljs-punctuation">-></span> F<br><span class="hljs-keyword">where</span> F: <span class="hljs-title function_ invoke__">Fn</span>(<span class="hljs-type">i32</span>)<span class="hljs-punctuation">-></span><span class="hljs-type">i32</span><br>{<br> <span class="hljs-keyword">return</span> | i | i * <span class="hljs-number">2</span>;<br>}<br><br><span class="hljs-comment">// 但下面这种是可以的</span><br><span class="hljs-keyword">fn</span> <span class="hljs-title function_">test</span><F>(arg: F) <span class="hljs-punctuation">-></span> F<br> <span class="hljs-keyword">where</span> F: <span class="hljs-built_in">Copy</span><br>{<br> arg<br>}<br></code></pre></td></tr></table></figure><ul><li>静态分派。我们可以用一种新的语法 <code>fn test() -> impl Fn(i32)-> i32</code> 来实现。</li><li>动态分派。就是把闭包装箱进入堆内存中,使用 <code>Box<dyn Fn(i32) -> i32></code> 这种 trait object 类型返回</li></ul></li></ul><h2 id="chap-23-动态分派和静态分派"><a class="markdownIt-Anchor" href="#chap-23-动态分派和静态分派"></a> chap 23 动态分派和静态分派</h2><ul><li><p>Rust 可以同时支持“静态分派”(static dispatch)和“动态分派”(dynamic dispatch)。所谓“静态分派”,是指具体调用哪个函数,在编译阶段就确定下来了。Rust 中的“静态分派”靠泛型以及 impl trait 来完成。对于不同的泛型类型参数,编译器会生成不同版本的函数,在编译阶段就确定好了应该调用哪个函数。所谓“动态分派”,是指具体调用哪个函数,在执行阶段才能确定。Rust 中的“动态分派”靠 Trait Object 来完成。Trait Object 本质上是指针,它可以指向不同的类型;指向的具体类型不同,调用的方法也就不同。</p></li><li><p>Traitobject:向 Trait 的指针。&dyn Trait、&mut dyn Trait、Box<dyn Trait>、const dyn Trait*、*mut dyn Trait 以及 Rc<dyn Trait> 等等都是 Trait Object。</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">pub</span> <span class="hljs-keyword">struct</span> <span class="hljs-title class_">TraitObject</span> {<br> <span class="hljs-keyword">pub</span> data: *<span class="hljs-title function_ invoke__">mut</span> (),<br> <span class="hljs-keyword">pub</span> vtable: *<span class="hljs-title function_ invoke__">mut</span> (),<br>}<br></code></pre></td></tr></table></figure><ul><li>自己在想:Trait Object 中指向实际数据的指针有什么用?<br />answer:访问 trait 中方法的 self 参数</li><li>Rust 的动态分派和 C++ 的动态分派,内存布局有所不同。在 C++ 里,如果一个类型里面有虚函数,那么每一个这种类型的变量内部都包含一个指向虚函数表的地址。而在 Rust 里面,对象本身不包含指向虚函数表的指针,这个指针是存在于 trait object 指针里面的。如果一个类型实现了多个 trait,那么不同的 trait object 指向的虚函数表也不一样。</li></ul></li><li><p>object safe。有以下条件之一则不是 object safe,不能创建 trait object</p><ul><li><strong>当 trait 有 Self:Sized 约束时</strong>。如果不希望一个 trait 通过 trait object 的方式使用,可以为它加上 Self:Sized 约束。同理,如果我们想阻止一个函数在虚函数表中出现,可以专门为该函数加上 Self:Sized 约束。</li><li><strong>当函数中有 Self 类型作为参数(除了 self)或者返回类型时</strong>。Rust 规定,如果函数中除了 self 这个参数之外,还在其他参数或者返回值中用到了 Self 类型,那么这个函数就不是 object safe 的。这样的函数是不能使用 trait object 来调用的。这样的方法是不能在虚函数表中存在的。</li><li><strong>当函数第一个参数不是 self 时</strong>。如果有“静态方法”,那这个“静态方法”是不满足 object safe 条件的。这个条件几乎是显然的,编译器没有办法把静态方法加入到虚函数表中。如果一个 trait 中存在静态方法,而又希望通过 trait object 来调用其他的方法,那么我们需要在这个静态方法后面加上 Self:Sized 约束,将它从虚函数表中剔除。</li><li><strong>当函数有泛型参数时</strong>。通过 trait object 调用成员的方法是通过 vtable 虚函数表来进行查找并调用。现在需要被查找的函数成了泛型函数,而泛型函数在 Rust 中是编译阶段自动展开的。这里有一个根本性的冲突问题。Rust 选择的解决方案是,禁止使用 trait object 来调用泛型函数,泛型函数是从虚函数表中剔除了的。这个行为跟 C++ 是一样的。C++ 中同样规定了类的虚成员函数不可以是 template 方法。</li></ul></li></ul>]]></content>
<categories>
<category>Rust</category>
</categories>
</entry>
<entry>
<title>深入浅出 Rust笔记 Series 2</title>
<link href="/2023/02/06/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BA%20Rust%E7%AC%94%E8%AE%B0%20Series%202/"/>
<url>/2023/02/06/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BA%20Rust%E7%AC%94%E8%AE%B0%20Series%202/</url>
<content type="html"><![CDATA[<h1 id="系列导航"><a class="markdownIt-Anchor" href="#系列导航"></a> 系列导航</h1><ul><li><a href="https://young-flash.github.io/2023/01/30/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BA%20Rust%E7%AC%94%E8%AE%B0%20Series%201/">深入浅出 Rust笔记 Series 1</a></li><li><a href="https://young-flash.github.io/2023/02/06/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BA%20Rust%E7%AC%94%E8%AE%B0%20Series%202/">深入浅出 Rust笔记 Series 2</a></li><li><a href="https://young-flash.github.io/2023/03/09/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BA%20Rust%E7%AC%94%E8%AE%B0%20Series%203/">深入浅出 Rust笔记 Series 3</a></li><li><a href="https://young-flash.github.io/2023/03/09/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BA%20Rust%E7%AC%94%E8%AE%B0%20Series%204/">深入浅出 Rust笔记 Series 4</a></li></ul><p>这是我在阅读范长春的《深入浅出 Rust》时做的笔记,绝大部分内容源自此书,另有小部分内容源自 <a href="https://course.rs/about-book.html">Rust 圣经</a>。这两份资料是我入门 Rust 的主要材料。</p><h1 id="第二部分-内存安全"><a class="markdownIt-Anchor" href="#第二部分-内存安全"></a> 第二部分 内存安全</h1><p>Rust 希望通过语言的机制和编译器的功能,把程序员易犯错、不易检查的问题解决在编译期,避免运行时的内存错误。这一部分主要探讨 Rust 是如何达到内存安全特性的。</p><h2 id="chap-10-内存管理基础"><a class="markdownIt-Anchor" href="#chap-10-内存管理基础"></a> chap 10 内存管理基础</h2><ul><li><p>segmentation fault 形成:进程空间中的每个段通过硬件 MMU 映射到真正的物理空间;在这个映射过程中可以给不同的段设置不同的访问权限,比如代码段就是只能读不能写;进程在执行过程中,如果违反了这些权限,CPU 会直接产生一个硬件异常;硬件异常会被操作系统内核处理,一般内核会向对应的进程发送一条信号;如果没有实现自己特殊的信号处理函数,默认情况下,这个进程会直接非正常退出;如果操作系统打开了 core dump 功能,在进程退出的时候操作系统会把它当时的内存状态、寄存器状态以及各种相关信息保存到一个文件中,供用户以后调试使用。</p></li><li><p>非内存安全的情况</p><ul><li>空指针:解引用空指针是不安全的。这块地址空间一般是受保护的,对空指针解引用在大部分平台上会产生 segfault。</li><li>野指针:指的是未初始化的指针。它的值取决于它这个位置以前遗留下来的是什么值。所以它可能指向任意一个地方。对它解引用,可能会造成 segfault,也可能不会,纯粹凭运气。但无论如何,这个行为都不会是预期内的行为,是一定会产生 bug 的。</li><li>悬空指针:指的是内存空间在被释放了之后,继续使用。它跟野指针类似,同样会读写已经不属于这个指针的内容。</li><li>使用未初始化内存:不只是指针类型,任何一种类型不初始化就直接使用都是危险的,造成的后果完全无法预测。</li><li>非法释放:内存分配和释放要配对。如果对同一个指针释放两次,会制造出内存错误。如果指针并不是内存分配器返回的值,对其执行释放操作,也是危险的。</li><li>缓冲区溢出:指针访问越界了,结果也是类似于野指针,会读取或者修改临近内存空间的值,造成危险。</li><li>执行非法函数指针:如果一个函数指针不是准确地指向一个函数地址,那么调用这个函数指针会导致一段随机数据被当成指令来执行,是非常危险的。</li><li>数据竞争:在有并发的场景下,针对同一块内存同时读写,且没有同步措施。</li></ul></li><li><p>一些内存错误是不算在“内存安全”范畴内的,比如内存泄漏以及内存耗尽。另外,panic 也不属于内存安全相关的问题。</p></li><li><p>panic 和 core dump 之间有重要区别。panic 是发生不可恢复错误后,程序主动执行的一种错误处理机制;而 core dump 则是程序失控之后,触发了操作系统的保护机制而被动退出的。发生 panic 的时候,此处就是确定性的第一现场,我们可以根据 call stack 信息很快找到事发地点,然后修复。panic 是防止更严重内存安全错误的重要机制。</p></li></ul><p></p><h2 id="chap-11-所有权和移动语义"><a class="markdownIt-Anchor" href="#chap-11-所有权和移动语义"></a> chap 11 所有权和移动语义</h2><h3 id="111-所有权"><a class="markdownIt-Anchor" href="#111-所有权"></a> 11.1 所有权</h3><ul><li><p>“所有权”代表着以下意义:</p><ul><li>每个值在 Rust 中都有一个变量来管理它,这个变量就是这个值、这块内存的所有者;</li><li>每个值在一个时间点上只有一个管理者;</li><li>当变量所在的作用域结束的时候,变量以及它代表的值将会被销毁。</li></ul></li><li><p>在 Rust 中不可以做“赋值运算符重载”,若需要“深复制”,必须手工调用 clone 方法。这个 clone 方法来自于 std::clone::Clone 这个 trait。clone 方法里面的行为是可以自定义的。</p></li></ul><h3 id="112-移动语义"><a class="markdownIt-Anchor" href="#112-移动语义"></a> 11.2 移动语义</h3><ul><li><p>一个变量可以把它拥有的值转移给另外一个变量,称为“所有权转移”。<strong>赋值语句、函数调用、函数返回等,都有可能导致所有权转移。</strong></p></li><li><p>Rust 中的变量绑定操作,默认是 move 语义,执行了新的变量绑定后,原来的变量就不能再被使用。</p></li><li><p>“语义”不代表最终的执行效率。“语义”只是规定了什么样的代码是编译器可以接受的,以及它执行后的效果可以用怎样的思维模型去理解。<strong>编译器有权在不改变语义的情况下做任何有利于执行效率的优化。语义和优化是两个阶段的事情。</strong></p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">fn</span> <span class="hljs-title function_">create</span>() <span class="hljs-punctuation">-></span> BigObject {<br> <span class="hljs-keyword">let</span> <span class="hljs-variable">local</span> = …;<br> <span class="hljs-keyword">return</span> local;<br>}<br><span class="hljs-keyword">let</span> <span class="hljs-variable">v</span> = <span class="hljs-title function_ invoke__">create</span>();<br><br><span class="hljs-comment">// 完全可能被优化为类似如下的效果:</span><br><br><span class="hljs-keyword">fn</span> <span class="hljs-title function_">create</span>(p: &<span class="hljs-keyword">mut</span> BigObject) {<br> ptr::<span class="hljs-title function_ invoke__">write</span>(p, …);<br>}<br><span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">v</span>: BigObject = <span class="hljs-title function_ invoke__">uninitialized</span>();<br><span class="hljs-title function_ invoke__">create</span>(&<span class="hljs-keyword">mut</span> v);<br></code></pre></td></tr></table></figure><p>编译器可以提前在当前调用栈中把大对象的空间分配好,然后把这个对象的指针传递给子函数,由子函数执行这个变量的初始化。这样就避免了大对象的复制工作,参数传递只是一个指针而已。这么做是完全满足移动语义要求的,而且编译器还有权利做更多类似的优化。</p></li></ul><h3 id="113-复制语义"><a class="markdownIt-Anchor" href="#113-复制语义"></a> 11.3 复制语义</h3><ul><li><p>对于一些简单类型,比如整数、bool,在赋值的时候默认采用复制操作</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">fn</span> <span class="hljs-title function_">main</span>() {<br> <span class="hljs-keyword">let</span> <span class="hljs-variable">v1</span> : <span class="hljs-type">isize</span> = <span class="hljs-number">0</span>;<br> <span class="hljs-keyword">let</span> <span class="hljs-variable">v2</span> = v1;<br> <span class="hljs-built_in">println!</span>(<span class="hljs-string">"{}"</span>, v1);<br>}<br></code></pre></td></tr></table></figure><p>在 Rust 中有一部分“特殊照顾”的类型,其变量绑定操作是 copy 语义。所谓的 copy 语义,是指在执行变量绑定操作的时候,v2 是对 v1 所属数据的一份复制。v1 所管理的这块内存依然存在,并未失效,而 v2 是新开辟了一块内存,它的内容是从 v1 管理的内存中复制而来的。和手动调用 clone 方法效果一样,<code>let v2=v1;</code> 等效于 <code>let v2 = v1.clone();</code>。</p></li><li><p>copy 语义与 move 语义:<strong>这两个操作本身是一样的,都是简单的内存复制,区别在于复制完<br />以后,原先那个变量的生命周期是否结束。</strong></p></li><li><p>在普通变量绑定、函数传参、模式匹配等场景下,凡是实现了 std::marker::Copy trait 的类型,都会执行 copy 语义。基本类型,比如数字、字符、bool 等,都实现了 Copy trait,因此具备 copy 语义。</p></li><li><p>对于自定义类型,默认是没有实现 Copy trait 的,可以手动添上。Copy 继承了 Clone,要实现 Copy trait 必须同时实现 Clone trait。只要一个类型的所有成员都具有 Clone trait,就可以使用 <code>#[derive(Copy, Clone)]</code> 来让编译器实现 Clone trait 了。</p></li></ul><h3 id="114-box-类型"><a class="markdownIt-Anchor" href="#114-box-类型"></a> 11.4 Box 类型</h3><ul><li>Box 类型是 Rust 中一种常用的指针类型。<strong>Box 代表“拥有所有权的指针”</strong>,类似于 C++ 里面的 unique_ptr(严格来说,unique_ptr<T> 更像 Option<Box<T>>)</li><li>Box 类型永远执行的是 move 语义,不能是 copy 语义。Rust 中的 copy 语义就是浅复制。对于 Box 这样的类型而言,浅复制必然会造成二次释放问题。</li><li>它包裹的值会被强制分配在堆上</li><li><img src="/img/blog_pic/2023/image-20230118104121-gclzz19.png" alt="image" /></li><li>由于 <code>Box</code> 是简单的封装,除了将值存储在堆上外,并没有其它性能上的损耗。而性能和功能往往是鱼和熊掌,因此 <code>Box</code> 相比其它智能指针,功能较为单一,可以在以下场景中使用它:<ul><li>特意的将数据分配在堆上</li><li>数据较大时,又不想在转移所有权时进行数据拷贝:当栈上数据转移所有权时,实际上是把数据拷贝了一份,最终新旧变量各自拥有不同的数据,因此所有权并未转移。而堆上则不然,底层数据并不会被拷贝,转移所有权仅仅是复制一份栈中的指针,再将新的指针赋予新的变量,然后让拥有旧指针的变量失效,最终完成了所有权的转移。</li><li>类型的大小在编译期无法确定,但是我们又需要固定大小的类型时</li><li>特征对象,用于说明对象实现了一个特征,而不是某个特定的类型</li></ul></li><li><a href="https://course.rs/advance/smart-pointer/box.html#boxleak">Box::leak</a> 使用场景:<strong>需要一个在运行期初始化的值,但是可以全局有效,也就是和整个程序活得一样久</strong></li></ul><h3 id="115-clone-vs-copy"><a class="markdownIt-Anchor" href="#115-clone-vs-copy"></a> 11.5 Clone VS Copy</h3><ul><li><p>std::marker::Copy:如果一个类型 impl 了 Copy trait,意味着任何时候都可以通过简单的内存复制(在 C 语言里按字节复制 memcpy)实现该类型的复制,并且不会产生任何内存安全问题。一旦一个类型实现了 Copy trait,那么它在变量绑定、函数参数传递、函数返回值传递等场景下,都是 copy 语义,而不再是默认的 move 语义。</p><ul><li>std::marker 模块里面所有的 trait 都是特殊的 trait。目前稳定的有四个,它们是 Copy、Send、Sized、Sync。<strong>它们的特殊之处在于:它们是跟编译器密切绑定的,impl 这些 trait 对编译器的行为有重要影响。在编译器眼里,它们与其他的 trait 不一样。这几个 trait 内部都没有方法,它们的唯一任务是给类型打一个“标记”,表明它符合某种约定——这些约定会影响编译器的静态检查以及代码生成。</strong></li></ul></li><li><p>Copy 的实现条件</p><ul><li>对于自定义类型,只有所有成员都实现了 Copy trait,这个类型才有资格实现 Copy trait。struct 和 enum 类型不会自动实现 Copy trait,只有当 struct 和 enum 内部的每个元素都是 Copy 类型时,编译器才允许针对此类型实现 Copy trait。而对于元组 tuple 类型,如果它的每一个元素都是 Copy 类型,那么这个 tuple 也是 Copy 类型。</li><li>常见的数字类型、bool 类型、共享借用指针&,都是具有 Copy 属性的类型。</li><li>Box、Vec、可写借用指针&mut 等类型都是不具备 Copy 属性的类型。</li><li><strong>对于数组类型,如果它内部的元素类型是 Copy,那么这个数组也是 Copy 类型。</strong></li></ul></li><li><p>std::clone::Clone:clone 方法一般用于“基于语义的复制”操作。所以,它做什么事情,跟具<br />体类型的作用息息相关。比如,对于 Box 类型,clone 执行的是“深复制”;而对于 Rc 类型,clone 做的事情就是把引用计数值加 1。<strong>对于实现了 copy 的类型,它的 clone 方法应该跟 copy 语义相容,等同于按字节复制。</strong></p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">pub</span> <span class="hljs-keyword">trait</span> <span class="hljs-title class_">Clone</span> : <span class="hljs-built_in">Sized</span> {<br> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">clone</span>(&<span class="hljs-keyword">self</span>) <span class="hljs-punctuation">-></span> <span class="hljs-keyword">Self</span>;<br> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">clone_from</span>(&<span class="hljs-keyword">mut</span> <span class="hljs-keyword">self</span>, source: &<span class="hljs-keyword">Self</span>) {<br> *<span class="hljs-keyword">self</span> = source.<span class="hljs-title function_ invoke__">clone</span>()<br> }<br>}<br></code></pre></td></tr></table></figure><p>有两个关联方法,分别是 clone_from 和 clone,clone_from 是有默认实现的,依赖于 clone 方法的实现。clone 方法没有默认实现,需要手动实现。</p><ul><li><code>#[derive(Clone)]</code> 让编译器自动生成那些重复的代码。编译器自动生成的 clone 方法非常机械,就是依次调用每个成员的 clone 方法。</li></ul></li><li><p>总结</p><ul><li>Copy 内部没有方法,Clone 内部有两个方法(clone、clone_from)</li><li>Copy trait 是给编译器用的,告诉编译器这个类型默认采用 copy 语义而不是 move 语义。Clone trait 是给程序员用的,必须手动调用 clone 方法它才能发挥作用。</li><li>Copy trait 不是想实现就能实现的,它对类型是有要求的,有些类型(Box<T>)不可能 impl Copy。而 Clone trait 则没有什么前提条件,任何类型都可以实现(unsized 类型除外,因为无法使用 unsized 类型作为返回值)。</li><li>Copy trait 规定了这个类型在执行变量绑定、函数参数传递、函数返回等场景下的操作方式。即这个类型在这种场景下,必然执行的是“简单内存复制”操作,这是由编译器保证的,程序员无法控制。Clone trait 里面的 clone 方法究竟会执行什么操作,则是取决于程序员自己写的逻辑。一般情况下,clone 方法应该执行一个“深复制”操作,但这不是强制性的</li><li>Rust 规定了在 T:Copy 的情况下 Clone trait 代表的含义。即:当某变量 t:T 符合 T:Copy 时,它调用 t.clone() 方法的含义必须等同于“简单内存复制”。也就是说,<code>t.clone()</code> 的行为必须等同于 <code>let x = std::ptr::read(&t);</code> ,也等同于 <code>let x = t;</code> 。</li></ul></li></ul><h3 id="析构"><a class="markdownIt-Anchor" href="#析构"></a> 析构</h3><ul><li><p>在 Rust 中编写“析构函数”的办法是 impl std::ops::Drop。Drop trait 的定义如下:</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">trait</span> <span class="hljs-title class_">Drop</span> {<br> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">drop</span>(&<span class="hljs-keyword">mut</span> <span class="hljs-keyword">self</span>);<br>}<br></code></pre></td></tr></table></figure></li><li><p>主动析构:用户主动调用析构函数是非法的。需调用标准库中的 std::mem::drop()</p><ul><li><p>std::mem::drop() 实现</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs rust"><span class="hljs-meta">#[inline]</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">drop</span><T>(_x: T) { }<br></code></pre></td></tr></table></figure><ul><li>drop 函数不需要任何的函数体,只需要参数为“值传递”即可。将对象的所有权移入函数中,什么都不用做,编译器就会自动释放掉这个对象了。</li><li>因为这个 drop 函数的关键在于使用 move 语义把参数传进来,使得变量的所有权从调用方移动到 drop 函数体内,参数类型一定要是 T,而不是&T 或者其他引用类型。函数体本身其实根本不重要,重要的是把变量的所有权 move 进入这个函数体中,函数调用结束的时候该变量的生命周期结束,变量的析构函数会自动调用,管理的内存空间也会自然释放。这个过程完全符合前面讲的生命周期、move 语义,无须编译器做特殊处理。</li><li>因此,对于 Copy 类型的变量,对它调用 std::mem::drop() 是没有意义的。因为 Copy 类型在函数参数传递的时候执行的是复制语义,原来的那个变量依然存在,传入函数中的只是一个复制品,因此原变量的生命周期不会受到影响。</li></ul></li></ul></li><li><p>变量遮蔽(Shadowing)不会导致变量生命周期提前结束,它不等同于 drop。</p></li><li><p>注意:用下划线来绑定一个变量,那么这个变量会当场执行析构,而不是等到当前语句块结束的时候再执行。下划线是特殊符号,不是普通标识符。</p></li><li><p>std::mem::drop()函数和 std::ops::Drop::drop()方法的区别</p><ol><li>std::mem::drop()函数是一个独立的函数,不是某个类型的成员方法,它由程序员主动调用,作用是使变量的生命周期提前结束;std::ops::Drop::drop()方法是一个 trait 中定义的方法,当变量的生命周期结束的时候,编译器会自动调用,手动调用是不允许的。</li><li>std::mem::drop<T>(_x:T)的参数类型是 T,采用的是 move 语义;std::ops::Drop::drop(&mut self)的参数类型是&mut Self,采用的是可变借用。在析构函数调用过程中,程序员还有机会读取或者修改此对象的属性。</li></ol></li></ul><h2 id="chap-12-借用和生命周期"><a class="markdownIt-Anchor" href="#chap-12-借用和生命周期"></a> chap 12 借用和生命周期</h2><ul><li><p>生命周期简而言之就是引用的有效作用域。生命周期符号使用单引号开头,后面跟一个合法的名字。生命周期标记和泛型类型参数是一样的,都需要先声明后使用。</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">fn</span> <span class="hljs-title function_">test</span><<span class="hljs-symbol">'a</span>>(arg: &<span class="hljs-symbol">'a</span> T) <span class="hljs-punctuation">-></span> &<span class="hljs-symbol">'a</span> <span class="hljs-type">i32</span> {<br> &arg.member<br>}<br></code></pre></td></tr></table></figure></li><li><p>一个生命周期标注,它自身并不具有什么意义,因为生命周期的作用就是告诉编译器多个引用之间的关系。例如,有一个函数,它的第一个参数 <code>first</code> 是一个指向 <code>i32</code> 类型的引用,具有生命周期 <code>'a</code>,该函数还有另一个参数 <code>second</code>,它也是指向 <code>i32</code> 类型的引用,并且同样具有生命周期 <code>'a</code>。此处生命周期标注仅仅说明,这两个参数 <code>first</code> 和 <code>second</code> 至少活得和’a 一样久,至于到底活多久或者哪个活得更久都无法得知。</p></li><li><p>借用指针类型都有一个生命周期泛型参数,它们的完整写法应该是&'a T、&'a mut T,只不过在做局部变量的时候,生命周期参数是可以省略的。</p></li><li><p>生命周期之间有重要的包含关系。<strong>如果生命周期’a 比’b 更长或相等,则记为’a: 'b,意思是’a 至少不会比’b 短</strong>。'static 是一个特殊的生命周期,它代表的是这个程序从开始到结束的整个阶段,所以它比其他任何生命周期都长。这意味着,任意一个生命周期’a 都满足’static: 'a。</p></li><li><p>Rust 的引用类型是支持“协变”的。在编译器眼里,生命周期就是一个区间,生命周期参数就是一个普通的泛型参数,它可以被特化为某个具体的生命周期。</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">fn</span> <span class="hljs-title function_">select</span><<span class="hljs-symbol">'a</span>>(arg1: &<span class="hljs-symbol">'a</span> <span class="hljs-type">i32</span>, arg2: &<span class="hljs-symbol">'a</span> <span class="hljs-type">i32</span>) <span class="hljs-punctuation">-></span> &<span class="hljs-symbol">'a</span> <span class="hljs-type">i32</span> {<br> <span class="hljs-keyword">if</span> *arg1 > *arg2 {<br> arg1<br> }<br> <span class="hljs-keyword">else</span> {<br> arg2<br> }<br>}<br><br><span class="hljs-keyword">fn</span> <span class="hljs-title function_">main</span>() {<br> <span class="hljs-keyword">let</span> <span class="hljs-variable">x</span> = <span class="hljs-number">1</span>;<br> <span class="hljs-keyword">let</span> <span class="hljs-variable">y</span> = <span class="hljs-number">2</span>;<br> <span class="hljs-keyword">let</span> <span class="hljs-variable">selected</span> = <span class="hljs-title function_ invoke__">select</span>(&x, &y);<br> <span class="hljs-built_in">println!</span>(<span class="hljs-string">"{}"</span>, selected);<br>}<br></code></pre></td></tr></table></figure><ul><li>select 函数引入了一个生命周期标记,两个参数以及返回值都是用的这个生命周期标记。在调用的时候,传递的实参其实是具备不同的生命周期的。x 的生命周期明显大于 y 的生命周期,&x 可存活的范围要大于&y 可存活的范围,将它们的实际生命周期分别记录为’x 和’y。select 函数的形式参数要求的是同样的生命周期,而实际参数是两个不同生命周期的引用,这个类型之所以可以匹配成功,就是因为生命周期的协变特性。编译器可以把&x 和&y 的生命周期都缩小到某个生命周期’a 以内,且满足’x:'a,'y:'a。返回的 selected 变量具备’a 生命周期,也并没有超过’x 和’y 的范围。所以,最终的生命周期检查可以通过。</li></ul></li><li><p>编译器使用三条消除规则来确定哪些场景不需要显式地去标注生命周期。其中第一条规则应用在输入生命周期上,第二、三条应用在输出生命周期上。若编译器发现三条规则都不适用时,就会报错,提示你需要手动标注生命周期。</p><ol><li>每一个引用参数都会获得独自的生命周期例如一个引用参数的函数就有一个生命周期标注: <code>fn foo<'a>(x: &'a i32)</code>,两个引用参数的有两个生命周期标注:<code>fn foo<'a, 'b>(x: &'a i32, y: &'b i32)</code>, 依此类推。</li><li>若只有一个输入生命周期(函数参数中只有一个引用类型),那么该生命周期会被赋给所有的输出生命周期,也就是所有返回值的生命周期都等于该输入生命周期例如函数 <code>fn foo(x: &i32) -> &i32</code>,<code>x</code> 参数的生命周期会被自动赋给返回值 <code>&i32</code>,因此该函数等同于 <code>fn foo<'a>(x: &'a i32) -> &'a i32</code></li><li>若存在多个输入生命周期,且其中一个是 <code>&self</code> 或 <code>&mut self</code>,则 <code>&self</code> 的生命周期被赋给所有的输出生命周期拥有 <code>&self</code> 形式的参数,说明该函数是一个 <code>方法</code>,该规则让方法的使用便利度大幅提升。</li></ol></li></ul><h2 id="chap-15-内部可变性"><a class="markdownIt-Anchor" href="#chap-15-内部可变性"></a> chap 15 内部可变性</h2><ul><li><p>Rust 的 borrow checker 的核心思想是“共享不可变,可变不共享”。但是只有这个规则是不够的,在某些情况下,我们的确需要在存在共享的情况下可变。为了让这种情况是可控的、安全的,Rust 还设计了一种“内部可变性”(interior mutability)</p></li><li><p>承袭可变性:<strong>Rust 中的 mut 关键字不能在声明类型的时候使用,只能跟变量一起使用。类型本身不能规定自己是否是可变的。一个变量是否是可变的,取决于它的使用环境,而不是它的类型。可变还是不可变取决于变量的使用方式。不能在类型声明的时候指定可变性,比如在 struct 中对某部分成员使用 mut 修饰,这是不合法的。只能在变量声明的时候指定可变性。也不能针对变量的某一部分成员指定可变性,其他部分保持不变。</strong></p></li><li><p>存在 &mut T 就不能存在 &T 的原因:这会引发内存安全问题。比如同时拥有 Vec 的可变引用和不可变引用,通过可变引用向该 Vec 中 push 数据,发生扩容后再去读原来的不可变引用,此时那块内存已经失效(迭代器失效)</p></li><li><p>内部可变性——可以通过共享指针修改它内部的值。虽然粗略一看,Cell 类型似乎违反了 Rust 的“唯一修改权”原则。可以存在多个指向 Cell 类型的不可变引用,同时还能利用不可变引用改变 Cell 内部的值。但实际上,这个类型是完全符合“内存安全”的。再想想,为什么 Rust 要尽力避免 alias 和 mutation 同时存在?因为假如同时有可变指针和不可变指针指向同一块内存,有可能出现通过一个可变指针修改内存的过程中,数据结构处于被破坏状态的情况下,被其他的指针观测到。Cell 类型是不会出现这样的情况的。因为 Cell 类型把数据包裹在内部,用户无法获得指向内部状态的指针,这意味着每次方法调用都是执行的一次完整的数据移动操作。每次方法调用之后,Cell 类型的内部都处于一个正确的状态,不可能观察到数据被破坏掉的状态。多个共享指针指向 Cell 类型的状态如下图所示,Cell 就是一个“壳”,它把数据严严实实地包裹在里面,所有的指针只能指向 Cell,不能直接指向数据。修改数据只能通过 Cell 来完成,用户无法创造一个直接指向数据的指针。</p><ul><li><img src="/img/blog_pic/2023/image-20230122104639-2m2f737.png" alt="image" /></li></ul></li><li><p>内部可变性的实现是因为 Rust 使用了 <code>unsafe</code> 来做到这一点,但是对于使用者来说,这些都是透明的,因为这些不安全代码都被封装到了安全的 API 中 由于 <code>Cell</code> 类型针对的是实现了 <code>Copy</code> 特征的值类型,因此在实际开发中,<code>Cell</code> 使用的并不多,因为时实际要解决的往往是可变、不可变引用共存导致的问题,此时就需要借助于 <code>RefCell</code> 来达成目的。Cell 类型没办法制造出直接指向内部数据的指针,而 RefCell 可以;Cell 只适用于 Copy 类型,用于提供值,而 RefCell 用于提供引用。RefCell 类型放弃了编译阶段的 alias+mutation 原则,但依然会在执行阶段保证 alias+mutation 原则。</p></li><li><table><thead><tr><th>Rust 规则</th><th>智能指针带来的额外规则</th></tr></thead><tbody><tr><td>一个数据只有一个所有者</td><td><code>Rc/Arc</code> 让一个数据可以拥有多个所有者</td></tr><tr><td>要么多个不可变借用,要么一个可变借用</td><td><code>RefCell</code> 实现编译期可变、不可变引用共存</td></tr><tr><td>违背规则导致<strong>编译错误</strong></td><td>违背规则导致运行时<code>panic</code></td></tr></tbody></table><ul><li>可以看出,<code>Rc/Arc</code> 和 <code>RefCell</code> 合在一起,解决了 Rust 中严苛的所有权和借用规则带来的某些场景下难使用的问题。但是它们并不是银弹,例如 <code>RefCell</code> 实际上并没有解决可变引用和引用可以共存的问题,只是将报错从编译期推迟到运行时,从编译器错误变成了 <code>panic</code> 异常</li></ul></li><li><p>内部可变性小例子</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><code class="hljs rust"><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">trait</span> <span class="hljs-title class_">Messenger</span> {<br> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">send</span>(&<span class="hljs-keyword">self</span>, msg: <span class="hljs-type">String</span>);<br>}<br><br><span class="hljs-comment">// --------------------------</span><br><span class="hljs-comment">// 我们的代码中的数据结构和实现</span><br><span class="hljs-keyword">struct</span> <span class="hljs-title class_">MsgQueue</span> {<br> msg_cache: <span class="hljs-type">Vec</span><<span class="hljs-type">String</span>>,<br>}<br><br><span class="hljs-keyword">impl</span> <span class="hljs-title class_">Messenger</span> <span class="hljs-keyword">for</span> <span class="hljs-title class_">MsgQueue</span> {<br> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">send</span>(&<span class="hljs-keyword">self</span>, msg: <span class="hljs-type">String</span>) {<br> <span class="hljs-keyword">self</span>.msg_cache.<span class="hljs-title function_ invoke__">push</span>(msg)<br> }<br>}<br></code></pre></td></tr></table></figure><ul><li><p>如上所示,外部库中定义了一个消息发送器特征 <code>Messenger</code>,它只有一个发送消息的功能:<code>fn send(&self, msg: String)</code>,因为发送消息不需要修改自身,因此原作者在定义时,使用了 <code>&self</code> 的不可变借用,这个无可厚非。</p><p>要在自己的代码中使用该特征实现一个异步消息队列,出于性能的考虑,消息先写到本地缓存(内存)中,然后批量发送出去,因此在 <code>send</code> 方法中,需要将消息先行插入到本地缓存 <code>msg_cache</code> 中。但是问题来了,该 <code>send</code> 方法的签名是 <code>&self</code>,因此上述代码会报错:</p></li></ul><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs rust">error[E0596]: cannot borrow `<span class="hljs-keyword">self</span>.msg_cache` <span class="hljs-keyword">as</span> mutable, <span class="hljs-keyword">as</span> it is behind a `&` reference<br> -<span class="hljs-punctuation">-></span> src/main.rs:<span class="hljs-number">11</span>:<span class="hljs-number">9</span><br> |<br><span class="hljs-number">2</span> | <span class="hljs-keyword">fn</span> <span class="hljs-title function_">send</span>(&<span class="hljs-keyword">self</span>, msg: <span class="hljs-type">String</span>);<br> | ----- help: consider changing that to be a mutable reference: `&<span class="hljs-keyword">mut</span> <span class="hljs-keyword">self</span>`<br>...<br><span class="hljs-number">11</span> | <span class="hljs-keyword">self</span>.msg_cache.<span class="hljs-title function_ invoke__">push</span>(msg)<br> | ^^^^^^^^^^^^^^^^^^ `<span class="hljs-keyword">self</span>` is a `&` reference, so the data it refers to cannot be borrowed <span class="hljs-keyword">as</span> mutable<br></code></pre></td></tr></table></figure><ul><li>在报错的同时,编译器大聪明还善意地给出了提示:将 <code>&self</code> 修改为 <code>&mut self</code>,但是。。。我们实现的特征是定义在外部库中,因此该签名根本不能修改。值此危急关头, <code>RefCell</code> 闪亮登场:</li></ul><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">use</span> std::cell::RefCell;<br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">trait</span> <span class="hljs-title class_">Messenger</span> {<br> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">send</span>(&<span class="hljs-keyword">self</span>, msg: <span class="hljs-type">String</span>);<br>}<br><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">struct</span> <span class="hljs-title class_">MsgQueue</span> {<br> msg_cache: RefCell<<span class="hljs-type">Vec</span><<span class="hljs-type">String</span>>>,<br>}<br><br><span class="hljs-keyword">impl</span> <span class="hljs-title class_">Messenger</span> <span class="hljs-keyword">for</span> <span class="hljs-title class_">MsgQueue</span> {<br> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">send</span>(&<span class="hljs-keyword">self</span>, msg: <span class="hljs-type">String</span>) {<br> <span class="hljs-keyword">self</span>.msg_cache.<span class="hljs-title function_ invoke__">borrow_mut</span>().<span class="hljs-title function_ invoke__">push</span>(msg)<br> }<br>}<br><br><span class="hljs-keyword">fn</span> <span class="hljs-title function_">main</span>() {<br> <span class="hljs-keyword">let</span> <span class="hljs-variable">mq</span> = MsgQueue {<br> msg_cache: RefCell::<span class="hljs-title function_ invoke__">new</span>(Vec::<span class="hljs-title function_ invoke__">new</span>()),<br> };<br> mq.<span class="hljs-title function_ invoke__">send</span>(<span class="hljs-string">"hello, world"</span>.<span class="hljs-title function_ invoke__">to_string</span>());<br>}<br><br></code></pre></td></tr></table></figure><ul><li>这个 MQ 功能很弱,但是并不妨碍我们演示内部可变性的核心用法:通过包裹一层 <code>RefCell</code>,成功的让 <code>&self</code> 中的 <code>msg_cache</code> 成为一个可变值,然后实现对其的修改。</li></ul></li><li><p>性能上看,<code>RefCell</code> 由于是非线程安全的,因此无需保证原子性,性能虽然有一点损耗(需要维护一个借用计数器),但是依然非常好,而 <code>Cell</code> 则完全不存在任何额外的性能损耗。</p><ul><li>RefCell 原理:RefCell 内部有一个“借用计数器”,调用 borrow 方法的时候,计数器里面的“共享引用计数”值就加 1。当这个 borrow 结束的时候,会将这个值自动减 1(如下图所示)。同样,borrow_mut 方法被调用的时候,它就记录一下当前存在“可变引用”。如果“共享引用”和“可变引用”同时出现了,就会报错。</li><li><img src="/img/blog_pic/2023/image-20230122111220-h8k92j6.png" alt="image" /></li></ul></li><li><p><code>Cell::get_mut(&mut self)</code> 可以获取 &mut T,由此可以改变 Cell 内部包裹的值;这个方法和 <code>Cell::set(&self, , val: T)</code> 有何区别? 回答:区别在于 self 的类型,前者是可变引用,后者是不可变引用。在能获取到可变引用的情况下修改内部的数据是理所当然的,但正是在不可变引用下还能修改体现了内部可变性。</p></li><li><p>Cell 和 RefCell 部分 API</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">impl</span><T> Cell<T> {<br> <span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">get_mut</span>(&<span class="hljs-keyword">mut</span> <span class="hljs-keyword">self</span>) <span class="hljs-punctuation">-></span> &<span class="hljs-keyword">mut</span> T { }<br> <span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">set</span>(&<span class="hljs-keyword">self</span>, val: T) { }<br> <span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">swap</span>(&<span class="hljs-keyword">self</span>, other: &<span class="hljs-keyword">Self</span>) { }<br> <span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">replace</span>(&<span class="hljs-keyword">self</span>, val: T) <span class="hljs-punctuation">-></span> T { }<br> <span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">into_inner</span>(<span class="hljs-keyword">self</span>) <span class="hljs-punctuation">-></span> T { }<br>}<br><br><span class="hljs-keyword">impl</span><T:<span class="hljs-built_in">Copy</span>> Cell<T> {<br> <span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">get</span>(&<span class="hljs-keyword">self</span>) <span class="hljs-punctuation">-></span> T { }<br>}<br></code></pre></td></tr></table></figure><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">impl</span><T: ?<span class="hljs-built_in">Sized</span>> RefCell<T> {<br> <span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">borrow</span>(&<span class="hljs-keyword">self</span>) <span class="hljs-punctuation">-></span> Ref<T> { }<br> <span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">try_borrow</span>(&<span class="hljs-keyword">self</span>) <span class="hljs-punctuation">-></span> <span class="hljs-type">Result</span><Ref<T>, BorrowError> { }<br> <span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">borrow_mut</span>(&<span class="hljs-keyword">self</span>) <span class="hljs-punctuation">-></span> RefMut<T> { }<br> <span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">try_borrow_mut</span>(&<span class="hljs-keyword">self</span>) <span class="hljs-punctuation">-></span> <span class="hljs-type">Result</span><RefMut<T>, BorrowMutError> { }<br> <span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">get_mut</span>(&<span class="hljs-keyword">mut</span> <span class="hljs-keyword">self</span>) <span class="hljs-punctuation">-></span> &<span class="hljs-keyword">mut</span> T { }<br>}<br></code></pre></td></tr></table></figure></li></ul><h2 id="chap-16-解引用智能指针"><a class="markdownIt-Anchor" href="#chap-16-解引用智能指针"></a> chap 16 解引用(智能指针)</h2><ul><li><p>解引用操作可以被自定义。方法是实现标准库中的 std::ops::Deref 或者 std::ops::DerefMut 这两个 trait。</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">pub</span> <span class="hljs-keyword">trait</span> <span class="hljs-title class_">Deref</span> {<br> <span class="hljs-keyword">type</span> <span class="hljs-title class_">Target</span>: ?<span class="hljs-built_in">Sized</span>;<br> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">deref</span>(&<span class="hljs-keyword">self</span>) <span class="hljs-punctuation">-></span> &<span class="hljs-keyword">Self</span>::Target;<br>}<br><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">trait</span> <span class="hljs-title class_">DerefMut</span>: Deref {<br> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">deref_mut</span>(&<span class="hljs-keyword">mut</span> <span class="hljs-keyword">self</span>) <span class="hljs-punctuation">-></span> &<span class="hljs-keyword">mut</span> <span class="hljs-keyword">Self</span>::Target;<br>}<br></code></pre></td></tr></table></figure></li><li><p>Rust 提供的“自动解引用”机制,是在某些场景下“隐式地”“自动地”做了一些事情:Rust 编译器做了隐式的 deref 调用,当它找不到这个成员方法的时候,会自动尝试使用 deref 方法后再找该方法,一直循环下去。一般情况下,在函数调用的时候,编译器会帮我们尝试自动解引用。但在某些情况下,编译器不会为我们自动插入自动解引用的代码。</p></li><li><p>自动 deref 的规则是,如果类型 T 可以解引用为 U,即 <code>T:Deref<U></code>,则&T 可以转为&U。<strong>让智能指针透明。这就是自动 Deref 的意义。</strong></p></li><li><p>std::rc::Rc<T>:Rc 指针及其 clone 出的指针对它指向的内部数据只有读功能,和共享引用&一致,因此,它是安全的。区别在于,共享引用对数据完全没有所有权,不负责内存的释放,Rc 指针会在引用计数值减到 0 的时候释放内存。Rust 里面的 Rc<T>类型类似于 C++ 里面的 shared_ptr<const T>类型,且强制不可为空。</p><ul><li>Rc 实现了 Clone 和 Drop 这两个 trait。在 clone 方法中,它没有对它内部的数据实行深复制,而是将强引用计数值加 1;在 drop 方法中,也没有直接把内部数据释放掉,而是将强引用计数值减 1,当强引用计数值减到 0 的时候,才会析构掉共享的那块数据。当弱引用计数值也减为 0 的时候,才说明没有任何 Rc/Weak 指针指向这块内存,它占用的内存才会被彻底释放。</li><li>内部的引用计数是 Cell<usize> 类型</li></ul></li><li><p>std::borrow::Cow:当它只需要对所指向的数据进行只读访问的时候,它就只是一个借用指针;当它需要写数据功能时,它会先分配内存,执行复制操作,再对自己拥有所有权的内存进行写入操作。</p></li><li><p>零开销原则:</p><blockquote><p>C++ implementations obey the zero-overhead principle:What you don’t use,you don’t pay for. And further:What you do use,you couldn’t hand code any better. ——Stroustrup</p></blockquote></li></ul>]]></content>
<categories>
<category>Rust</category>
</categories>
</entry>
<entry>
<title>深入浅出 Rust 笔记 Series 1</title>
<link href="/2023/01/30/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BA%20Rust%E7%AC%94%E8%AE%B0%20Series%201/"/>
<url>/2023/01/30/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BA%20Rust%E7%AC%94%E8%AE%B0%20Series%201/</url>
<content type="html"><![CDATA[<h1 id="系列导航"><a class="markdownIt-Anchor" href="#系列导航"></a> 系列导航</h1><ul><li><a href="https://young-flash.github.io/2023/01/30/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BA%20Rust%E7%AC%94%E8%AE%B0%20Series%201/">深入浅出 Rust笔记 Series 1</a></li><li><a href="https://young-flash.github.io/2023/02/06/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BA%20Rust%E7%AC%94%E8%AE%B0%20Series%202/">深入浅出 Rust笔记 Series 2</a></li><li><a href="https://young-flash.github.io/2023/03/09/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BA%20Rust%E7%AC%94%E8%AE%B0%20Series%203/">深入浅出 Rust笔记 Series 3</a></li><li><a href="https://young-flash.github.io/2023/03/09/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BA%20Rust%E7%AC%94%E8%AE%B0%20Series%204/">深入浅出 Rust笔记 Series 4</a></li></ul><p>这是我在阅读范长春的《深入浅出 Rust》时做的笔记,绝大部分内容源自此书,另有小部分内容源自 <a href="https://course.rs/about-book.html">Rust 圣经</a>。这两份资料是我入门 Rust 的主要材料。</p><h1 id="第一部分-基础知识"><a class="markdownIt-Anchor" href="#第一部分-基础知识"></a> 第一部分 基础知识</h1><h2 id="chap-04-函数"><a class="markdownIt-Anchor" href="#chap-04-函数"></a> chap 04 函数</h2><h3 id="41-简介"><a class="markdownIt-Anchor" href="#41-简介"></a> 4.1 简介</h3><ul><li><p>函数可以当成头等公民(first class value)被复制到一个值中,这个值可以像函数一样被调用。示例如下:</p> <figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">fn</span> <span class="hljs-title function_">add2</span>((x,y) : (<span class="hljs-type">i32</span>,<span class="hljs-type">i32</span>)) <span class="hljs-punctuation">-></span> <span class="hljs-type">i32</span> {<br>x + y<br>}<br><br><span class="hljs-keyword">fn</span> <span class="hljs-title function_">main</span>() {<br><span class="hljs-keyword">let</span> <span class="hljs-variable">p</span> = (<span class="hljs-number">1</span>, <span class="hljs-number">3</span>);<br><span class="hljs-comment">// func 是一个局部变量</span><br><span class="hljs-keyword">let</span> <span class="hljs-variable">func</span> = add2;<br><span class="hljs-comment">// func 可以被当成普通函数一样被调用</span><br><span class="hljs-built_in">println!</span>(<span class="hljs-string">"evaluation output {}"</span>, <span class="hljs-title function_ invoke__">func</span>(p));<br>}<br></code></pre></td></tr></table></figure></li><li><p><strong>每一个函数都具有自己单独的类型,但是这个类型可以自动转换到 fn 类型</strong></p> <figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">fn</span> <span class="hljs-title function_">main</span>() {<br><span class="hljs-comment">// 先让 func 指向 add1</span><br><span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">func</span> = add1;<br><span class="hljs-comment">// 再重新赋值,让 func 指向 add2</span><br>func = add2;<br>}<br><br>error[E0308]: mismatched types<br>-<span class="hljs-punctuation">-></span> test.rs:<span class="hljs-number">11</span>:<span class="hljs-number">12</span><br>|<br><span class="hljs-number">11</span> | func = add2;<br>| ^^^^ expected <span class="hljs-keyword">fn</span> <span class="hljs-title function_">item</span>, found a different <span class="hljs-keyword">fn</span> <span class="hljs-title function_">item</span><br>|<br>= note: expected <span class="hljs-keyword">type</span> `<span class="hljs-title function_ invoke__">fn</span>((<span class="hljs-type">i32</span>, <span class="hljs-type">i32</span>)) <span class="hljs-punctuation">-></span> <span class="hljs-type">i32</span> {add1}`<br>found <span class="hljs-keyword">type</span> `<span class="hljs-title function_ invoke__">fn</span>((<span class="hljs-type">i32</span>, <span class="hljs-type">i32</span>)) <span class="hljs-punctuation">-></span> <span class="hljs-type">i32</span> {add2}`<br></code></pre></td></tr></table></figure></li><li><p>虽然 add1 和 add2 有同样的参数类型和同样的返回值类型,但它们是不同类型,所以这里报错了。修复方案是让 func 的类型为通用的 fn 类型即可:</p> <figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">// 写法一,用 as 类型转换</span><br><span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">func</span> = add1 <span class="hljs-keyword">as</span> <span class="hljs-title function_ invoke__">fn</span>((<span class="hljs-type">i32</span>,<span class="hljs-type">i32</span>))<span class="hljs-punctuation">-></span><span class="hljs-type">i32</span>;<br><span class="hljs-comment">// 写法二,用显式类型标记</span><br><span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">func</span> : <span class="hljs-title function_ invoke__">fn</span>((<span class="hljs-type">i32</span>,<span class="hljs-type">i32</span>))<span class="hljs-punctuation">-></span><span class="hljs-type">i32</span> = add1;<br></code></pre></td></tr></table></figure></li><li><p>Rust 的函数体内也允许定义其他 item,包括静态变量、常量、函数、trait、类型、模块等。当你需要一些 item 仅在此函数内有用的时候,可以把它们直接定义到函数体内,以避免污染外部的命名空间。</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">fn</span> <span class="hljs-title function_">test_inner</span>() {<br> <span class="hljs-keyword">static</span> INNER_STATIC: <span class="hljs-type">i64</span> = <span class="hljs-number">42</span>;<br> <span class="hljs-comment">// 函数内部定义的函数</span><br> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">internal_incr</span>(x: <span class="hljs-type">i64</span>) <span class="hljs-punctuation">-></span> <span class="hljs-type">i64</span> {<br> x + <span class="hljs-number">1</span><br> }<br> <span class="hljs-keyword">struct</span> <span class="hljs-title class_">InnerTemp</span>(<span class="hljs-type">i64</span>);<br> <span class="hljs-keyword">impl</span> <span class="hljs-title class_">InnerTemp</span> {<br> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">incr</span>(&<span class="hljs-keyword">mut</span> <span class="hljs-keyword">self</span>) {<br> <span class="hljs-keyword">self</span>.<span class="hljs-number">0</span> = <span class="hljs-title function_ invoke__">internal_incr</span>(<span class="hljs-keyword">self</span>.<span class="hljs-number">0</span>);<br> }<br> }<br> <span class="hljs-comment">// 函数体,执行语句</span><br> <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">t</span> = <span class="hljs-title function_ invoke__">InnerTemp</span>(INNER_STATIC);<br> t.<span class="hljs-title function_ invoke__">incr</span>();<br> <span class="hljs-built_in">println!</span>(<span class="hljs-string">"{}"</span>, t.<span class="hljs-number">0</span>);<br>}<br></code></pre></td></tr></table></figure></li></ul><h3 id="42-发散函数"><a class="markdownIt-Anchor" href="#42-发散函数"></a> 4.2 发散函数</h3><ul><li><p>Rust 支持一种特殊的发散函数(Diverging functions),它的返回类型是感叹号!。</p></li><li><p>发散类型 ! 的最大特点就是,它可以被转换为任意一个类型。</p></li><li><p>在 Rust 中,有以下这些情况永远不会返回,它们的类型就是!</p><ul><li>panic!以及基于它实现的各种函数/宏,比如 unimplemented!、unreachable!;</li><li>死循环 loop{};</li><li>进程退出函数 std::process::exit 以及类似的 libc 中的 exec 一类函数。</li></ul></li></ul><h3 id="43-main-函数"><a class="markdownIt-Anchor" href="#43-main-函数"></a> 4.3 main 函数</h3><ul><li><p>与其他编程语言也因此为 main 函数设计了参数和返回值类型相比,Rust 的设计稍微有点不一样,传递参数和返回状态码都由单独的 API 来完成,示例如下:</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">fn</span> <span class="hljs-title function_">main</span>() {<br> <span class="hljs-keyword">for</span> <span class="hljs-variable">arg</span> <span class="hljs-keyword">in</span> std::env::<span class="hljs-title function_ invoke__">args</span>() {<br> <span class="hljs-built_in">println!</span>(<span class="hljs-string">"Arg: {}"</span>, arg);<br> }<br> std::process::<span class="hljs-title function_ invoke__">exit</span>(<span class="hljs-number">0</span>);<br>}<br><br>编译,执行并携带几个参数,可以看到:<br>$ test -opt1 opt2 -- opt3<br>Arg: test<br>Arg: -opt1<br>Arg: opt2<br>Arg: --<br>Arg: opt3<br></code></pre></td></tr></table></figure></li><li><p>用 std::env::var() 以及 std::env::vars() 函数读取环境变量</p><ul><li>var() 函数可以接受一个字符串类型参数,用于查找当前环境变量中是否存在这个名字的环境变量,vars() 函数不携带参数,可以返回所有的环境变量</li><li>此前,Rust 的 main 函数只支持无参数、无返回值类型的声明方式,即 main 函数的签名固定为:fn main()->()。但是,在引入了?符号作为错误处理语法糖之后,就变得不那么优雅了,因为?符号要求当前所在的函数返回的是 Result 类型,这样一来,问号就无法直接在 main 函数中使用了。为了解决这个问题,Rust 设计组扩展了 main 函数的签名,使它变成了一个泛型函数,这个函数的返回类型可以是任何一个满足 Terminationtrait 约束的类型,其中()、bool、Result 都是满足这个约束的它们都可以作为 main 函数的返回类型。关于这个问题,可以参见第 33 章</li></ul></li></ul><h3 id="44-const-fn"><a class="markdownIt-Anchor" href="#44-const-fn"></a> 4.4 const fn</h3><ul><li>函数可以用 const 关键字修饰,这样的函数可以在编译阶段被编译器执行,返回值也被视为编译期常量。const 函数是在编译阶段执行的,因此相比普通函数有许多限制,并非所有的表达式和语句都可以在其中使用。</li></ul><h2 id="chap-05-trait"><a class="markdownIt-Anchor" href="#chap-05-trait"></a> chap 05 trait</h2><ul><li><p>Rust 语言中的 trait 是非常重要的概念。在 Rust 中,trait 这一个概念承担了多种职责。</p></li><li><p><strong>trait 本身既不是具体类型,也不是指针类型,它只是定义了针对类型的、抽象的“约束”。<strong>不同的类型可以实现同一个 trait,满足同一个 trait 的类型可能具有不同的大小。因此</strong>,trait 在编译阶段没有固定大小</strong>,目前我们不能直接使用 trait 作为实例变量、参数、返回值。</p></li><li><p><a href="https://course.rs/basic/trait/trait.html#%E7%89%B9%E5%BE%81%E7%BA%A6%E6%9D%9Ftrait-bound">impl trait</a> 只是一个语法糖。<code>impl Trait</code> 是静态分发。用于指定未命名但是具体存在的类型(可以表达一个不用装箱的匿名类型,以及它所满足的基本接口),其实现了指定的 <code>Trait</code>,可用作函数参数类型和返回值类型。<strong>跟泛型函数的主要区别是:泛型函数的类型参数是函数的调用者指定的;而 impl trait 的具体类型是函数的实现体指定的(还不太理解)。</strong></p><p><img src="/img/blog_pic/2023/image-20230118144746-y4rop2h.png" alt="image" /></p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">notify</span>(item: &<span class="hljs-keyword">impl</span> <span class="hljs-title class_">Summary</span>) {<br> <span class="hljs-built_in">println!</span>(<span class="hljs-string">"Breaking news! {}"</span>, item.<span class="hljs-title function_ invoke__">summarize</span>());<br>}<br><br><span class="hljs-comment">// https://rust-book.cs.brown.edu/ch10-02-traits.html#trait-bound-syntax</span><br><span class="hljs-comment">// The impl Trait syntax works for straightforward cases </span><br><span class="hljs-comment">// but is actually syntax sugar for a longer form known as a trait bound;</span><br><span class="hljs-comment">// it looks like this:</span><br><br><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">notify</span><T: Summary>(item: &T) {<br> <span class="hljs-built_in">println!</span>(<span class="hljs-string">"Breaking news! {}"</span>, item.<span class="hljs-title function_ invoke__">summarize</span>());<br>}<br></code></pre></td></tr></table></figure><ul><li><p><code>impl dyn Trait</code>:给 <code>Trait Object</code> 增加方法。<a href="https://zhuanlan.zhihu.com/p/257090324">参考</a></p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">trait</span> <span class="hljs-title class_">Animal</span> {<br> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">walk</span>(&<span class="hljs-keyword">self</span>) {<br> <span class="hljs-built_in">println!</span>(<span class="hljs-string">"walk"</span>);<br> }<br> }<br> <br> <span class="hljs-keyword">impl</span> <span class="hljs-title class_">dyn</span> Animal {<br> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">talk</span>(&<span class="hljs-keyword">self</span>) {<br> <span class="hljs-built_in">println!</span>(<span class="hljs-string">"talk"</span>);<br> }<br> }<br> <br> <span class="hljs-keyword">struct</span> <span class="hljs-title class_">Person</span>;<br> <br> <span class="hljs-keyword">impl</span> <span class="hljs-title class_">Animal</span> <span class="hljs-keyword">for</span> <span class="hljs-title class_">Person</span> {}<br> <br> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">demo</span>() <span class="hljs-punctuation">-></span> <span class="hljs-type">Box</span><<span class="hljs-keyword">dyn</span> Animal> {<br> <span class="hljs-keyword">let</span> <span class="hljs-variable">p</span> = Person;<br> Box::<span class="hljs-title function_ invoke__">new</span>(p)<br> }<br> <br> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">main</span>() {<br> <span class="hljs-keyword">let</span> <span class="hljs-variable">p</span> = Person;<br> p.<span class="hljs-title function_ invoke__">walk</span>();<br> <br> <span class="hljs-keyword">let</span> <span class="hljs-variable">p1</span> = <span class="hljs-title function_ invoke__">demo</span>();<br> p1.<span class="hljs-title function_ invoke__">walk</span>();<br> p1.<span class="hljs-title function_ invoke__">talk</span>();<br> }<br></code></pre></td></tr></table></figure></li></ul></li><li><p>参考</p><ul><li><a href="https://zhuanlan.zhihu.com/p/109990547">捋捋 Rust 中的 impl Trait 和 dyn Trait</a></li></ul></li></ul><h3 id="51-成员方法"><a class="markdownIt-Anchor" href="#51-成员方法"></a> 5.1 成员方法</h3><ul><li><p>Self 类型:所有的 trait 中都有一个隐藏的类型 Self(大写 S),代表当前这个实现了此 trait 的具体类型。Rust 中 Self(大写 S)和 self(小写 s)都是关键字,大写 S 的是类型名,小写 s 的是变量名。</p></li><li><p><strong>self 参数同样也可以指定类型,当然这个类型是有限制的,必须是包装在 Self 类型之上的类型</strong>。对于第一个 self 参数,常见的类型有 self:Self、self:&Self、self:&mut Self 等类型。对于以上这些类型,Rust 提供了一种简化的写法,我们可以将参数简写为 self、&self、&mut self。<strong>self 参数甚至可以是 Box 指针类型 self:Box<Self></strong>。self 参数只能用在第一个参数的位置。请注意“变量 self”和“类型 Self”的大小写不同。</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">trait</span> <span class="hljs-title class_">T</span> {<br> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">method1</span>(<span class="hljs-keyword">self</span>: <span class="hljs-keyword">Self</span>);<br> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">method2</span>(<span class="hljs-keyword">self</span>: &<span class="hljs-keyword">Self</span>);<br> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">method3</span>(<span class="hljs-keyword">self</span>: &<span class="hljs-keyword">mut</span> <span class="hljs-keyword">Self</span>);<br>}<br><span class="hljs-comment">// 上下两种写法是完全一样的</span><br><span class="hljs-keyword">trait</span> <span class="hljs-title class_">T</span> {<br> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">method1</span>(<span class="hljs-keyword">self</span>);<br> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">method2</span>(&<span class="hljs-keyword">self</span>);<br> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">method3</span>(&<span class="hljs-keyword">mut</span> <span class="hljs-keyword">self</span>);<br>}<br></code></pre></td></tr></table></figure></li><li><p>trait 中定义的函数,也可以称作关联函数(associated function)。函数的第一个参数如果是 Self 相关的类型,且命名为 self(小写 s),这个参数可以被称为“receiver”(接收者)。具有 receiver 参数的函数,我们称为“方法”(method),可以通过变量实例使用小数点来调用。没有 receiver 参数的函数,我们称为“静态函数”(static function),可以通过类型加双冒号::的方式来调用。在 Rust 中,函数和方法没有本质区别</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">struct</span> <span class="hljs-title class_">Circle</span> {<br> radius: <span class="hljs-type">f64</span>,<br>}<br><br><span class="hljs-keyword">trait</span> <span class="hljs-title class_">Shape</span> {<br> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">area</span>(<span class="hljs-keyword">self</span>: &<span class="hljs-keyword">Self</span>) <span class="hljs-punctuation">-></span> <span class="hljs-type">f64</span>;<br>}<br><br><span class="hljs-keyword">impl</span> <span class="hljs-title class_">Shape</span> <span class="hljs-keyword">for</span> <span class="hljs-title class_">Circle</span> {<br> <span class="hljs-comment">// Self 类型就是 Circle</span><br> <span class="hljs-comment">// self 的类型是 &Self,即 &Circle</span><br> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">area</span>(&<span class="hljs-keyword">self</span>) <span class="hljs-punctuation">-></span> <span class="hljs-type">f64</span> {<br> <span class="hljs-comment">// 访问成员变量,需要用 self.radius</span><br> std::f64::consts::PI * <span class="hljs-keyword">self</span>.radius * <span class="hljs-keyword">self</span>.radius<br> }<br>}<br><br><br><span class="hljs-keyword">let</span> <span class="hljs-variable">c</span> = Circle { radius : <span class="hljs-number">2f64</span>};<br><span class="hljs-comment">// 第一个参数名字是 self,可以使用小数点语法调用</span><br><span class="hljs-built_in">println!</span>(<span class="hljs-string">"The area is {}"</span>, c.<span class="hljs-title function_ invoke__">area</span>()); <span class="hljs-comment">// 这里用 c 或者 &c 都可以。Deref ?</span><br></code></pre></td></tr></table></figure></li><li><p>针对一个类型,我们可以直接对它 impl 来增加成员方法,无须 trait 名字。<strong>可以看作是为该类型 impl 了一个匿名的 trait</strong>。用这种方式定义的方法叫作这个类型的“内在方法”(inherent methods)</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">struct</span> <span class="hljs-title class_">Circle</span> {<br> radius: <span class="hljs-type">f64</span>,<br>}<br><br><span class="hljs-keyword">impl</span> <span class="hljs-title class_">Circle</span> {<br> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">get_radius</span>(&<span class="hljs-keyword">self</span>) <span class="hljs-punctuation">-></span> <span class="hljs-type">f64</span> { <span class="hljs-keyword">self</span>.radius }<br>}<br></code></pre></td></tr></table></figure></li></ul><h3 id="52-静态方法"><a class="markdownIt-Anchor" href="#52-静态方法"></a> 5.2 静态方法</h3><ul><li><p>没有 receiver 参数的方法(第一个参数不是 self 参数的方法)称作“静态方法”。静态方法可以通过 Type::FunctionName()的方式调用。需要注意的是,即便我们的第一个参数是 Self 相关类型,只要变量名字不是 self,就不能使用小数点的语法调用函数。</p><ul><li>在标准库中就有一些这样的例子。Box 的一系列方法 Box::into_raw(b:Self)Box::leak(b:Self),以及 Rc 的一系列方法 Rc::try_unwrap(this:Self)Rc::downgrade(this:&Self),都是这种情况。它们的 receiver 不是 self 关键字,这样设计的目的是强制用户用 Rc::downgrade(&obj)的形式调用,而禁止 obj.downgrade()形式的调用。这样源码表达出来的意思更清晰,不会因为 Rc<T> 里面的成员方法和 T 里面的成员方法重名而造成误解问题(这又涉及 Deref trait 的内容,读者可以把第 16 章读完再回看这一段)。</li></ul></li></ul><h3 id="53-扩展方法"><a class="markdownIt-Anchor" href="#53-扩展方法"></a> 5.3 扩展方法</h3><ul><li><p>可以利用 trait 给其他的类型添加成员方法,就像 C# 里面的“扩展方法”一样。哪怕这个类型不是在当前的项目中声明的,我们依然可以为它增加一些成员方法。</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">trait</span> <span class="hljs-title class_">Double</span> {<br> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">double</span>(&<span class="hljs-keyword">self</span>) <span class="hljs-punctuation">-></span> <span class="hljs-keyword">Self</span>;<br>}<br><span class="hljs-keyword">impl</span> <span class="hljs-title class_">Double</span> <span class="hljs-keyword">for</span> <span class="hljs-title class_">i32</span> {<br> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">double</span>(&<span class="hljs-keyword">self</span>) <span class="hljs-punctuation">-></span> <span class="hljs-type">i32</span> { *<span class="hljs-keyword">self</span> * <span class="hljs-number">2</span> }<br>}<br><span class="hljs-keyword">fn</span> <span class="hljs-title function_">main</span>() {<br><span class="hljs-comment">// 可以像成员方法一样调用</span><br> <span class="hljs-keyword">let</span> <span class="hljs-variable">x</span> : <span class="hljs-type">i32</span> = <span class="hljs-number">10</span>.<span class="hljs-title function_ invoke__">double</span>();<br> <span class="hljs-built_in">println!</span>(<span class="hljs-string">"{}"</span>, x);<br>}<br></code></pre></td></tr></table></figure></li><li><p>在声明 trait 和 impl trait 的时候,Rust 规定了一个 Coherence Rule(一致性规则)或称为 <strong>Orphan Rule(孤儿规则)</strong>:impl 块要么与 trait 的声明在同一个的 crate 中,要么与类型的声明在同一个 crate 中。</p><ul><li>也就是说,如果 trait 来自于外部 crate,而且类型也来自于外部 crate,编译器不允许你为这个类型 impl 这个 trait。它们之中必须至少有一个是在当前 crate 中定义的。</li><li>上游开发者在给别人写库的时候,尤其要注意,一些比较常见的标准库中的 trait,如 Display Debug ToString Default 等,应该尽可能地提供好。否则,使用这个库的下游开发者是没办法帮我们把这些 trait 实现的。</li></ul></li></ul><h3 id="54-完整函数调用语法"><a class="markdownIt-Anchor" href="#54-完整函数调用语法"></a> 5.4 完整函数调用语法</h3><ul><li><p>Fully Qualified Syntax 提供一种无歧义的函数调用语法,允许程序员精确地指定想调用的是那个函数。以前也叫 UFCS(universal function call syntax),也就是所谓的“通用函数调用语法”,包括成员方法和静态方法。</p></li><li><p>具体写法为 T as TraitName::item</p></li><li><p>如果一个类型同时实现了这两个 trait,那么如果我们使用 obj.method() 这样的语法执行方法调用的话,就会出现歧义,编译器不知道你具体想调用哪个方法,编译错误信息为“multiple applicable items in scope”。此时就有必要使用完整的函数调用语法来进行方法调用,只有这样写,才能清晰明白且无歧义地表达清楚期望调用的是哪个函数。</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">trait</span> <span class="hljs-title class_">Cook</span> {<br> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">start</span>(&<span class="hljs-keyword">self</span>);<br>}<br><span class="hljs-keyword">trait</span> <span class="hljs-title class_">Wash</span> {<br> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">start</span>(&<span class="hljs-keyword">self</span>);<br>}<br><span class="hljs-keyword">struct</span> <span class="hljs-title class_">Chef</span>;<br><span class="hljs-keyword">impl</span> <span class="hljs-title class_">Cook</span> <span class="hljs-keyword">for</span> <span class="hljs-title class_">Chef</span> {<br> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">start</span>(&<span class="hljs-keyword">self</span>) { <span class="hljs-built_in">println!</span>(<span class="hljs-string">"Cook::start"</span>);}<br>}<br><span class="hljs-keyword">impl</span> <span class="hljs-title class_">Wash</span> <span class="hljs-keyword">for</span> <span class="hljs-title class_">Chef</span> {<br> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">start</span>(&<span class="hljs-keyword">self</span>) { <span class="hljs-built_in">println!</span>(<span class="hljs-string">"Wash::start"</span>);}<br>}<br><span class="hljs-keyword">fn</span> <span class="hljs-title function_">main</span>() {<br> <span class="hljs-keyword">let</span> <span class="hljs-variable">me</span> = Chef;<br> <span class="hljs-comment">// me.start(); // 编译错误</span><br><br> <span class="hljs-comment">// 应写为下面这种形式</span><br> <Cook>::<span class="hljs-title function_ invoke__">start</span>(&me);<br> <Chef <span class="hljs-keyword">as</span> Wash>::<span class="hljs-title function_ invoke__">start</span>(&me);<br>}<br></code></pre></td></tr></table></figure><ul><li>由此也可以看到,所谓的“成员方法”也没什么特殊之处,它跟普通的静态方法的唯一区别是,第一个参数是 self,而这个 self 只是一个普通的函数参数而已。只不过这种成员方法也可以通过变量加小数点的方式调用。<strong>变量加小数点的调用方式在大部分情况下看起来更简单更美观,完全可以视为一种语法糖。</strong></li><li><strong>通过小数点语法调用方法调用,有一个“隐藏着”的“取引用”步骤</strong>。虽然我们看起来源代码长的是这个样子 me.start(),但是真正传递给 start()方法的参数是&me 而不是 me,这一步是编译器自动做的。不论这个方法接受的 self 参数究竟是 Self、&Self 还是&mut Self,最终在源码上,我们都是统一的写法:variable.method()。而如果用 UFCS 语法来调用这个方法,就不能让编译器自动取引用了,必须手动写清楚。</li></ul></li></ul><h3 id="55-trait-约束和继承"><a class="markdownIt-Anchor" href="#55-trait-约束和继承"></a> 5.5 trait 约束和继承</h3><ul><li><p>Rust 的 trait 的另外一个大用处是,作为泛型约束使用。</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">fn</span> <span class="hljs-title function_">my_print</span><T : <span class="hljs-built_in">Debug</span>>(x: T) {<br> <span class="hljs-built_in">println!</span>(<span class="hljs-string">"The value is {:?}."</span>, x);<br>}<br></code></pre></td></tr></table></figure><p>上面这段代码中,my_print 函数引入了一个泛型参数 T,所以它的参数不是一个具体类型,而是一组类型。冒号后面加 trait 名字,就是这个泛型参数的约束条件。它要求这个 T 类型实现 Debug 这个 trait。</p><ul><li><p>泛型约束还有另外一种写法,即 where 子句</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">fn</span> <span class="hljs-title function_">my_print</span><T>(x: T) <span class="hljs-keyword">where</span> T: <span class="hljs-built_in">Debug</span> {<br> <span class="hljs-built_in">println!</span>(<span class="hljs-string">"The value is {:?}."</span>, x);<br>}<br></code></pre></td></tr></table></figure></li></ul></li><li><p>trait 继承</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">trait</span> <span class="hljs-title class_">Base</span> { ... }<br><span class="hljs-keyword">trait</span> <span class="hljs-title class_">Derived</span> : Base { ... }<br></code></pre></td></tr></table></figure><p>这表示 Derived trait 继承了 Base trait。它表达的意思是,满足 Derived 的类型,必然也满足 Base trait。所以,我们在针对一个具体类型 impl Derived 的时候,编译器也会要求我们同时 impl Base。</p><p>实际上,在编译器的眼中,<code>trait Derived:Base{}</code> 等同于 <code>trait Derived where Self:Base{}</code>。这两种写法没有本质上的区别,都是给 Derived 这个 trait 加了一个约束条件,即实现 Derived trait 的具体类型,也必须满足 Base trait 的约束。</p></li></ul><h3 id="56-derive-自动-impl-某些-trait"><a class="markdownIt-Anchor" href="#56-derive-自动-impl-某些-trait"></a> 5.6 derive: 自动 impl 某些 trait</h3><ul><li><p>语法:在你希望 impl trait 的类型前面写 #[derive(…)],括号里面是你希望 impl 的 trait 的名字。</p></li><li><p>目前,Rust 支持的可以自动 derive 的 trait 有以下这些:</p><ul><li>Debug, Clone, Copy, Hash</li><li>RustcEncodable RustcDecodable</li><li>PartialEq, Eq, ParialOrd, Ord</li><li>Default, FromPrimitive</li><li>Send, Sync</li><li>这些 trait 都是标准库内部的较特殊的 trait,它们可能包含有成员方法,但是成员方法的逻辑有一个简单而一致的“模板”可以使用,编译器就机械化地重复这个模板,帮我们实现这个默认逻辑。当然也可以手动实现。</li></ul></li></ul><h3 id="59-总结"><a class="markdownIt-Anchor" href="#59-总结"></a> 5.9 总结</h3><ul><li><p>trait 其他用处:</p><ul><li>trait 本身可以携带泛型参数;</li><li>trait 可以用在泛型参数的约束中;</li><li>trait 可以为一组类型 impl,也可以单独为某一个具体类型 impl,而且它们可以同时存在;</li><li>trait 可以为某个 trait impl,而不是为某个具体类型 impl;</li><li>trait 可以包含关联类型,而且还可以包含类型构造器,实现高阶类型的某些功能;</li><li><strong>trait 可以实现泛型代码的静态分派,也可以通过 trait object 实现动态分派;</strong></li><li>trait 可以不包含任何方法,用于给类型做标签(marker),以此来描述类型的一些重要特性</li><li>trait 可以包含常量。</li></ul></li></ul><h2 id="chap-06-数组和字符串"><a class="markdownIt-Anchor" href="#chap-06-数组和字符串"></a> chap 06 数组和字符串</h2><h3 id="61-数组"><a class="markdownIt-Anchor" href="#61-数组"></a> 6.1 数组</h3><ul><li><p>数组类型的表示方式为[T; n]。其中 T 代表元素类型;<strong>n 代表元素个数;它必须是编译期常量整数。</strong></p><ul><li>对于两个数组类型,只有元素类型和元素个数都完全相同,这两个数组才是同类型的。数组与指针之间不能隐式转换。同类型的数组之间可以互相赋值。</li><li>多维数组:[[T: m]; n]</li></ul></li><li><p>数组切片:对数组取借用 borrow 操作,可以生成一个“数组切片”(Slice)。数组切片对数组没有“所有权”。<strong>可以把数组切片看作专门用于指向数组的指针,是对数组的另外一个“视图”。</strong></p><ul><li>比如,有一个数组[T;n],它的借用指针的类型就是&[T;n]。它可以通过编译器内部魔法转换为数组切片类型&[T]。<strong>数组切片实质上还是指针,它不过是在类型系统中丢弃了编译阶段定长数组类型的长度信息,而将此长度信息存储为运行期的值。</strong></li></ul></li><li><p>数组切片(Slice)是指向一个数组的指针,而它比指针又多了一点东西——它不止包含有一个指向数组的指针,切片本身还含带长度信息。<strong>Slice 是胖指针类型。胖指针的设计,避免了数组类型作为参数传递时自动退化为裸指针类型,丢失了长度信息的问题,保证了类型安全</strong></p></li><li><p>动态大小类型(Dynamic Sized Type,DST)。DST 指的是编译阶段无法确定占用空间大小的类型。为了安全性,指向 DST 的指针一般是胖指针。</p><ul><li><p>对于 DST 类型,Rust 有如下限制:</p><ul><li>只能通过指针来间接创建和操作 DST 类型,&[T]、Box<[T]> 可以,[T]不可以</li><li>enum 中不能包含 DST 类型,struct 中只有最后一个元素可以是 DST,其他地方不行,如果包含有 DST 类型,那么这个结构体也就成了 DST 类型。</li></ul></li></ul></li><li><p>边界检查:在 Rust 中靠编译阶段静态检查是无法消除数组越界的行为的。若不确定使用的“索引”是否合法,应该使用 get()方法调用来获取数组中的元素,这个方法不会引起 panic!,它的返回类型是 Option<T></p><ul><li>一般情况下,Rust 不鼓励大量使用“索引”操作。正常的“索引”操作都会执行一次“边界检查”。从执行效率上来说,Rust 比 C/C++ 的数组索引效率低一点,因为 C/C++ 的索引操作是不执行任何安全性检查的,它们对应的 Rust 代码相当于调用 get_unchecked()函数。在 Rust 中,更加地道的做法是尽量使用“迭代器”方法。</li></ul></li></ul><h3 id="62-字符串"><a class="markdownIt-Anchor" href="#62-字符串"></a> 6.2 字符串</h3><p>Rust 的字符串涉及两种类型,一种是&str,另外一种是 String。</p><h4 id="621-str"><a class="markdownIt-Anchor" href="#621-str"></a> 6.2.1 &str</h4><ul><li>str 是 Rust 的内置类型。&str 是对 str 的借用,也是一个胖指针。</li><li>Rust 的字符串内部默认是使用 utf-8 编码格式的。而内置的 char 类型是 4 字节长度的,存储的内容是 Unicode Scalar Value。所以,Rust 里面的字符串不能视为 char 类型的数组,而更接近 u8 类型的数组。</li><li>&str 类型是对一块字符串区间的借用,它对所指向的内存空间没有所有权,哪怕&mut str 也一样。</li></ul><h4 id="622-string"><a class="markdownIt-Anchor" href="#622-string"></a> 6.2.2 String</h4><ul><li>String 类型跟&str 类型的主要区别是,它有管理内存空间的权力。</li><li>String 实现了 Deref<Target=str> 的 trait。所以在很多情况下 &String 类型可以被编译器自动转换为&str 类型。</li><li>Rust 的 String 类型类似于 std::string,而 Rust 的&str 类型类似于 std::string_view。</li></ul><p></p>]]></content>
<categories>
<category>Rust</category>
</categories>
</entry>
<entry>
<title>GSoC 2022 Series - 4</title>
<link href="/2022/09/08/GSoC%202022%20Series%204/"/>
<url>/2022/09/08/GSoC%202022%20Series%204/</url>
<content type="html"><![CDATA[<h2 id="系列导航"><a class="markdownIt-Anchor" href="#系列导航"></a> 系列导航</h2><ul><li><a href="https://young-flash.github.io/2022/05/21/GSoC%202022%20Series%201/">GSoC 2022 Series - 1</a></li><li><a href="https://young-flash.github.io/2022/05/23/GSoC%202022%20Series%202/">GSoC 2022 Series - 2</a></li><li><a href="https://young-flash.github.io/2022/07/02/GSoC%202022%20Series%203/">GSoC 2022 Series - 3</a></li><li><a href="https://young-flash.github.io/2022/09/08/GSoC%202022%20Series%204/">GSoC 2022 Series - 4</a></li></ul><p>开个坑,关于将互斥锁改为共享锁这一部分后面再写,这里先放一下结项的 final report</p><h1 id="gsoc-final-report"><a class="markdownIt-Anchor" href="#gsoc-final-report"></a> GSoC final report</h1><ul><li>project: <a href="https://summerofcode.withgoogle.com/programs/2022/projects/Qr45UY5M">Introduce WebSockets into rTorrent</a></li><li>Organization: <a href="https://ccextractor.org/">CCExtractor Development</a></li><li>Contributor: <a href="https://github.com/Young-Flash">Dongyang Zheng</a></li><li>mentor: <a href="https://github.com/jesec">jesec</a></li></ul><h1 id="introduction"><a class="markdownIt-Anchor" href="#introduction"></a> Introduction</h1><p>This GSoC project will replace the antique SCGI protocol in rTorrent with Websocket, which will allows real-time events, less serialization/transfer overheads, better security, etc. There isn’t a modern c++ websockets library that supports unix domain socket, we will add this important feature into uWebsockets. And we also replace the global mutex in libtorrent with shared_mutex to improve concurrency.</p><h1 id="what-was-done"><a class="markdownIt-Anchor" href="#what-was-done"></a> What was done</h1><ul><li>introduce websocket into rTorrent and implement “server push”, that is, client can subscribe some specific topics, once the event occurs,server will push the notification to client automatically</li></ul><p><img src="/img/blog_pic/2022/ws_postman_screenshot.png" alt="" /></p><ul><li>add unix domain socket support for uWebSockets, now websocket can listen on unix domain socket, with better security.</li><li>replace global mutex in libtorrent with shared_mutex</li></ul><h1 id="link-to-work"><a class="markdownIt-Anchor" href="#link-to-work"></a> Link to work</h1><ul><li><a href="https://github.com/Young-Flash/translator">qualification task before get selected</a></li><li><a href="https://github.com/Young-Flash/uWebSockets">unix domain socket support for uWebsocket</a></li><li><a href="https://github.com/Young-Flash/libtorrent">replace mutex with shared_mutex in libtorrent</a></li><li><a href="https://github.com/CCExtractor/rtorrent-ws">project code repo</a></li><li><a href="https://github.com/CCExtractor/rtorrent-ws/commits/master">code commits</a></li><li><a href="https://young-flash.github.io/2022/05/21/GSoC%202022%20Series%201/">related blogs (records of the development process)</a></li></ul><h1 id="acknowledgements"><a class="markdownIt-Anchor" href="#acknowledgements"></a> Acknowledgements</h1><p>Not only technical things I have learned but also the ability to think and tackle problems. Mentor jesec is very great, he gave me a lot of guidance and inspiration in the process of completing the project, work with jesec is wonderful, thanks jesec for help. Thanks Google and GSoC program and CCExtractor Development community provided such a good activity.</p>]]></content>
<categories>
<category>GSoC</category>
</categories>
<tags>
<tag>开源</tag>
</tags>
</entry>
<entry>
<title>微软实习小记</title>
<link href="/2022/07/29/%E5%BE%AE%E8%BD%AF%E5%AE%9E%E4%B9%A0%E5%B0%8F%E8%AE%B0/"/>
<url>/2022/07/29/%E5%BE%AE%E8%BD%AF%E5%AE%9E%E4%B9%A0%E5%B0%8F%E8%AE%B0/</url>
<content type="html"><![CDATA[<p>5 月中旬入职,经历了差不多半个月的 remote 后终于到苏州 onsite 了。How time flies,现在实习已经接近尾声了,在微软实习的这段经历会是我宝贵的体验。</p><p>因为疫情还在学校 remote 的时候其实并没有实习的感觉,虽说 mentor 也有在 teams 上和我联系,给我讲实习要做的 task,但不能到实地体验总归还是差点意思。入职两个星期后终于能来到办公室了,线下实地的实习体验才是我真正想要的体验。入职第一天先配了设备,HP 的工作站,32G 内存 + 400G 固态,日常开发足够了,4K 的显示器是我最满意的哈哈哈,一开始由于设备紧张每个实习生只能领一个,后来我死缠烂打找 AA 又要了一个,两个 4K 显示器,一个字就是爽!</p><table><thead><tr><th><img src="/img/blog_pic/2022/ms4.jpg" alt="" /></th><th><img src="/img/blog_pic/2022/ms5.jpg" alt="" /></th></tr></thead><tbody><tr><td>从大老板朋友圈盗的图</td><td></td></tr></tbody></table><p>组里同事总体来说都比较年轻,好几个都是去年校招入职的,还有几个工作了比较久很有经验的同事,我觉得这个搭配还是很好的,新老搭配,团队有年轻人的活力也有老司机的经验,组里的氛围我挺喜欢的,平时都是一块去饭堂吃饭,大家相处都比较融洽,希望以后正式工作了也能在这么一个团队里哈哈哈。组里的同事都很优秀,好几个海归,也有从腾讯华为跳过来的,我 mentor 是 senior,是个有多年经验的老兵了。mentor 人很好,有什么不懂的或者遇到什么问题请教他都能给我解答,跟我讲了任务后就放手让我去做,没有催进度,都是我自己做了点什么东西后跟他说的,没啥工作压力,应该微软总体来说都是如此吧,当然不排除也有一些比较忙的组,不过我所在的 CMD 部门感觉都还好,没听说过经常加班的情况,五点多去吃饭后有的直接走了,也有的再回办公室待一会儿的,大多晚上 7 点多就都走了;但是偶尔也有比较忙的时候,前段时间看我 mentor 好几天到八九点才走。Anyway,微软在 work life balance 这方面是很有优势的。</p><p><img src="/img/blog_pic/2022/ms-demo.png" alt="" /></p><p>今天在一个 meeting 上做个了 demo 讲了自己实习期间做的任务,第一次全英的汇报有些紧张,开始之前 mentor 还来和我说不用紧张随便讲哈哈哈。准备得还算充分,前一天晚上把自己要的东西过了几遍所以汇报的时候比较顺利,结束后还有同事夸我口语不错哈哈哈。虽然只有不到十分钟的时间,但这次汇报足够让我印象深刻了。组里的业务需要和外国同事对接,所以平时同事们开会也经常会讲英文,这也是在外企工作的一个特点吧,我觉得有这种机会还是挺不错的,是提升自己英语的好机会。</p><table><thead><tr><th><img src="/img/blog_pic/2022/ms6.jpg" alt="" /></th><th><img src="/img/blog_pic/2022/ms7.jpg" alt="" /></th></tr></thead></table><p>放点随手拍的照片吧</p><table><thead><tr><th><img src="/img/blog_pic/2022/ms1.jpg" alt="" /></th><th><img src="/img/blog_pic/2022/ms12.jpg" alt="" /></th></tr></thead></table><table><thead><tr><th><img src="/img/blog_pic/2022/ms8.jpg" alt="" /></th><th><img src="/img/blog_pic/2022/ms9.jpg" alt="" /></th></tr></thead></table><table><thead><tr><th><img src="/img/blog_pic/2022/ms10.jpg" alt="茶水间的花" /></th><th><img src="/img/blog_pic/2022/ms11.jpg" alt="不是说 win 11 不会蓝屏吗???" /></th></tr></thead></table> <!-- 差不多还有两个星期实习就结束了,希望秋招顺利毕业也顺利哈哈哈哈 --><center>更新</center><p>聚散终有别,每段旅途、每段人生经历都有终点。而我的微软实习经历也走到了最后,还剩下最后两三天在想离职待办的时候心中有了要离开这里的感觉,在这里的三个月是很美好的一段经历,想起要离开了难免有些不舍。last day 的中午时候 manager 请了我和 mentor 吃饭,下午的时候我点了 18 杯奶茶请同事们喝,做个最后的留念哈哈哈,晚上再和几个同事在园区里的小馆子里吃了顿饭。</p><table><thead><tr><th><img src="/img/blog_pic/2022/%E7%95%99%E8%A8%80.jpg" alt="" /></th><th><img src="/img/blog_pic/2022/ms%E6%90%9C%E7%B4%A2%E6%88%AA%E5%9B%BE.jpg" alt="" /></th></tr></thead></table><table><thead><tr><th><img src="/img/blog_pic/2022/ms%E5%A4%A7%E6%A5%BC.jpg" alt="" /></th><th><img src="/img/blog_pic/2022/%E6%9C%88%E4%BA%AE%E6%B9%BE.jpg" alt="" /></th></tr></thead></table><p>至此,微软实习经历结束,will I back?</p>]]></content>
<categories>
<category>工作</category>
</categories>
</entry>
<entry>
<title>GSoC 2022 Series - 3</title>
<link href="/2022/07/02/GSoC%202022%20Series%203/"/>
<url>/2022/07/02/GSoC%202022%20Series%203/</url>
<content type="html"><![CDATA[<h2 id="系列导航"><a class="markdownIt-Anchor" href="#系列导航"></a> 系列导航</h2><ul><li><a href="https://young-flash.github.io/2022/05/21/GSoC%202022%20Series%201/">GSoC 2022 Series - 1</a></li><li><a href="https://young-flash.github.io/2022/05/23/GSoC%202022%20Series%202/">GSoC 2022 Series - 2</a></li><li><a href="https://young-flash.github.io/2022/07/02/GSoC%202022%20Series%203/">GSoC 2022 Series - 3</a></li><li><a href="https://young-flash.github.io/2022/09/08/GSoC%202022%20Series%204/">GSoC 2022 Series - 4</a></li></ul><h2 id="设计与实现"><a class="markdownIt-Anchor" href="#设计与实现"></a> 设计与实现</h2><p>mentor 希望在 rtorrent 中引入 websocket 的同时还保留 scgi。现在 rtorrent 有 3 个线程,我的想法是在一个独立的线程来运行 websocket 服务,届时整个进程中将会有 4 个线程。引入 RpcThreadManager 类来管理 scgi 和 websocket 线程,类中的成员变量是 ThreadWorker 和 WebsocketsThread 的指针,成员方法是 ThreadWorker 类中暴露的 public 方法,这样只需要将 global.h 中的全局变量 worker_thread 的类型从 ThreadWorker 改为 RpcThreadManager 从而不需要改变调用接口的定义。</p><p>利用 uWebsockets 来实现 websocket 服务,所有的逻辑都放在 WebsocketsThread 这个类中,主要的成员变量有:</p><ul><li><code>std::unique_ptr<std::thread> m_websockets_thread</code>:<br />当前运行 websocket 服务的线程指针,析构时 join 掉线程</li><li><code>uWS::App* m_websockets_app</code>:<br />websocket App 对象,后续调用其 publish 方法向 client 推送通知</li><li><code>std::pair<std::string, int>* listen_info</code>:<br />websocket 服务监听方式(unix domain socket 还是 tcp:ip port 以及监听的地址信息</li><li><code>std::vector<uWS::WebSocket<false, true, ConnectionData>*> all_connection</code>:<br />保存与所有 client 的连接,析构时 close</li></ul><p>主要的成员变量有:</p><ul><li><code>void start_thread();</code><br />初始化 uWS::App 对象并创建线程运行 websocket 服务</li><li><code>void publish_ws_topic(std::string_view topic, std::string_view message);</code><br />外部向 client 推送通知的接口</li><li><code>void handle_request(const std::string_view&);</code><br />收到 client 的 JSON-RPC 命令后分派给 RpcManager 处理然后返回处理结果</li><li><code>~WebsocketsThread();</code><br />关闭掉所有 ws 连接,清理资源,join 掉当前线程</li></ul><h2 id="遇到的问题"><a class="markdownIt-Anchor" href="#遇到的问题"></a> 遇到的问题</h2><h3 id="如何优雅结束进程"><a class="markdownIt-Anchor" href="#如何优雅结束进程"></a> 如何优雅结束进程?</h3><p>每次退出的时候都会阻塞在 <code>m_websockets_thread->join();</code> 上,按照 uWebsockets 文档的描述,清理掉 App 对象依赖的资源后其 run 方法会自动退出,可是执行了 <code>us_listen_socket_close(0, m_listen_socket);</code> 之后 run 方法还是没有返回。后来尝试了在关闭 socket 之前 close 掉与所有 client 的连接后 run 方法就成功返回了,进而 m_websockets_thread 就能顺利 join 掉了。</p><h3 id="bazel-build"><a class="markdownIt-Anchor" href="#bazel-build"></a> bazel build</h3><p>我是在 windows 上用 wsl 环境开发的。项目中用到了 bazel 构建系统所以我也得支持这种构建方式,运行 bazel build rtorrent 后报错 ERROR: /home/flash/.cache/bazel/_bazel_flash/deb97f29152e01999a06e8171eedc056/external/cares/BUILD.bazel:115:8: Executing genrule @cares//:configure failed: (Exit 127): bash failed: error executing command /bin/bash -c … (remaining 1 argument skipped) <code>/bin/bash: $'\r': command not found</code>。这是 wsl 的锅,参考<a href="https://askubuntu.com/questions/966488/how-do-i-fix-r-command-not-found-errors-running-bash-scripts-in-wsl">这里</a>用 dos2unix 把有问题的文件格式化之后重新执行 bazel build rtorrent 就成功了</p><h3 id="推送通知到-client-时程序就-segmentation-fault-了"><a class="markdownIt-Anchor" href="#推送通知到-client-时程序就-segmentation-fault-了"></a> 推送通知到 client 时程序就 Segmentation fault 了</h3><p>一开始还以为是不同线程之间内存的访问可见性问题,但是线程之间是共享内存的呀,在创建好 <code>uWS::App</code> 对象后就通过指针保存到 m_websockets_app,而且在 websocket 线程中和外部调用线程中 <code>m_websockets_app</code> 打印出来的指针值都是一样的,为什么一调用方法就出错???这个问题困扰了我好久,后来突然想到是不是 publish 的时候 App 还没初始化好?结果还真是!!!知道了原因之后就很好解决了:publish的时候对 m_websockets_app 判空就 OK 了。我在 debug 的时候看到 m_websockets_app 为 NULL 的时候才想到这一点,如果没有想到的话肯定会走不少弯路(其实已经走了不少了)</p><p>想到在 leetcode 用 C++ 做题的时候,如果访问了空指针会给出报错原因</p><p><img src="/img/blog_pic/2022/leetcodeC++%E9%94%99%E8%AF%AF.png" alt="" /></p><p>如果当时 IDE 在我访问还没初始化的 m_websockets_app 也能给出这样的提示那么我马上就知道问题在哪了。后来 Google 到了 sanitize 编译选项,在访问空指针的测试代码中用<br /><code>set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=null -fsanitize=leak -fsanitize=address")</code> 这些选项编译就能有类似的提示。但是在 rtorrent 中加入这些编译选项后还是没有,不知道为什么。。。</p><h3 id="hash-string-乱码"><a class="markdownIt-Anchor" href="#hash-string-乱码"></a> hash string 乱码</h3><p>我想在返回消息中带上当前 torrent 的 hash string 给 client 作为判断是针对哪个 torrent 的事件通知,看代码应该是 <code>download->info()->hash().str()</code> 这个方法,结果 json 无法解析,打印出来一看是乱码。一开始还在想会不会又是 wsl 的锅,后来放到虚拟机上跑还是一样。最后是从 rpc 命令入手的:发现有一条 “d.hash” 的命令可以返回 torrent 的 hash string,那么 rtorrent 中肯定有处理这条命令的代码,通过在 IDE 中搜索 “d.hash” 找到 src/command_download.cc line 802,发现原来还要 transform 一下才可以:<code>torrent::utils::transform_hex_str(download->info()->hash().str())</code></p><h3 id="scgi-和-websocket-监听同一个-socket"><a class="markdownIt-Anchor" href="#scgi-和-websocket-监听同一个-socket"></a> scgi 和 websocket 监听同一个 socket</h3><p>给 mentor 看完代码后 mentor 说能不能让 scgi 和 websocket 监听同一个 socket,后面我去了解到了 <code>SO_REUSEPORT</code> 这个 socket option,在 scgi 中开启后程序可以启动,但是 scgi 服务却不能正常运行,接下来尝试了让一个 tcp echo server 和 websocket 监听在同一个 port 上,发现了消息错乱的现象:websocket 的第一个 http 报文有可能被 tcp echo server 收了,返回的内容自然不是 websocket client 能处理就报错了,二者都是运行在 tcp 之上,不能保证 client 发来的消息被正确的 server 收到。</p>]]></content>
<categories>
<category>GSoC</category>
</categories>
<tags>
<tag>开源</tag>
</tags>
</entry>
<entry>
<title>GSoC 2022 Series - 2</title>
<link href="/2022/05/23/GSoC%202022%20Series%202/"/>
<url>/2022/05/23/GSoC%202022%20Series%202/</url>
<content type="html"><![CDATA[<h2 id="系列导航"><a class="markdownIt-Anchor" href="#系列导航"></a> 系列导航</h2><ul><li><a href="https://young-flash.github.io/2022/05/21/GSoC%202022%20Series%201/">GSoC 2022 Series - 1</a></li><li><a href="https://young-flash.github.io/2022/05/23/GSoC%202022%20Series%202/">GSoC 2022 Series - 2</a></li><li><a href="https://young-flash.github.io/2022/07/02/GSoC%202022%20Series%203/">GSoC 2022 Series - 3</a></li><li><a href="https://young-flash.github.io/2022/09/08/GSoC%202022%20Series%204/">GSoC 2022 Series - 4</a></li></ul><h2 id="熟悉代码"><a class="markdownIt-Anchor" href="#熟悉代码"></a> 熟悉代码</h2><p>联系 mentor 后他让我去看 src/rpc 下的代码,JSON-RPC 协议以及 SCGI 协议的实现都在这里,原先只有 XML-RPC,mentor 为它增加了 JSON-RPC 的支持:根据 XML-RPC 中的接口定义了一个 IRPC 接口类,然后让原先的 XML RPC 以及新增的 JSON RPC 继承自这个接口,通过 RpcManager 将 RPC 请求根据其内容是 XML 还是 JSON 分派到对应的处理类中然后再返回结果。一开始看代码还是比较混的,画一下分析图对于理清类之间的关系和工作流程会很有帮助:</p><p><img src="/img/blog_pic/2022/rtorrent-jsonRPC.jpg" alt="" /></p><p>此时的 RPC 的网络通信部分是由 SCGI 协议实现的,而这个项目的目标之一是将古老的 SCGI 协议替换为更加 Modern 的 Websocket 协议。当时我的想法是照猫画虎地增加 websockets.h、 <a href="http://websockets.cc">websockets.cc</a> 这样两个文件去实现 Websocket 协议。彼时的我还不知道 SCGI 协议是基于 rTorrent 的核心依赖 libtorrent 中的 event loop 实现的,也不清楚代码中 <code>Scgi</code> 和 <code>SCgiTask</code> 这两个类的关系,后来花了更多时间去阅读代码,结合 libtorrent 中 event loop 部分的代码理解之后才理清楚:<code>Scgi</code> 和 <code>SCgiTask</code> 的关系类似于 reactor 模型中主线程和子线程的关系,<code>Scgi</code> 是 listener,<code>activate</code> 之后把自己放在 event loop 中,<code>event_read</code> 中将每一个可读事件封装为 <code>SCgiTask</code> 然后将其放入 event loop 中。</p><h2 id="uwebsockets-和-usockets"><a class="markdownIt-Anchor" href="#uwebsockets-和-usockets"></a> uWebsockets 和 uSockets</h2><p><a href="https://github.com/uNetworking/uWebSockets">uWebsockets</a> 是一个 C++ 纯头文件实现的 websocket 库,性能优越,API 很简洁优雅,github 上有 13 k starts,计划用这个库来引入 websocket,不过在编译安装这个库的过程中还是遇到了很多麻烦的。uWebsockets 能做到纯头文件实现是因为底层用于 uSockets 作为网络库,在实验室的 GPU 上编译 uWebSockets 时报错 <code>lto1: fatal error: bytecode stream in file ‘uSockets/gcd.o’ generated with GCC compiler older than 10.0 compilation terminated</code>。 想着是链接器版本太低于是安装新的链接器,结果编译安装新版 binutils-2.38;成功安装了新版链接器后还是不行;转而安装版本低于 10.0 的 gcc … 装了 9.2 版本的还是不行。。。。。。吃完饭后在 Fisher 的虚拟机那里试了一下,一下子就编译过了。。。回来装了个 20.04 的 ubutun 虚拟机然后果然编译成功了,接下来就完全在虚拟机中开发了。这个过程记录在了前面的<a href="https://young-flash.github.io/2022/03/27/uWebsockets%20%E7%BC%96%E8%AF%91%E5%AE%89%E8%A3%85/">这篇博客</a>中。</p><p>弄完这些后我还用 uWebsockets 写了一个小 <a href="https://github.com/Young-Flash/websockets-demo">demo</a> 然后给 mentor 看,现在看来这个 demo 真是有够简单的。。。mentor 指出了这个库不支持 unix domain socket,但是这项特性对于 rTorrent 来说很重要:</p><blockquote><p>The feature is essential because rTorrent RPC interface has arbitrary command execution capabilities but doesn’t have access control of its own. Unix socket could be access restricted as a file, much straightforward than a port.</p></blockquote><h2 id="unix-domain-socket"><a class="markdownIt-Anchor" href="#unix-domain-socket"></a> unix domain socket</h2><p>SCGI 的可以跑在 TCP/IP 的端口上,也可以跑在 unix domain socket 上,当时我还不知道什么是 unix domain socket,一番 Google 下来后才算有所了解。mentor 说跑在 unix domain socket 比较安全,希望 websocket 也能跑在 unix domain socket 上,但是 uWebsockets 不支持 unix domain socket,此时只有两个选择:一是自己为 uWebsockets 增加 unix domain socket 支持,而是另寻它库,当时觉得自己增加 unix domain socket 支持肯定很难就去寻找其他库。试了 libwebsocket,有 unix domain socket 支持,不过是 C 写的,mentor 说接口 old-style,确实很难看懂。他建议我去实现 uWebsockets 的 unix domain socket 支持,查了一些资料,感觉也还是没啥思路。转而去寻找是否有其他支持 Unix domain socket 的 Websockets 实现,看了 websocketpp 和 boost 的 websocket 好像都没有。</p><p>结果最后只能硬着头皮尝试在 uWebsockets 上实现这项特性,首先在 uWebsockets 和 uSockets 中翻遍了所有跟 unix domain socket 有关的 issue,确实有那么几个,其中对我有启发的是<a href="https://github.com/uNetworking/uWebSockets/issues/1364">这个</a>,让我知道了从 uSockets 的 <code>bsd.c</code> 这个文件中的创建 socket 部分入手,接着还在 uWebsockets 中提了一个 <a href="https://github.com/uNetworking/uWebSockets/discussions/1438">discussion</a>,@了好多人都没人理我。。。实际动手操作后竟然真的让我做出来了,比想象中的简单许多,这也得益于 uWebsockets 本身的设计和编码都很优秀。在 uSockets 中提了 <a href="https://github.com/uNetworking/uSockets/pull/178">PR</a>,想着这要是能合进去了那可就太棒了,不过作者认为这还不够,需要考虑兼容其他协议(其实作者的意思我也不太理解),这 PR 就先放着吧,日后有时间再 work on it</p><h2 id="qualification-task"><a class="markdownIt-Anchor" href="#qualification-task"></a> qualification task</h2><p>mentor 后来发布了 qualification task:</p><blockquote><p>Implement a minimal WebSockets -> SCGI “translator” program in C++. Note that you don’t have to implement it inside rTorrent, and you don’t have to worry about the complicated stuff like events. <a href="https://gist.github.com/jesec/858daa0cfb3948b4209127550bf4fe1e">Here</a> is a simple JSON-RPC client for rTorrent. Currently it accepts command line arguments. Your task, basically, is to let it accept arguments from WebSockets instead. Alternatively, just passthrough the payload and pipe back the response.The experience you gained here will also help with the main project.</p></blockquote><p>感觉不太难,clone 了 mentor 给的 simple JSON-RPC client 想跑起来的时候遇到了问题:在尝试用 ip:port 连接 rtorrent 的时候总是报错 Failed to connect: -1,用 unix domain socket 连接的时候就正常,搞了很久还不知道是什么原因。。。后来在详细看代码才发现是 mentor 在 <code>sin->sin_port = ::htons(std::stoi(port))</code> 这里竟然写错了变量名。。。。。。slack 上联系他说了这个事情,问他是不是自己都没用 ip:port 跑过,果然如此。。。还在想着会不会也是 qualification task 的内容之一。</p><p>这个 task 我是很认真完成了的,放在了代码都放在了我 github 上,在<a href="https://github.com/Young-Flash/translator">这里</a>可以看到。对于内部数据流转的这个设计甚至自我感觉良好哈哈哈哈哈哈哈</p><p><img src="/img/blog_pic/2022/%E6%B5%81%E7%A8%8B%E5%9B%BE.png" alt="" /></p><h2 id="more"><a class="markdownIt-Anchor" href="#more"></a> more</h2><p>4 月 15 号的时候联系 mentor 说我为 uWebSockets 增加了 unix domain socket 的支持,并且计划向官方 repo 提 PR,不过 mentor 也说他 open to a custom fork so that shouldn’t be a big problem,这样的话就算 PR 不被 merge 的话也可以用我自己的 fork。5 月上旬提交了两个 commit,在 rTorrent 中引入了 websocket,支持 TCP/IP 端口以及 unix domain socket 的连接,跟 mentor 说了这事儿,得到反馈还不错,至此感觉应该没啥大问题了哈哈哈哈</p>]]></content>
<categories>
<category>GSoC</category>
</categories>
<tags>
<tag>开源</tag>
</tags>
</entry>
<entry>
<title>GSoC 2022 Series - 1</title>
<link href="/2022/05/21/GSoC%202022%20Series%201/"/>
<url>/2022/05/21/GSoC%202022%20Series%201/</url>
<content type="html"><![CDATA[<h2 id="系列导航"><a class="markdownIt-Anchor" href="#系列导航"></a> 系列导航</h2><ul><li><a href="https://young-flash.github.io/2022/05/21/GSoC%202022%20Series%201/">GSoC 2022 Series - 1</a></li><li><a href="https://young-flash.github.io/2022/05/23/GSoC%202022%20Series%202/">GSoC 2022 Series - 2</a></li><li><a href="https://young-flash.github.io/2022/07/02/GSoC%202022%20Series%203/">GSoC 2022 Series - 3</a></li><li><a href="https://young-flash.github.io/2022/09/08/GSoC%202022%20Series%204/">GSoC 2022 Series - 4</a></li></ul><h2 id="前言"><a class="markdownIt-Anchor" href="#前言"></a> 前言</h2><p>昨天早上 5 点多起来看 GSoC 的中选结果公布,发现自己<a href="https://summerofcode.withgoogle.com/programs/2022/projects/Qr45UY5M">成功中选</a>了,意料之内但也还是挺开心的哈哈哈哈。决定开个坑记录下自己整个 GSoC 的全过程,一方面作为自己的总结回顾,另一方面或许也可以为后来者提供一些参考。</p><h2 id="过程"><a class="markdownIt-Anchor" href="#过程"></a> 过程</h2><h3 id="选择项目"><a class="markdownIt-Anchor" href="#选择项目"></a> 选择项目</h3><p>GSoC 的项目真是又多又杂啊,甚至还有保护鲸鱼的组织和项目,这就是 diversity 吗哈哈哈。一开始并没有想好尝试哪个项目,想着从自己的技术栈出发找一些项目看看,我对 Linux C++ 比较有兴趣所以想找一些这方面的项目,一番浏览下来发现了 <a href="https://wiki.videolan.org/SoC_2022/">Develop a MPD server inside VLC</a>、<a href="https://github.com/casbin/SummerOfCode2022#casbin-for-cc">Casbin for C/C++</a>、HAIKU 以及 <a href="https://ccextractor.org/public/gsoc/rtorrent-modern-rpc/">Implement a modern RPC interface for rTorrent</a>,其中 VLC 和 HAIKU 初看上去不太契合我,Casbin 的有尝试过跑一下项目不过也没啥思路,最后发现了 rTorrent 的这个项目,主要是为 rTorrent 引入 Websockets 协议,涉及到网络编程和 Modern C++,二者我都有一点基础并且也都挺感兴趣的所以就锁定这个项目了。并没有想过同时尝试多个项目,因为不想花很多时间精力去多线程,认定好一个然后 all in 就好了,尽人事听天命。</p><h3 id="熟悉项目"><a class="markdownIt-Anchor" href="#熟悉项目"></a> 熟悉项目</h3><p>rTorrent 是一个命令行式的种子下载程序,通过 SCGI 协议实现 JSON RPC 从而支持浏览器端的 GUI。原来是由 <a href="https://github.com/rakshasa/rtorrent">rakshasa</a> 开发的,我要参与的这个是 jesec (同时也是我的项目导师) 的另一个 <a href="https://github.com/jesec/rtorrent">distribution</a>,主要的区别可以看<a href="https://flood.js.org/Changelog-4.5/">这里</a>。选定好这个项目后我首先把它 clone 下来,由于是 CMake 构建的项目所以在 CLion 里面开发调试也比较方便,接着粗略浏览一下代码,结合代码去理解这个项目的需求,这一点还是花了我一些时间的,比如了解 SCGI 协议,JSON-RPC 以及 torrent 的一些基本知识。</p><h3 id="套磁"><a class="markdownIt-Anchor" href="#套磁"></a> 套磁</h3><p>了解的差不多了就在 slack 上联系导师,首先介绍了自己的基本情况,个人技术栈,去年开源之夏的经历以及接下来的微软实习,配置好了项目的开发环境并且成功跑起来了。</p><blockquote><p>hello jesec. I am Dongyang Zheng, interesting in GSoC project [Implement a modern RPC interface for rTorrent]. I guess you are the mentor of this project according to the info in slack and github, am I right 😄 ?<br />First let me introduce myself, now I am in the second year of my master`s degree in South China Normal University, majoring computer science and technology. My major programming language is C++ and I really like it, I understand the syntax of C++, new features in modern C++, some knowledge about CMake and network programming over TCP/IP.<br />I have open source experience. I participated in a similar activity similar to GSoC last summer and successfully passed the review of mentor and organizers. The project I am responsible for is to encapsulate the JDBC protocol for a graph database named <a href="https://github.com/vesoft-inc/nebula">Nebula Graph</a>, it was done by me almost independently (of course, with the guidance of my mentor), the output (code and documentation) have been merged into offical github repository. Now I am now also a volunteer contributor in this open source community and continue to maintain this project. You can see the activity info <a href="https://summer.iscas.ac.cn/#/org/prodetail/210360225?lang=en">here</a> and now the outcome can be seen <a href="https://github.com/nebula-contrib/nebula-jdbc">here</a>.<br />These days I got json-rpc, libwebsockets ready on my dev machine and tried to build and run rTorrent on my computer. The basic conditions for development are in place. I wonder how far along the project is and where I should start if I want to work on it ?</p></blockquote><p>第二天 mentor 就回复我了,说我的 profile looks great,和我讲了一下项目的目标,以及接下来会发布 qualification tasks to identify the best candidate。关于套磁的过程及 qualification tasks 我应该会记录在下一篇中,先给自己开个坑。</p><h3 id="申请"><a class="markdownIt-Anchor" href="#申请"></a> 申请</h3><p>关于 proposal 的撰写我参考了这个 <a href="https://github.com/saketkc/fos-proposals">repo</a>,主要写了 6 个点:Personal Information、Project Description、About me、Homework、Plan、Timeline 和 Why me。重点是 Homework 和 Plan 部分,分别介绍了自己前期的一些准备工作以及若是能中选这个项目后的开发计划,后续我再把我这份 proposal 回馈给这个 repo。</p><h2 id="中选"><a class="markdownIt-Anchor" href="#中选"></a> 中选</h2><p>我对自己的前期工作以及和 mentor 的交流过程感觉还是挺不错的,所以认为自己中选的概率还是比较大的。GSoC 从截止提交 proposal 到公布中选中间隔了一个月,还是让人等得够久的,中间我给 rTorrent 提交了两个 <a href="https://github.com/Young-Flash/rtorrent/commit/8e25383e69ce8b3017fa599167aaf66ac9d07d5b">commit</a>,初步引入了 websocket,给 mentor 看后他说 looks good at the first glance,提到要同时保留 SCGI 和 websockets 以及支持 Bazel 构建,至此我就觉得中选应该没问题了,继续等 Google 公布结果吧。终于在昨天收到了中选邮件!!!</p><p><img src="/img/blog_pic/2022/GSoC-accepted.png" alt="" /></p>]]></content>
<categories>
<category>GSoC</category>
</categories>
<tags>
<tag>开源</tag>
</tags>
</entry>
<entry>
<title>Windows 上为 uSockets 搭建开发环境</title>
<link href="/2022/04/17/Windows%20%E4%B8%8A%E4%B8%BA%20uSockets%20%E6%90%AD%E5%BB%BA%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83/"/>
<url>/2022/04/17/Windows%20%E4%B8%8A%E4%B8%BA%20uSockets%20%E6%90%AD%E5%BB%BA%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83/</url>
<content type="html"><![CDATA[<h2 id="前言"><a class="markdownIt-Anchor" href="#前言"></a> 前言</h2><p>前两天为 uWebSockets 增加了服务端的 unix domain socket 支持,联系了作者是否愿意接受我的 PR,得到的<a href="https://github.com/uNetworking/uWebSockets/discussions/1438#discussioncomment-2570789">回复</a>是需要能够跨平台并且客户端也需要支持。于是打算尝试在 Windows 上跑起来这个项目,然后在其上增加 unix domain socket 支持。搭建环境的过程很艰辛,花了我整整一天多的时间,C/C++ 开发还是在 Linux 下比较舒服。。。</p><h2 id="过程"><a class="markdownIt-Anchor" href="#过程"></a> 过程</h2><h3 id="af_unix-windows-sdk"><a class="markdownIt-Anchor" href="#af_unix-windows-sdk"></a> AF_UNIX && Windows SDK</h3><p>Windows 后来支持了unix domain socket,需要下来安装 SDK 才能体验这项特性,具体可以看这篇<a href="https://devblogs.microsoft.com/commandline/af_unix-comes-to-windows/">博客</a>,最新的 SDK 要 3 个 G,在网上搜了以前的版本,1 个 G 多点,这样也不用下太久。安装完之后就可以找到 <code>afunix.h</code> 头文件了。具体的 example 在微软的另一篇<a href="https://devblogs.microsoft.com/commandline/windowswsl-interop-with-af_unix/">博客</a>里。</p><h3 id="vcpkg"><a class="markdownIt-Anchor" href="#vcpkg"></a> vcpkg</h3><p>C/C++ 免不了要依赖第三方库,如果每一项依赖都得由自己手动编译安装就很繁琐了。vcpkg 是一个跨平台的包管理工具,安装步骤在<a href="https://github.com/microsoft/vcpkg/blob/master/README_zh_CN.md#%E5%BF%AB%E9%80%9F%E5%BC%80%E5%A7%8B-windows">这里</a>。我在安装完之后下载 libuv 的过程中遇到这个错误:<code>Error: in triplet x64-windows: Unable to find a valid Visual Studio instance</code>,一番 Google 之后解决了,需要在为 Visual Studio 安装 English language 以及安装一个组件:<strong>C++ CMake tools for Windows</strong>。具体看<a href="https://otland.net/threads/compiling-tfs-with-vcpkg-unable-to-find-a-valid-visual-studio-instance.279856/">这里</a>。</p><h3 id="工具链"><a class="markdownIt-Anchor" href="#工具链"></a> 工具链</h3><p>VS 的工具链和 GNU 的不同,GNU 的 g++ 和 gcc 在 VS 中都是 cl,命令格式也不一样,比如 g++ 中指定 include 目录是 -I<code>path</code>,cl 中是 /I<code>path</code>,-std=c<ins>17 在 cl 中是 /std:c</ins>17。。。一开始不知道这些差异,总是在 cl 中习惯性地写上 g++ 的命令格式导致出现了很多莫名其妙的问题。</p><p>make 运行 uSockets 中的 Makefile 时还报错找不到 <code>CC</code>,后来看了 Makefile 后知道 <code>CC</code> 是里面定义的一个变量,代指编译器。解决方法是在 make 命令中显示指明 <code>CC</code>:make CC=gcc.exe,但是这样编译出来的是一个个 .o 文件(linux 下的文件格式,Windows 中对应的是 .obj),并且还报错找不到 <code>uv.h</code>,后来灵光一现把命令换成 <code>make CC=cl.exe</code> 就编译出 .obj 了,由于 Makefile 中没有为 Windows 定义把 .obj 打包成静态链接库的命令,所以我手动把编译出来的 .obj 打包成一个 .lib 了,步骤参考这篇<a href="https://blog.csdn.net/lixiangminghate/article/details/78943496">博客</a>。这里还有一个坑,VS 中有 link.exe,Git 中也有,而且在我的环境变量设置中 Git 的路径是靠前的,结果执行 link 的时候报错 <code>/usr/bin/link: extra operand</code>,接着在命令行中显示指定 VS 的 link 路径后执行打包命令就成功了,得知原因后把 Git 的路径挪到 VS 后面,这样每次执行 link 的时候就不用显示指定路径了。</p><h3 id="环境变量的设置"><a class="markdownIt-Anchor" href="#环境变量的设置"></a> 环境变量的设置</h3><p>编译的过程总是遇到找不到头文件以及运行时找不到动态链接的问题,猜想应该是环境变量的问题,参考了这篇<a href="https://blog.csdn.net/weixin_43997331/article/details/104764873">博客</a>,设置了一系列环境变量之后就 OK 了。下面是我的环境变量设置:</p><p>头文件:<code>INCLUDE</code></p><figure class="highlight apache"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs apache"><span class="hljs-attribute">E</span>:\WindowsSDK\Lib\<span class="hljs-number">10</span>.<span class="hljs-number">0</span>.<span class="hljs-number">20344</span>.<span class="hljs-number">0</span>\um\x64<br><span class="hljs-attribute">E</span>:\WindowsSDK\Lib\<span class="hljs-number">10</span>.<span class="hljs-number">0</span>.<span class="hljs-number">20344</span>.<span class="hljs-number">0</span>\ucrt\x64<br><span class="hljs-attribute">D</span>:\developmentTools\VisualStudio2022\VC\Tools\MSVC\<span class="hljs-number">14</span>.<span class="hljs-number">31</span>.<span class="hljs-number">31103</span>\lib\x64<br><span class="hljs-attribute">D</span>:\environment\vcpkg\installed\x64-windows\lib<br></code></pre></td></tr></table></figure><p>静态链接库文:<code>LIB</code></p><figure class="highlight apache"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs apache"><span class="hljs-attribute">E</span>:\WindowsSDK\Lib\<span class="hljs-number">10</span>.<span class="hljs-number">0</span>.<span class="hljs-number">20344</span>.<span class="hljs-number">0</span>\um\x64<br><span class="hljs-attribute">E</span>:\WindowsSDK\Lib\<span class="hljs-number">10</span>.<span class="hljs-number">0</span>.<span class="hljs-number">20344</span>.<span class="hljs-number">0</span>\ucrt\x64<br><span class="hljs-attribute">D</span>:\developmentTools\VisualStudio2022\VC\Tools\MSVC\<span class="hljs-number">14</span>.<span class="hljs-number">31</span>.<span class="hljs-number">31103</span>\lib\x64<br><span class="hljs-attribute">D</span>:\environment\vcpkg\installed\x64-windows\lib<br></code></pre></td></tr></table></figure><p>动态链接库:在 <code>Path</code> 中新增 vcpkg 存放 .dll 的路径<br /><code>D:\environment\vcpkg\installed\x64-windows\bin</code></p><h3 id="机器体系结构不匹配"><a class="markdownIt-Anchor" href="#机器体系结构不匹配"></a> 机器体系结构不匹配</h3><p>在 CLion 中运行的时候总是报错<code>“模块计算机类型“x64”与目标计算机类型“X86”</code>的错误,我的机器是 x64 的,这实在让人迷惑,我设置在 CMake 中设置了一系列指令指定机器类型和编译器以及链接器的路径,结果还是得到一样或者反过来的错误。。。</p><figure class="highlight cmake"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs cmake"><span class="hljs-keyword">set</span>(CMAKE_EXE_LINKER_FLAGS <span class="hljs-string">"/machine:x64"</span>)<br><br><span class="hljs-keyword">set</span>(CMAKE_C_COMPILER <span class="hljs-string">"D:/developmentTools/VisualStudio2022/VC/Tools/MSVC/14.31.31103/bin/Hostx64/x64/cl.exe"</span>)<br><span class="hljs-keyword">set</span>(CMAKE_CXX_COMPILER <span class="hljs-string">"D:/developmentTools/VisualStudio2022/VC/Tools/MSVC/14.31.31103/bin/Hostx64/x64/cl.exe"</span>)<br><span class="hljs-keyword">set</span>(CMAKE_RC_COMPILER <span class="hljs-string">"E:/WindowsSDK/bin/10.0.20344.0/x64/rc.exe"</span>)<br><span class="hljs-keyword">set</span>(CMAKE_MT_COMPILER <span class="hljs-string">"E:/WindowsSDK/bin/10.0.20344.0/x64/mt.exe"</span>)<br><span class="hljs-keyword">SET</span>(CMAKE_C_LINK_EXECUTABLE <span class="hljs-string">"D:/developmentTools/VisualStudio2022/VC/Tools/MSVC/14.31.31103/bin/Hostx64/x64/link.exe"</span>)<br></code></pre></td></tr></table></figure><p>后面想着去看看 CLion 中 toolchain 的设置,结果发现是 VS 的 Architecture 默认是 x86,换成 amd64 后就可以了。</p><h3 id="总结"><a class="markdownIt-Anchor" href="#总结"></a> 总结</h3><p>这个过程可谓是一波 N 折。。。不过最后成功在 CLion 中跑起来的时候心情还是很激动的。。。至于宇宙最强的 Visual Studio,我是真的不会用。。。。。。</p><h2 id="参考"><a class="markdownIt-Anchor" href="#参考"></a> 参考</h2><ul><li><a href="https://github.com/uNetworking/uSockets">uSockets</a></li><li><a href="https://blog.csdn.net/weixin_43997331/article/details/104764873">在Windows命令行中使用cl.exe</a></li><li><a href="https://blog.csdn.net/lixiangminghate/article/details/78943496">obj文件打包为静态库,静态库中抽取obj文件</a></li><li><a href="https://cwang.me/2020/02/06/check-dll-type/">Windows 中查看库文件(lib 或 dll)是 32 位还是 64 位</a></li></ul>]]></content>
<categories>
<category>技术</category>
</categories>
<tags>
<tag>C++</tag>
<tag>环境搭建</tag>
<tag>GSoC</tag>
</tags>
</entry>
<entry>
<title>uWebsockets 编译安装</title>
<link href="/2022/03/27/uWebsockets%20%E7%BC%96%E8%AF%91%E5%AE%89%E8%A3%85/"/>
<url>/2022/03/27/uWebsockets%20%E7%BC%96%E8%AF%91%E5%AE%89%E8%A3%85/</url>
<content type="html"><![CDATA[<h2 id="前言"><a class="markdownIt-Anchor" href="#前言"></a> 前言</h2><p>最近由于项目需要用到 websocket 所以看了一下 C++ 实现的 websocket 协议。在 Github 中找到了 <a href="https://github.com/uNetworking/uWebSockets">uWebSockets</a>,uWebSockets 最新版以 C++ 17 实现了 websocket 协议,目前拥有 13.7k starts,在 C++ 中是主流 websocket 实现。我在编译安装这个库上还是花了挺多时间了,在这里记录一下我的编译安装过程。</p><h2 id="环境设置"><a class="markdownIt-Anchor" href="#环境设置"></a> 环境设置</h2><p>uWebSockets 以纯头文件的形式用 C++ 17 实现,repo 中的示例还用到了 C++ 20 的特性,所以最好需要支持 C++ 20 的编译器版本(我用的是 gcc-9.20)。</p><p>在实验室的服务器上尝试编译时遇到了 <code>lto1: fatal error: bytecode stream in file ‘uSockets/gcd.o’ generated with GCC compiler older than 10.0 compilation terminated</code> 的错误,看起来是编译器 ABI 兼容性问题。可能是因为我装了多个不同版本的 gcc 同时环境变量的设置也比较混乱,尝试了各种升级版本和设置环境变量均无果后转而在新装的虚拟机(Ubutun 20.04)中编译安装了。在编译时最好确保编译环境“干净简洁”。</p><h2 id="步骤"><a class="markdownIt-Anchor" href="#步骤"></a> 步骤</h2><p>uWebSockets 能做到纯头文件实现是因为它底层依赖了 <a href="https://github.com/uNetworking/uSockets">uSockets</a>,uSockets 封装了各种网络操作。根据编译选项的不同需要安装好它的各种依赖项比如 openssl、libuv、zlib 等。</p><h3 id="克隆-uwebsockets-到本地"><a class="markdownIt-Anchor" href="#克隆-uwebsockets-到本地"></a> <strong>克隆 uWebSockets 到本地</strong></h3><p><code>git clone https://github.com/uNetworking/uWebSockets</code></p><h3 id="克隆-usockets-到-uwebsockets-中"><a class="markdownIt-Anchor" href="#克隆-usockets-到-uwebsockets-中"></a> <strong>克隆 uSockets 到 uWebSockets 中</strong></h3><p>项目克隆下来之后会发现里面的 uSockets 文件夹是空的,所以需要删掉该文件夹然后克隆 uSockets 到项目中。<br /><code>cd uWebSockets</code><br /><code>rm -rf uSockets</code><br /><code>git clone https://github.com/uNetworking/uSockets</code></p><h3 id="开启-ssl-选项编译"><a class="markdownIt-Anchor" href="#开启-ssl-选项编译"></a> <strong>开启 ssl 选项编译</strong></h3><p><code>WITH_OPENSSL=1 make</code></p><h3 id="安装"><a class="markdownIt-Anchor" href="#安装"></a> <strong>安装</strong></h3><p>注意到 uWebSockets 的 Makefile 中有写 install 命令,执行 <code>make install</code> 后会在 <code>/usr/local/include/</code> 下新建一个 <code>uWebSockets</code> 文件夹然后将 src 下的所有 .h 文件拷贝到其中。这时候还不算全部安装成功,前面提到 uWebSockets 依赖了 uSockets,所以我们需要安装好 uSockets。</p><p>经过前面的 make 编译后来到 uSockets 文件夹,发现其中多出了各种 .o 目标文件和一个 .a 静态链接库(其实这个 .a 就是多出的各个 .o 的集合,可以用 <code>readelf -A uSockets.a</code> 查看)。uSockets 的 Makefile 中没有写好 install 命令,所以我们手动把 src 下的 <code>libuSockets.h</code> 和编译出来的 uSockets.a 静态链接库安装到系统目录下:<code>libuSockets.h</code> 需要拷贝到 <code>/usr/local/include</code> 下,<code>uSockets.a</code> 需要拷贝到 <code>/usr/lib/x86_64-linux-gnu</code> 下然后重命名为 <code>libuSockets.a</code></p><h3 id="链接"><a class="markdownIt-Anchor" href="#链接"></a> <strong>链接</strong></h3><p>使用 uWebSockets 写完程序后编译时需要手动链接依赖的库文件。</p><p><code>-lz -lpthread -luSockets -lssl -lcrypto</code></p><h2 id="websocket-demo"><a class="markdownIt-Anchor" href="#websocket-demo"></a> websocket-demo</h2><p>这是一个关于如何使用 uWebsockets 来实现一个支持发布和订阅的 websocket 服务器 demo,代码在<a href="https://github.com/Young-Flash/websockets-demo">这里</a>。</p><p>有两个线程,主线程监听 ip: port 等待 websockets 客户端连接,客户端会在它发送到服务器的消息中指明要订阅的 topic,另一个线程(publishtopicrandom)模拟 topic 发布,如果某个 topic 已经由特定的客户端订阅,那么在服务端发布该 topic 时与该 topic 相关的消息将自动被发送到相应的客户端。mutex 和 condition_variable 用于同步两个线程,只有在客户端订阅后,线程 publishTopicRandomly 才会随机发布主题。</p><h2 id="参考"><a class="markdownIt-Anchor" href="#参考"></a> 参考</h2><ul><li><a href="https://github.com/uNetworking/uWebSockets">uWebSockets</a></li><li><a href="https://github.com/uNetworking/uSockets">uSockets</a></li><li><a href="https://github.com/Young-Flash/websockets-demo">websockets-demo</a></li></ul>]]></content>
<categories>
<category>技术</category>
</categories>
<tags>
<tag>C++</tag>
<tag>Linux</tag>
<tag>环境搭建</tag>
<tag>websocket</tag>
<tag>GSoC</tag>
</tags>
</entry>
<entry>
<title>Linux 为当前用户安装新版的 GCC 和 CMake</title>
<link href="/2022/03/15/Linux%20%E4%B8%BA%E5%BD%93%E5%89%8D%E7%94%A8%E6%88%B7%E5%AE%89%E8%A3%85%E6%96%B0%E7%89%88%E7%9A%84%20GCC%20%E5%92%8C%20CMake/"/>
<url>/2022/03/15/Linux%20%E4%B8%BA%E5%BD%93%E5%89%8D%E7%94%A8%E6%88%B7%E5%AE%89%E8%A3%85%E6%96%B0%E7%89%88%E7%9A%84%20GCC%20%E5%92%8C%20CMake/</url>
<content type="html"><![CDATA[<h2 id="前言"><a class="markdownIt-Anchor" href="#前言"></a> 前言</h2><p>在实验室服务器上编译一个 C++ 项目的时候遇到了 GCC 和 CMake 版本太低的问题,第一反应是升级服务器的 GCC 和 CMake 版本,但是这样的话突然升级版本可能对其他同学有影响,所以最好还是为自己配置一套新的环境吧。主要的过程是下载、编译后在 ~/.bashrc 中设置当前用户环境变量。</p><h2 id="安装-gcc"><a class="markdownIt-Anchor" href="#安装-gcc"></a> 安装 GCC</h2><h3 id="步骤"><a class="markdownIt-Anchor" href="#步骤"></a> 步骤</h3><ol><li><p><strong>下载</strong><br />当前 gcc 最新版本是 11.2.0,首先在自己用户空间下新建一个目录下载该版本的 gcc(其他<a href="http://ftp.gnu.org/gnu/gcc/">版本</a>可以在这里找到)<br /><code>wget http://ftp.gnu.org/gnu/gcc/gcc-11.2.0/gcc-11.2.0.tar.gz</code></p></li><li><p><strong>解压</strong><br /><code>tar -zxvf gcc-11.2.0.tar.gz</code><br />来到解压后的 gcc-11.2.0 目录,下载所需依赖(GMP, MPFR、MPC 和 ISL),可能不能一次性下载成功,这样的话删掉已下载的依赖然后重新执行下面的命令。<br /><code>./contrib/download_prerequisites</code></p></li><li><p><strong>在 gcc-11.2.0 外新建一个目录作为编译后的安装路径:</strong><br /><code>mkdir gcc_11.2.0-flash</code></p></li><li><p><strong>来到 gcc-11.2.0 目录下创建一个 build 目录用于编译然后配置编译属性:</strong><br /><code>../configure --disable-checking --enable-languages=c,c++ --disable-multilib --prefix="gcc_11.2.0-flash 的绝对路径" --enable-threads=posix</code></p></li><li><p><strong>编译并安装</strong><br />make -j 64(服务器 64 核我全开了) && make install</p></li><li><p><strong>在 ~/.bashrc 中设置用户环境变量后执行 <code>source ~/.bashrc</code> 使其生效</strong></p></li></ol><figure class="highlight txt"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs txt">export PATH=/home/dongyang/env_of_flash/gcc-11.2.0-flash/bin:/home/dongyang/env_of_flash/gcc-11.2.0-flash/lib64:$PATH<br>export LD_LIBRARY_PATH=/home/dongyang/env_of_flash/gcc-11.2.0-flash/lib:$LD_LIBRARY_PATH<br><br># 1.gcc/g++等程序本身的路径<br>export PATH=/home/dongyang/env_of_flash/gcc-11.2.0-flash/bin:$PATH<br><br><br># 2.gcc头文件路径<br>export C_INCLUDE_PATH=$C_INCLUDE_PATH:/home/dongyang/env_of_flash/gcc-11.2.0-flash/include<br><br># 3.g++头文件路径<br>export CPLUS_INCLUDE_PATH=$CPLUS_INCLUDE_PATH:/home/dongyang/env_of_flash/gcc-11.2.0-flash/include<br><br># 4.动态链接库路径<br>export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/dongyang/env_of_flash/gcc-11.2.0-flash/lib64<br><br># 5.静态库路径<br>export LIBRARY_PATH=$LIBRARY_PATH:/home/dongyang/env_of_flash/gcc-11.2.0-flash/lib<br></code></pre></td></tr></table></figure><ol start="7"><li><strong>验证</strong><br /><code>gcc -v</code> 验证当前 gcc 版本,<code>which gcc</code> 查看其路径</li></ol><h3 id="踩坑"><a class="markdownIt-Anchor" href="#踩坑"></a> 踩坑</h3><p>make 编译的时候遇到 fatal error No space left on device 的错误,参考 stackoverflow 上<a href="https://stackoverflow.com/questions/31493663/unable-to-compile-with-make-fatal-error-no-space-left-on-device">这个问题</a>的解决方法,在终端中执行 <code>export TMPDIR=~/tmp</code> 后解决了</p><h2 id="安装-cmake"><a class="markdownIt-Anchor" href="#安装-cmake"></a> 安装 CMake</h2><p>CMake 的安装比起 GCC 简单许多因为少了编译的过程,也就少了潜在的报错的机会哈哈哈哈</p><h3 id="步骤-2"><a class="markdownIt-Anchor" href="#步骤-2"></a> 步骤</h3><ol><li><strong>下载</strong><br />当前最新版本是 3.23.0-rc3。在<a href="https://cmake.org/download/">这里</a>找到想要的版本然后下载并解压</li></ol><ul><li><code>wget https://github.com/Kitware/CMake/releases/download/v3.23.0-rc3/cmake-3.23.0-rc3.tar.gz</code></li><li><code>tar -zxvf cmake-3.23.0-rc3.tar.gz</code></li></ul><ol start="2"><li><p><strong>设置用户环境变量</strong><br />在 ~/.bashrc 中加上 <code>export PATH=/home/dongyang/env_of_flash/cmake-3.23.0-rc3/bin:$PATH</code> 然后在终端运行 <code>source ~/.bashrc</code> 使其生效</p></li><li><p><strong>验证</strong><br /><code>cmake --version</code> 查看当前 CMake 版本;<code>which cmake</code> 查看可执行文件所在路径</p></li></ol><h2 id="参考"><a class="markdownIt-Anchor" href="#参考"></a> 参考</h2><ul><li><a href="https://stackoverflow.com/questions/31493663/unable-to-compile-with-make-fatal-error-no-space-left-on-device">fatal error No space left on device</a></li><li><a href="https://www.cnblogs.com/jessepeng/p/11674780.html">非root安装GCC9.1.0</a></li></ul>]]></content>
<categories>
<category>技术</category>
</categories>
<tags>
<tag>C++</tag>
<tag>Linux</tag>
<tag>CMake</tag>
<tag>环境搭建</tag>
</tags>
</entry>
<entry>
<title>通过 GitHub Action 自动发布 Maven 包到中央仓库</title>
<link href="/2022/03/11/%E9%80%9A%E8%BF%87%20GitHub%20Action%20%E8%87%AA%E5%8A%A8%E5%8F%91%E5%B8%83%20Maven%20%E5%8C%85%E5%88%B0%E4%B8%AD%E5%A4%AE%E4%BB%93%E5%BA%93/"/>
<url>/2022/03/11/%E9%80%9A%E8%BF%87%20GitHub%20Action%20%E8%87%AA%E5%8A%A8%E5%8F%91%E5%B8%83%20Maven%20%E5%8C%85%E5%88%B0%E4%B8%AD%E5%A4%AE%E4%BB%93%E5%BA%93/</url>
<content type="html"><![CDATA[<h2 id="前言"><a class="markdownIt-Anchor" href="#前言"></a> 前言</h2><p>前阵子微软 leader round 面试的时候聊到我做的的开源项目 <a href="https://github.com/nebula-contrib/nebula-jdbc">nebula-jdbc</a>,面试官问到了这个项目的被使用情况,我知道这个项目有人在用(因为有人在 issue 中提问了哈哈哈哈)但是不知道有多少人在用,所以当时只回答了这个 repo 的 watch、fork 和 star 数量,因为我并没有把这个项目打成 jar 包发布到 maven 中央仓库。事后想想确实有必要打包发布,一方面方便用户使用,一方面也能看到被使用的情况。</p><h2 id="流程"><a class="markdownIt-Anchor" href="#流程"></a> 流程</h2><h3 id="配置-gpg"><a class="markdownIt-Anchor" href="#配置-gpg"></a> 配置 GPG</h3><p>GnuPG,简称 GPG,是 GPG 标准的一个免费实现。不管是 Linux 还是 Windows 平台,都可以使用。GPGneng 可以为文件生成签名、管理密匙以及验证签名。发布到Maven仓库中的所有文件都要使用 GPG 签名保证不被篡改。具体的配置过程可以参考这篇<a href="https://www.timberkito.com/?p=141#hwpDMCYX">博客</a>。最终需要用到 GPG 密钥(导出的一长串)和 Passphrase(自己在生成密钥过程中设置的密码)</p><h3 id="注册-sonatype-账号"><a class="markdownIt-Anchor" href="#注册-sonatype-账号"></a> 注册 Sonatype 账号</h3><p>不能直接向 maven 中央仓库中发包,需要发布到 Nexus 然后由它定期自动同步到 maven 的中央仓库。首先需要<a href="https://link.zhihu.com/?target=https%3A//issues.sonatype.org/secure/Signup%21default.jspa">注册 Sonatype 账号</a>,然后提交一个 issue 说明你要发布的这个 jar 包的信息,具体过程可以参考这篇<a href="https://blog.csdn.net/qq_36838191/article/details/81027586">博客</a>。一开始我不知道还有这个步骤,然后发包时就报了 403 forBiden 的错误。。。</p><h3 id="github-action"><a class="markdownIt-Anchor" href="#github-action"></a> GitHub Action</h3><p><a href="https://docs.github.com/cn/actions">GitHub Action</a> 是 GitHub 推出的实现 CI/CD(持续集成、持续交付/部署)的手段。可以创建工作流程来构建和测试存储库的每个拉取请求,或将合并的拉取请求部署到生产环境,由 GitHub 提供虚拟机来运行你在 workflow 文件中写好的执行流程。</p><p>GitHub Action 可以针对不同的时间设置不同的处理流程,这次我要实现的目标是在 release 一个新版本时自动打包发布,所以监听的是 released 事件,处理流程复用了 samuelmeuli/action-maven-publish。事实上 GitHub Action 能做的事情远不止如此,比如还可以在新 pr 提交的时候自动跑一遍测试用例以及做一些定时任务之类的…</p><p>首先在仓库的 Actions 中 new workflow,工作流的 yml 文件内容如下:</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><code class="hljs yml"><span class="hljs-comment"># 相当于脚本用途的一个声明</span><br><span class="hljs-attr">name:</span> <span class="hljs-string">Maven</span> <span class="hljs-string">Central</span> <span class="hljs-string">Repo</span> <span class="hljs-string">Deployment</span><br><span class="hljs-comment"># 触发脚本的事件 这里为发布release之后触发</span><br><span class="hljs-attr">on:</span><br> <span class="hljs-attr">release:</span><br> <span class="hljs-attr">types:</span> [<span class="hljs-string">released</span>]<br><span class="hljs-comment"># 定义一个发行任务</span><br><span class="hljs-attr">jobs:</span><br> <span class="hljs-attr">release:</span><br> <span class="hljs-comment"># 运行处理流程的 OS 环境</span><br> <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-18.04</span><br> <span class="hljs-attr">steps:</span><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Check</span> <span class="hljs-string">out</span> <span class="hljs-string">Git</span> <span class="hljs-string">repository</span><br> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v2</span><br><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Install</span> <span class="hljs-string">Java</span> <span class="hljs-string">and</span> <span class="hljs-string">Maven</span><br> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/setup-java@v1</span><br> <span class="hljs-attr">with:</span><br> <span class="hljs-attr">java-version:</span> <span class="hljs-number">8</span><br><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Release</span> <span class="hljs-string">Maven</span> <span class="hljs-string">package</span><br> <span class="hljs-attr">uses:</span> <span class="hljs-string">samuelmeuli/action-maven-publish@v1</span><br> <span class="hljs-attr">with:</span><br> <span class="hljs-comment"># 一系列需要的密码</span><br> <span class="hljs-attr">gpg_private_key:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.GPG_SECRET</span> <span class="hljs-string">}}</span> <span class="hljs-comment"># GPG 导出的那段长长的密钥</span><br> <span class="hljs-attr">gpg_passphrase:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.GPG_PASSWORD</span> <span class="hljs-string">}}</span> <span class="hljs-comment"># 自己设置的 GPG 密码</span><br> <span class="hljs-attr">nexus_username:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.OSSRH_USER</span> <span class="hljs-string">}}</span> <span class="hljs-comment"># sonatype 用户名(不是邮箱)</span><br> <span class="hljs-attr">nexus_password:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.OSSRH_PASSWORD</span> <span class="hljs-string">}}</span> <span class="hljs-comment"># sonatype 登录密码</span><br></code></pre></td></tr></table></figure><p>上面提到的一系列密码保存在 repo 的 <code>Settings</code>-><code>Secrets</code>-><code>Actios</code>-><code>Repository secrets</code> 下</p><h3 id="pomxml-配置"><a class="markdownIt-Anchor" href="#pomxml-配置"></a> pom.xml 配置</h3><p>具体可以参考如下:</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br></pre></td><td class="code"><pre><code class="hljs xml"><span class="hljs-meta"><?xml version=<span class="hljs-string">"1.0"</span> encoding=<span class="hljs-string">"UTF-8"</span>?></span><br><span class="hljs-tag"><<span class="hljs-name">project</span> <span class="hljs-attr">xmlns</span>=<span class="hljs-string">"http://maven.apache.org/POM/4.0.0"</span></span><br><span class="hljs-tag"> <span class="hljs-attr">xmlns:xsi</span>=<span class="hljs-string">"http://www.w3.org/2001/XMLSchema-instance"</span></span><br><span class="hljs-tag"> <span class="hljs-attr">xsi:schemaLocation</span>=<span class="hljs-string">"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">modelVersion</span>></span>4.0.0<span class="hljs-tag"></<span class="hljs-name">modelVersion</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">packaging</span>></span>jar<span class="hljs-tag"></<span class="hljs-name">packaging</span>></span><br><br> <span class="hljs-tag"><<span class="hljs-name">groupId</span>></span>io.github.young-flash<span class="hljs-tag"></<span class="hljs-name">groupId</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">artifactId</span>></span>flash-GAT<span class="hljs-tag"></<span class="hljs-name">artifactId</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">version</span>></span>4.0.0<span class="hljs-tag"></<span class="hljs-name">version</span>></span><br><br><br> <span class="hljs-tag"><<span class="hljs-name">properties</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">maven.compiler.source</span>></span>1.8<span class="hljs-tag"></<span class="hljs-name">maven.compiler.source</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">maven.compiler.target</span>></span>1.8<span class="hljs-tag"></<span class="hljs-name">maven.compiler.target</span>></span><br> <span class="hljs-tag"></<span class="hljs-name">properties</span>></span><br><br> <span class="hljs-comment"><!-- 项目信息 --></span><br> <span class="hljs-tag"><<span class="hljs-name">name</span>></span>Github-Action-Test-By-Flash<span class="hljs-tag"></<span class="hljs-name">name</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">description</span>></span>Github-Action-Test-By-Flash<span class="hljs-tag"></<span class="hljs-name">description</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">url</span>></span>https://github.com/Young-Flash/GitHub-Action-Test<span class="hljs-tag"></<span class="hljs-name">url</span>></span><br><br> <span class="hljs-comment"><!-- 项目地址信息 --></span><br> <span class="hljs-tag"><<span class="hljs-name">scm</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">connection</span>></span>scm:git:https://github.com/Young-Flash/GitHub-Action-Test<span class="hljs-tag"></<span class="hljs-name">connection</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">url</span>></span>https://github.com/Young-Flash/GitHub-Action-Test.git<span class="hljs-tag"></<span class="hljs-name">url</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">developerConnection</span>></span>scm:git:https://github.com/Young-Flash/GitHub-Action-Test<span class="hljs-tag"></<span class="hljs-name">developerConnection</span>></span><br> <span class="hljs-tag"></<span class="hljs-name">scm</span>></span><br><br> <span class="hljs-comment"><!-- licenses 信息 --></span><br> <span class="hljs-tag"><<span class="hljs-name">licenses</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">license</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">name</span>></span>Apache License, Version 2.0<span class="hljs-tag"></<span class="hljs-name">name</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">url</span>></span>https://www.apache.org/licenses/LICENSE-2.0.txt<span class="hljs-tag"></<span class="hljs-name">url</span>></span><br> <span class="hljs-tag"></<span class="hljs-name">license</span>></span><br> <span class="hljs-tag"></<span class="hljs-name">licenses</span>></span><br><br> <span class="hljs-comment"><!-- 开发者信息 --></span><br> <span class="hljs-tag"><<span class="hljs-name">developers</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">developer</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">name</span>></span>Flash<span class="hljs-tag"></<span class="hljs-name">name</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">email</span>></span>[email protected]<span class="hljs-tag"></<span class="hljs-name">email</span>></span><br> <span class="hljs-tag"></<span class="hljs-name">developer</span>></span><br> <span class="hljs-tag"></<span class="hljs-name">developers</span>></span><br><br> <span class="hljs-comment"><!-- 如果用到了 snapshot 版本的依赖则需要加上下面这个声明 --></span><br> <span class="hljs-tag"><<span class="hljs-name">repositories</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">repository</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">id</span>></span>snapshots<span class="hljs-tag"></<span class="hljs-name">id</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">url</span>></span>https://oss.sonatype.org/content/repositories/snapshots/<span class="hljs-tag"></<span class="hljs-name">url</span>></span><br> <span class="hljs-tag"></<span class="hljs-name">repository</span>></span><br> <span class="hljs-tag"></<span class="hljs-name">repositories</span>></span><br><br> <span class="hljs-tag"><<span class="hljs-name">dependencies</span>></span><br> <span class="hljs-comment"><!-- 你的依赖 --></span><br> <span class="hljs-tag"></<span class="hljs-name">dependencies</span>></span><br><br> <span class="hljs-tag"><<span class="hljs-name">profiles</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">profile</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">id</span>></span>deploy<span class="hljs-tag"></<span class="hljs-name">id</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">build</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">plugins</span>></span><br> <span class="hljs-comment"><!-- Javadoc plugin --></span><br> <span class="hljs-tag"><<span class="hljs-name">plugin</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">groupId</span>></span>org.apache.maven.plugins<span class="hljs-tag"></<span class="hljs-name">groupId</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">artifactId</span>></span>maven-javadoc-plugin<span class="hljs-tag"></<span class="hljs-name">artifactId</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">version</span>></span>3.2.0<span class="hljs-tag"></<span class="hljs-name">version</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">configuration</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">doclint</span>></span>none<span class="hljs-tag"></<span class="hljs-name">doclint</span>></span><br> <span class="hljs-tag"></<span class="hljs-name">configuration</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">executions</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">execution</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">id</span>></span>attach-javadocs<span class="hljs-tag"></<span class="hljs-name">id</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">goals</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">goal</span>></span>jar<span class="hljs-tag"></<span class="hljs-name">goal</span>></span><br> <span class="hljs-tag"></<span class="hljs-name">goals</span>></span><br> <span class="hljs-tag"></<span class="hljs-name">execution</span>></span><br> <span class="hljs-tag"></<span class="hljs-name">executions</span>></span><br> <span class="hljs-tag"></<span class="hljs-name">plugin</span>></span><br><br> <span class="hljs-comment"><!-- Source plugin --></span><br> <span class="hljs-tag"><<span class="hljs-name">plugin</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">groupId</span>></span>org.apache.maven.plugins<span class="hljs-tag"></<span class="hljs-name">groupId</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">artifactId</span>></span>maven-source-plugin<span class="hljs-tag"></<span class="hljs-name">artifactId</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">version</span>></span>3.2.0<span class="hljs-tag"></<span class="hljs-name">version</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">executions</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">execution</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">id</span>></span>attach-sources<span class="hljs-tag"></<span class="hljs-name">id</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">goals</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">goal</span>></span>jar-no-fork<span class="hljs-tag"></<span class="hljs-name">goal</span>></span><br> <span class="hljs-tag"></<span class="hljs-name">goals</span>></span><br> <span class="hljs-tag"></<span class="hljs-name">execution</span>></span><br> <span class="hljs-tag"></<span class="hljs-name">executions</span>></span><br> <span class="hljs-tag"></<span class="hljs-name">plugin</span>></span><br><br> <span class="hljs-comment"><!-- GPG plugin --></span><br> <span class="hljs-tag"><<span class="hljs-name">plugin</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">groupId</span>></span>org.apache.maven.plugins<span class="hljs-tag"></<span class="hljs-name">groupId</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">artifactId</span>></span>maven-gpg-plugin<span class="hljs-tag"></<span class="hljs-name">artifactId</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">version</span>></span>1.6<span class="hljs-tag"></<span class="hljs-name">version</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">executions</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">execution</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">id</span>></span>sign-artifacts<span class="hljs-tag"></<span class="hljs-name">id</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">phase</span>></span>verify<span class="hljs-tag"></<span class="hljs-name">phase</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">goals</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">goal</span>></span>sign<span class="hljs-tag"></<span class="hljs-name">goal</span>></span><br> <span class="hljs-tag"></<span class="hljs-name">goals</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">configuration</span>></span><br> <span class="hljs-comment"><!-- Prevent `gpg` from using pinentry programs --></span><br> <span class="hljs-tag"><<span class="hljs-name">gpgArguments</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">arg</span>></span>--pinentry-mode<span class="hljs-tag"></<span class="hljs-name">arg</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">arg</span>></span>loopback<span class="hljs-tag"></<span class="hljs-name">arg</span>></span><br> <span class="hljs-tag"></<span class="hljs-name">gpgArguments</span>></span><br> <span class="hljs-tag"></<span class="hljs-name">configuration</span>></span><br> <span class="hljs-tag"></<span class="hljs-name">execution</span>></span><br> <span class="hljs-tag"></<span class="hljs-name">executions</span>></span><br> <span class="hljs-tag"></<span class="hljs-name">plugin</span>></span><br> <span class="hljs-tag"></<span class="hljs-name">plugins</span>></span><br> <span class="hljs-tag"></<span class="hljs-name">build</span>></span><br> <span class="hljs-tag"></<span class="hljs-name">profile</span>></span><br> <span class="hljs-tag"></<span class="hljs-name">profiles</span>></span><br><br> <span class="hljs-tag"><<span class="hljs-name">distributionManagement</span>></span><br> <span class="hljs-comment"><!-- Central Repository --></span><br> <span class="hljs-tag"><<span class="hljs-name">snapshotRepository</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">id</span>></span>ossrh<span class="hljs-tag"></<span class="hljs-name">id</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">url</span>></span>https://oss.sonatype.org/content/repositories/snapshots<span class="hljs-tag"></<span class="hljs-name">url</span>></span><br> <span class="hljs-tag"></<span class="hljs-name">snapshotRepository</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">repository</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">id</span>></span>ossrh<span class="hljs-tag"></<span class="hljs-name">id</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">name</span>></span>Nexus Release Repository<span class="hljs-tag"></<span class="hljs-name">name</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">url</span>></span>https://oss.sonatype.org/service/local/staging/deploy/maven2/<span class="hljs-tag"></<span class="hljs-name">url</span>></span><br> <span class="hljs-tag"></<span class="hljs-name">repository</span>></span><br> <span class="hljs-tag"></<span class="hljs-name">distributionManagement</span>></span><br><br> <span class="hljs-tag"><<span class="hljs-name">build</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">plugins</span>></span><br> <span class="hljs-comment"><!-- Nexus Staging Plugin --></span><br> <span class="hljs-tag"><<span class="hljs-name">plugin</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">groupId</span>></span>org.sonatype.plugins<span class="hljs-tag"></<span class="hljs-name">groupId</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">artifactId</span>></span>nexus-staging-maven-plugin<span class="hljs-tag"></<span class="hljs-name">artifactId</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">version</span>></span>1.6.8<span class="hljs-tag"></<span class="hljs-name">version</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">extensions</span>></span>true<span class="hljs-tag"></<span class="hljs-name">extensions</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">configuration</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">serverId</span>></span>ossrh<span class="hljs-tag"></<span class="hljs-name">serverId</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">nexusUrl</span>></span>https://s01.oss.sonatype.org<span class="hljs-tag"></<span class="hljs-name">nexusUrl</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">autoReleaseAfterClose</span>></span>true<span class="hljs-tag"></<span class="hljs-name">autoReleaseAfterClose</span>></span><br> <span class="hljs-tag"></<span class="hljs-name">configuration</span>></span><br> <span class="hljs-tag"></<span class="hljs-name">plugin</span>></span><br> <span class="hljs-tag"></<span class="hljs-name">plugins</span>></span><br> <span class="hljs-tag"></<span class="hljs-name">build</span>></span><br><br><span class="hljs-tag"></<span class="hljs-name">project</span>></span><br></code></pre></td></tr></table></figure><h2 id="发布"><a class="markdownIt-Anchor" href="#发布"></a> 发布</h2><p>所有准备工作就绪后就可以尝试发布了,release 一个新版本,此时会自动触发你在 workflow 的 yml 文件中设置的执行流程,具体地运行情况可以在 Actions 中看到,可以会报错,这个就需要看报错信息自己去摸索解决方法了。</p><h2 id="踩坑"><a class="markdownIt-Anchor" href="#踩坑"></a> 踩坑</h2><h3 id="nexus-staging-maven-plugin168deploy-failed-403-forbidden"><a class="markdownIt-Anchor" href="#nexus-staging-maven-plugin168deploy-failed-403-forbidden"></a> “nexus-staging-maven-plugin:1.6.8:deploy failed: 403 - Forbidden”</h3><p>按照其他教程的步骤来,一切配置就绪后发布时遇到这个错误,查了很久都没找到解决方法。后来在这篇<a href="https://blog.csdn.net/qq_36838191/article/details/81027586">博客</a>中看到还有一个注册工单的步骤,完成工单后再发布就没问题了。过程中会有社区机器人验证你创建的工单的合法性,比如需要验证你对该 github 账号的所有权(让你建一个指定名字的临时仓库)和 groupId 对应的域名的所有权。</p><h3 id="the-pom-for-is-missing-no-dependency-information-available"><a class="markdownIt-Anchor" href="#the-pom-for-is-missing-no-dependency-information-available"></a> “The POM for … is missing, no dependency information available”</h3><p>在为项目引入依赖后发布时遇到这个错误,原因是我引入的那个依赖是 SNAPSHOT 版本,需要在 pom.xml 中加上 <repository> 声明</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs xml"><span class="hljs-tag"><<span class="hljs-name">repositories</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">repository</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">id</span>></span>snapshots<span class="hljs-tag"></<span class="hljs-name">id</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">url</span>></span>https://oss.sonatype.org/content/repositories/snapshots/<span class="hljs-tag"></<span class="hljs-name">url</span>></span><br> <span class="hljs-tag"></<span class="hljs-name">repository</span>></span><br><span class="hljs-tag"></<span class="hljs-name">repositories</span>></span><br></code></pre></td></tr></table></figure><p>包发布成功后需要一段时间才会同步到中央仓库,发布后成功后过小半天后可以先在 <a href="https://search.maven.org/">sonatype</a> 上查看</p><h2 id="参考"><a class="markdownIt-Anchor" href="#参考"></a> 参考</h2><ul><li><a href="https://stackoverflow.com/questions/45730541/the-pom-for-is-missing-no-dependency-information-available-even-though-it">The POM for … is missing, no dependency information available</a></li><li><a href="https://segmentfault.com/a/1190000039716048">使用CI/CD工具Github Action发布jar到Maven中央仓库</a></li><li><a href="https://juejin.cn/post/6892965219791093773#heading-3">通过GitHub Action自动部署Maven项目</a></li><li><a href="https://blogtech.top/2019/12/30/%E5%8F%91%E5%B8%83%E8%87%AA%E5%B7%B1%E7%9A%84%E9%A1%B9%E7%9B%AEJAR%E5%88%B0MAVEN%E4%B8%AD%E5%A4%AE%E4%BB%93%E5%BA%93/">发布自己的项目 JAR 到 MAVEN 中央仓库</a></li></ul>]]></content>
<categories>
<category>技术</category>
</categories>
<tags>
<tag>CI/CD</tag>
</tags>
</entry>
<entry>
<title>右值引用、移动语义、完美转发</title>
<link href="/2022/03/07/%E5%8F%B3%E5%80%BC%E5%BC%95%E7%94%A8%E3%80%81%E7%A7%BB%E5%8A%A8%E8%AF%AD%E4%B9%89%E3%80%81%E5%AE%8C%E7%BE%8E%E8%BD%AC%E5%8F%91/"/>
<url>/2022/03/07/%E5%8F%B3%E5%80%BC%E5%BC%95%E7%94%A8%E3%80%81%E7%A7%BB%E5%8A%A8%E8%AF%AD%E4%B9%89%E3%80%81%E5%AE%8C%E7%BE%8E%E8%BD%AC%E5%8F%91/</url>
<content type="html"><![CDATA[<h2 id="前言"><a class="markdownIt-Anchor" href="#前言"></a> 前言</h2><p>右值引用、移动语义、完美转发是 C++ 11 引入的一大新特性。即使在代码中没有显示地使用到他们我们也能得到性能上的提升,STL 中容器类就大量使用了这些新特性,在使用这些容器时我们”隐式“地从中受益。比如 vector 类在扩容时需要把原来的对象挪到新开辟的空间中,若数据类型重写了移动构造函数并且标记为 <a href="https://flash-flash.gitee.io/2021/12/18/%E4%B8%BA%E4%BB%80%E4%B9%88%E9%9C%80%E8%A6%81%E5%B0%86%E7%A7%BB%E5%8A%A8%E6%9E%84%E9%80%A0%E5%87%BD%E6%95%B0%E5%92%8C%E7%A7%BB%E5%8A%A8%E8%B5%8B%E5%80%BC%E8%BF%90%E7%AE%97%E7%AC%A6%E6%A0%87%E8%AE%B0%E4%B8%BA%20noexcept/">noexcept</a> 就可以将旧对象移动过去而不是使用拷贝构造的形式,就如 C++ Primer Plus 中所说:“对大多数程序员来说,右值引用带来的主要好处并非是让他们能够编写使用右值引用的代码,而是能够使用利用右值引用实现移动语义的库代码”。</p><h2 id="右值引用与移动语义"><a class="markdownIt-Anchor" href="#右值引用与移动语义"></a> 右值引用与移动语义</h2><p><strong>先说关系,右值引用的提出是为了实现移动语义,即移动语义是目的,右值引用是手段。</strong></p><ul><li>对于左值与右值一种简单的理解是左值是可以取地址的而右值不可以,右值引用是 C++ 11 中引入的新类型,用于标识字面常量(1,“string”)、表达式(x+y)和函数的按值返回。</li><li><strong>移动语义实现了将一个右值对象的资源转移到当前对象,避免了拷贝构造的开销</strong>;在序列容器类的扩容以及两个对象的 swap 等情况极大提高了效率。</li><li>移动语义主要体现在为某类重写的移动构造函数中,在实现中一般是进行指针值的拷贝然后置空右值对象中对应的指针。进行指针值的拷贝也即浅拷贝,从这点上看移动语义与浅拷贝有点相似,但实际上二者是两个不同的概念:浅拷贝是共享资源,而 move 是独占资源(<strong>窃取后置空原指针</strong>),浅拷贝因共享资源从而可能引发重复析构的问题,而 move 是独占则不会。</li></ul><h2 id="万能引用"><a class="markdownIt-Anchor" href="#万能引用"></a> 万能引用</h2><p>what:即可以接受左值引用也可以接受右值引用的参数类型,写作 <strong>T&&,注意只有当发生自动类型推断(如函数模板的类型自动推导,或 auto 关键字)时 T&& 才表示万能引用,否则只表示右值引用</strong></p><p>how:通过引用折叠实现(只有右值引用的右值引用会折叠成右值引用,其余情况都是折叠成左值引用):</p><ul><li>T& & -> T&</li><li>T& && -> T&</li><li>T&& & -> T&</li><li>T&& && -> T&&</li></ul><p>关于万能引用在我<a href="https://flash-flash.gitee.io/2021/12/31/C++%2011%20%E4%B8%87%E8%83%BD%E5%BC%95%E7%94%A8/">另一篇博客</a>中有提到。</p><h2 id="完美转发perfect-forwarding"><a class="markdownIt-Anchor" href="#完美转发perfect-forwarding"></a> 完美转发(perfect forwarding)</h2><p>为什么需要完美转发的原因:右值引用本身是一个左值,如将一个右值引用参数传递给某函数 f1,而在该函数中又将该参数传给另一个函数 f2,f2 会在进行实参形参的匹配时会将该参数视为左值,而我们希望无论如何转发该参数都能保持其为右值对象这一特点,因此需要完美转发:通过一个函数将参数继续转交给另一个函数进行处理,原参数可能是右值,可能是左值,如果还能继续保持参数的原有特征,那么它就是完美的。实现上要借助’万能引用’和 <code>std::forward</code>。</p><p>分析实现代码之前先看看 std::remove_reference</p><h3 id="stdremove_reference"><a class="markdownIt-Anchor" href="#stdremove_reference"></a> std::remove_reference</h3><p>std::forward 的实现中用到了 std::remove_reference。std::remove_reference 是 type_traits(类型萃取)之一,由类模板实现。</p><figure class="highlight sqf"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs sqf"><span class="hljs-comment">// 1</span><br>template<<span class="hljs-built_in">typename</span> <span class="hljs-variable">_Tp</span>><br> struct remove_reference<br> { typedef <span class="hljs-variable">_Tp</span> <span class="hljs-built_in">type</span>; };<br><br><span class="hljs-comment">// 2</span><br>template<<span class="hljs-built_in">typename</span> <span class="hljs-variable">_Tp</span>><br> struct remove_reference<<span class="hljs-variable">_Tp</span>&><br> { typedef <span class="hljs-variable">_Tp</span> <span class="hljs-built_in">type</span>; };<br><br><span class="hljs-comment">// 3</span><br>template<<span class="hljs-built_in">typename</span> <span class="hljs-variable">_Tp</span>><br> struct remove_reference<<span class="hljs-variable">_Tp</span>&&><br> { typedef <span class="hljs-variable">_Tp</span> <span class="hljs-built_in">type</span>; };<br></code></pre></td></tr></table></figure><p>1 接受原始类型,2 、3 作为 1 的特化接受 lvalue-ref、 rvalue-ref。当 remove_reference 模板实例化后该模板中定义的类型 <code>type</code> 就具化为模板参数的原始类型</p><h3 id="stdforward-代码解析"><a class="markdownIt-Anchor" href="#stdforward-代码解析"></a> std::forward 代码解析</h3><p>std::forward 是一个函数模板,针对转发左值和转发右值有不同的重载。</p><figure class="highlight sqf"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs sqf"><span class="hljs-comment">// 转发左值</span><br>template<<span class="hljs-built_in">typename</span> <span class="hljs-variable">_Tp</span>><br> constexpr <span class="hljs-variable">_Tp</span>&&<br> forward(<span class="hljs-built_in">typename</span> std::remove_reference<<span class="hljs-variable">_Tp</span>>::<span class="hljs-built_in">type</span>& <span class="hljs-variable">__t</span>) noexcept<br> { return static_cast<<span class="hljs-variable">_Tp</span>&&>(<span class="hljs-variable">__t</span>); }<br><br><span class="hljs-comment">// 转发右值</span><br>template<<span class="hljs-built_in">typename</span> <span class="hljs-variable">_Tp</span>><br> constexpr <span class="hljs-variable">_Tp</span>&&<br> forward(<span class="hljs-built_in">typename</span> std::remove_reference<<span class="hljs-variable">_Tp</span>>::<span class="hljs-built_in">type</span>&& <span class="hljs-variable">__t</span>) noexcept<br> {<br> static_assert(!std::is_lvalue_reference<<span class="hljs-variable">_Tp</span>>::value, <span class="hljs-string">"template argument substituting _Tp is an lvalue reference type"</span>);<br> return static_cast<<span class="hljs-variable">_Tp</span>&&>(<span class="hljs-variable">__t</span>);<br> }<br></code></pre></td></tr></table></figure><ul><li>返回值类型:都是 constexpr _Tp&&,常量表达式可在编译期确定,_Tp&& 是一个万能引用。</li><li>接收参数:用 remove_reference 得到参数的原始类型;转发左值版本给参数加上左值引用,转发右值版本给参数加上右值引用。</li><li>内部实现:两个重载的实现上基本一致,都利用 static_cast 将参数转化为左值引用或者右值引用,返回语句都是 <code>return static_cast<_Tp&&>(__t);</code>,这里用到了引用折叠的性质。略有不同的是转发右值的重载中多了一个 static_assert 在编译时避免将右值引用 forward 为左值引用。刚开始看到转发右值的版本中有 static_assert 而转发左值的版本中没有时还不太理解,后来在 Stack Overflow 上找到了一个类似的<a href="https://stackoverflow.com/questions/10335916/c11-why-is-static-assert-in-stdforward-necessary">提问</a>,转发右值的版本中的 static_assert 是为了避免类似 forward<string&>(string()))的情况。即不允许将一个“纯右值”转发为左值;但是允许将左值转发为右值,实际上这正是 ‘std::move()’ 做的事情。这其实是设计理念方面的问题,具体参见<a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2009/n2951.html">这里</a>。</li></ul><h2 id="参考"><a class="markdownIt-Anchor" href="#参考"></a> 参考</h2><ul><li><a href="https://segmentfault.com/a/1190000016041544">我理解的右值引用、移动语义和完美转发</a></li><li><a href="https://stackoverflow.com/questions/10335916/c11-why-is-static-assert-in-stdforward-necessary">c++11: why is static_assert in std::forward necessary?</a></li><li><a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2009/n2951.html">forward</a></li></ul>]]></content>
<categories>
<category>技术</category>
</categories>
<tags>
<tag>C++</tag>
</tags>
</entry>
<entry>
<title>函数调用的详细过程</title>
<link href="/2022/01/21/%E5%87%BD%E6%95%B0%E8%B0%83%E7%94%A8%E7%9A%84%E8%AF%A6%E7%BB%86%E8%BF%87%E7%A8%8B/"/>
<url>/2022/01/21/%E5%87%BD%E6%95%B0%E8%B0%83%E7%94%A8%E7%9A%84%E8%AF%A6%E7%BB%86%E8%BF%87%E7%A8%8B/</url>
<content type="html"><![CDATA[<h2 id="前言"><a class="markdownIt-Anchor" href="#前言"></a> 前言</h2><p>本文探讨函数调用时寄存器的行为及栈帧的变化,首先说明 x86 架构下通用寄存器的用途,调用者保存、被调用者保存以及栈帧的概念,然后概括发生函数调用时的一般过程。</p><h2 id="x86-架构下通用寄存器的用途"><a class="markdownIt-Anchor" href="#x86-架构下通用寄存器的用途"></a> x86 架构下通用寄存器的用途</h2><blockquote><p>请自行区分操作系统位数和cpu架构位数的区别。x64(x86-64),x86是CPU架构。如果你是x64的CPU装了32位系统,那么也不会使用到x64的寄存器(比如r8d),或者不能完整使用x64CPU的寄存器,比如rax。你只能使用该寄存器的一半:eax</p></blockquote><p><img src="/img/blog_pic/2022/x86%E6%9E%B6%E6%9E%84%E4%B8%8B%E9%80%9A%E7%94%A8%E5%AF%84%E5%AD%98%E5%99%A8%E7%9A%84%E7%94%A8%E9%80%94.png" alt="x86 架构下通用寄存器的用途" /></p><ul><li><strong>%rax 通常用于存储函数调用的返回结果</strong>,同时也用于乘法和除法指令中。在 imul 指令中,两个64位的乘法最多会产生128位的结果,需要 %rax 与 %rdx 共同存储乘法结果,在div 指令中被除数是128 位的,同样需要%rax 与 %rdx 共同存储被除数。</li><li><strong>%rsp 是堆栈指针寄存器,通常会指向栈顶位置</strong>,堆栈的 pop 和push 操作就是通过改变 %rsp 的值即移动堆栈指针的位置来实现的。sp所指向的栈顶是所能分配的栈空间的极限,如果栈空间不够则需要移动sp指针,分配更多的栈空间。</li><li><strong>%rbp 是栈帧指针,用于标识当前栈帧的起始位置</strong>。</li><li><strong>%rdi, %rsi, %rdx, %rcx,%r8, %r9 六个寄存器用于存储函数调用时的6个参数</strong>(如果有6个或6个以上参数的话)。</li><li>被标识为 “miscellaneous registers” 的寄存器,属于通用性更为广泛的寄存器,编译器或汇编程序可以根据需要存储任何数据</li></ul><h2 id="调用者保存-被调用者保存"><a class="markdownIt-Anchor" href="#调用者保存-被调用者保存"></a> 调用者保存、被调用者保存</h2><ul><li>当产生函数调用时,子函数内通常也会使用到通用寄存器,那么这些寄存器中之前保存的调用者(父函数)的值就会被覆盖。为了避免数据覆盖而导致从子函数返回时寄存器中的数据不可恢复,CPU 体系结构中就规定了通用寄存器的保存方式。</li><li>如果一个寄存器被标识为”Caller Save”, 那么在进行子函数调用前,就需要由调用者提前保存好这些寄存器的值,保存方法通常是把寄存器的值压入堆栈中,调用者保存完成后,在被调用者(子函数)中就可以随意覆盖这些寄存器的值了。</li><li>如果一个寄存被标识为“Callee Save”,那么在函数调用时,调用者就不必保存这些寄存器的值而直接进行子函数调用,进入子函数后,子函数在覆盖这些寄存器之前,需要先保存这些寄存器的值,即这些寄存器的值是由被调用者来保存和恢复的。</li></ul><h2 id="栈帧"><a class="markdownIt-Anchor" href="#栈帧"></a> 栈帧</h2><p>栈帧,stack frame,其本质就是一段内存空间,专门用于保存函数调用过程中的各种信息(本地局部变量,栈帧状态值(前栈帧的底部),返回地址等)。ebp 和 esp 之间的内存空间为当前栈帧,ebp 标识了当前栈帧的底部,esp 标识了当前栈帧的顶部</p><h2 id="函数调用的一般过程"><a class="markdownIt-Anchor" href="#函数调用的一般过程"></a> 函数调用的一般过程</h2><ol><li><p><strong>参数入栈:</strong> 将参数从右向左依次压入栈中(或者将参数存入寄存器中);</p></li><li><p><strong>返回地址入栈:</strong> 将当前代码区调用指令的下一条指令地址压入栈中,供函数返回时继续执行;</p></li><li><p><strong>代码区跳转:</strong> 处理器从当前代码区跳转到被调用函数的入口处;(第 2 、3 步由 <code>call</code> 指令完成)</p></li><li><p><strong>保存当前栈帧底部:</strong> 将当前 ebp (callee save register) 的值入栈(保存调用者的栈帧底部位置,便于函数调用结束后恢复原函数的栈帧底部位置 <code>push ebp</code>)</p></li><li><p><strong>更新栈帧底部:</strong> 将 esp 值装入 ebp <code>mov ebp esp</code> ,即开辟新栈帧</p></li><li><p><strong>给新栈帧分配空间:</strong> (向低地址)移动 esp</p></li><li><p><strong>执行被调用函数的代码然后保存返回值(在寄存器 eax 中)</strong></p></li><li><p><strong>恢复上一个栈帧(弹出当前栈帧):</strong></p><ul><li>将 esp 赋值为当前 ebp (<code>movq ebp esp</code>),降低栈顶,回收当前栈帧的空间;</li><li>将当前栈帧底部保存的前栈帧底部 ebp 值弹入 ebp 寄存器 (<code>pop ebp</code>),恢复出上一个栈帧;(8.1、8.2 由 <code>leave </code>指令完成)</li></ul></li><li><p><strong>返回:</strong> 调用 <code>ret </code>指令,把 esp 上移一个位置(第 2 步保存的返回地址),将函数返回地址弹给 eip 寄存器。当执行完成 <code>ret </code>后,esp 指向的是父栈帧的结尾处,父栈帧尾部存储的调用参数由编译器自动释放</p></li></ol><h3 id="示例代码"><a class="markdownIt-Anchor" href="#示例代码"></a> 示例代码</h3><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-built_in">intfunc_B</span>(<span class="hljs-type">int</span> arg_B1, <span class="hljs-type">int</span> arg_B2)<br>{<br> <span class="hljs-type">int</span> var_B1, var_B2;<br> var_B1=arg_B1+arg_B2;<br> var_B2=arg_B1-arg_B2;<br> <span class="hljs-keyword">return</span> var_B1*var_B2;<br>}<br><br><span class="hljs-built_in">intfunc_A</span>(<span class="hljs-type">int</span> arg_A1, <span class="hljs-type">int</span> arg_A2)<br>{<br> <span class="hljs-type">int</span> var_A;<br> var_A = <span class="hljs-built_in">func_B</span>(arg_A1,arg_A2) + arg_A1 ;<br> <span class="hljs-keyword">return</span> var_A;<br>}<br><br><span class="hljs-function"><span class="hljs-type">int</span> <span class="hljs-title">main</span><span class="hljs-params">(<span class="hljs-type">int</span> argc, <span class="hljs-type">char</span> **argv, <span class="hljs-type">char</span> **envp)</span></span><br><span class="hljs-function"></span>{<br> <span class="hljs-type">int</span> var_main;<br> var_main=<span class="hljs-built_in">func_A</span>(<span class="hljs-number">4</span>,<span class="hljs-number">3</span>);<br> <span class="hljs-keyword">return</span> var_main;<br>}<br></code></pre></td></tr></table></figure><h3 id="过程图示"><a class="markdownIt-Anchor" href="#过程图示"></a> 过程图示</h3><p><img src="/img/blog_pic/2022/%E5%87%BD%E6%95%B0%E8%B0%83%E7%94%A8%E7%9A%84%E5%AE%9E%E7%8E%B0.png" alt="函数调用的实现" /></p><h2 id="参考"><a class="markdownIt-Anchor" href="#参考"></a> 参考</h2><ul><li><a href="https://mp.weixin.qq.com/s/KriHHXF3WkzVgmCMN6rEUg">C/C++:堆栈面面观</a></li><li><a href="https://zhuanlan.zhihu.com/p/27339191">x86-64 下函数调用及栈帧原理</a></li><li><a href="https://leeshine.github.io/2019/03/17/stack-of-fuc-call/">C++函数调用栈过程</a></li><li>0day 安全软件漏洞分析技术(第二版) 第二章 (p40)</li></ul>]]></content>
<categories>
<category>技术</category>
</categories>
<tags>
<tag>操作系统</tag>
</tags>
</entry>
<entry>
<title>hardware_concurrency() 返回值是物理核数还是虚拟核数</title>
<link href="/2022/01/08/hardware_concurrency()%20%E8%BF%94%E5%9B%9E%E5%80%BC%E6%98%AF%E7%89%A9%E7%90%86%E6%A0%B8%E6%95%B0%E8%BF%98%E6%98%AF%E8%99%9A%E6%8B%9F%E6%A0%B8%E6%95%B0/"/>
<url>/2022/01/08/hardware_concurrency()%20%E8%BF%94%E5%9B%9E%E5%80%BC%E6%98%AF%E7%89%A9%E7%90%86%E6%A0%B8%E6%95%B0%E8%BF%98%E6%98%AF%E8%99%9A%E6%8B%9F%E6%A0%B8%E6%95%B0/</url>
<content type="html"><![CDATA[<h2 id="前言"><a class="markdownIt-Anchor" href="#前言"></a> 前言</h2><p>早上面试的时候被问到项目中多线程的实现方式以及<strong>线程池中线程数量的该如何确定?<strong>我回答了 hardware_concurrency(),接着被追问 hardware_concurrency() 返回值是物理核数还是虚拟核数?我说名字里有 hardware,应该是物理核数,没想到猜错了哈哈哈哈哈面试官说是</strong>虚拟核数</strong>.</p><h2 id="hardware_concurrency"><a class="markdownIt-Anchor" href="#hardware_concurrency"></a> hardware_concurrency()</h2><p><strong>cplusplus 上对于 hardware_concurrency() 的描述:</strong></p><blockquote><ul><li>Returns the number of hardware thread contexts.</li><li>The interpretation of this value is system- and implementation- specific, and may not be exact, but just an approximation.</li><li>Note that this does not need to match the actual number of processors or cores available in the system: A system can support multiple threads per processing unit, or restrict the access to its resources to the program.</li><li>If this value is not computable or well defined, the function returns 0.</li></ul></blockquote><p>第一点提到其返回值是硬件线程上下文的数量,第三点提到该返回值可能并不与真是的处理器或者核数相一致(因为系统可以在一个处理单元上运行多个线程)。</p><p><strong>StackOverflow 上有一个<a href="https://stackoverflow.com/questions/27971195/handle-stdthreadhardware-concurrency">问题</a>提到这个方法,其中有一个回答:</strong></p><blockquote><p>Even when hardware_concurrency is implemented, it cannot be relied as a direct mapping to the number of cores. This is what the standard says it returns - The number of hardware thread contexts. And goes on to state - This value should only be considered to be a hint If your machine has hyperthreading enabled, it’s entirely possible the value returned will be 2x the number of cores. If you want a reliable answer, you’ll need to use whatever facilities your OS provides. – PraetorianJan 15 '15 at 19:21</p></blockquote><p>说到该返回值 cannot be relied as a direct mapping to the number of core,也就是不直接与(物理)核数对应,如果开启了超线程技术,那么返回值将会是(物理)核数的 2 倍,也就是虚拟核数。</p><h2 id="物理核-虚拟核"><a class="markdownIt-Anchor" href="#物理核-虚拟核"></a> 物理核、虚拟核</h2><p>实际上,核的概念分为 物理核(Physical Core) 和 逻辑核(Logical Core),在我们说多核处理器的时候,其实是有二义性的——我们不知道讲的是多个 物理核 还是 逻辑核 。</p><ul><li>物理核<ul><li>物理上、真实存在的 核(Core),可以在硬件层面上看到数量的 核(Core)</li></ul></li><li>逻辑核<ul><li>理解上,只是逻辑上存在的 核(Core) 无法通过硬件层面判断出数量</li><li>实际上,基于 Intel 的 超线程技术(Hyper-Threading) 将一个物理核拆分成两个逻辑核,更加高效地执行 CPU 指令以及利用 L1 缓存</li></ul></li></ul><p>除此之外,无论是 Linux / Mac 还是 Windows 的资源管理器,我们看到的数量都是逻辑核的数量,计算公式大致如下:</p><ul><li>物理核数 = 物理核数(Dual Core = 双核 / Quad-Core = 四核)</li><li>逻辑核数 = 物理核数 or 物理核数 x 2(开启了 HT)</li></ul><h2 id="超线程-hyper-threading"><a class="markdownIt-Anchor" href="#超线程-hyper-threading"></a> 超线程 Hyper-Threading</h2><p>有时候我们谈到双核四线程的时候,实质上谈到的就是超线程技术(Hyper-Threading),超线程技术是一种硬件创新,允许一个以上的线程运行在每个核心,更多的线程意味着可以并行地完成更多的工作。</p><blockquote><ul><li>How does Hyper-Threading work? When Intel® Hyper-Threading Technology is active, the CPU exposes two execution contexts per physical core. This means that one physical core now works like two “logical cores” that can handle different software threads. The ten-core Intel® Core™ i9-10900K processor, for example, has 20 threads when Hyper-Threading is enabled.</li><li>Two logical cores can work through tasks more efficiently than a traditional single-threaded core. By taking advantage of idle time when the core would formerly be waiting for other tasks to complete, Intel® Hyper-Threading Technology improves CPU throughput (by up to 30% in server applications3).</li></ul></blockquote><p><img src="/img/blog_pic/2022/%E5%8D%95%E6%A0%B8%E5%8F%8C%E7%BA%BF%E7%A8%8B.png" alt="单核双线程" /></p><p>一个支持超线程技术的 CPU 物理核心,里面会有两套独立的寄存器但共用剩下的计算单元和缓存,比如 ALU(算术逻辑单元) 和 L1 / L2 缓存。反之,若不支持超线程的 CPU 物理核心中只会有一套寄存器,也能通过这个图直观的理解超线程带来的一些效率提升。从一个运行在开启了超线程的 CPU 的操作系统的角度来看,此时它管理着两个 CPU(逻辑上的),这两个逻辑 cpu 在同一个物理 cpu 上运行。 <strong>不过这样并不会产生两倍于传统处理器的处理能力,也不可能提供完全的并行处理能力,因为它们不能作为两个完整的独立的处理器来执行。</strong></p><p><img src="/img/blog_pic/2022/%E5%A4%9A%E6%A0%B8%E5%A4%9A%E7%BA%BF%E7%A8%8B.png" alt="多核多线程" /></p><p>四核八线程就是将四个单核两线程的物理核心装在一个芯片(Chip) 上。</p><h2 id="在-windows-系统上开启关闭虚拟化hyper-v"><a class="markdownIt-Anchor" href="#在-windows-系统上开启关闭虚拟化hyper-v"></a> 在 Windows 系统上开启/关闭虚拟化(Hyper-V)</h2><p>左图是在我的电脑上开启了 Hyper-V,右图是女朋友的电脑,没有开启 Hyper-V。同样是四个内核,开了 Hyper-V 后资源管理器中显示的逻辑处理器数量翻倍了。</p><table><thead><tr><th><img src="/img/blog_pic/2022/%E5%BC%80%E5%90%AF%E8%99%9A%E6%8B%9F%E5%8C%96.png" alt="开启虚拟化" /></th><th><img src="/img/blog_pic/2022/%E5%85%B3%E9%97%AD%E8%99%9A%E6%8B%9F%E5%8C%96.png" alt="关闭虚拟化" /></th></tr></thead></table><h2 id="参考"><a class="markdownIt-Anchor" href="#参考"></a> 参考</h2><ul><li><a href="http://www.cplusplus.com/reference/thread/thread/hardware_concurrency/">cplusplus-reference-hardware_concurrency</a></li><li><a href="https://stackoverflow.com/questions/27971195/handle-stdthreadhardware-concurrency">Handle std:🧵:hardware_concurrency()</a></li><li><a href="https://davex.pw/2020/09/18/cpu-physical-and-logical-cores-and-hyper-threding/">了解 CPU 之物理核、逻辑核、超线程</a></li><li><a href="https://www.intel.com/content/www/us/en/gaming/resources/hyper-threading.html">What Is Hyper-Threading?</a></li><li><a href="https://www.daniloaz.com/en/differences-between-physical-cpu-vs-logical-cpu-vs-core-vs-thread-vs-socket/">Differences between physical CPU vs logical CPU vs Core vs Thread vs Socket</a></li></ul>]]></content>
<categories>
<category>技术</category>
</categories>
<tags>
<tag>操作系统</tag>
</tags>
</entry>
<entry>
<title>TCP 四次挥手:为什么服务端需要收到客户端的 ACK 后才能 Closed</title>
<link href="/2022/01/03/TCP%20%E5%9B%9B%E6%AC%A1%E6%8C%A5%E6%89%8B%EF%BC%9A%E4%B8%BA%E4%BB%80%E4%B9%88%E6%9C%8D%E5%8A%A1%E7%AB%AF%E9%9C%80%E8%A6%81%E6%94%B6%E5%88%B0%E5%AE%A2%E6%88%B7%E7%AB%AF%E7%9A%84%20ACK%20%E5%90%8E%E6%89%8D%E8%83%BD%20Closed/"/>
<url>/2022/01/03/TCP%20%E5%9B%9B%E6%AC%A1%E6%8C%A5%E6%89%8B%EF%BC%9A%E4%B8%BA%E4%BB%80%E4%B9%88%E6%9C%8D%E5%8A%A1%E7%AB%AF%E9%9C%80%E8%A6%81%E6%94%B6%E5%88%B0%E5%AE%A2%E6%88%B7%E7%AB%AF%E7%9A%84%20ACK%20%E5%90%8E%E6%89%8D%E8%83%BD%20Closed/</url>
<content type="html"><![CDATA[<h2 id="前言"><a class="markdownIt-Anchor" href="#前言"></a> 前言</h2><p>今天逛牛客网时看到一个帖子,里面提了这样一个问题:</p><blockquote><p>为什么服务端处于 last-ack 后需要收到客户端的 ACK 后才能 closed ?<br />last-ack 状态下设置一个定时器超时后直接 closed 可以吗?</p></blockquote><p>第一感觉好像可以,不过细想之下就会发现是不可以的。</p><h2 id="理由"><a class="markdownIt-Anchor" href="#理由"></a> 理由</h2><ol><li><p>如果服务端没有收到第四次挥手的 ACK,那么它没法确认自己第三次挥手的 FIN 有没有被客户端接受;此时假设客户端没有接受到服务端的 FIN 而服务端如问题假设的那样在 last-ack 下超时而关闭了,那么客户端还是处于 FIN_WAIT_1 状态(正常情况下这个状态表示客户端已经关闭连接不能发送数据,但是还在等待接受服务端的数据直到接收到服务端的 FIN),而此时服务端已经 closed,再也不会给客户端发送 FIN,导致客户端在 FIN_WAIT_1 状态下一直等待。</p></li><li><p>服务端处于 last-ack 后需要收到客户端的 ACK 后才能 closed:服务端收到客户端第四次挥手的 ACK 意味着服务端第三次挥手的 FIN 被对方成功(客户端可以顺利转移到 TIME_WAIT 状态等待 2MSL 后 closed),如果服务端没收这最后一次挥手的 ACK ,那么它无法确认自己上一次挥手的 FIN 丢失了还是对方回复的 ACK 丢失了,此时就会重发 FIN 直到收到最后一个 ACK。</p></li><li><p>如果服务端处于 last-ack 状态后客户端断开连接导致服务端不可能收到第四次挥手的 ACK,这种情况下服务端会重传 FIN 直到重传次数达到内核参数 tcp_orphan_retries(默认值为 8) 后关闭连接</p></li></ol><p><img src="/img/blog_pic/2022/TCP%E5%9B%9B%E6%AC%A1%E6%8C%A5%E6%89%8B%E5%9B%BE%E7%A4%BA.png" alt="TCP四次挥手图示" /></p><h2 id="小结"><a class="markdownIt-Anchor" href="#小结"></a> 小结</h2><p><strong>思考这种问题要从 TCP 的确认机制和当时处于哪个状态以及该状态的含义出发。</strong><br />其他类似问题:</p><ul><li><p>第一次握手的 SYN 报文丢失如何处理?</p><ul><li>客户端端重传,重传次数由内核参数 tcp_syn_retries(默认值为 5) 规定</li></ul></li><li><p>第二次握手的 SYN + ACK 报文丢失如何处理?</p><ul><li>客户端端重传,重传次数由内核参数 tcp_syn_retries(默认值为 5) 规定;对其而言与第一次握手包丢失一致</li><li>服务端端重传,重传次数由内核参数 tcp_synack_retries(默认值为 5) 规定</li></ul></li><li><p>第三次握手的 ACK 报文丢失如何处理?</p><ul><li>服务端重传第二次的 SYN + ACK,或者接收到客户端发送了带有 ACK 的数据包</li></ul></li><li><p>第一次挥手的 FIN 丢失如何处理?</p><ul><li>客户端端重传,重传次数由内核参数 tcp_orphan_retries(默认值为 5) 规定</li></ul></li><li><p>第二次挥手的 ACK 丢失如何处理?</p><ul><li>客户端端重传,重传次数由内核参数 tcp_orphan_retries(默认值为 5) 规定;对其而言与第一次挥手包丢失一致</li><li>服务端不会重传,因为丢失的是 ACK 报文(接收方收到 ACK 报文后不会回复确认报文,否则的话就无限循环了)</li></ul></li><li><p>第三次挥手的 FIN 丢失如何处理?</p><ul><li>服务端端重传,重传次数由内核参数 tcp_orphan_retries(默认值为 5) 规定;与客户端重传第一次的 FIN 报文段机制一致</li></ul></li><li><p>第四次挥手的 ACK 丢失如何处理?</p><ul><li>服务端重传第三次挥手的 FIN,客户端此时处于 TIME_WAIT 状态,在 2 MSL 内收到 FIN,重传 ACK,重置定时器</li></ul></li></ul><h2 id="参考"><a class="markdownIt-Anchor" href="#参考"></a> 参考</h2><ul><li><a href="https://itectec.com/network/tcp-why-is-the-last-ack-needed-in-tcp-four-way-termination/">Tcp – Why is the last ACK needed in TCP four way termination</a></li></ul>]]></content>
<categories>
<category>技术</category>
</categories>
<tags>
<tag>计算机网络</tag>
</tags>
</entry>
<entry>
<title>C++ 11 万能引用</title>
<link href="/2021/12/31/C++%2011%20%E4%B8%87%E8%83%BD%E5%BC%95%E7%94%A8/"/>
<url>/2021/12/31/C++%2011%20%E4%B8%87%E8%83%BD%E5%BC%95%E7%94%A8/</url>
<content type="html"><![CDATA[<h2 id="引用折叠规则"><a class="markdownIt-Anchor" href="#引用折叠规则"></a> 引用折叠规则</h2><ul><li>T& & -> T&</li><li>T& && -> T&</li><li>T&& & -> T&</li><li>T&& && -> T&&</li></ul><p>小结:只有右值引用的右值引用会折叠成右值引用,其余情况都是折叠成左值引用</p><h2 id="完美转发"><a class="markdownIt-Anchor" href="#完美转发"></a> 完美转发</h2><p>某些场景下有参数转发的需求:最理想的情况下是外层的Wrapper(下面的 factory 函数)就像不存在一样而内层函数就像直接被调用一样<br />调用 wrapper 时传递的是左值,内层函数被调用时得到的就是左值;调用 wrapper 时传递的是右值,内层函数被调用时得到的就是右值。</p><h2 id="万能引用"><a class="markdownIt-Anchor" href="#万能引用"></a> 万能引用</h2><p>以引用形式作为参数的函数,一般只能匹配左值或者只能匹配右值</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-function"><span class="hljs-type">int</span> <span class="hljs-title">foo</span><span class="hljs-params">(<span class="hljs-type">int</span> & i)</span></span>; <span class="hljs-comment">// 只能接受左值引用参数</span><br><span class="hljs-function"><span class="hljs-type">int</span> <span class="hljs-title">foo</span><span class="hljs-params">(<span class="hljs-type">int</span> && i)</span></span>; <span class="hljs-comment">// 只能接受右值引用参数</span><br></code></pre></td></tr></table></figure><ul><li>若是想以左值引用形式接受右值引用参数,则必须将参数声明为 const 的左值引用;此时若是有多个形参,针对每个形参都要重载 const 和非 const 版本,是很复杂的排列组合问题,且无法触发移动语义。</li><li>此时需要万能引用:以一种形式的参数形式接受左值引用和右值引用;万能引用的两个前提条件:模板形式;&& 型参数</li></ul><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-function"><span class="hljs-keyword">template</span><<span class="hljs-keyword">typename</span> T, <span class="hljs-keyword">typename</span> Arg></span><br><span class="hljs-function">shared_ptr<T> <span class="hljs-title">factory</span><span class="hljs-params">(Arg && arg)</span> </span>{<br> <span class="hljs-keyword">return</span> <span class="hljs-built_in">shared_ptr</span><T>(<span class="hljs-keyword">new</span> <span class="hljs-built_in">T</span>(std::forward<Arg>(arg)));<br>}<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">CTemp</span>{<br> <span class="hljs-type">int</span> data;<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-built_in">CTemp</span>(<span class="hljs-type">int</span>& arg) : <span class="hljs-built_in">data</span>(arg){<br> cout << <span class="hljs-string">"CTemp(int& arg) called\n"</span>;<br> }<br> <span class="hljs-built_in">CTemp</span>(<span class="hljs-type">int</span>&& arg) : <span class="hljs-built_in">data</span>(arg){<br> cout << <span class="hljs-string">"CTemp(int&& arg) called\n"</span>;<br> }<br>};<br><br><span class="hljs-function"><span class="hljs-type">int</span> <span class="hljs-title">main</span><span class="hljs-params">()</span> </span>{<br> <span class="hljs-type">int</span> value = <span class="hljs-number">1</span>;<br> <span class="hljs-keyword">auto</span> p1 = <span class="hljs-built_in">factory</span><CTemp>(value); <br> <span class="hljs-keyword">auto</span> p2 = <span class="hljs-built_in">factory</span><CTemp>(<span class="hljs-number">2</span>);<br><br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br><br><br></code></pre></td></tr></table></figure><h2 id="运行结果"><a class="markdownIt-Anchor" href="#运行结果"></a> 运行结果</h2><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-built_in">CTemp</span>(<span class="hljs-type">int</span>& arg) <span class="hljs-function">called</span><br><span class="hljs-function"><span class="hljs-title">CTemp</span><span class="hljs-params">(<span class="hljs-type">int</span>&& arg)</span> called</span><br></code></pre></td></tr></table></figure><p>说明 factory 函数将接收到的参数“完美”转发(不改变参数的左右值属性)给内层函数</p><h2 id="参考"><a class="markdownIt-Anchor" href="#参考"></a> 参考</h2><ul><li><a href="https://www.bilibili.com/video/BV15g411w7Up?p=1&share_medium=android&share_plat=android&share_session_id=ba802588-457d-4590-a1ab-a3297a38498b&share_source=WEIXIN&share_tag=s_i&timestamp=1639629464&unique_k=8h0hOXB">C++新标准002_动动小手指就能实现 " 完美转发 "</a></li></ul>]]></content>
<categories>
<category>技术</category>
</categories>
<tags>
<tag>C++</tag>
</tags>
</entry>
<entry>
<title>为什么需要将移动构造函数和移动赋值运算符标记为 noexcept</title>
<link href="/2021/12/18/%E4%B8%BA%E4%BB%80%E4%B9%88%E9%9C%80%E8%A6%81%E5%B0%86%E7%A7%BB%E5%8A%A8%E6%9E%84%E9%80%A0%E5%87%BD%E6%95%B0%E5%92%8C%E7%A7%BB%E5%8A%A8%E8%B5%8B%E5%80%BC%E8%BF%90%E7%AE%97%E7%AC%A6%E6%A0%87%E8%AE%B0%E4%B8%BA%20noexcept/"/>
<url>/2021/12/18/%E4%B8%BA%E4%BB%80%E4%B9%88%E9%9C%80%E8%A6%81%E5%B0%86%E7%A7%BB%E5%8A%A8%E6%9E%84%E9%80%A0%E5%87%BD%E6%95%B0%E5%92%8C%E7%A7%BB%E5%8A%A8%E8%B5%8B%E5%80%BC%E8%BF%90%E7%AE%97%E7%AC%A6%E6%A0%87%E8%AE%B0%E4%B8%BA%20noexcept/</url>
<content type="html"><![CDATA[<h2 id="前言"><a class="markdownIt-Anchor" href="#前言"></a> 前言</h2><p>在看 C++ Primer 时注意到这个问题,以下是书中关于这个问题的解释:容器类在扩容时会将旧空间的对象转移到新空间,如果容器不能确定该对象的移动构造函数不会抛出异常的话,就会”谨慎“地使用拷贝构造函数,这样即便过程中发生了异常也能保证不破坏原来容器中的对象(移动一个对象会改变其内容)。</p><h2 id="正文"><a class="markdownIt-Anchor" href="#正文"></a> 正文</h2><blockquote><p>由于移动操作“窃取”资源,它通常不分配任何资源。因此,移动操作通常不会抛出任何异常。当编写一个不抛出异常的移动操作时,我们应该将此事通知标准库。我们将看到,除非标准库知道我们的移动构造函数不会抛出异常,否则它会认为移动我们的类对象时可能会抛出异常,并且为了处理这种可能性而做一些额外的工作。<br /> 一种通知标准库的方法是在我们的构造函数中指明noexcept。noexcept是新标准引入的,我们将在18.1.4节(第690页)中讨论更多细节。目前重要的是要知道, noexcept是我们承诺一个函数不抛出异常的一种方法。我们在一个函数的参数列表后指定noexcept。在一个构造函数中,noexcept出现在参数列表和初始化列表开始的冒号之间。我们必须在类头文件的声明中和定义中(如果定义在类外的话)都指定noexcept。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs C++"><span class="hljs-keyword">class</span> <span class="hljs-title class_">StrVec</span>{<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-built_in">StrVec</span>(StrVec&& rhs) <span class="hljs-keyword">noexcept</span>;<br>};<br><br>StrVec::<span class="hljs-built_in">StrVec</span>(StrVec&& rhs) <span class="hljs-keyword">noexcept</span> {<br><br>}<br></code></pre></td></tr></table></figure></blockquote><p>搞清楚为什么需要noexcept能帮助我们深入理解标准库是如何与我们自定义的类型交互的。我们需要指出一个移动操作不抛出异常,这是因为两个相互关联的事实:首先,虽然移动操作通常不抛出异常,但抛出异常也是允许的;其次,标准库容器能对异常发生时其自身的行为提供保障。例如,vector保证,如果我们调用push_back时发生异常,vector自身不会发生改变。<br /> 现在让我们思考push_back内部发生了什么。类似对应的Strvec操作(参见13.5节,第466页),对一个vector调用push_back可能要求为vector重新分配内存空间。当重新分配vector 的内存时,vector将元素从旧空间移动到新内存中,就像我们在reallocate中所做的那样(参见13.5节,第469页)。<br /> 如我们刚刚看到的那样,移动一个对象通常会改变它的值。如果重新分配过程使用了移动构造函数,且在移动了部分而不是全部元素后抛出了一个异常,就会产生问题。旧空间中的移动源元素已经被改变了,而新空间中未构造的元素可能尚不存在。在此情况下,vector将不能满足自身保持不变的要求。<br /> 另一方面,如果vector使用了拷贝构造函数且发生了异常,它可以很容易地满足要求。在此情况下,当在新内存中构造元素时,旧元素保持不变。如果此时发生了异常,vector可以释放新分配的(但还未成功构造的)内存并返回。vector原有的元素仍然存在。<br /> 为了避免这种潜在问题,除非vector知道元素类型的移动构造函数不会抛出异常,否则在重新分配内存的过程中,它就必须使用拷贝构造函数而不是移动构造函数。如果希望在vector重新分配内存这类情况下对我们自定义类型的对象进行移动而不是拷贝,就必须显式地告诉标准库我们的移动构造函数可以安全使用。我们通过将移动构造函数(及移动赋值运算符)标记为noexcept来做到这一点。</p><h2 id="参考"><a class="markdownIt-Anchor" href="#参考"></a> 参考</h2><ul><li>C++ Primer 中文版</li></ul>]]></content>
<categories>
<category>技术</category>
</categories>
<tags>
<tag>C++</tag>
</tags>
</entry>
<entry>
<title>写一个死锁</title>
<link href="/2021/12/12/%E5%86%99%E4%B8%80%E4%B8%AA%E6%AD%BB%E9%94%81/"/>
<url>/2021/12/12/%E5%86%99%E4%B8%80%E4%B8%AA%E6%AD%BB%E9%94%81/</url>
<content type="html"><![CDATA[<h2 id="代码说明"><a class="markdownIt-Anchor" href="#代码说明"></a> 代码说明</h2><p><strong>这是游双的《Linux高性能服务器编程》第 14 章第 5 节中的代码示例,我用 C++11 改写了一下。</strong></p><blockquote><p>主线程试图先占有互斥锁 mutex_a,然后操作被该锁保护的变量 a,但操作完毕之后,主线程并没有立即释放互斥锁 mutex_a,而是又申请互斥 mutex_b,并在两个互斥锁的保护下,操作变量 a 和 b,最后才一起释放这两个互斥锁;与此同时,子线程则按照相反的顺序来申请互斥锁mutex_a, mutex_b,并在两个锁的保护下操作变量 a 和 b。我们用sleep函数来模拟连续两次调用 pthread_mutex_lock 之间的时间差,以确保代码的两个线程各自先占有一个互斥锁(主线程占有 mutex_a,子线程占有 mutex_b),然后等待另外一个互斥锁(主线程等待 mutex_b,子线程等待 mutex_a)。这样,两个线程就僵持住了,谁都不能继续往下执行,从而形成死锁。如果代码中不加入 sleep 函数,则这段代码或许总能成功地行,从而为程序留下了一个潜在的BUG。</p></blockquote><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><iostream></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><ostream></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><thread></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><mutex></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><unistd.h></span></span><br><br>std::mutex mtx_a;<br>std::mutex mtx_b;<br><br><span class="hljs-type">int</span> a = <span class="hljs-number">0</span>;<br><span class="hljs-type">int</span> b = <span class="hljs-number">0</span>;<br><br><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">thread_work</span><span class="hljs-params">()</span> </span>{<br><br> std::cout << <span class="hljs-string">"son want mutex b"</span> << std::endl;<br> mtx_b.<span class="hljs-built_in">lock</span>();<br> b++;<br> std::cout << <span class="hljs-string">"son got mutex b, now b is: "</span> << b << std::endl;<br> <br> std::cout << <span class="hljs-string">"son want mutex a"</span> << std::endl;<br> mtx_a.<span class="hljs-built_in">lock</span>();<br> a += b;<br> mtx_a.<span class="hljs-built_in">unlock</span>();<br> mtx_b.<span class="hljs-built_in">unlock</span>();<br>}<br><br><span class="hljs-function"><span class="hljs-type">int</span> <span class="hljs-title">main</span><span class="hljs-params">()</span> </span>{<br><br> std::cout << <span class="hljs-string">"father want mutex a"</span> << std::endl;<br> mtx_a.<span class="hljs-built_in">lock</span>();<br> a++;<br> std::cout << <span class="hljs-string">"father got mutex a, now a is: "</span> << a << std::endl;<br><br> <span class="hljs-function">std::thread <span class="hljs-title">son</span><span class="hljs-params">(thread_work)</span></span>;<br> <br> <span class="hljs-built_in">sleep</span>(<span class="hljs-number">1</span>);<br> std::cout << <span class="hljs-string">"father want mutex b"</span> << std::endl;<br> mtx_b.<span class="hljs-built_in">lock</span>();<br> b += a;<br> mtx_b.<span class="hljs-built_in">unlock</span>();<br> mtx_a.<span class="hljs-built_in">unlock</span>();<br><br> son.<span class="hljs-built_in">join</span>();<br><br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><h2 id="编译运行"><a class="markdownIt-Anchor" href="#编译运行"></a> 编译运行</h2><p>因为用到了线程库,编译时需要链接 pthread 库</p><p><code>g++ -o <输出的可执行程序文件名> <源文件名> -lpthread</code></p><h2 id="参考"><a class="markdownIt-Anchor" href="#参考"></a> 参考</h2><ul><li>《Linux高性能服务器编程》</li></ul>]]></content>
<categories>
<category>技术</category>
</categories>
<tags>
<tag>典型实现</tag>
</tags>
</entry>
<entry>
<title>父子进程通过 pipe() 管道通信时一方需关闭读端另一方需关闭写端</title>
<link href="/2021/12/11/%E7%88%B6%E5%AD%90%E8%BF%9B%E7%A8%8B%E9%80%9A%E8%BF%87%20pipe()%20%E7%AE%A1%E9%81%93%E9%80%9A%E4%BF%A1%E6%97%B6%E4%B8%80%E6%96%B9%E9%9C%80%E5%85%B3%E9%97%AD%E8%AF%BB%E7%AB%AF%E5%8F%A6%E4%B8%80%E6%96%B9%E9%9C%80%E5%85%B3%E9%97%AD%E5%86%99%E7%AB%AF/"/>
<url>/2021/12/11/%E7%88%B6%E5%AD%90%E8%BF%9B%E7%A8%8B%E9%80%9A%E8%BF%87%20pipe()%20%E7%AE%A1%E9%81%93%E9%80%9A%E4%BF%A1%E6%97%B6%E4%B8%80%E6%96%B9%E9%9C%80%E5%85%B3%E9%97%AD%E8%AF%BB%E7%AB%AF%E5%8F%A6%E4%B8%80%E6%96%B9%E9%9C%80%E5%85%B3%E9%97%AD%E5%86%99%E7%AB%AF/</url>
<content type="html"><![CDATA[<h2 id="前言"><a class="markdownIt-Anchor" href="#前言"></a> 前言</h2><p>前两天在看游双的《Linux高性能服务器编程》时注意到下面这段话:</p><blockquote><p>管道能在父、子进程间传递数据,利用的是fork调用之后两个管道文件描述符(fd[0]和fd[1])都保持打开。一对这样的文件描述符只能保证父、子进程间一个方向的数据传输,父进程和子进程必须有一个关闭fd[0],另一个关闭fd[1]。比如,我们要使用管道实现从父进程向子进程写数据,就应该按照图13-1所示来操作。</p></blockquote><p><img src="/img/blog_pic/2021/%E7%88%B6%E8%BF%9B%E7%A8%8B%E9%80%9A%E8%BF%87%E7%AE%A1%E9%81%93%E5%90%91%E5%AD%90%E8%BF%9B%E7%A8%8B%E5%86%99%E6%95%B0%E6%8D%AE.png" alt="父进程通过管道向子进程写数据" /></p><p>有一些思考:</p><ul><li><p><strong>Q1: 为什么一方需关闭读端另一方需关闭写端?不关闭会有什么问题?</strong></p></li><li><p><strong>Q2: 双方都让读写端同时打开,能否实现双向通信?</strong></p></li></ul><h2 id="文件在内核中的表示"><a class="markdownIt-Anchor" href="#文件在内核中的表示"></a> 文件在内核中的表示</h2><p>文件在内核中由一些数据结构表示,主要是以下三个表:</p><ul><li>描述符表(descriptor table)。每个进程都有它独立的描述符表,它的表项是由进程打开的文件描述符来索引的。每个打开的描述符表项指向文件表中的一个表项。</li><li>文件表(file table)。打开文件的集合是由一张文件表来表示的,所有的进程共享这张表。每个文件表的表项组成(针对我们的目的)包括当前的文件位置、引用计数(reference count)(即当前指向该表项的描述符表项数),以及一个指向v-node表中对应表项的指针。关闭一个描述符会减少相应的文件表表项中的引用计数。内核不会删除这个文件表表项,直到它的引用计数为零。</li><li>v-node表(v-node table)。同文件表一样,所有的进程共享这张v-node表。每个表项包含stat结构中的大多数信息,包括st_mode和 st_size成员。</li></ul><p><img src="/img/blog_pic/2021/%E6%89%93%E5%BC%80%E6%96%87%E4%BB%B6%E7%9A%84%E5%86%85%E6%A0%B8%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E8%A1%A8%E7%A4%BA.png" alt="打开文件的内核数据结构表示" /></p><h2 id="pipe"><a class="markdownIt-Anchor" href="#pipe"></a> pipe()</h2><p>pipe在内核中创建一个匿名管道(内核内存中维护的缓冲器),借助这个匿名管道读写数据实现两个进程通信。</p><p><strong>关于匿名管道与有名管道:</strong></p><blockquote><ul><li>匿名管道没有文件实体,没有名字,创建后只能让有血缘关系进程获得;有名管道(FIFO)有文件实体,但不存储数据。可以按照操作文件的方式对管道进行操作。</li><li>FIFO 在文件系统中作为一个特殊文件存在,但 FIFO 中的内容却存放在内存中。有名管道(FIFO)不同于匿名管道之处在于它提供了一个路径名与之关联,以 FIFO 的文件形式存在于文件系统中。FIFO 有名字,不相关的进程可以通过打开有名管道进行通信。</li></ul></blockquote><p>父进程通过 pipe() 开辟出匿名管道后调用 fork() 创建出子进程,由于子进程共享父进程打开的文件所以子进程也持有这条管道。</p><p><img src="/img/blog_pic/2021/%E5%AD%90%E8%BF%9B%E7%A8%8B%E5%A6%82%E4%BD%95%E7%BB%A7%E6%89%BF%E7%88%B6%E8%BF%9B%E7%A8%8B%E6%89%93%E5%BC%80%E7%9A%84%E6%96%87%E4%BB%B6.png" alt="子进程如何继承父进程打开的文件" /></p><p><img src="/img/blog_pic/2021/%E7%88%B6%E5%AD%90%E8%BF%9B%E7%A8%8B%E5%85%B1%E4%BA%AB%E5%8C%BF%E5%90%8D%E7%AE%A1%E9%81%93.png" alt="父子进程共享匿名管道" /></p><h2 id="不关闭会有什么问题"><a class="markdownIt-Anchor" href="#不关闭会有什么问题"></a> 不关闭会有什么问题?</h2><blockquote><p>管道的读写是是符合生产者–消费者模型。写入内容对应于生产内容;读取管道对应于消费内容。当所有的生产者都退场以后,消费者应当有方法判断这种情况,而不是傻傻等待已经不存在的生产者继续生产,以至于陷入永久的阻塞。</p></blockquote><blockquote><p>当对管道的读取端调用read函数(默认是阻塞的)返回0 时,就意味着所有的生产者都已经退场了,作为消费者的读取进程,就不需要继续等待新的内容了。什么情况下对管道读取端的描述符调用read会返回0呢?同时满足下面两个条件,对管道读取端描述符调用read返回值就是0</p><ol><li>所有的相关进程都已经关闭了管道的写入端描述符</li><li>管道中的已有内容已经被全部读取</li></ol></blockquote><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><cerrno></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><cstdlib></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><ostream></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><sys/types.h></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><sys/socket.h></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdlib.h></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdio.h></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><sys/wait.h></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><unistd.h></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><iostream></span></span><br><br><span class="hljs-keyword">using</span> std::cout;<br><span class="hljs-keyword">using</span> std::endl;<br><br><span class="hljs-function"><span class="hljs-type">int</span> <span class="hljs-title">main</span> <span class="hljs-params">()</span></span><br><span class="hljs-function"></span>{<br> <span class="hljs-type">int</span> fd[<span class="hljs-number">2</span>];<br> <span class="hljs-type">int</span> r = <span class="hljs-built_in">pipe</span>(fd);<br> <span class="hljs-keyword">if</span> (r == <span class="hljs-number">-1</span>){<br> <span class="hljs-built_in">perror</span>(<span class="hljs-string">"创建管道失败"</span>);<br> <span class="hljs-built_in">exit</span>(<span class="hljs-number">1</span>);<br> }<br><br> <span class="hljs-keyword">if</span> (fork()){ <span class="hljs-comment">// 父进程</span><br> <span class="hljs-type">int</span> val = <span class="hljs-number">0</span>;<br> <span class="hljs-built_in">close</span>(fd[<span class="hljs-number">0</span>]); <span class="hljs-comment">// 关闭读端</span><br> <span class="hljs-type">int</span> times = <span class="hljs-number">3</span>;<br> <span class="hljs-keyword">while</span> (times--){<br> <span class="hljs-built_in">sleep</span>(<span class="hljs-number">1</span>);<br> ++val;<br> <span class="hljs-built_in">write</span>(fd[<span class="hljs-number">1</span>], &val, <span class="hljs-built_in">sizeof</span>(val));<br> cout << <span class="hljs-string">"父进程发送数据: "</span> << val << endl;<br> }<br> <span class="hljs-built_in">close</span>(fd[<span class="hljs-number">1</span>]); <span class="hljs-comment">// 关闭写端</span><br> <span class="hljs-built_in">wait</span>(<span class="hljs-literal">nullptr</span>); <span class="hljs-comment">// 阻塞等待子进程退出</span><br> cout << <span class="hljs-string">"父进程结束"</span> << endl;<br> } <span class="hljs-keyword">else</span> { <span class="hljs-comment">// 子进程</span><br> <span class="hljs-type">int</span> val;<br> <span class="hljs-built_in">close</span>(fd[<span class="hljs-number">1</span>]); <span class="hljs-comment">// 关闭写端</span><br> <span class="hljs-keyword">while</span> (<span class="hljs-number">1</span>){<br> cout << <span class="hljs-string">"子进程阻塞在 read() 等待接收数据"</span> << endl;<br> <span class="hljs-type">int</span> result = <span class="hljs-built_in">read</span>(fd[<span class="hljs-number">0</span>], &val, <span class="hljs-built_in">sizeof</span>(val));<br> <span class="hljs-keyword">if</span> (result == <span class="hljs-number">0</span>) {<br> cout << <span class="hljs-string">"子进程发现无数据可读且没有进程持有写端的 fd,退出 read"</span> << endl;<br> <span class="hljs-keyword">break</span>;<br> }<br> <span class="hljs-keyword">else</span> cout << <span class="hljs-string">"子进程成功接收"</span> << result << <span class="hljs-string">"字节数据: "</span> << val << endl; <br> }<br> cout << <span class="hljs-string">"子进程退出循环"</span> << endl;<br> }<br>}<br></code></pre></td></tr></table></figure><p>注释掉第 39 行,程序运行结果:看到子进程一直阻塞在 read (),父进程的 wait() 由于子进程一直阻塞也不能完成。最后需要 ctrl + c 手动关闭</p><p><img src="/img/blog_pic/2021/read%E9%98%BB%E5%A1%9E.png" alt="" /></p><p>打开第 39 行,程序运行结果:子进程发现无数据可读且没有进程持有写端的 fd,退出 read(),父进程的 wait() 成功回收子进程后退出</p><p><img src="/img/blog_pic/2021/read%E8%BF%94%E5%9B%9E.png" alt="" /></p><h2 id="父子进程同时打开读写端能否实现双向通信"><a class="markdownIt-Anchor" href="#父子进程同时打开读写端能否实现双向通信"></a> 父子进程同时打开读写端,能否实现双向通信?</h2><p>不能,会有抢数据问题。进程被调度是无规律的,双方都读写的话无法保证一个写进程写完数据后立即调度读进程来接收,所以自己写的数据很可能会被自己读取。</p><h2 id="参考"><a class="markdownIt-Anchor" href="#参考"></a> 参考</h2><ul><li><p>《Linux高性能服务器编程》</p></li><li><p>《深入理解计算机系统》</p></li><li><p><a href="https://www.cnblogs.com/whiteHome/p/4863516.html">关于pipe管道的读写端关闭问题</a></p></li><li><p><a href="https://www.colourso.top/linux-pipefifo/">Linux 进程间通信——匿名管道和有名管道</a></p></li><li><p><a href="https://blog.csdn.net/m0_50668851/article/details/111565373">linux 管道关闭无用的写入端</a></p></li></ul>]]></content>
<categories>
<category>技术</category>
</categories>
<tags>
<tag>Linux</tag>
<tag>IPC</tag>
</tags>
</entry>
<entry>
<title>将一个 makefile 项目转化为 CMake 项目</title>
<link href="/2021/12/05/%E5%B0%86%E4%B8%80%E4%B8%AA%20makefile%20%E9%A1%B9%E7%9B%AE%E8%BD%AC%E5%8C%96%E4%B8%BA%20CMake%20%E9%A1%B9%E7%9B%AE/"/>
<url>/2021/12/05/%E5%B0%86%E4%B8%80%E4%B8%AA%20makefile%20%E9%A1%B9%E7%9B%AE%E8%BD%AC%E5%8C%96%E4%B8%BA%20CMake%20%E9%A1%B9%E7%9B%AE/</url>
<content type="html"><![CDATA[<h2 id="前言"><a class="markdownIt-Anchor" href="#前言"></a> 前言</h2><p>在 GitHub 看到一个 <a href="https://github.com/qinguoyi/TinyWebServer">Web 服务器项目</a>,想用来回顾一下网络编程方面的知识。发现这是一个由 makefile 构建的项目,而现在主流的构建方式是 CMake,所以想先把它转化为一个 CMake 项目。主要是根据项目结构以及 makefile 文件编写 CMakeLists.txt。</p><h2 id="准备工作"><a class="markdownIt-Anchor" href="#准备工作"></a> 准备工作</h2><ul><li>在 Linux 中安装 MySQL,推荐在 docker 中安装。(踩坑:一开始是直接在 Linux 中安装,后来想改成 docker 安装,在 docker 中安装后启动时报错端口被占用和一个别的错误,最后发现是一开始直接安装的 MySQL 和 docker 中的冲突的,原来的 mysqld 进程一直占用着 3306 端口, kill -9 也杀不掉,最后把直接安装的 MySQL 卸载干净了才能顺利启动 docker 中的 MySQL)</li><li>安装 mysql 开发库:sudo apt-get install libmysqlclient-dev (安装在 /usr/include/mysql 目录下)</li></ul><h2 id="项目结构"><a class="markdownIt-Anchor" href="#项目结构"></a> 项目结构</h2><p>这个项目有三个分支,我选择从 raw_version 分支入手。该分支的项目结构如下:</p><p><img src="/img/blog_pic/2021/makefile%E9%A1%B9%E7%9B%AE%E7%BB%93%E6%9E%84.png" alt="" /></p><p>有些模块的接口定义(.h)和实现(.cpp)分离了而有些模块没有,个人觉得这是一个不够规范的地方。makefile 文件的内容如下:</p><figure class="highlight makefile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><code class="hljs makefile"><span class="hljs-section">server: main.c \</span><br>./threadpool/threadpool.h \<br>./http/http_conn.cpp ./http/http_conn.h \<br>./lock/locker.h ./log/log.cpp ./log/log.h ./log/block_queue.h \<br>./CGImysql/sql_connection_pool.cpp ./CGImysql/sql_connection_pool.h<br><br>g++ -o server \<br>main.c \<br>./threadpool/threadpool.h \<br>./http/http_conn.cpp ./http/http_conn.h \<br>./lock/locker.h ./log/log.cpp ./log/log.h \<br>./CGImysql/sql_connection_pool.cpp ./CGImysql/sql_connection_pool.h \<br>-lpthread -lmysqlclient<br><br><br><span class="hljs-section">clean:</span><br>rm -r server<br></code></pre></td></tr></table></figure><p>总体来说是比较简单的。这里将一些头文件(.h)也一并加入,我尝试过去掉这些头文件后同样能正常编译运行。</p><h2 id="编写-cmakeliststxt"><a class="markdownIt-Anchor" href="#编写-cmakeliststxt"></a> 编写 CMakeLists.txt</h2><p>首先新建了 <code>include</code> 文件夹,在其中放置各个模块的头文件,定义和实现没有分离的也一并放入其中。此时项目结构如下:</p><p><img src="/img/blog_pic/2021/cmake%E9%A1%B9%E7%9B%AE%E7%BB%93%E6%9E%84.png" alt="" /></p><p>根据此时的项目结构,结合已有的 makefile 文件编写 CMakeLists.txt 如下:</p><figure class="highlight cmake"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><code class="hljs cmake"><span class="hljs-keyword">cmake_minimum_required</span>(VERSION <span class="hljs-number">3.0</span>) <br><br><span class="hljs-keyword">project</span>(WebServerCmake)<br><br><span class="hljs-keyword">set</span>(CMAKE_BUILD_TYPE Debug)<br><span class="hljs-keyword">set</span>(CMAKE_CXX_FLAGS <span class="hljs-string">"${CMAKE_CXX_FLAGS} -g"</span>)<br><br><span class="hljs-keyword">set</span>(path <span class="hljs-variable">${CMAKE_SOURCE_DIR}</span>)<br><br><span class="hljs-comment"># 引入头文件搜索目录</span><br><span class="hljs-keyword">include_directories</span>(<span class="hljs-variable">${path}</span>/<span class="hljs-keyword">include</span>)<br><br><span class="hljs-comment"># 生成可执行文件(比起原来的 makefile 少了一些 .h 头文件)</span><br><span class="hljs-keyword">add_executable</span>(WebServerCmake<br> main.cpp<br> CGImysql/sql_connection_pool.cpp<br> log/log.cpp<br> http/http_conn.cpp)<br><br><span class="hljs-comment"># 链接动态库,对应于原 makefile 中的 -lpthread -lmysqlclient</span><br><span class="hljs-keyword">target_link_libraries</span>(WebServerCmake libmysqlclient.so)<br><span class="hljs-keyword">target_link_libraries</span>(WebServerCmake libpthread.so)<br></code></pre></td></tr></table></figure><p>还需要修改一下修改后文件的 #include 路径;之后运行下面的命令后就可以构建并运行项目了:</p><figure class="highlight jboss-cli"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs jboss-cli">mkdir build && <span class="hljs-keyword">cd</span> build<br>cmake <span class="hljs-string">..</span><br>make<br><span class="hljs-string">./WebServerCmake</span> 1256<br></code></pre></td></tr></table></figure><h2 id="在-vscode-中运行并调试项目"><a class="markdownIt-Anchor" href="#在-vscode-中运行并调试项目"></a> 在 vscode 中运行并调试项目</h2><p>主要是配置 .vscode 文件夹中的 launch.json 和 tasks.json。在 vscode 中启动 c++ 项目调试便是依靠这两个文件的配置</p><p>tasks.json 中的命令会在 launch.json 中的 命令之前前执行,即对应<code>preLaunchTask</code>,可以在 tasks.json 中配置执行 cd build && cmake … && make 这些命令</p><p>tasks.json</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><code class="hljs json"><span class="hljs-punctuation">{</span> <br> <span class="hljs-attr">"version"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"2.0.0"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"options"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"cwd"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"${workspaceFolder}/build"</span> <span class="hljs-comment">// 进入 build 文件夹</span><br> <span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"tasks"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><br> <span class="hljs-punctuation">{</span><br> <span class="hljs-comment">// 运行 cmake .. 命令</span><br> <span class="hljs-attr">"type"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"shell"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"label"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"cmake"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"command"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"cmake"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"args"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><br> <span class="hljs-string">".."</span><br> <span class="hljs-punctuation">]</span><br> <span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span><br> <span class="hljs-punctuation">{</span><br> <span class="hljs-comment">// 运行 make 命令</span><br> <span class="hljs-attr">"label"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"make"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"group"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"kind"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"build"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"isDefault"</span><span class="hljs-punctuation">:</span> <span class="hljs-keyword">true</span><br> <span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"command"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"make"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"args"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><br><br> <span class="hljs-punctuation">]</span><br> <span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span><br> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"label"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"Build"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"dependsOrder"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"sequence"</span><span class="hljs-punctuation">,</span> <span class="hljs-comment">// 按列出的顺序执行任务依赖项</span><br> <span class="hljs-attr">"dependsOn"</span><span class="hljs-punctuation">:</span><span class="hljs-punctuation">[</span><br> <span class="hljs-string">"cmake"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-string">"make"</span><br> <span class="hljs-punctuation">]</span><br> <span class="hljs-punctuation">}</span><br> <span class="hljs-punctuation">]</span><br><span class="hljs-punctuation">}</span><br></code></pre></td></tr></table></figure><p>launch.json</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><code class="hljs jso">{<br> "version": "0.2.0",<br> "configurations": [<br> {<br> "name": "g++ - 生成和调试活动文件",<br> "type": "cppdbg",<br> "request": "launch",<br> "program": "${workspaceFolder}/build/WebServerCmake", // 可执行文件的位置<br> "args": [<br> "1256" // 命令行启动时的参数 ./WebServerCmake 1256<br> ],<br> "stopAtEntry": false,<br> "cwd": "${fileDirname}",<br> "environment": [],<br> "externalConsole": false,<br> "MIMode": "gdb",<br> "setupCommands": [<br> {<br> "description": "为 gdb 启用整齐打印",<br> "text": "-enable-pretty-printing",<br> "ignoreFailures": true<br> }<br> ],<br> "preLaunchTask": "Build", // 这里要和 task.json 中 的 Build 一致<br> "miDebuggerPath": "/usr/bin/gdb"<br> }<br> ]<br>}<br></code></pre></td></tr></table></figure><h2 id="参考"><a class="markdownIt-Anchor" href="#参考"></a> 参考</h2><ul><li><p><a href="https://www.runoob.com/docker/docker-install-mysql.html">Docker 安装 MySQL</a></p></li><li><p><a href="https://blog.csdn.net/qq_40861091/article/details/89345860">Linux下安装完MySQL之后,include文件夹下没有包含mysql.h头文件的解决办法</a></p></li><li><p><a href="https://blog.csdn.net/zhizhengguan/article/details/107068970">C/C++编程:CMakeList链接mysql出现/usr/bin/ld: 找不到 -lmysqlclient</a></p></li><li><p><a href="https://www.bilibili.com/video/BV13K411M78v?from=search&seid=16549448524734942123&spm_id_from=333.337.0.0">手把手教会VSCode的C++环境搭建,多文件编译,Cmake,json调试配置( Windows篇)</a></p></li></ul>]]></content>
<categories>
<category>技术</category>
</categories>
<tags>
<tag>CMake</tag>
</tags>
</entry>
<entry>
<title>nebula-jdbc 文档</title>
<link href="/2021/11/19/nebula-jdbc%20%E6%96%87%E6%A1%A3/"/>
<url>/2021/11/19/nebula-jdbc%20%E6%96%87%E6%A1%A3/</url>
<content type="html"><![CDATA[<h1 id="前言"><a class="markdownIt-Anchor" href="#前言"></a> 前言</h1><p>这是我在开源之夏 Summer 2021 中负责的<a href="https://summer.iscas.ac.cn/#/org/prodetail/210360225?lang=en">项目</a>的文档,目前这个项目已经通过导师和主办方审核顺利结项啦哈哈哈哈哈。中期报告在我前面的博客中,代码已经合并到 <a href="https://www.vesoft.com/cn/">vesoft</a> 的 Github 仓库中,源码及文档见<a href="https://github.com/vesoft-inc/nebula-jdbc">此处</a>。</p><h1 id="nebula-jdbc-文档"><a class="markdownIt-Anchor" href="#nebula-jdbc-文档"></a> nebula-jdbc 文档</h1><h2 id="introduction"><a class="markdownIt-Anchor" href="#introduction"></a> Introduction</h2><p>nebula-jdbc 是基于 <a href="https://github.com/vesoft-inc/nebula-java">nebula-java</a> 封装的,在其基础上对接了 JDBC 协议,实现 JDBC 的相关接口。比起 <a href="https://github.com/vesoft-inc/nebula-java">nebula-java</a> 你可能更加熟悉 JDBC 的 API,使用 nebula-jdbc 你可以不必熟悉 <a href="https://github.com/vesoft-inc/nebula-java">nebula-java</a> 的 API(熟悉的话会更好,这样你会理解为什么我们连接字符串的格式是为什么与传统的 JDBC 连接字符串不同),像在 java 程序中操作关系型数据库一样操作 Nebula 服务</p><h2 id="architecture"><a class="markdownIt-Anchor" href="#architecture"></a> Architecture</h2><p>nebula-jdbc 主要的一些类和接口的关系如下:(蓝色实线是类之间的 extends 关系,绿色实线是接口之间的 implements 关系,绿色虚线是抽象类与接口之间的 implements 关系)</p><p><img src="/img/blog_pic/2021/nebula-jdbc%E9%A1%B9%E7%9B%AE%E6%9E%B6%E6%9E%84.png" alt="nebula-jdbc项目架构" /></p><p>用户首先通过 <code>NebulaDriver</code> 注册驱动,其中有 <code>NebulaPool</code> 属性,用于获取 <code>Session</code> 与数据库通信。<code>NebulaDriver</code> 中提供三个构造函数,无参构造函数按照默认参数配置 <code>NebulaPool</code>,接收一个 <code>Properties</code> 类型参数的构造函数可以自定义 <code>NebulaPool</code> 配置,接收一个 <code>String</code> 类型参数的构造函数可以只指定连接地址,其余参数按照默认配置。</p><p>注册驱动后用户可以通过 <code>DriverManager::getConnection(String url)</code> 获取 <code>Connection</code>。在 <code>NebulaConnection</code> 的构造函数中会通过 <code>NebulaDriver</code> 中的 <code>NebulaPool</code> 获取 <code>Session</code> 接着连接到在 url 中指定的图空间(graphSpace)。</p><p>获取到 <code>Connection</code> 后用户可以通过 <code>Connection::createStatement</code> 和 <code>Connection::prepareStatement</code> 拿到 <code>Statement</code> 或者 <code>PreparedStatement</code> 对象,调用其中的 <code>executeQuery、executeUpdate、execute</code> 方法向数据库发送命令,数据库执行此命令后的结果会封装在 <code>NebulaResult</code> 中,再调用其中各种获取数据的方法可以得到不同数据类型的数据。</p><h2 id="usage"><a class="markdownIt-Anchor" href="#usage"></a> Usage</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 获取并注册默认的 NebulaDriver,默认的连接地址是 127.0.0.1:9669,其余的默认参数可以查看 NebulaDriver::setDefaultPoolProperties()</span><br><span class="hljs-type">NebulaDriver</span> <span class="hljs-variable">defaultDriver</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">NebulaDriver</span>();<br><br><span class="hljs-comment">// 如果只想设置连接地,其余参数按照默认配置,则可以使用 NebulaDriver (String address)</span><br><span class="hljs-type">NebulaDriver</span> <span class="hljs-variable">customizedUrlDriver</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">NebulaDriver</span>(<span class="hljs-string">"192.168.66.116:9669"</span>);<br><br><span class="hljs-comment">// 如果要连接特定的服务地址以及自定义连接配置,可以使用自定义 NebulaDriver:将配置参数封装在一个 Properties 对象中,然后调用 NebulaDriver::NebulaDriver(Properties poolProperties)</span><br><span class="hljs-type">Properties</span> <span class="hljs-variable">poolProperties</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Properties</span>();<br>ArrayList<HostAddress> addressList = <span class="hljs-keyword">new</span> <span class="hljs-title class_">ArrayList</span><>();<br>addressList.add(<span class="hljs-keyword">new</span> <span class="hljs-title class_">HostAddress</span>(<span class="hljs-string">"192.168.66.226"</span>, <span class="hljs-number">9669</span>));<br>addressList.add(<span class="hljs-keyword">new</span> <span class="hljs-title class_">HostAddress</span>(<span class="hljs-string">"192.168.66.222"</span>, <span class="hljs-number">9670</span>));<br><br>poolProperties.put(<span class="hljs-string">"addressList"</span>, addressList);<br>poolProperties.put(<span class="hljs-string">"minConnsSize"</span>, <span class="hljs-number">2</span>);<br>poolProperties.put(<span class="hljs-string">"maxConnsSize"</span>, <span class="hljs-number">12</span>);<br>poolProperties.put(<span class="hljs-string">"timeout"</span>, <span class="hljs-number">1015</span>);<br>poolProperties.put(<span class="hljs-string">"idleTime"</span>, <span class="hljs-number">727</span>);<br>poolProperties.put(<span class="hljs-string">"intervalIdle"</span>, <span class="hljs-number">1256</span>);<br>poolProperties.put(<span class="hljs-string">"waitTime"</span>, <span class="hljs-number">1256</span>);<br><br><span class="hljs-type">NebulaDriver</span> <span class="hljs-variable">customizedDriver</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">NebulaDriver</span>(poolProperties);<br><br><span class="hljs-comment">// 获取 Connection</span><br><span class="hljs-type">Connection</span> <span class="hljs-variable">connection</span> <span class="hljs-operator">=</span> DriverManager.getConnection(<span class="hljs-string">"jdbc:nebula://JDBC_TEST_SPACE"</span>, <span class="hljs-string">"root"</span>, <span class="hljs-string">"nebula123"</span>);<br><br><span class="hljs-comment">// 获取 Statement 并执行</span><br><span class="hljs-type">Statement</span> <span class="hljs-variable">statement</span> <span class="hljs-operator">=</span> connection.createStatement();<br><br><span class="hljs-type">String</span> <span class="hljs-variable">queryStatementNgql</span> <span class="hljs-operator">=</span> <span class="hljs-string">"match (v:testNode) return v.theString as theString, v.theInt as theInt"</span>;<br><span class="hljs-type">ResultSet</span> <span class="hljs-variable">queryStatementResult</span> <span class="hljs-operator">=</span> statement.executeQuery(queryStatementNgql);<br><br><span class="hljs-comment">// 获取结果</span><br><span class="hljs-keyword">while</span> (queryStatementResult.next()){<br><span class="hljs-type">String</span> <span class="hljs-variable">theString</span> <span class="hljs-operator">=</span> queryStatementResult.getString(<span class="hljs-string">"theString"</span>);<br><span class="hljs-type">int</span> <span class="hljs-variable">theInt</span> <span class="hljs-operator">=</span> queryStatementResult.getInt(<span class="hljs-number">2</span>);<br>}<br><br><span class="hljs-type">String</span> <span class="hljs-variable">insertTestNode</span> <span class="hljs-operator">=</span> <span class="hljs-string">"INSERT VERTEX testNode (theString, theInt, theDouble, theTrueBool, theFalseBool, theDate, theTime, theDatetime) VALUES "</span> +<br> <span class="hljs-string">"\"testNode_7\":(\"Young\", 20, , 12.56, true, false, date(\"1949-10-01\"), time(\"15:00:00.000\"), datetime(\"1949-10-01T15:00:00.000\")); "</span>;<br>statement.executeUpdate(insertTestNode);<br><br><span class="hljs-comment">// 获取 PreparedStatement,设置参数并执行</span><br><span class="hljs-type">String</span> <span class="hljs-variable">insertPreparedStatementNgql</span> <span class="hljs-operator">=</span> <span class="hljs-string">"INSERT VERTEX testNode (theString, theInt, theDouble, theTrueBool, theFalseBool, theDate, theTime, theDatetime) VALUES "</span> +<br> <span class="hljs-string">"\"testNode_8\":(?, ?, ?, ?, ?, ?, ?, ?); "</span>;<br><span class="hljs-type">PreparedStatement</span> <span class="hljs-variable">insertPreparedStatement</span> <span class="hljs-operator">=</span> connection.prepareStatement(insertPreparedStatementNgql);<br><br>insertPreparedStatement.setString(<span class="hljs-number">1</span>, <span class="hljs-string">"YYDS"</span>);<br>insertPreparedStatement.setInt(<span class="hljs-number">2</span>, <span class="hljs-number">98</span>);<br>insertPreparedStatement.setDouble(<span class="hljs-number">3</span>, <span class="hljs-number">12.56</span>);<br>insertPreparedStatement.setBoolean(<span class="hljs-number">4</span>, <span class="hljs-literal">true</span>);<br>insertPreparedStatement.setBoolean(<span class="hljs-number">5</span>, <span class="hljs-literal">false</span>);<br>insertPreparedStatement.setDate(<span class="hljs-number">6</span>, Date.valueOf(<span class="hljs-string">"1949-10-01"</span>));<br>insertPreparedStatement.setTime(<span class="hljs-number">7</span>, Time.valueOf(<span class="hljs-string">"15:00:00"</span>));<br><span class="hljs-comment">// 类型转换后再调用 setDatetime</span><br><span class="hljs-type">NebulaPreparedStatement</span> <span class="hljs-variable">nebulaPreparedStatement</span> <span class="hljs-operator">=</span> (NebulaPreparedStatement) insertPreparedStatement;<br>nebulaPreparedStatement.setDatetime(<span class="hljs-number">8</span>, <span class="hljs-keyword">new</span> <span class="hljs-title class_">java</span>.util.Date());<br><br>insertPreparedStatement.execute();<br><br><span class="hljs-comment">// 关闭连接</span><br>connection.close();<br></code></pre></td></tr></table></figure><h2 id="q-a"><a class="markdownIt-Anchor" href="#q-a"></a> Q & A</h2><ul><li>连接字符串"jdbc:nebula://graphSpace"中不用指定连接地址吗?</li></ul><p>由于地址列表已经在 <code>NebulaDriver</code> 中配置(默认或自定义),所以连接字符串不需要指定地址,只需要指定图空间。</p><ul><li><code>PreparedStatement</code> 是否有预编译功能?</li></ul><p>服务端暂不支持。</p><ul><li><code>executeQuery</code>、<code>executeUpdate</code>、<code>execute</code> 的使用场景?</li></ul><p><code>executeQuery</code> 专门用于查询 Nebula 中的数据,此时 nGql 需包含查询关键字 [“match”, “lookup”, “go”, “fetch”, “find”, “subgraph”],返回查询结果 <code>ResultSet</code>;<code>executeUpdate</code> 用于修改数据,nGql 需包含修改关键字 [“update”, “delete”, “insert”, “upsert”, “create”, “drop”, “alter”, “rebuild”],返回查询结果 <code>0</code>;<code>execute</code> 用于其他 admin 操作,执行成功则返回查询 <code>true</code>。</p><ul><li><code>executeUpdate</code> 的返回结果是0,为什么不是受到该语句影响的数据量?</li></ul><p>目前服务端没有 updateCount 统计返回给用户。假如用户一条插入语句里面同时插入多个点或者多条边,这里面可能有部分成功,但服务端只会返回告诉用户失败了,但是其实用户可能能查到部分数据。统一返回0给用户。</p><ul><li>查询语句中返回点、边、路径后在 Result 中应该如何获得?</li></ul><p>将 <code>ResultSet</code> 转为 <code>NebulaResultSet</code>,然后调用 <code>getNode</code>、<code>getEdge</code>、<code>getPath</code>;对于列表、集合、映射也是如此。</p>]]></content>
<categories>
<category>技术</category>
</categories>
<tags>
<tag>开源</tag>
<tag>开源之夏</tag>
</tags>
</entry>
<entry>
<title>实现一个简单的 shared_ptr</title>
<link href="/2021/10/26/%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84%20shared_ptr/"/>
<url>/2021/10/26/%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84%20shared_ptr/</url>
<content type="html"><![CDATA[<h2 id="raii"><a class="markdownIt-Anchor" href="#raii"></a> RAII</h2><p>Resource Acquisition Is Initialization,资源获取即初始化。以类对象的形式管理资源,结合类对象的生命周期控制资源的获取和释放,构造时获取资源析构时释放资源。C++ 保证栈对象离开作用域时总会被正确析构,所以在构造时获取的资源总能被正确释放。</p><h2 id="代码"><a class="markdownIt-Anchor" href="#代码"></a> 代码</h2><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-keyword">template</span> <<span class="hljs-keyword">typename</span> T><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">MySharedPtr</span> {<br><br><span class="hljs-keyword">public</span>:<br><br> <span class="hljs-comment">// 注意这里的参数不能为 const T*,因为 rawPtr 是非 const,不能接收一个 const 的赋值</span><br> <span class="hljs-built_in">MySharedPtr</span>(T* _rawPtr = <span class="hljs-literal">nullptr</span>) : <span class="hljs-built_in">rawPtr</span>(_rawPtr), <span class="hljs-built_in">counter</span>(<span class="hljs-keyword">new</span> <span class="hljs-built_in">int</span>(<span class="hljs-number">1</span>)) {}<br><br> <span class="hljs-built_in">MySharedPtr</span>(<span class="hljs-type">const</span> MySharedPtr<T>& source) : <span class="hljs-built_in">rawPtr</span>(source.rawPtr), <span class="hljs-built_in">counter</span>(source.counter) {<br> <span class="hljs-built_in">increase</span>();<br> }<br><br> ~<span class="hljs-built_in">MySharedPtr</span>() {<br> <span class="hljs-built_in">decrease</span>();<br> }<br><br> MySharedPtr& <span class="hljs-keyword">operator</span>=(<span class="hljs-type">const</span> MySharedPtr<T>& source) {<br> <span class="hljs-keyword">if</span>(<span class="hljs-keyword">this</span> == &source) <span class="hljs-keyword">return</span> *<span class="hljs-keyword">this</span>;<br> <span class="hljs-built_in">decrease</span>(); <span class="hljs-comment">// 自己现在要更改指向了, 释放管理的旧资源</span><br> rawPtr = source.rawPtr;<br> counter = source.counter;<br> <span class="hljs-built_in">increase</span>();<br> <span class="hljs-keyword">return</span> *<span class="hljs-keyword">this</span>;<br> }<br><br> T& <span class="hljs-keyword">operator</span>*() {<br> <span class="hljs-keyword">return</span> *rawPtr;<br> }<br><br> T* <span class="hljs-keyword">operator</span>->() {<br> <span class="hljs-keyword">return</span> rawPtr;<br> }<br><br> <span class="hljs-function"><span class="hljs-type">int</span> <span class="hljs-title">getCount</span><span class="hljs-params">()</span> </span>{<br> <span class="hljs-keyword">return</span> *counter;<br> }<br><br><span class="hljs-keyword">private</span>:<br><br> T* rawPtr;<br> <span class="hljs-type">int</span>* counter; <span class="hljs-comment">// 所有指向 rawPtr 的智能指针共享一份 counter</span><br><br> <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">increase</span><span class="hljs-params">()</span> </span>{<br> <span class="hljs-keyword">if</span>(counter) ++(*counter);<br> }<br><br> <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">decrease</span><span class="hljs-params">()</span> </span>{<br> <span class="hljs-keyword">if</span>(--(*counter) == <span class="hljs-number">0</span>) {<br> <span class="hljs-keyword">delete</span> rawPtr;<br> <span class="hljs-keyword">delete</span> counter;<br> }<br> }<br><br>};<br></code></pre></td></tr></table></figure><h2 id="待完善-增加自定义释放"><a class="markdownIt-Anchor" href="#待完善-增加自定义释放"></a> <s>待完善</s> 增加自定义释放</h2><p>C++ 中的 shared_ptr 支持在初始化时指定析构方式(默认是释放指针),shared_ptr 也可以用来管理其他资源比如一个数据库连接,析构函数中应该释放此连接,此时就应该自定义该 shared_ptr 的析构方式</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-keyword">template</span> <<span class="hljs-keyword">typename</span> T><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">MySharedPtr</span> {<br><br><span class="hljs-keyword">public</span>:<br><br> <span class="hljs-comment">// 释放器的默认值是 delete 指针</span><br> <span class="hljs-built_in">MySharedPtr</span>(T* _rawPtr = <span class="hljs-literal">nullptr</span>, <span class="hljs-built_in">void</span> (*_release)(T* pointer) = [](T* pointer){<span class="hljs-keyword">delete</span> pointer;}) : <span class="hljs-built_in">rawPtr</span>(_rawPtr), <span class="hljs-built_in">counter</span>(<span class="hljs-keyword">new</span> <span class="hljs-built_in">int</span>(<span class="hljs-number">1</span>)) {<br> release = _release;<br> }<br><br> <span class="hljs-built_in">MySharedPtr</span>(<span class="hljs-type">const</span> MySharedPtr<T>& source) : <span class="hljs-built_in">rawPtr</span>(source.rawPtr), <span class="hljs-built_in">counter</span>(source.counter) {<br> <span class="hljs-built_in">increase</span>();<br> }<br><br> ~<span class="hljs-built_in">MySharedPtr</span>() {<br> <span class="hljs-built_in">decrease</span>();<br> }<br><br> MySharedPtr& <span class="hljs-keyword">operator</span>=(<span class="hljs-type">const</span> MySharedPtr<T>& source) {<br> <span class="hljs-keyword">if</span>(<span class="hljs-keyword">this</span> == &source) <span class="hljs-keyword">return</span> *<span class="hljs-keyword">this</span>;<br> <span class="hljs-built_in">decrease</span>(); <span class="hljs-comment">// 自己现在要更改指向了, 释放管理的旧资源</span><br> rawPtr = source.rawPtr;<br> counter = source.counter;<br> <span class="hljs-built_in">increase</span>();<br> <span class="hljs-keyword">return</span> *<span class="hljs-keyword">this</span>;<br> }<br><br> T& <span class="hljs-keyword">operator</span>*() {<br> <span class="hljs-keyword">return</span> *rawPtr;<br> }<br><br> T* <span class="hljs-keyword">operator</span>->() {<br> <span class="hljs-keyword">return</span> rawPtr;<br> }<br><br> <span class="hljs-function"><span class="hljs-type">int</span> <span class="hljs-title">getCount</span><span class="hljs-params">()</span> </span>{<br> <span class="hljs-keyword">return</span> *counter;<br> }<br><br><span class="hljs-keyword">private</span>:<br><br> T* rawPtr;<br> <span class="hljs-type">int</span>* counter; <span class="hljs-comment">// 所有指向 rawPtr 的智能指针共享一份 counter</span><br> <span class="hljs-built_in">void</span> (*release)(T* pointer); <span class="hljs-comment">// 保存构造时传入的释放方式</span><br><br> <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">increase</span><span class="hljs-params">()</span> </span>{<br> <span class="hljs-keyword">if</span>(counter) ++(*counter);<br> }<br><br> <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">decrease</span><span class="hljs-params">()</span> </span>{<br> <span class="hljs-keyword">if</span>(--(*counter) == <span class="hljs-number">0</span>) {<br> <span class="hljs-built_in">release</span>(rawPtr); <span class="hljs-comment">// 调用用户自定义的释放器</span><br> <span class="hljs-keyword">delete</span> counter;<br> }<br> }<br><br>};<br></code></pre></td></tr></table></figure>]]></content>
<categories>
<category>技术</category>
</categories>
<tags>
<tag>C++</tag>
<tag>典型实现</tag>
<tag>RAII</tag>
</tags>
</entry>
<entry>
<title>C++类中的特殊函数 </title>
<link href="/2021/10/11/C++%E7%B1%BB%E4%B8%AD%E7%9A%84%E7%89%B9%E6%AE%8A%E5%87%BD%E6%95%B0/"/>
<url>/2021/10/11/C++%E7%B1%BB%E4%B8%AD%E7%9A%84%E7%89%B9%E6%AE%8A%E5%87%BD%E6%95%B0/</url>
<content type="html"><![CDATA[<h2 id="代码"><a class="markdownIt-Anchor" href="#代码"></a> 代码</h2><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">class</span> <span class="hljs-title class_">String</span>{<br><span class="hljs-keyword">private</span>:<br> <span class="hljs-type">char</span>* str;<br> <span class="hljs-type">int</span> len;<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-comment">// 默认构造函数</span><br> <span class="hljs-built_in">String</span> () {<br> len = <span class="hljs-number">0</span>;<br> str = <span class="hljs-literal">nullptr</span>;<br> }<br><br> <span class="hljs-comment">// 构造函数</span><br> <span class="hljs-built_in">String</span> (<span class="hljs-type">const</span> <span class="hljs-type">char</span>* s) {<br> len = std::<span class="hljs-built_in">strlen</span>(s);<br> str = <span class="hljs-keyword">new</span> <span class="hljs-type">char</span>[len + <span class="hljs-number">1</span>];<br> std::<span class="hljs-built_in">strcpy</span>(str, s);<br> }<br><br> <span class="hljs-comment">// 拷贝构造函数</span><br> <span class="hljs-built_in">String</span> (<span class="hljs-type">const</span> String& st) {<br> <span class="hljs-comment">// 拷贝构造函数的参数必须是引用类型而不能是类类型,类类型属于传值,而传值的方式会调用该类的拷贝构造函数,从而造成无穷递归地调用拷贝构造函数</span><br> <span class="hljs-comment">// const,防止对实参的意外修改;能够接受 const 和 非 const 类型;使函数能够正确生成并使用临时对象</span><br> len = st.len;<br> str = <span class="hljs-keyword">new</span> <span class="hljs-type">char</span>[len + <span class="hljs-number">1</span>];<br> std::<span class="hljs-built_in">strcpy</span>(str, st.str);<br> }<br> <span class="hljs-comment">// 移动构造函数</span><br> <span class="hljs-built_in">String</span> (String&& st) <span class="hljs-keyword">noexcept</span> {<br> len = st.len;<br> str = st.str;<br> <span class="hljs-comment">// 上面的语句拿了 st 的资源,所以要下面要置空,否则会有多个指针指向同一块内存的危险</span><br> st.str = <span class="hljs-literal">nullptr</span>; st.len = <span class="hljs-number">0</span>;<br> }<br><br> <span class="hljs-comment">// 赋值运算符</span><br> String& <span class="hljs-keyword">operator</span>= (<span class="hljs-type">const</span> String& st) {<br> <span class="hljs-keyword">if</span> (<span class="hljs-keyword">this</span> == &st) <span class="hljs-keyword">return</span> *<span class="hljs-keyword">this</span>;<br> <span class="hljs-keyword">delete</span>[] str; <span class="hljs-comment">// 注意这里是赋值,原来的对象本身有内容,所以要释放掉</span><br> len = st.len;<br> str = <span class="hljs-keyword">new</span> <span class="hljs-type">char</span>[len + <span class="hljs-number">1</span>];<br> std::<span class="hljs-built_in">strcpy</span>(str, st.str);<br> <span class="hljs-keyword">return</span> *<span class="hljs-keyword">this</span>;<br> }<br> <span class="hljs-comment">// 移动赋值运算符</span><br> String& <span class="hljs-keyword">operator</span>= (String&& st) <span class="hljs-keyword">noexcept</span> {<br> <span class="hljs-keyword">if</span> (<span class="hljs-keyword">this</span> == &st) <span class="hljs-keyword">return</span> *<span class="hljs-keyword">this</span>;<br> <span class="hljs-keyword">delete</span>[] str; <span class="hljs-comment">// 注意这里是赋值,原来的对象本身有内容,所以要释放掉</span><br> len = st.len;<br> str = st.str;<br> <span class="hljs-comment">// 上面的语句拿了 st 的资源,所以要下面要置空,否则会有多个指针指向同一块内存的危险</span><br> st.str = <span class="hljs-literal">nullptr</span>; st.len = <span class="hljs-number">0</span>;<br> <span class="hljs-keyword">return</span> *<span class="hljs-keyword">this</span>;<br> }<br><br> <span class="hljs-comment">// 读写非 const 的对象</span><br> <span class="hljs-type">char</span>& <span class="hljs-keyword">operator</span>[] (<span class="hljs-type">int</span> i) {<br> <span class="hljs-keyword">return</span> str[i];<br> }<br> <span class="hljs-comment">// 读 const 的类对象</span><br> <span class="hljs-type">const</span> <span class="hljs-type">char</span>& <span class="hljs-keyword">operator</span>[] (<span class="hljs-type">int</span> i) <span class="hljs-type">const</span>{<br> <span class="hljs-keyword">return</span> str[i];<br> }<br><br> <span class="hljs-comment">// 重载 << 运算符;写成友元是为了可以这样使用(cout << object)否则只能(object << cout,因为二元运算符重载为成员函数的话只能设置一个参数作为右侧运算量,而左侧运算量就是对象本身)</span><br> <span class="hljs-keyword">friend</span> ostream& <span class="hljs-keyword">operator</span><< (ostream& os, <span class="hljs-type">const</span> String& st) {<br> os << st.str;<br> <span class="hljs-keyword">return</span> os; <span class="hljs-comment">// 返回 ostream& 是为了链式调用</span><br> }<br><br> ~<span class="hljs-built_in">String</span>() {<br> <span class="hljs-keyword">delete</span>[] str; len = <span class="hljs-number">0</span>;<br> }<br>};<br></code></pre></td></tr></table></figure><h2 id="参考"><a class="markdownIt-Anchor" href="#参考"></a> 参考</h2><ul><li>《C++ Primer Plus 中文版》 chapter 12</li></ul>]]></content>
<categories>
<category>技术</category>
</categories>
<tags>
<tag>C++</tag>
</tags>
</entry>
<entry>
<title>实现 LRU 缓存</title>
<link href="/2021/10/04/%E5%AE%9E%E7%8E%B0%20LRU%20%E7%BC%93%E5%AD%98/"/>
<url>/2021/10/04/%E5%AE%9E%E7%8E%B0%20LRU%20%E7%BC%93%E5%AD%98/</url>
<content type="html"><![CDATA[<h2 id="描述"><a class="markdownIt-Anchor" href="#描述"></a> 描述</h2><p><a href="https://leetcode-cn.com/problems/lru-cache/"><strong>LRU 缓存机制</strong></a></p><ul><li>LRUCache(int capacity) 以正整数作为容量 capacity 初始化 LRU 缓存</li><li>int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。</li><li>void put(int key, int value) 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字-值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。</li></ul><p><code>输入</code><br />[“LRUCache”, “put”, “put”, “get”, “put”, “get”, “put”, “get”, “get”, “get”]<br />[[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]<br /><code>输出</code><br />[null, null, null, 1, null, -1, null, -1, 3, 4]</p><h2 id="细节"><a class="markdownIt-Anchor" href="#细节"></a> 细节</h2><ul><li>排出节点时没有把 tail 的 pre 置为排出点的 pre</li><li>设计的 Node 节点只有 val 没有保存 key</li><li>删除 map 中的键值对时删错</li><li>unordered_map 原地构造插入的方法为 .emplace(key, value);</li><li>head, tail 成员声明时没有初始化</li></ul><h2 id="实现"><a class="markdownIt-Anchor" href="#实现"></a> 实现</h2><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-keyword">class</span> <span class="hljs-title class_">LRUCache</span> {<br><span class="hljs-keyword">private</span>:<br> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Node</span>{<br> <span class="hljs-keyword">public</span>: <span class="hljs-comment">// 注意是 public,或者改成 struct</span><br> <span class="hljs-type">int</span> key; <span class="hljs-type">int</span> value;<br> Node* pre; Node* next;<br> <span class="hljs-built_in">Node</span>(<span class="hljs-type">int</span> _key = <span class="hljs-number">0</span>, <span class="hljs-type">int</span> _value = <span class="hljs-number">0</span>, Node* _pre = <span class="hljs-literal">nullptr</span>, Node* _next = <span class="hljs-literal">nullptr</span>) : <span class="hljs-built_in">value</span>(_value), <span class="hljs-built_in">key</span>(_key), <span class="hljs-built_in">pre</span>(_pre), <span class="hljs-built_in">next</span>(_next) {}<br> };<br> unordered_map<<span class="hljs-type">int</span>, Node*> map; <span class="hljs-comment">// 快速查找</span><br> <span class="hljs-type">int</span> capacity; <span class="hljs-comment">// 缓存的最大容量</span><br> <span class="hljs-type">int</span> size; <span class="hljs-comment">// 当前缓存大小</span><br> <span class="hljs-comment">// 双链表的头尾,统一操作(记得初始化)</span><br> Node* head = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Node</span>();<br> Node* tail = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Node</span>();<br> <span class="hljs-comment">// 下面两个指针用于保存要调整到最前面的节点的前后节点</span><br> Node* pre;<br> Node* next;<br> unordered_map<<span class="hljs-type">int</span>, Node*>::iterator iter; <span class="hljs-comment">// 保存每次根据 key 找到的 value 的迭代器</span><br><br><span class="hljs-keyword">public</span>:<br> <span class="hljs-built_in">LRUCache</span>(<span class="hljs-type">int</span> _capacity) {<br> capacity = _capacity; size = <span class="hljs-number">0</span>;<br> head->next = tail; tail->pre = head;<br> }<br><br> <span class="hljs-function"><span class="hljs-type">int</span> <span class="hljs-title">get</span><span class="hljs-params">(<span class="hljs-type">int</span> key)</span> </span>{<br> iter = map.<span class="hljs-built_in">find</span>(key);<br> <span class="hljs-keyword">if</span>(iter != map.<span class="hljs-built_in">end</span>()){<br> <span class="hljs-built_in">adjust</span>(iter->second);<br> <span class="hljs-keyword">return</span> iter->second->value;<br> }<span class="hljs-keyword">else</span> <span class="hljs-keyword">return</span> <span class="hljs-number">-1</span>;<br> }<br><br> <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">put</span><span class="hljs-params">(<span class="hljs-type">int</span> key, <span class="hljs-type">int</span> value)</span> </span>{<br> <span class="hljs-type">int</span> index = <span class="hljs-built_in">get</span>(key);<br> <span class="hljs-keyword">if</span>(index == <span class="hljs-number">-1</span>){ <span class="hljs-comment">// 不存在</span><br> Node* temp = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Node</span>(key, value);<br> <span class="hljs-keyword">if</span>(size == capacity){ <span class="hljs-comment">// 容量不够</span><br> Node* end = tail->pre;<br> end->pre->next = tail;<br> tail->pre = end->pre;<br> map.<span class="hljs-built_in">erase</span>(end->key); <span class="hljs-comment">// 注意这里擦去的是 end->key 而不是 key</span><br> <span class="hljs-keyword">delete</span> end; <span class="hljs-comment">// 注意这句一定要在擦去 key 之后,否则就是引用悬空指针了(其实不 delete 也能通过所有测试用例)</span><br> --size;<br> }<br> temp->next = head->next;<br> head->next->pre = temp;<br> temp->pre = head;<br> head->next = temp;<br> ++size;<br> map.<span class="hljs-built_in">emplace</span>(key, temp);<br> }<span class="hljs-keyword">else</span>{ <span class="hljs-comment">// 存在</span><br> iter->second->value = value;<br> <span class="hljs-built_in">adjust</span>(iter->second);<br> }<br> }<br><br> <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">adjust</span><span class="hljs-params">(Node* item)</span></span>{<br> pre = item->pre;<br> next = item->next;<br> pre->next = next;<br> next->pre = pre;<br><br> item->next = head->next;<br> head->next->pre = item;<br> item->pre = head;<br> head->next = item;<br> }<br><br>};<br></code></pre></td></tr></table></figure><h2 id="参考"><a class="markdownIt-Anchor" href="#参考"></a> 参考</h2><ul><li><a href="https://leetcode-cn.com/problems/lru-cache/solution/gong-shui-san-xie-she-ji-shu-ju-jie-gou-68hv2/">设计数据结构:实现一个 LRUCache</a></li></ul>]]></content>
<categories>
<category>技术</category>
</categories>
<tags>
<tag>典型实现</tag>
<tag>LeetCode</tag>
</tags>
</entry>
<entry>
<title>为图数据库 Nebula Graph 对接 JDBC 协议中期报告</title>
<link href="/2021/08/09/%E4%B8%BA%E5%9B%BE%E6%95%B0%E6%8D%AE%E5%BA%93%20Nebula%20Graph%20%E5%AF%B9%E6%8E%A5%20JDBC%20%E5%8D%8F%E8%AE%AE%E4%B8%AD%E6%9C%9F%E6%8A%A5%E5%91%8A/"/>
<url>/2021/08/09/%E4%B8%BA%E5%9B%BE%E6%95%B0%E6%8D%AE%E5%BA%93%20Nebula%20Graph%20%E5%AF%B9%E6%8E%A5%20JDBC%20%E5%8D%8F%E8%AE%AE%E4%B8%AD%E6%9C%9F%E6%8A%A5%E5%91%8A/</url>
<content type="html"><![CDATA[<h2 id="前言"><a class="markdownIt-Anchor" href="#前言"></a> 前言</h2><p>文章收录于 <a href="https://mp.weixin.qq.com/s/wUPfZgzZSRR-C6JLP8mAxg">开源之夏官方公众号</a> 及 <a href="https://nebula-graph.com.cn/posts/what-is-nebula-jdbc/">Nebula Graph 博客栏目</a></p><p><a href="https://summer.iscas.ac.cn/#/homepage?lang=en">开源之夏</a>:<strong>开源软件供应链点亮计划 - 暑期 2021</strong> 是由中国科学院软件研究所与 openEuler 社区共同举办的一项面向高校学生的暑期活动,旨在鼓励在校学生积极参与开源软件的开发维护,促进优秀开源软件社区的蓬勃发展。中科院联合包括 Nebula Graph 在内的国内各大开源社区,针对重要开源软件的开发与维护提供项目,并向全球高校学生开放报名。学生在自由选择项目后,与社区导师沟通实现方案并撰写项目计划书。被选中的学生将在社区导师指导下,按计划完成开发工作,并将成果贡献给社区。</p><h2 id="1-项目信息"><a class="markdownIt-Anchor" href="#1-项目信息"></a> 1. <strong>项目信息</strong></h2><h3 id="11-项目编号"><a class="markdownIt-Anchor" href="#11-项目编号"></a> 1.1. 项目编号</h3><pre><code>210360225</code></pre><h3 id="12-项目详情"><a class="markdownIt-Anchor" href="#12-项目详情"></a> 1.2. 项目详情</h3><p>图数据库 Nebula Graph 支持 JDBC 协议:让 Nebula Graph 可以对接 JDBC 协议,实现 Nebula JDBC driver,实现 JDBC 的相关接口。要求:用户可直接使用 JDBC 驱动操作 Nebula 服务,项目 repo 有自动运行的单元测试。</p><h3 id="13-nebula-graph-简介"><a class="markdownIt-Anchor" href="#13-nebula-graph-简介"></a> 1.3. Nebula Graph 简介</h3><p>一个可靠的分布式、线性扩容、性能高效的图数据库;世界上唯一能够容纳千亿个顶点和万亿条边,并提供毫秒级查询延时的图数据库解决方案。</p><p>Nebula Graph 特性:</p><ul><li><strong>开源</strong>:致力于与 <a href="https://discuss.nebula-graph.com.cn/">社区合作</a> 普及及促进图数据库的发展;</li><li><strong>安全</strong>:具有基于角色的权限控制 <a href="https://nebula-graph.com.cn/posts/access-control-design-code-nebula-graph/">权限控制</a> ,授权才能访问;</li><li><strong>扩展性</strong>:Nebula Graph 支持多种类型 <a href="https://nebula-graph.io/cn/posts/nebula-graph-storage-engine-overview/">存储引擎</a> 查询语言也可以拓展支持新的算法;</li><li><strong>高性能</strong>:Nebula Graph 在维持高吞吐量的同时依旧能做到 <a href="https://discuss.nebula-graph.com.cn/t/topic/782/5">低时延的读写</a>;</li><li><strong>扩容</strong>:基于shared-nothing <a href="https://nebula-graph.io/cn/posts/nebula-graph-architecture-overview/">分布式架</a> Nebula Graph 支持线性扩容;</li><li><strong>兼容</strong>: openCypher:逐步兼容 <a href="https://docs.nebula-graph.com.cn/2.0/2.quick-start/0.FAQ/#opencypher">openCypher9</a> ,Cypher 用户可轻松上手 Nebula Graph;</li><li><strong>高可用</strong>:Nebula Graph 支持多种类型<a href="https://nebula-graph.io/cn/posts/introduction-to-snapshot-in-nebula-graph/">快照</a>方式实现数据恢复,保证在局部失败的情况下服务的高可用性;</li><li><strong>2.0 GA</strong>:一线互联网大厂诸如京东、美团、小红书在生产环境使用 Nebula Graph,<a href="https://space.bilibili.com/472621355/channel/detail?cid=175657">应用的场景</a>.</li></ul><p>Nebula Graph 具有活跃的社区与及时的技术支持,这是<a href="https://nebula-graph.com.cn/">官网</a>和<a href="https://github.com/vesoft-inc/nebula-graph">Github仓库</a>,欢迎关注及使用 Nebula Graph,一起成为 Nebula Graph 的 contributor,为图数据库的发展贡献力量!!!</p><h2 id="2-项目落地"><a class="markdownIt-Anchor" href="#2-项目落地"></a> 2. <strong>项目落地</strong></h2><h3 id="21-方案描述"><a class="markdownIt-Anchor" href="#21-方案描述"></a> 2.1. 方案描述:</h3><p><strong>前期</strong>了解 Nebula Graph 相关功能,掌握其基本使用;调研JDBC的驱动开发, 阅读JDBC规范文档,了解一些需要实现的接口;<strong>中期</strong>参考 Neo4j 的 <a href="https://github.com/neo4j-contrib/neo4j-jdbc">neo4j-jdbc</a> 实现,克隆 <a href="https://github.com/vesoft-inc/nebula-java">nebula-java</a> 项目, 学习源码,了解项目代码的主要逻辑和代码风格;<strong>后期</strong>利用已有的轮子 <a href="https://github.com/vesoft-inc/nebula-java">nebula-java</a> 实现与数据库的通信,编写代码为 Nebula Graph 实现 JDBC 的相关接口, 编写单元测试。</p><h3 id="22-实现描述"><a class="markdownIt-Anchor" href="#22-实现描述"></a> 2.2. 实现描述</h3><p>这个项目实现的思路很清晰:implements JDBC 规范中的一系列接口 (主要位于 java.sql 包中),实现接口中的方法。JDBC 规范中所有的类加起来需要实现的方法有好几百个。JDBC 主要面向的数据库是传统的关系型数据库 (RDB),而 Nebula Graph 作为新一代的图数据库,比起久经发展的关系型数据库来说没有它那么完备的功能特性,但是又比关系型数据库多出许多新的特点,所以 JDBC 规范中的方法对于 Nebula Graph 而言既有多余(不需要真正实现)也有不足(需要实现但是没有在相关接口中定义)</p><p>在具体的实现中,定义出一些抽象类直接 implements 规范中的主要接口,再定义出具体的实现类实现接口中一些重要的方法,这样一来实现类中的方法在阅读时不会显得很杂很乱。对于接口中需要实现的方法:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">for</span>( method : 接口的方法 ){<br> <span class="hljs-keyword">if</span>(method BELONG_TO 不需要具体实现的方法){<br> <span class="hljs-comment">// 比如 Statement::getGeneratedKeys()</span><br> 在该抽象类中 Override,方法体中抛出一个SQLFeatureNotSupportedException;<br> }<span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span>(method BELONG_TO 需要实现但是不是核心方法){<br> <span class="hljs-comment">// 比如 Statement::isClosed()</span><br> 在该抽象类中 Override;<br> }<span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span>(method BELONG_TO 需要实现且是核心方法){<br> <span class="hljs-comment">// 比如 Statement::execute(String nGql)</span><br> 在具体实现类中 Override<br> }<span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span>(method BELONG_TO 在接口中没有定义但是需要实现){<br> <span class="hljs-comment">// 比如 NebulaResult::getNode getEdge getPath (点,边,路径是图数据库特有概念)</span><br> 在具体实现类中实现<br> }<br>}<br></code></pre></td></tr></table></figure><p>项目中主要的一些 implements 和 extends 关系如下:(蓝色实线是类之间的 extends 关系,绿色实线是接口之间的 implements 关系,绿色虚线是抽象类与接口之间的 implements 关系)</p><p><img src="/img/blog_pic/2021/nebula-jdbc%E9%A1%B9%E7%9B%AE%E6%9E%B6%E6%9E%84.png" alt="nebula-jdbc项目架构" /></p><p><strong>工作流程及类中主要方法分析</strong>:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 用户首先通过 NebulaDriver 注册驱动,其中有 NebulaPool 属性,用于获取 Session 与数据库通信</span><br><span class="hljs-comment">// NebulaDriver 中提供两个构造函数,无参构造函数配置默认的 NebulaPool,接收一个 Properties 类型参数的构造函数可以自定义 NebulaPool 配置</span><br><br><span class="hljs-keyword">public</span> <span class="hljs-title function_">NebulaDriver</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> SQLException {<br> <span class="hljs-built_in">this</span>.setDefaultPoolProperties();<br> <span class="hljs-built_in">this</span>.initNebulaPool();<br> <span class="hljs-comment">// 将自身注册到 DriverManager</span><br> DriverManager.registerDriver(<span class="hljs-built_in">this</span>);<br>}<br><br><span class="hljs-keyword">public</span> <span class="hljs-title function_">NebulaDriver</span><span class="hljs-params">(Properties poolProperties)</span> <span class="hljs-keyword">throws</span> SQLException {<br> <span class="hljs-built_in">this</span>.poolProperties = poolProperties;<br> <span class="hljs-built_in">this</span>.initNebulaPool();<br> <span class="hljs-comment">// 将自身注册到 DriverManager</span><br> DriverManager.registerDriver(<span class="hljs-built_in">this</span>);<br>}<br><br><span class="hljs-comment">// 注册驱动后用户可以 DriverManager::getConnection(String url) 获取连接。在 NebulaConnection 的构造函数中会通过 NebulaDriver 中的 NebulaPool 获取 Session 接着连接访问在 url 中指定的图空间</span><br><br><span class="hljs-comment">// 获取到 Connection 后用户可以用 Connection::createStatement 和 Connection::prepareStatement 拿到 Statement 或者 PreparedStatement 对象,调用其中的 execute 方法向数据库发送命令,数据库执行此命令后的结果会封装在 NebulaResult 中,再调用其中各种获取数据的方法可以得到不同数据类型的数据</span><br><br><span class="hljs-comment">// 目前 NebulaResult 中实现的获取数据方法有以下这些,Nebula Graph 中不同的数据类型都有对应实现</span><br><span class="hljs-keyword">public</span> String <span class="hljs-title function_">getString</span><span class="hljs-params">()</span>;<br><span class="hljs-keyword">public</span> <span class="hljs-type">int</span> <span class="hljs-title function_">getInt</span><span class="hljs-params">()</span>;<br><span class="hljs-keyword">public</span> <span class="hljs-type">long</span> <span class="hljs-title function_">getLong</span><span class="hljs-params">()</span>;<br><span class="hljs-keyword">public</span> <span class="hljs-type">boolean</span> <span class="hljs-title function_">getBoolean</span><span class="hljs-params">()</span>;<br><span class="hljs-keyword">public</span> <span class="hljs-type">double</span> <span class="hljs-title function_">getDouble</span><span class="hljs-params">()</span>;<br><span class="hljs-keyword">public</span> java.sql.Date <span class="hljs-title function_">getDate</span><span class="hljs-params">()</span>;<br><span class="hljs-keyword">public</span> java.sql.Time <span class="hljs-title function_">getTime</span><span class="hljs-params">()</span>;<br><span class="hljs-keyword">public</span> DateTimeWrapper <span class="hljs-title function_">getDateTime</span><span class="hljs-params">()</span>;<br><span class="hljs-keyword">public</span> Node <span class="hljs-title function_">getNode</span><span class="hljs-params">()</span>;<br><span class="hljs-keyword">public</span> Relationship <span class="hljs-title function_">getEdge</span><span class="hljs-params">()</span>;<br><span class="hljs-keyword">public</span> PathWrapper <span class="hljs-title function_">getPath</span><span class="hljs-params">()</span>;<br><span class="hljs-keyword">public</span> List <span class="hljs-title function_">getList</span><span class="hljs-params">()</span>;<br><span class="hljs-keyword">public</span> Set <span class="hljs-title function_">getSet</span><span class="hljs-params">()</span>;<br><span class="hljs-keyword">public</span> Map <span class="hljs-title function_">getMap</span><span class="hljs-params">()</span>;<br></code></pre></td></tr></table></figure><h2 id="3-项目进度"><a class="markdownIt-Anchor" href="#3-项目进度"></a> 3. <strong>项目进度</strong></h2><h3 id="31-已完成工作"><a class="markdownIt-Anchor" href="#31-已完成工作"></a> 3.1. 已完成工作:</h3><ul><li>部署 Nebula Graph 并掌握其基本使用;</li><li>阅读 <a href="https://download.oracle.com/otn-pub/jcp/jdbc-4_2-mrel2-spec/jdbc4.2-fr-spec.pdf">JDBC 规范文档</a> , 明确实现要求;</li><li>学习 <a href="https://github.com/vesoft-inc/nebula-java">nebula-java</a> 源码;</li><li>(较计划提前)完成以下实现:</li></ul><p><img src="/img/blog_pic/2021/%E5%B7%B2%E5%AE%8C%E6%88%90.png" alt="" /></p><h3 id="32-遇到的问题及解决方案"><a class="markdownIt-Anchor" href="#32-遇到的问题及解决方案"></a> 3.2. 遇到的问题及解决方案:</h3><ul><li>如何与数据库通信的问题:</li></ul><p>项目前期过程中不知道如何与数据库通信,在研究友商 Neo4j 的 <a href="https://github.com/neo4j-contrib/neo4j-jdbc">neo4j-jdbc</a> 实现后利用 Http 框架通过 Nebula Graph 的 api (粗糙地)实现了与数据库的通信;完成后与导师联系询问该想法是否可行,导师告诉我可以用已有的轮子 <a href="https://github.com/vesoft-inc/nebula-java">nebula-java</a>,通过 rpc 与 Nebula Graph 通信。</p><ul><li>关于获取 Connection 的问题:</li></ul><p>NebulaPoolConfig 类中的一些参数是可配置的,我的想法是以在连接字符串中指定的形式进行配置,如:“jdbc:nebula://ip:port/graphSpace?maxConnsSize=10&reconnect=true”。咨询导师后导师建议可以让用户获取连接的时候,支持两种接口,一种是用默认配置,一种是让用户指定配置,如:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// default configuration</span><br>DriverManager.getConnection(url, username, password);<br><br><span class="hljs-comment">// customized configuration</span><br>DriverManager.getConnection(url,config);<br></code></pre></td></tr></table></figure><ul><li>关于 PreparedStatement 的问题:</li></ul><p>关系型数据库支持查询语句预编译的功能,PreparedStatement 可以向 DBMS 发送 SQL 让其预编译然后再传参数,提高了性能且能防止 SQL 注入攻击;目前 Nebula Graph 暂无此功能, 所以在本地解析 nGql 中的占位符再将参数填充进去,本质上与 Statement 相同。</p><ul><li>nebula-java 版本问题:</li></ul><p>一开始在项目中引入的依赖的 2.0.0 版本,在一次查询中发现其路径返回结果与控制台返回结果不一致,咨询导师后发现这是这个版本中的 bug,改用最新的 2.0.0-SNAPSHOT 版本。</p><ul><li>updateCount 问题:</li></ul><p>JDBC 接口中一些方法要求返回值是收到此方法影响的数据量 (updateCount) ,但目前服务端没有 updateCount 统计返回给用户。假如用户一条插入语句里面同时插入多个点或者多条边,这里面可能有部分成功,但服务端只会返回告诉用户失败了,但是其实用户可能能查到部分数据。这个 updateCount 按照0返回,然后在接口添加注释说明不支持。</p><ul><li>NebulaPool 初始化问题:</li></ul><p>一开始我是在初始化 NebulaConnection 时初始化 NebulaPool 再获取 Session,而且搞混了对于 NebulaPool 的配置和对于 Session 的配置。这样的话用户每次获取 Connection 时都会重新初始化 NebulaPool,是不合理的,我提交代码到 Gitlab 导师 review 后指出了我的错误,建议我将 NebulaPool 的 初始化和关闭移到 NebulaDriver 中,再提高默认配置和自定义配置两种方式初始化 NebulaPool。</p><h3 id="33-后续工作安排"><a class="markdownIt-Anchor" href="#33-后续工作安排"></a> 3.3. 后续工作安排:</h3><ul><li>完成接口中应该实现但未实现的方法;</li><li>完成单元测试。</li></ul>]]></content>
<categories>
<category>技术</category>
</categories>
<tags>
<tag>开源</tag>
<tag>开源之夏</tag>
</tags>
</entry>
<entry>
<title>Windows 10 使用 Docker 安装 Nebula Graph</title>
<link href="/2021/06/10/Windows%2010%20%E4%BD%BF%E7%94%A8%20Docker%20%E5%AE%89%E8%A3%85%20Nebula%20Graph/"/>
<url>/2021/06/10/Windows%2010%20%E4%BD%BF%E7%94%A8%20Docker%20%E5%AE%89%E8%A3%85%20Nebula%20Graph/</url>
<content type="html"><![CDATA[<h2 id="前言"><a class="markdownIt-Anchor" href="#前言"></a> 前言</h2><p>这篇文章记录了我是如何在 Windows 上通过 Docker 安装 NG 的,感觉 NG 的安装比其他数据库麻烦一些,所以记录一下。可以结合这篇文章和官网的<a href="https://docs.nebula-graph.com.cn/2.0.1/2.quick-start/2.deploy-nebula-graph-with-docker-compose/">教程</a>进行安装。</p><h2 id="安装-docker"><a class="markdownIt-Anchor" href="#安装-docker"></a> 安装 Docker</h2><p>Docker 官网下载对应的安装包 <a href="https://docs.docker.com/engine/install/">Install Docker Engine</a>,Windows 的 Docker Desktop 自带了 Docker Compose 所以不需要额外安装。具体的安装可以参考</p><ol><li><a href="https://www.jianshu.com/p/736984429364">安装 Hyper-V</a></li><li><a href="https://jingyan.baidu.com/article/ac6a9a5e1f164a2b653eac33.html">开启 Hyper-V</a></li><li><a href="https://www.jianshu.com/p/74291add4ee5">安装 Docker</a></li></ol><h2 id="clone-仓库"><a class="markdownIt-Anchor" href="#clone-仓库"></a> clone 仓库</h2><ol><li><a href="https://github.com/vesoft-inc/nebula-docker-compose">nebula-docker-compose</a> 数据库的后端(推荐v2.0.0分支)</li><li><a href="https://github.com/vesoft-inc/nebula-graph-studio">nebula-graph-studio</a> (前端,master 分支)</li></ol><h2 id="安装并开启-nebula-docker-compose后端"><a class="markdownIt-Anchor" href="#安装并开启-nebula-docker-compose后端"></a> 安装并开启 nebula-docker-compose(后端)</h2><ul><li>来到 nebula-docker-compose 所在文件夹,按 shift 同时右键,选择打开 powershell</li><li>输入命令 <code>docker-compose up -d</code> (由于网络原因可能有些资源下载失败, 多试几次就好),成功之后在 Docker 中会多出一些镜像</li></ul><p><img src="/img/blog_pic/2022/NG%E5%AE%89%E8%A3%85-1.png" alt="" /></p><p><img src="/img/blog_pic/2022/NG%E5%AE%89%E8%A3%85-2.png" alt="" /></p><ul><li>上一步完成之后输入命令 <code>docker run --rm -ti --network nebula-docker-compose_nebula-net --entrypoint=/bin/sh vesoft/nebula-console:v2-nightly</code> (这里的<code>nebula-docker-compose_nebula-net</code>是网络的名字,可以在命令行使用 <code>docker network ls</code> 查看网络)</li></ul><p><img src="/img/blog_pic/2022/NG%E5%AE%89%E8%A3%85-3.png" alt="" /></p><ul><li>输入 <code>nebula-console -u user -p password --address=graphd --port=9669</code> (graphd不用改),成功之后输入 <code>show hosts</code> 检查 <code>nebula-storaged</code> 进程状态</li></ul><p><img src="/img/blog_pic/2022/NG%E5%AE%89%E8%A3%85-4.png" alt="" /></p><ul><li>这时可以在命令行中来到 nebula-docker-compose 所在文件夹, 输入 <code>docker-compose ps</code> 列出 Nebula Graph 服务的状态和端口</li></ul><p><img src="/img/blog_pic/2022/NG%E5%AE%89%E8%A3%85-5.png" alt="" /></p><ul><li>此时后端算是成功部署并且开启了,停止服务可以使用 <code>docker-compose down</code></li></ul><h2 id="安装并开启-nebula-graph-studio前端"><a class="markdownIt-Anchor" href="#安装并开启-nebula-graph-studio前端"></a> 安装并开启 nebula-graph-studio(前端)</h2><ul><li>来到 nebula-graph-studio 所在文件夹,同样在此打开 powershell,docker-compose pull 拉取镜像(一次拉取不成功就多试几次,拉完之后 Docker 中同样会出现这些镜像)</li></ul><p><img src="/img/blog_pic/2022/NG%E5%AE%89%E8%A3%85-6.png" alt="" /></p><p><img src="/img/blog_pic/2022/NG%E5%AE%89%E8%A3%85-7.png" alt="" /></p><ul><li>继续输入命令 <code>docker-compose up -d</code> 开启 Studio 服务</li></ul><p><img src="/img/blog_pic/2022/NG%E5%AE%89%E8%A3%85-8.png" alt="" /></p><ul><li>启动成功后,在浏览器地址栏输入 <code>http://ip address:7001</code>,在浏览器窗口中能看到以下登录界面,表示已经成功部署并启动 Studio。</li></ul><p><img src="/img/blog_pic/2022/NG%E5%AE%89%E8%A3%85-9.png" alt="" /></p><ul><li>host 填你的 ip 地址:9669 (命令行中用 ipconfig 可以常看,好像不能填127.0.0.1),用户名 user, 密码 password</li></ul><p><img src="/img/blog_pic/2022/NG%E5%AE%89%E8%A3%85-10.png" alt="" /></p><ul><li>接下来可以按照官网的<a href="https://docs.nebula-graph.com.cn/2.0.1/2.quick-start/4.nebula-graph-crud/">教程</a>练习 CRUD</li></ul>]]></content>
<categories>
<category>技术</category>
</categories>
<tags>
<tag>开源</tag>
<tag>环境搭建</tag>
<tag>开源之夏</tag>
</tags>
</entry>
<entry>
<title>在 IDEA 中配置并运行主站代码</title>
<link href="/2021/04/02/%E5%9C%A8%20IDEA%E4%B8%AD%E9%85%8D%E7%BD%AE%E5%B9%B6%E8%BF%90%E8%A1%8C%E4%B8%BB%E7%AB%99%E4%BB%A3%E7%A0%81/"/>
<url>/2021/04/02/%E5%9C%A8%20IDEA%E4%B8%AD%E9%85%8D%E7%BD%AE%E5%B9%B6%E8%BF%90%E8%A1%8C%E4%B8%BB%E7%AB%99%E4%BB%A3%E7%A0%81/</url>
<content type="html"><![CDATA[<h2 id="1-前言"><a class="markdownIt-Anchor" href="#1-前言"></a> 1. 前言</h2><p>MyEclipse 8.5 又丑又难用,据说只能用它改主站代码,我坚信 MyEclipse 能做到的 IntelliJ IDEA 绝对能做到并且做得更好</p><h2 id="2-相关工作"><a class="markdownIt-Anchor" href="#2-相关工作"></a> 2. 相关工作</h2><p>不知道谁写的 SSH 开发教程</p><h2 id="3-过程"><a class="markdownIt-Anchor" href="#3-过程"></a> 3. 过程</h2><p>文中提到的安装包都放在小龙的局域网里</p><h3 id="31-安装-tortoisesvn"><a class="markdownIt-Anchor" href="#31-安装-tortoisesvn"></a> 3.1 安装 TortoiseSVN</h3><p><a href="https://blog.csdn.net/u010758410/article/details/80532992">https://blog.csdn.net/u010758410/article/details/80532992</a></p><p>安装完后可以在 SVN (地址:<a href="http://192.168.66.2:89/svn/repo/trunk/scholat">http://192.168.66.2:89/svn/repo/trunk/scholat</a>) 里拉取主站代码,用户名和密码问华哥要</p><p><img src="/img/blog_pic/2021/%E9%85%8D%E7%BD%AE1.png" alt="" /></p><p>右键 checkout 到指定目录</p><h3 id="32-配置tomcat"><a class="markdownIt-Anchor" href="#32-配置tomcat"></a> 3.2 配置Tomcat</h3><p>tomcat 7 同样放在局域网里</p><p><img src="/img/blog_pic/2021/%E9%85%8D%E7%BD%AE2.png" alt="" /></p><p>配置项目发布</p><p><img src="/img/blog_pic/2021/%E9%85%8D%E7%BD%AE3.png" alt="" /></p><h3 id="33-更改编码方式"><a class="markdownIt-Anchor" href="#33-更改编码方式"></a> 3.3 更改编码方式</h3><p>尝试运行一下,会报一卡成错。这时将项目编码方式改为 UTF-8,有些文件会提示不是 UTF-8 编码叫你换成 GBK ,别听它的。<img src="/img/blog_pic/2021/%E9%85%8D%E7%BD%AE4.png" alt="" /></p><p>并且指定项目环境为JDK 6</p><p><img src="/img/blog_pic/2021/%E9%85%8D%E7%BD%AE5.png" alt="" /></p><p><img src="/img/blog_pic/2021/%E9%85%8D%E7%BD%AE6.png" alt="" /></p><p><img src="/img/blog_pic/2021/%E9%85%8D%E7%BD%AE7.png" alt="" /></p><p>做完上面这些应该可以了,</p><p><img src="/img/blog_pic/2021/%E9%85%8D%E7%BD%AE8.png" alt="" /></p>]]></content>
<categories>
<category>工具</category>
</categories>
<tags>
<tag>环境搭建</tag>
<tag>IDE</tag>
</tags>
</entry>
<entry>
<title>redis 缓存在课程平台中的使用</title>
<link href="/2020/11/09/redis%E7%BC%93%E5%AD%98%E5%9C%A8%E9%A1%B9%E7%9B%AE%E4%B8%AD%E7%9A%84%E4%BD%BF%E7%94%A8/"/>
<url>/2020/11/09/redis%E7%BC%93%E5%AD%98%E5%9C%A8%E9%A1%B9%E7%9B%AE%E4%B8%AD%E7%9A%84%E4%BD%BF%E7%94%A8/</url>
<content type="html"><![CDATA[<h3 id="redis缓存在项目中的使用"><a class="markdownIt-Anchor" href="#redis缓存在项目中的使用"></a> redis缓存在项目中的使用</h3><h4 id="1-环境搭建"><a class="markdownIt-Anchor" href="#1-环境搭建"></a> 1、环境搭建</h4><p>redis数据库(Redis-x64-5.0.10.zip)和桌面可视化工具(RedisDesktopManager_42636.zip)放在小龙局域网下的toShare文件夹中。项目目前用的是在我的主机上配置好的redis数据库,如果只是看使用过程中缓存生存的过程可以只安装可视化工具,连接我的数据库就可以(连接名自定义,地址:192.168.66.116,没有密码)。如果想搭建自己的redis数据库,用上面的安装包,自己查一下安装步骤即可。</p><h4 id="2-具体使用"><a class="markdownIt-Anchor" href="#2-具体使用"></a> 2、具体使用</h4><p>参考博客:<a href="https://www.cnblogs.com/coding-one/p/12402543.html">https://www.cnblogs.com/coding-one/p/12402543.html</a></p><p>缓存一般用在查询方法,在ServiceImpl类中的某个查询方法上加上 @Cacheable 注解即可。注解中需要填写缓存名和key。</p><ul><li><p>缓存名:有两个参数来指定:value、cacheNames,我使用的是cacheNames,并且重写了构造方法,在缓存名最后加上#再加上数字,表示这条缓存的有效时间(单位:秒)。示例:@Cacheable( cacheNames = “scholat#60” , keyGenerator = “keyGenerator”),表示这个方法生成的缓存名为scholat,有效时间为60秒,过期后redis会自动删除缓存。</p></li><li><p>key:redis是键值数据库,每一条记录都有唯一的key与之对应(上面的缓存名可以视为一个缓存分组,改分组下有对应不同key的缓存)。建议自己在注解中指定key,不要用默认生成的。key可以用spring表达式(SpEl)描述,也可以在redis配置类中自定义 keyGenerator(如果不指定key的话会使用默认的SimpleKeyGenerator)。我在config包下的RedisConfig中自定义了一个keyGenerator,有需要的可以自己的情况再定义其他keyGenerator。假设你定义的keyGenerator写法如下</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs java"> <span class="hljs-keyword">public</span> KeyGenerator <span class="hljs-title function_">mykeyGenerator</span><span class="hljs-params">()</span> {<br> <span class="hljs-keyword">return</span> (o, method, objects) -> {<br><span class="hljs-comment">// ***</span><br> };<br> }<br></code></pre></td></tr></table></figure><p>用法为@Cacheable( cacheNames = , keyGenerator = “mykeyGenerator”)</p></li><li><p>注意:使用缓存后,如果其他方法(增删改方法)会改变这个查询方法的返回值,记得要清除原来的缓存。比如在查询某课程的所有班级方法和修改班级方法,前端在修改班级后会刷新,如果不清除缓存的话刷新后的结果还是没有变化(尽管实际上更改已经写到数据库中了)。可以考虑更新缓存@CachePut或者删除缓存@CacheEvict,不过这两个注解我还没用上,我是自己写的手动删除缓存(stringRedisTemplate.delete(keys); 具体可见CourseDiscussReplyServiceImpl类中的addReply和deleteReply方法),这些可控制性更强,缺点是需要在方法体内自己写代码,没有注解那么优雅。</p></li></ul>]]></content>
<categories>
<category>技术</category>
</categories>
<tags>
<tag>Redis</tag>
</tags>
</entry>
<entry>
<title>有类IP地址、子网掩码(划分子网)、无类域间路由(CIDR)</title>
<link href="/2020/05/06/%E6%9C%89%E7%B1%BBIP%E5%9C%B0%E5%9D%80%E3%80%81%E5%AD%90%E7%BD%91%E6%8E%A9%E7%A0%81%EF%BC%88%E5%88%92%E5%88%86%E5%AD%90%E7%BD%91%EF%BC%89%E3%80%81%E6%97%A0%E7%B1%BB%E5%9F%9F%E9%97%B4%E8%B7%AF%E7%94%B1%EF%BC%88CIDR%EF%BC%89/"/>
<url>/2020/05/06/%E6%9C%89%E7%B1%BBIP%E5%9C%B0%E5%9D%80%E3%80%81%E5%AD%90%E7%BD%91%E6%8E%A9%E7%A0%81%EF%BC%88%E5%88%92%E5%88%86%E5%AD%90%E7%BD%91%EF%BC%89%E3%80%81%E6%97%A0%E7%B1%BB%E5%9F%9F%E9%97%B4%E8%B7%AF%E7%94%B1%EF%BC%88CIDR%EF%BC%89/</url>
<content type="html"><![CDATA[<p>IP协议是网络层中的核心协议,对于IP地址的管理与划分则是IP协议的重点。对于IP地址的管理划分有三种方式:</p><ol><li>有类IP地址</li><li>子网掩码(划分子网)</li><li>无类域间路由(CIDR)</li></ol><p>这三种方式由何而来、有什么作用、以及彼此之间有什么关系呢?</p><h2 id="1-有类ip地址"><a class="markdownIt-Anchor" href="#1-有类ip地址"></a> 1、有类IP地址:</h2><p>这是最早的对IP地址进行分配和管理的方式,将其分为A、、B、C、D、E,总共5类。<strong>IP地址 = 网络号 + 主机号</strong>。A、B、C 类是我们用于分配给一般主机使用的;D、E 则不作一般用途:D类用做组播地址(IP数据报三种传输方式:单播、广播、组播),E类作为科研保留网络。<br />A、B、C类网络对网络数和主机数都有固定的限制:<img src="/img/blog_pic/2020/%E6%9C%89%E7%B1%BBIP%E5%9C%B0%E5%9D%80.png" alt="" /><br />因为这个固定限制,使得后来面对我们日益增大的网络规模时给IP地址的管理与分配带来诸多不便。比如现在有一个机构需要互联1000台主机,为其分配一个C类网络则主机数(254)不足,分配一个B类网络则主机数(65534)浪费。</p><h2 id="2-子网掩码划分子网"><a class="markdownIt-Anchor" href="#2-子网掩码划分子网"></a> 2、子网掩码(划分子网):</h2><ul><li>有类IP地址使用之初是满足网络需求的,在设计的时候并未考虑到后来的网络规模变化如此之大,以至于40多亿的 IPv4<br />地址不够使用。所以我们需要一种更好的方式来管理和分配网络,使得IP地址能够被充分利用不至于浪费。</li><li>子网掩码(subnet mask)就是这样产生的。通过向主机号借位产生子网号,使得IP地址的结构变成了<strong>网络号+子网号+主机号</strong>。即<strong>由原来的两层变成了三层</strong>。即子网规划的任务就是通过向主机位借位来创建子网,把大网络划分为小网络。</li><li>将IP分组中的目的IP地址与子网掩码按位与运算, 提取子网地址。子网掩码:网络号、子网号全取1;主机号全取0。是子网掩码确定了网络号和主机号,而不是网络号和主机号确定了子网掩码。</li></ul><h2 id="3-无类域间路由classless-interdomain-routing"><a class="markdownIt-Anchor" href="#3-无类域间路由classless-interdomain-routing"></a> 3、无类域间路由(Classless Interdomain Routing):</h2><ul><li>子网掩码还是在传统的ABCDE类网下划分的,将原来的两层划为三层,而CIDR消除了有类的界限,让子网掩码的三层结构回到两层,分配IP地址的时候不再以类别来分,而是按照可变长的地址块来分配,提高 IPv4 地址空间分配效率。</li><li>无类地址格式: a.b.c.d / x, 其中x为前缀长度,表示网络号的位数。</li><li>CIDR还可以提高路由效率:将多个子网聚合为一个较大的子网,构造超网(supernetting)。有效减少了路由器中路由表项,由此提高了路由效率。(这里的路由聚合与前面的子网划分相反。前者是将小网络聚合成大网络减少路由表项以提高路由效率;后者是为了将大网络划为小网络以充分使用每一个IP地址)</li></ul><h2 id="4-总结"><a class="markdownIt-Anchor" href="#4-总结"></a> 4、总结:</h2><p>从有类IP地址 到 子网掩码(划分子网) 再到 无类域间路由(CIDR),后来者解决了前者在设计之初未考虑到而在实际应用中充分暴露出来的问题,是对IP地址管理与分配的升级。</p><h2 id="5-参考"><a class="markdownIt-Anchor" href="#5-参考"></a> 5、参考:</h2><ul><li><a href="https://www.icourse163.org/course/HIT-154005?tid=1206679208">MOOC 哈工大 计算机网络</a></li></ul>]]></content>
<categories>
<category>技术</category>
</categories>
<tags>
<tag>计算机网络</tag>
</tags>
</entry>
<entry>
<title>selenium + pytesseract 实现自动识别验证码实现自动打卡</title>
<link href="/2020/05/02/selenium%20+%20pytesseract%20%E5%AE%9E%E7%8E%B0%E8%87%AA%E5%8A%A8%E8%AF%86%E5%88%AB%E9%AA%8C%E8%AF%81%E7%A0%81%E5%AE%9E%E7%8E%B0%E8%87%AA%E5%8A%A8%E6%89%93%E5%8D%A1/"/>
<url>/2020/05/02/selenium%20+%20pytesseract%20%E5%AE%9E%E7%8E%B0%E8%87%AA%E5%8A%A8%E8%AF%86%E5%88%AB%E9%AA%8C%E8%AF%81%E7%A0%81%E5%AE%9E%E7%8E%B0%E8%87%AA%E5%8A%A8%E6%89%93%E5%8D%A1/</url>
<content type="html"><![CDATA[<h2 id="1-步骤"><a class="markdownIt-Anchor" href="#1-步骤"></a> 1、步骤:</h2><p>利用 chromedriver 打开浏览器 --> 登陆网站(识别验证码)–> 自动化操作</p><h2 id="2-难点"><a class="markdownIt-Anchor" href="#2-难点"></a> 2、难点:</h2><p><strong>2.1、登陆网站:</strong></p><p>本来想用 cookie,但是我们学校的网站的 cookie 中 httponly = False,不能用这种方式绕过登陆。只能识别出验证码后登陆,过了登陆这关后便是一马平川。</p><p><strong>2.2、网站元素的识别:</strong></p><p>世界上最遥远的距离,不是生与死,而是你在我眼前,我却识别不了你。<br />selenium 提供多种元素识别方式,常用的有 id、name、class、xpath 等,一种方式识别不了就换另一种,针对不同元素(不同网页)识别的方式也不同,可参考<a href="https://blog.csdn.net/qq_32897143/article/details/80383502">https://blog.csdn.net/qq_32897143/article/details/80383502</a></p><h2 id="3-环境要求"><a class="markdownIt-Anchor" href="#3-环境要求"></a> 3、环境要求:</h2><ul><li>selenium</li><li>pytesseract</li><li>pillow</li></ul><p>在命令行使用pip命令安装以上第三方库,默认的安装方式很慢,使用镜像网站安装会快很多,直接复制下面这行到 cmd<br />pip install --index <a href="https://pypi.mirrors.ustc.edu.cn/simple/">https://pypi.mirrors.ustc.edu.cn/simple/</a> selenium</p><p>安装完以上库后,还需安装chromedriver(selenium的浏览器驱动,edge或firefox也可)和 tesseract(用于识别出验证码)</p><p>安装 chromedriver:打开<a href="http://npm.taobao.org/mirrors/chromedriver/">网站</a>,找到对应自己的版本后下载,将 chromedriver.exe 文件复制到python的Scripts文件夹下(在cmd中用where python命令可找到自己的安装路径)</p><p>安装tesseract:参照<a href="https://blog.csdn.net/showgea/article/details/82656515">https://blog.csdn.net/showgea/article/details/82656515</a></p><h2 id="4-完整代码"><a class="markdownIt-Anchor" href="#4-完整代码"></a> 4、完整代码:</h2><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> selenium <span class="hljs-keyword">import</span> webdriver<br><span class="hljs-keyword">from</span> selenium.webdriver.support.select <span class="hljs-keyword">import</span> Select <span class="hljs-comment">#专门用来处理下拉框</span><br><span class="hljs-keyword">import</span> pytesseract<br><span class="hljs-keyword">import</span> os<br><span class="hljs-keyword">import</span> sys,time<br><span class="hljs-keyword">from</span> PIL <span class="hljs-keyword">import</span> Image,ImageEnhance<br>website= <span class="hljs-string">'https://www.xxx.com'</span> <span class="hljs-comment">#打卡的网站</span><br>img_address = <span class="hljs-string">'C:\\Users\\HP--\\Pictures\\喜欢的照片\\image1.png'</span> <span class="hljs-comment">#将验证码图片下载下来后存放的地址,</span><br> <br>driver = webdriver.Chrome()<br>driver.maximize_window()d<br>cnt = <span class="hljs-number">0</span> <span class="hljs-comment">#计数多少次识别验证码才成功</span><br><br><span class="hljs-keyword">while</span> <span class="hljs-literal">True</span>: <span class="hljs-comment">#循环识别验证码直到成功</span><br>driver.get(website)<br>driver.get_screenshot_as_file(img_adress) <span class="hljs-comment">#将整个网页截图</span><br><br>img =Image.<span class="hljs-built_in">open</span>(img_address)<br>box = (<span class="hljs-number">1217</span> , <span class="hljs-number">496</span> , <span class="hljs-number">1310</span> , <span class="hljs-number">541</span>) <span class="hljs-comment">#设置要裁剪的区域</span><br>img = img.crop(box) <span class="hljs-comment">#裁剪出只有验证码的图片(用【画图】打开截下的图片,鼠标滑到验证码的左上和右下,两个坐标就是要裁剪的区域。顺序:左上横坐标,左上纵坐标,右下横坐标,右下纵坐标)</span><br>img.save(img_address) <span class="hljs-comment">#将图片更新为截下的只有验证码的图</span><br><br>vc = pytesseract.image_to_string(img_address) <span class="hljs-comment">#保存验证码</span><br>account = <span class="hljs-string">'xxxxx'</span><br>password = <span class="hljs-string">'xxxxx'</span><br><br><span class="hljs-keyword">try</span>:<br>driver.find_element_by_name(<span class="hljs-string">"account"</span>).send_keys(account)<br>driver.find_element_by_name(<span class="hljs-string">"password"</span>).send_keys(password)<br>driver.find_element_by_name(<span class="hljs-string">"rancode"</span>).send_keys(verfication_code)<br>driver.find_element_by_class_name(<span class="hljs-string">"login"</span>).click()<br><br><span class="hljs-keyword">if</span>(driver.find_element_by_id(<span class="hljs-string">"rancode-tips"</span>)): <span class="hljs-comment">#这里是验证码识别不正确点击登陆后出现的错误信息</span><br>cnt += <span class="hljs-number">1</span><br><span class="hljs-keyword">continue</span><br><span class="hljs-keyword">except</span>: <span class="hljs-comment">#正确识别后成功登陆,退出while循环</span><br><span class="hljs-keyword">break</span> <br><br><span class="hljs-keyword">try</span>: <span class="hljs-comment">#会出现一些我不知道原因的错误,所以还是用异常捕获吧</span><br>driver.find_element_by_class_name(<span class="hljs-string">"bdorange.bg_health"</span>).click()<br>driver.find_element_by_link_text(<span class="hljs-string">"健康打卡"</span>).click()<br>driver.find_element_by_id(<span class="hljs-string">"cph_right_ok_submit"</span>).click()<br><br>opt = driver.find_element_by_id(<span class="hljs-string">"cph_right_e_area"</span>)<br>s = Select(opt)<br>s.select_by_visible_text(<span class="hljs-string">'XX省'</span>) <br><span class="hljs-comment">#以上三句用select方法处理下拉框</span><br><br>driver.find_element_by_id(<span class="hljs-string">'cph_right_e_location'</span>).send_keys(<span class="hljs-string">'XX市'</span>)<br>driver.find_element_by_id(<span class="hljs-string">'cph_right_e_observation_0'</span>).click()<br>driver.find_element_by_id(<span class="hljs-string">'cph_right_e_health_0'</span>).click()<br>driver.find_element_by_id(<span class="hljs-string">'cph_right_e_temp'</span>).send_keys(<span class="hljs-string">'36.6'</span>)<br>driver.find_element_by_id(<span class="hljs-string">'cph_right_e_survey01_0'</span>).click()<br>driver.find_element_by_id(<span class="hljs-string">'cph_right_e_submit'</span>).click()<br><span class="hljs-keyword">except</span>:<br><span class="hljs-keyword">pass</span><br><br>driver.quit()<br><span class="hljs-built_in">print</span>(<span class="hljs-string">'经过{}次尝试,自动健康打卡完成!'</span>.<span class="hljs-built_in">format</span>(cnt))<br></code></pre></td></tr></table></figure><h2 id="5-说明"><a class="markdownIt-Anchor" href="#5-说明"></a> 5、说明:</h2><p>以上程序不具有设备无关性,因为是直接截图下来后保存再截下验证码所在区域那块,不同计算机的屏幕不一样,所以验证码的坐标值应该也不一样。如果写完程序想发给同学试试<strong>记得要做一些修改</strong>,不然Ta会以为你不行!!!selenium 是用来模仿人在浏览器上的操作的,若是想更好的地模仿,应该在每下操作后用 time.sleep() 停顿一两秒。</p>]]></content>
<categories>
<category>瞎搞</category>
</categories>
<tags>
<tag>mess</tag>
</tags>
</entry>
</search>