-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsearch.xml
967 lines (838 loc) · 248 KB
/
search.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>C++关键字之explicit</title>
<url>/2022/12/06/C++%E5%85%B3%E9%94%AE%E5%AD%97%E4%B9%8Bexplicit/</url>
<content><![CDATA[<p>在C++中 <code>explicit</code>关键字只能用来修饰单参构造函数。</p>
<p>与之对应的关键字为 <code>implicit</code>,其含义为“隐式的”,类的构造函数默认声明为 <code>implicit</code>。</p>
<h2 id="1-隐式声明的构造函数"><a href="#1-隐式声明的构造函数" class="headerlink" title="1. 隐式声明的构造函数"></a>1. 隐式声明的构造函数</h2><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">CxString</span></span><br><span class="line">{ </span><br><span class="line"><span class="keyword">public</span>: </span><br><span class="line"> <span class="type">char</span> *_pstr; </span><br><span class="line"> <span class="type">int</span> _size; </span><br><span class="line"> <span class="built_in">CxString</span>(<span class="type">int</span> size) </span><br><span class="line"> { </span><br><span class="line"> _size = size; <span class="comment">// string的预设大小 </span></span><br><span class="line"> _pstr = (<span class="type">char</span>*)<span class="built_in">malloc</span>(size + <span class="number">1</span>); <span class="comment">// 分配string的内存 </span></span><br><span class="line"> <span class="built_in">memset</span>(_pstr, <span class="number">0</span>, size + <span class="number">1</span>); </span><br><span class="line"> } </span><br><span class="line"> <span class="built_in">CxString</span>(<span class="type">const</span> <span class="type">char</span> *p) </span><br><span class="line"> { </span><br><span class="line"> <span class="type">int</span> size = <span class="built_in">strlen</span>(p); </span><br><span class="line"> _pstr = (<span class="type">char</span>*)<span class="built_in">malloc</span>(size + <span class="number">1</span>); <span class="comment">// 分配string的内存 </span></span><br><span class="line"> <span class="built_in">strcpy</span>(_pstr, p); <span class="comment">// 复制字符串 </span></span><br><span class="line"> _size = <span class="built_in">strlen</span>(_pstr); </span><br><span class="line"> } </span><br><span class="line"> <span class="comment">// 析构函数这里不讨论, 省略... </span></span><br><span class="line">}; </span><br><span class="line"></span><br><span class="line"> <span class="function">CxString <span class="title">string1</span><span class="params">(<span class="number">1</span>)</span></span>; <span class="comment">// 正确,调用 CxString(int size) </span></span><br><span class="line"> CxString string2 = <span class="number">2</span>; <span class="comment">// 正确,调用 CxString(int size) </span></span><br><span class="line"> CxString string3; <span class="comment">// 错误,无默认的构造函数</span></span><br><span class="line"> <span class="function">CxString <span class="title">string4</span><span class="params">(<span class="string">"123"</span>)</span></span>; <span class="comment">// 正确,调用 CxString(const char *p) </span></span><br><span class="line"> CxString string5 = <span class="string">"456"</span>; <span class="comment">// 正确,调用 CxString(const char *p) </span></span><br><span class="line"> CxString string6 = <span class="string">'a'</span>; <span class="comment">// 正确,但是调用 CxString(int size),size的值为'a'的ascii码</span></span><br></pre></td></tr></table></figure></div>
<p>在C++中,如果构造函数只有一个参数时,在编译的时候就会有一个缺省的类型转换操作,也就是将对应的数据类型转换为该类对象。所以说:</p>
<div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="code"><pre><span class="line">CxString string2 = <span class="number">2</span>;</span><br></pre></td></tr></table></figure></div>
<p>实际上等同于:</p>
<div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="function">CxString <span class="title">string2</span><span class="params">(<span class="number">2</span>)</span></span>;</span><br><span class="line"><span class="comment">// 或以下形式</span></span><br><span class="line"><span class="function">CxString <span class="title">tmp</span><span class="params">(<span class="number">2</span>)</span></span>;</span><br><span class="line">CxString string2 = tmp; </span><br></pre></td></tr></table></figure></div>
<p>关于<code>string6</code>的情况类似,只不过将<code>'a'</code>转化成了对应的ascii码。<br>但是以上两种写法并不具有很好的可读性,为了避免这种情况产生,可以使用<code>explicit</code>关键字阻止隐式的自动类型转换。</p>
<h2 id="2-显式声明的构造函数"><a href="#2-显式声明的构造函数" class="headerlink" title="2.显式声明的构造函数"></a>2.显式声明的构造函数</h2><p>使用<code>explicit</code>关键字可以阻止类构造函数的隐式类型转换。</p>
<div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">CxString</span></span><br><span class="line">{ </span><br><span class="line"><span class="keyword">public</span>: </span><br><span class="line"> <span class="type">char</span> *_pstr; </span><br><span class="line"> <span class="type">int</span> _size; </span><br><span class="line"> <span class="function"><span class="keyword">explicit</span> <span class="title">CxString</span><span class="params">(<span class="type">int</span> size)</span> </span></span><br><span class="line"><span class="function"> </span>{ </span><br><span class="line"> _size = size; <span class="comment">// string的预设大小 </span></span><br><span class="line"> _pstr = (<span class="type">char</span>*)<span class="built_in">malloc</span>(size + <span class="number">1</span>); <span class="comment">// 分配string的内存 </span></span><br><span class="line"> <span class="built_in">memset</span>(_pstr, <span class="number">0</span>, size + <span class="number">1</span>); </span><br><span class="line"> } </span><br><span class="line"> <span class="built_in">CxString</span>(<span class="type">const</span> <span class="type">char</span> *p) </span><br><span class="line"> { </span><br><span class="line"> <span class="type">int</span> size = <span class="built_in">strlen</span>(p); </span><br><span class="line"> _pstr = (<span class="type">char</span>*)<span class="built_in">malloc</span>(size + <span class="number">1</span>); <span class="comment">// 分配string的内存 </span></span><br><span class="line"> <span class="built_in">strcpy</span>(_pstr, p); <span class="comment">// 复制字符串 </span></span><br><span class="line"> _size = <span class="built_in">strlen</span>(_pstr); </span><br><span class="line"> } </span><br><span class="line"> <span class="comment">// 析构函数这里不讨论, 省略... </span></span><br><span class="line">}; </span><br><span class="line"> <span class="function">CxString <span class="title">string1</span><span class="params">(<span class="number">1</span>)</span></span>; <span class="comment">// 正确,调用 CxString(int size) </span></span><br><span class="line"> CxString string2 = <span class="number">2</span>; <span class="comment">// 错误 </span></span><br><span class="line"> CxString string3; <span class="comment">// 错误,无默认的构造函数</span></span><br><span class="line"> <span class="function">CxString <span class="title">string4</span><span class="params">(<span class="string">"123"</span>)</span></span>; <span class="comment">// 正确,调用 CxString(const char *p) </span></span><br><span class="line"> CxString string5 = <span class="string">"456"</span>; <span class="comment">// 正确,调用 CxString(const char *p)</span></span><br><span class="line"> CxString string6 = <span class="string">'a'</span>; <span class="comment">// 正确,但是调用 CxString(int size),size的值为'a'的ascii码</span></span><br></pre></td></tr></table></figure></div>
<h2 id="3-特殊情况:多参构造函数"><a href="#3-特殊情况:多参构造函数" class="headerlink" title="3.特殊情况:多参构造函数"></a>3.特殊情况:多参构造函数</h2><p>文章开头提到<code>explicit</code>关键字只适用于单参构造函数,因为如果类构造函数的参数大于等于两个时,不会触发隐式类型转换,那么<code>explicit</code>关键字随之无效,但是有一种特殊情况,也就是<strong>除了第一个参数以外的其他参数都有默认值的时候,<code>explicit</code>关键字仍然有效</strong>。</p>
<h2 id="4-总结"><a href="#4-总结" class="headerlink" title="4.总结"></a>4.总结</h2><p><code>explicit</code>关键字只用于声明类的单参构造函数(包括上述特殊情况)。<br>《Effective C++》提到:被声明为<code>explicit</code>的构造函数通常比其<code>non-explicit</code>兄弟更受欢迎。因为它们禁止编译器执行非预期(往往也不被期望)的类型转换。</p>
]]></content>
<tags>
<tag>C++</tag>
</tags>
</entry>
<entry>
<title>C++关键字之constexpr</title>
<url>/2022/12/06/C++%E5%85%B3%E9%94%AE%E5%AD%97%E4%B9%8Bconstexpr/</url>
<content><![CDATA[<p><code>constexpr</code>关键字的功能是使指定的常量表达式获得在程序编译阶段计算出结果的能力,而不必等到程序运行阶段。C++ 11 标准中,<code>constexpr</code>可用于修饰普通变量、函数(包括模板函数)以及类的构造函数。</p>
<h2 id="1-constexpr修饰普通变量"><a href="#1-constexpr修饰普通变量" class="headerlink" title="1.constexpr修饰普通变量"></a>1.<code>constexpr</code>修饰普通变量</h2><p>C++11 标准中,定义变量时可以用<code>constexpr</code>修饰,从而使该变量获得在编译阶段即可计算出结果的能力。<br>需要注意的是:使用 constexpr 修饰普通变量时,变量必须经过初始化且初始值必须是一个常量表达式。举个例子:</p>
<div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><iostream></span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span>{</span><br><span class="line"> <span class="keyword">constexpr</span> <span class="type">int</span> num = <span class="number">1</span> + <span class="number">2</span> + <span class="number">3</span>;</span><br><span class="line"> <span class="type">int</span> url[num] = {<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>,<span class="number">4</span>,<span class="number">5</span>,<span class="number">6</span>};</span><br><span class="line"> cout<< url[<span class="number">1</span>] << endl; <span class="comment">//2</span></span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div>
<h2 id="2-constexpr修饰函数"><a href="#2-constexpr修饰函数" class="headerlink" title="2.constexpr修饰函数"></a>2.<code>constexpr</code>修饰函数</h2><p><code>constexpr</code>还可以用于修饰函数的返回值,这样的函数又称为<strong>常量表达式函数</strong>。<br>但是<code>constexpr</code>并不能修饰任意函数的返回值,对于函数有以下几个要求:</p>
<p>一、<strong>整个函数的函数体中,除了可以包含<code>using</code>指令、<code>typedef</code>语句以及<code>static_assert</code>断言外,只能包含一条 <code>return</code>返回语句。</strong><br>对于C++11来说要求如上,但在C++14中解除了对<code>constexpr</code>函数的大部分限制。详情可以查阅相关文档。</p>
<div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">constexpr</span> <span class="type">int</span> <span class="title">func</span><span class="params">(<span class="type">int</span> x)</span> </span>{</span><br><span class="line"> <span class="type">int</span> ret = <span class="number">1</span> + <span class="number">2</span> + x;</span><br><span class="line"> <span class="keyword">return</span> ret;</span><br><span class="line">}<span class="comment">//错误写法</span></span><br><span class="line"><span class="function"><span class="keyword">constexpr</span> <span class="type">int</span> <span class="title">func</span><span class="params">(<span class="type">int</span> x)</span> </span>{</span><br><span class="line"> <span class="comment">//可以添加 using 执行、typedef 语句以及 static_assert 断言</span></span><br><span class="line"> <span class="keyword">return</span> <span class="number">1</span> + <span class="number">2</span> + x;</span><br><span class="line">}<span class="comment">//正确写法</span></span><br></pre></td></tr></table></figure></div>
<p>二、<strong>该函数必须有返回值,并且类型不能是<code>void</code></strong></p>
<div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">constexpr</span> <span class="type">void</span> <span class="title">func</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="comment">//函数体</span></span><br><span class="line">}<span class="comment">//错误,通过此函数无法获得一个常量</span></span><br></pre></td></tr></table></figure></div>
<p>三、<strong>函数在使用之前,必须有对应的定义语句。</strong><br>对于其他的函数来说,在使用时前给出声明即可,并不需要在给出定义。但是对于常量表达式函数来说,由于其返回值会在编译过程中进行计算,所以使用之前一定需要知道函数具体的定义。</p>
<div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><iostream></span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"></span><br><span class="line"><span class="comment">//普通函数的声明</span></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">noconst_func</span><span class="params">(<span class="type">int</span> x)</span></span>;</span><br><span class="line"><span class="comment">//常量表达式函数的声明</span></span><br><span class="line"><span class="function"><span class="keyword">constexpr</span> <span class="type">int</span> <span class="title">func</span><span class="params">(<span class="type">int</span> x)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">//常量表达式函数的定义</span></span><br><span class="line"><span class="function"><span class="keyword">constexpr</span> <span class="type">int</span> <span class="title">func</span><span class="params">(<span class="type">int</span> x)</span></span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="number">1</span> + <span class="number">2</span> + x;</span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="comment">//调用常量表达式函数</span></span><br><span class="line"> <span class="type">int</span> a[<span class="built_in">func</span>(<span class="number">3</span>)] = { <span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>,<span class="number">4</span> };</span><br><span class="line"> cout << a[<span class="number">2</span>] << endl;</span><br><span class="line"> <span class="comment">//调用普通函数</span></span><br><span class="line"> cout << <span class="built_in">noconst_func</span>(<span class="number">3</span>) << endl;</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br><span class="line"><span class="comment">//普通函数的定义</span></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">noconst_func</span><span class="params">(<span class="type">int</span> x)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="number">1</span> + <span class="number">2</span> + x;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div>
<p>四、<strong>返回的表达式必须是常量表达式</strong></p>
<div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><iostream></span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> num = <span class="number">3</span>;</span><br><span class="line"><span class="function"><span class="keyword">constexpr</span> <span class="type">int</span> <span class="title">func</span><span class="params">(<span class="type">int</span> x)</span></span>{</span><br><span class="line"> <span class="keyword">return</span> num + x;</span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="comment">//调用常量表达式函数</span></span><br><span class="line"> <span class="type">int</span> a[<span class="built_in">func</span>(<span class="number">3</span>)] = { <span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>,<span class="number">4</span> };</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}<span class="comment">//error:func(3) 的结果不是常量</span></span><br></pre></td></tr></table></figure></div>
<p>常量表达式函数的返回值必须是常量表达式的原因很简单,如果想在程序编译阶段获得某个函数返回的常量,则该函数的<code>return</code>语句中就不能包含程序运行阶段才能确定值的变量(上述代码中的<code>num</code>)。</p>
<p><strong>注:在常量表达式函数的<code>return</code>语句中,不能包含赋值的操作(例如 <code>return x=1</code> 在常量表达式函数中不允许的)。另外,用 <code>constexpr</code> 修改函数时,函数本身也是支持递归的。</strong></p>
<div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">constexpr</span> <span class="type">int</span> <span class="title">fact</span><span class="params">(<span class="type">int</span> n)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="comment">// 递归求阶乘</span></span><br><span class="line"> <span class="keyword">return</span> n == <span class="number">1</span> ? <span class="number">1</span> : n * <span class="built_in">fact</span>(n - <span class="number">1</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div>
<h2 id="3-constexpr修饰类的构造函数"><a href="#3-constexpr修饰类的构造函数" class="headerlink" title="3.constexpr修饰类的构造函数"></a>3.<code>constexpr</code>修饰类的构造函数</h2><p>对于 C++ 内置类型的数据,可以直接用<code>constexpr</code>修饰,但如果是自定义的数据类型(结构体或者类),是不可以直接用<code>constexpr</code>修饰的。</p>
<div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><iostream></span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"></span><br><span class="line"><span class="comment">//自定义类型的定义</span></span><br><span class="line"><span class="keyword">constexpr</span> <span class="keyword">struct</span> <span class="title class_">myType</span> {</span><br><span class="line"> <span class="type">const</span> <span class="type">char</span>* name;</span><br><span class="line"> <span class="type">int</span> age;</span><br><span class="line"> <span class="comment">//其它结构体成员</span></span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">constexpr</span> <span class="keyword">struct</span> <span class="title class_">myType</span> mt { <span class="string">"zhangsan"</span>, <span class="number">10</span> };</span><br><span class="line"> cout << mt.name << <span class="string">" "</span> << mt.age << endl;</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}<span class="comment">//error:constexpr不能修饰自定义类型</span></span><br></pre></td></tr></table></figure></div>
<p>为了自定义一个可以产生常量的类型,可以在该类型的内部添加一个常量构造函数。将上述错误例程修改如下:</p>
<div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><iostream></span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"></span><br><span class="line"><span class="comment">//自定义类型的定义</span></span><br><span class="line"><span class="keyword">struct</span> <span class="title class_">myType</span> {</span><br><span class="line"> <span class="function"><span class="keyword">constexpr</span> <span class="title">myType</span><span class="params">(<span class="type">char</span> *name,<span class="type">int</span> age)</span>:name(name),age(age){</span>};</span><br><span class="line"> <span class="type">const</span> <span class="type">char</span>* name;</span><br><span class="line"> <span class="type">int</span> age;</span><br><span class="line"> <span class="comment">//其它结构体成员</span></span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">constexpr</span> <span class="keyword">struct</span> <span class="title class_">myType</span> mt { <span class="string">"shiszhi"</span>, <span class="number">20</span> };</span><br><span class="line"> cout << mt.name << <span class="string">" "</span> << mt.age << endl;</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}<span class="comment">//output:shiszhi 20</span></span><br></pre></td></tr></table></figure></div>
<p>注意: <strong><code>constexpr</code>修饰类的构造函数时,要求该构造函数的函数体必须为空,且采用初始化列表的方式为各个成员赋值时,必须使用常量表达式。</strong><br>前面提到,<code>constexpr</code>可用于修饰函数,当然也可以修饰类内的成员函数,只要满足上述四个条件即可。不过需要注意的是,<strong>在C++11标准中,不支持用<code>constexpr</code>修饰带有<code>virtual</code>的成员方法。</strong></p>
<h2 id="4-constexpr修饰模板函数"><a href="#4-constexpr修饰模板函数" class="headerlink" title="4.constexpr修饰模板函数"></a>4.<code>constexpr</code>修饰模板函数</h2><p>C++11语法中,<code>constexpr</code>可以修饰模板函数,但由于模板中类型的不确定性,因此模板函数实例化后的函数是否符合常量表达式函数的要求也是不确定的。<br>针对这种情况,C++11标准规定,如果<code>constexpr</code>修饰的模板函数实例化结果不满足常量表达式函数的要求,则<code>constexpr</code>会被自动忽略,即该函数就等同于一个普通函数。</p>
<div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><iostream></span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"></span><br><span class="line"><span class="comment">//自定义类型的定义</span></span><br><span class="line"><span class="keyword">struct</span> <span class="title class_">myType</span> {</span><br><span class="line"> <span class="type">const</span> <span class="type">char</span>* name;</span><br><span class="line"> <span class="type">int</span> age;</span><br><span class="line"> <span class="comment">//其它结构体成员</span></span><br><span class="line">};</span><br><span class="line"><span class="comment">//模板函数</span></span><br><span class="line"><span class="function"><span class="keyword">template</span><<span class="keyword">typename</span> T></span></span><br><span class="line"><span class="function"><span class="keyword">constexpr</span> T <span class="title">display</span><span class="params">(T t)</span></span>{</span><br><span class="line"> <span class="keyword">return</span> t;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">struct</span> <span class="title class_">myType</span> stu{<span class="string">"shiszhi"</span>, <span class="number">20</span>};</span><br><span class="line"> <span class="comment">//普通函数,stu未定义常量表达式构造函数</span></span><br><span class="line"> <span class="keyword">struct</span> <span class="title class_">myType</span> ret = <span class="built_in">display</span>(stu);</span><br><span class="line"> cout << ret.name << <span class="string">" "</span> << ret.age << endl;</span><br><span class="line"> <span class="comment">//常量表达式函数</span></span><br><span class="line"> <span class="keyword">constexpr</span> <span class="type">int</span> ret1 = <span class="built_in">display</span>(<span class="number">20</span>);</span><br><span class="line"> cout << ret1 << endl;</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div>
<h2 id="5-总结"><a href="#5-总结" class="headerlink" title="5.总结"></a>5.总结</h2><p>《Effective Modern C++》中提到:只要有可能使用<code>constexpr</code>,就使用它。比起非<code>constexpr</code>对象或者<code>constexpr</code>函数而言,<code>constexpr</code>对象或者<code>constexpr</code>函数可以用在一个作用域更广的语境中。</p>
]]></content>
<tags>
<tag>C++</tag>
</tags>
</entry>
<entry>
<title>FFmpeg之FFmpeg简介</title>
<url>/2022/12/07/FFmpeg%E4%B9%8BFFmpeg%E7%AE%80%E4%BB%8B/</url>
<content><![CDATA[<h2 id="1-FFmpeg定义"><a href="#1-FFmpeg定义" class="headerlink" title="1. FFmpeg定义"></a>1. FFmpeg定义</h2><p>FFmpeg是一款音视频编解码工具,同时也是一组音视频编解码开发套件,为开发者提供了丰富的音视频处理的调用接口。<br>FFmpeg提供了多种媒体格式的封装和解封装,包括多种音视频解码、多种协议的流媒体、多种色彩格式转换、多种采样率转换、多种码率转换等;FFmpeg框架提供了多种丰富的插件模块,包含封装与解封装的插件、编码与解码的插件等。</p>
<h2 id="2-FFmpeg的基本组成"><a href="#2-FFmpeg的基本组成" class="headerlink" title="2. FFmpeg的基本组成"></a>2. FFmpeg的基本组成</h2><p>FFmpeg框架的基本组成包含<code>AVFormat</code>、<code>AVCodec</code>、<code>AVFilter</code>、<code>AVDevice</code>、<code>AVUtil</code>等模块,结构图如下:<br><img lazyload src="/images/loading.svg" data-src="/2022/12/07/FFmpeg%E4%B9%8BFFmpeg%E7%AE%80%E4%BB%8B/2-1.png" alt="图2-1 FFmpeg基本组成模块"></p>
<ol>
<li><strong>FFmpeg的封装模块AVFormat</strong><br>AVFormat中实现了目前多媒体领域中的绝大多数媒体封装格式,包括封装和解封装,如MP4、FLV、KV、TS等文件封装格式,RTMP、RTSP、MMS、HLS等网络协议封装格式。<br>FFmpeg是否支持某种媒体封装格式,取决于编译时是否包含了该格式的封装库、根据实际需求可以进行媒体封装格式的扩展,增加自己定制的封装格式。</li>
<li><strong>FFmpeg的编解码模块AVCodec</strong><br>AVCodec中实现了目前多媒体领域绝大多数常用的编解码格式,既支持编码,也支持解码。<br>AVCodec除了支持MPEG4、AAC、MJPEG等自带的媒体编解码格式之外,还支持第三方编解码器。<br>如果希望增加自己的编码格式,或者硬件编解码,则需要在AVCodec中增加相应的编解码模块。</li>
<li><strong>FFmpeg的滤镜模块AVFilter</strong><br>AVFilter库提供了一个通用的音频、视频、字幕等滤镜处理框架。在AVFilter中,滤镜框架可以有多个输入和多个输出。可以参考以下示例:<br><img lazyload src="/images/loading.svg" data-src="/2022/12/07/FFmpeg%E4%B9%8BFFmpeg%E7%AE%80%E4%BB%8B/2-2.png" alt="图2-2 AVFilter使用样例"><br>上图中所示的滤镜处理将输入的视频切割成了两部分流,一部分流抛给crop滤镜与vflip滤镜处理模块进行操作,另一部分保持原样,当crop滤镜与vflip滤镜处理操作完成之后,将流合并到原有的overlay图层中,并显示在最上一层,输出新的视频。对应的命令如下:</li>
</ol>
<div class="highlight-container" data-rel="Shell"><figure class="iseeu highlight shell"><table><tr><td class="code"><pre><span class="line">ffmpeg -i INPUT -vf "split [main][tmp]; [tmp] crop=iw:ih/2:0:0, vflip [flip]; [main][flip] overlay=0:H/2" OUTPUT</span><br></pre></td></tr></table></figure></div>
<p>下面详细说明一下规则,具体如下:</p>
<ul>
<li>相同的Filter线性链之间用逗号分隔</li>
<li>不同的Filter线性链之间用分号分隔</li>
</ul>
<p>在上述示例中,crop与vflip使用的是同一个滤镜处理的线性链,split滤镜和overlay滤镜使用的是另外一个线性链,一个线性链与另一个线性链汇合时时通过方括号<code>[]</code>括起来的标签进行标识的。<br>4. <strong>FFmpeg的视频图像转换计算模块swscale</strong><br>swscale模块提供高级别的图像转换API,允许其进行图像缩放和像素格式转换。<br>例如:将图像从1080P转换成720P或者480P等的缩放,或者将图像数据从YUV420P转换成YUYV等的转换。<br>5. <strong>FFmpeg的音频转换计算模块swresample</strong><br>swresample模块提供了高级别的音频重采样API。例如它允许操作音频采样、音频通道布局转换与布局调整。</p>
<h2 id="3-FFmpeg的编解码工具ffmpeg"><a href="#3-FFmpeg的编解码工具ffmpeg" class="headerlink" title="3. FFmpeg的编解码工具ffmpeg"></a>3. <strong>FFmpeg的编解码工具ffmpeg</strong></h2><p>一个简单的例子:</p>
<div class="highlight-container" data-rel="Shell"><figure class="iseeu highlight shell"><table><tr><td class="code"><pre><span class="line">ffmpeg -i input.mp4 output.avi</span><br></pre></td></tr></table></figure></div>
<p>这条简单的命令通过<code>-i</code>指定输入源,然后进行转码和转封装,将结果输出到<code>output.avi</code>中,这条命令主要进行以下流程:</p>
<ol>
<li>获得输入源<code>input.mp4</code></li>
<li>转码、转封装</li>
<li>输出文件<code>output.avi</code></li>
</ol>
<p>下面这条命令和上面的命令具有类似的作用:</p>
<div class="highlight-container" data-rel="Shell"><figure class="iseeu highlight shell"><table><tr><td class="code"><pre><span class="line">ffmpeg -i input.mp4 -f avi output.dat</span><br></pre></td></tr></table></figure></div>
<p>此命令通过<code>-f</code>选项指定输出文件的容器格式,两条命令输出的文件名不同,除此之外完全相同。</p>
<p>ffmpeg的主要工作流程如下:</p>
<ol>
<li>解封装(Demuxing)</li>
<li>解码(Decoding)</li>
<li>编码(Encoding)</li>
<li>封装(Muxing)</li>
</ol>
<p>其中需要经过六个步骤,分别为:</p>
<ol>
<li>读取输入源</li>
<li>进行音视频的解封装</li>
<li>解码每一帧音视频数据</li>
<li>编码每一帧音视频数据</li>
<li>进行音视频的重新封装</li>
<li>输出到目标</li>
</ol>
<p><img lazyload src="/images/loading.svg" data-src="/2022/12/07/FFmpeg%E4%B9%8BFFmpeg%E7%AE%80%E4%BB%8B/3-1.png" alt="图3-1 ffmpeg转码工作流程"><br>在上图所示的工作流程中可以看出:ffmpeg首先读取输入源,然后通过Demuxer进行解封装(通过调用<code>libavformat</code>中的接口实现),然后通过Decoder进行解码(通过调用<code>libavcodec</code>中的接口实现),然后通过Encoder将对应的数据进行编码(通过调用<code>libavcodec</code>中的接口实现),接下来将编码后的音视频数据包通过Muxer进行封装(通过调用<code>libavformat</code>中的接口实现),输出称为输出流。</p>
<h2 id="4-FFmpeg的播放器ffplay"><a href="#4-FFmpeg的播放器ffplay" class="headerlink" title="4. FFmpeg的播放器ffplay"></a>4. FFmpeg的播放器ffplay</h2><p>ffplay为FFmpeg提供的播放器,使用FFmpeg的<code>avformat</code>与<code>avcodec</code>可以播放各种媒体文件或流。如果要使用ffplay,需要系统有各种SDL(Simple DirectMedia Layer)提供基础支撑。<br>ffmpeg提供了音视频显示和播放相关的图像信息、音频的波形信息等。</p>
<h2 id="5-Ffmpeg的多媒体分析器ffprobe"><a href="#5-Ffmpeg的多媒体分析器ffprobe" class="headerlink" title="5. Ffmpeg的多媒体分析器ffprobe"></a>5. Ffmpeg的多媒体分析器ffprobe</h2><p>ffprobe可以从媒体文件或者媒体流中获得想要了解的媒体信息,比如音频的参数、视频的参数、媒体容器的参数信息等。<br>其可以帮助分析某个媒体容器中的音频是什么编码格式、视频是什么编码格式,同时可以得到媒体文件中媒体的总时长、复合码率等信息。除此之外也可以分析媒体文件中每个包的长度、包的类型、帧的信息等。</p>
<h2 id="6-FFmpeg编码支持与定制"><a href="#6-FFmpeg编码支持与定制" class="headerlink" title="6. FFmpeg编码支持与定制"></a>6. FFmpeg编码支持与定制</h2><h3 id="6-1-FFmpeg的编解码器支持"><a href="#6-1-FFmpeg的编解码器支持" class="headerlink" title="6.1. FFmpeg的编解码器支持"></a>6.1. FFmpeg的编解码器支持</h3><p>编码器支持:</p>
<div class="highlight-container" data-rel="Shell"><figure class="iseeu highlight shell"><table><tr><td class="code"><pre><span class="line">ffmpeg -encoders</span><br></pre></td></tr></table></figure></div>
<p>解码器支持:</p>
<div class="highlight-container" data-rel="Shell"><figure class="iseeu highlight shell"><table><tr><td class="code"><pre><span class="line">ffmpeg -decoders</span><br></pre></td></tr></table></figure></div>
<h3 id="6-2-FFmpeg的封装及解封装支持"><a href="#6-2-FFmpeg的封装及解封装支持" class="headerlink" title="6.2. FFmpeg的封装及解封装支持"></a>6.2. FFmpeg的封装及解封装支持</h3><p>封装支持</p>
<div class="highlight-container" data-rel="Shell"><figure class="iseeu highlight shell"><table><tr><td class="code"><pre><span class="line">ffmpeg -muxers</span><br></pre></td></tr></table></figure></div>
<p>解封装支持</p>
<div class="highlight-container" data-rel="Shell"><figure class="iseeu highlight shell"><table><tr><td class="code"><pre><span class="line">ffmpeg -demuxers</span><br></pre></td></tr></table></figure></div>
<h3 id="6-3-FFmpeg的通信协议支持"><a href="#6-3-FFmpeg的通信协议支持" class="headerlink" title="6.3. FFmpeg的通信协议支持"></a>6.3. FFmpeg的通信协议支持</h3><div class="highlight-container" data-rel="Shell"><figure class="iseeu highlight shell"><table><tr><td class="code"><pre><span class="line">ffmpeg -protocols</span><br></pre></td></tr></table></figure></div>
<h2 id="7-小结"><a href="#7-小结" class="headerlink" title="7.小结"></a>7.小结</h2><p>本文主要介绍了FFmpeg的一些基础知识,重点在整个音视频处理流程以及对流媒体传输协议的支持。</p>
]]></content>
<tags>
<tag>FFmpeg</tag>
</tags>
</entry>
<entry>
<title>WebRTC基础之RTP/RTCP</title>
<url>/2022/12/08/WebRTC%E5%9F%BA%E7%A1%80%E4%B9%8BRTP-RTCP/</url>
<content><![CDATA[<h2 id="1-RTP-Header扩展-Transport-sequence-number"><a href="#1-RTP-Header扩展-Transport-sequence-number" class="headerlink" title="1. RTP Header扩展 - Transport sequence number"></a>1. RTP Header扩展 - Transport sequence number</h2><p>下面是RTP固定报头结构:</p>
<div class="highlight-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="code"><pre><span class="line"> 0 1 2 3</span><br><span class="line"> 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line">|V=2|P|X| CC |M| PT | sequence number |</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line">| timestamp |</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line">| synchronization source (SSRC) identifier |</span><br><span class="line">+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+</span><br><span class="line">| contributing source (CSRC) identifiers |</span><br><span class="line">| .... |</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br></pre></td></tr></table></figure></div>
<p>报头中的<code>sequence number</code>字段用于记录RTP包的序列号。一般情况下一个传输通道(PeerConnection)只包含一路视频流,这个<code>sequence number</code>可以满足大多数需求。但某些情况下,一个连接可能传输多个视频流,这些视频流复用一个传输通道。此时一个PeerConnection可能包含多个不同的视频流。在这些视频流中,RTP报头的<code>sequence number</code>是单独计数的。</p>
<p>假设同一个PeerConnection下,我们传输两个视频流A与B,它们的RTP包记为Ra(n),Rb(n),n表示<code>sequence number</code>。<br>这样我们观察同一个PeerConnection下,视频流按如下形式传输: Ra(1),Ra(2),Rb(1),Rb(2),Ra(3),Ra(4),Rb(3),Rb(4)</p>
<p>在对某条PeerConnection进行带宽估计时,我们需要估计整条PeerConnection下所有视频流,而不是单独某个流。这样为了做一个RTP session(传输层)级别的带宽估计,原有各个流的<code>sequence number</code>就不能满足我们需要了,需要使用RTP报头扩展用于记录<code>transport sequence number</code>,同一个PeerConnection连接下的所有流的<code>transport sequence number</code>,使用统一的计数器进行计数,方便进行同一个PeerConnection下的带宽估计。</p>
<p>仍使用上面的例子,视频流A与B,它们的RTP包记为Ra(n,m),Rb(n,m),n表示<code>sequence number</code>,m表示<code>transport sequence number</code>。<br>这样同一个PeerConnection下,视频流按如下形式传输: Ra(1,1),Ra(2,2),Rb(1,3),Rb(2,4),Ra(3,5),Ra(4,6),Rb(3,7),Rb(4,8)</p>
<p>RTP <code>transport sequence number</code>报头定义如下:</p>
<div class="highlight-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="code"><pre><span class="line"> 0 1 2 3</span><br><span class="line"> 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line">| 0xBE | 0xDE | length=1 |</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line">| ID | L=1 |transport-wide sequence number | zero padding |</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br></pre></td></tr></table></figure></div>
<p>由于属于RTP报头扩展,所以可以看到以<code>0xBEDE</code>固定字段开头,表示One-Byte Header类型的扩展。<code>transport sequence number</code>占两个字节,存储在One-Byte Header的<code>Extension data</code>字段。由于按4字节对齐,所以还有值为0的填充数据。</p>
<h2 id="2-TransportFeedback-RTCP"><a href="#2-TransportFeedback-RTCP" class="headerlink" title="2. TransportFeedback RTCP"></a>2. TransportFeedback RTCP</h2><p>Transport-cc中,收流客户端通过TransportFeedback RTCP向发送端反馈收到的各个RTP包的到达时间信息。<br>下面是TransportFeedback包的格式:</p>
<div class="highlight-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="code"><pre><span class="line"> 0 1 2 3</span><br><span class="line"> 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1</span><br><span class="line"> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line"> |V=2|P| FMT=15 | PT=205 | length |</span><br><span class="line"> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line"> 0 | SSRC of packet sender |</span><br><span class="line"> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line"> 4 | SSRC of media source |</span><br><span class="line"> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line"> 8 | base sequence number | packet status count |</span><br><span class="line"> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line">12 | reference time | fb pkt. count |</span><br><span class="line"> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line">16 | packet chunk | packet chunk |</span><br><span class="line"> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line"> . .</span><br><span class="line"> . .</span><br><span class="line"> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line"> | packet chunk | recv delta | recv delta |</span><br><span class="line"> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line"> . .</span><br><span class="line"> . .</span><br><span class="line"> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line"> | recv delta | recv delta | zero padding |</span><br><span class="line"> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br></pre></td></tr></table></figure></div>
<ul>
<li><code>base sequence number</code>:2字节,TransportFeedback包中记录的第一个RTP包的<code>transport sequence number</code>,在反馈的各个TransportFeedback RTCP包中,这个字段不一定是递增的,也有可能比之前的RTCP包小</li>
<li><code>packet status count</code>:2字节,表示这个TransportFeedback包记录了多少个RTP包信息,这些RTP的<code>transport sequence number</code>以<code>base sequence number</code>为基准,比如记录的第一个RTP包的<code>transport sequence number</code>为<code>base sequence number</code>,那么记录的第二个RTP包<code>transport sequence number</code>为<code>base sequence number+1</code></li>
<li><code>reference time</code>:3字节,表示参考时间,以64ms为单位,RTCP包记录的RTP包到达时间信息以这个<code>reference time</code>为基准进行计算</li>
<li><code>feedback packet count</code>:1字节,用于计数发送的每个TransportFeedback包,相当于RTCP包的序列号。可用于检测TransportFeedback包的丢包情况</li>
<li><code>packet chunk</code>:2字节,记录RTP包的到达状态,记录的这些RTP包<code>transport sequence number</code>通过<code>base sequence number</code>计算得到</li>
<li><code>recv delta</code>: 1字节,对于<code>packet received</code>状态的包,也就是收到的RTP包,在<code>recv delta</code>列表中添加对应的的到达时间间隔信息,用于记录RTP包到达时间信息。通过前面的<code>reference time</code>以及<code>recv delta</code>信息,我们就可以得到RTP包到达时间</li>
</ul>
<h3 id="2-1-Packet-Chunk"><a href="#2-1-Packet-Chunk" class="headerlink" title="2.1 Packet Chunk"></a>2.1 Packet Chunk</h3><p>首先先了解下RTP包状态,目前定义了如下四种状态,每个状态值2bits,用来标识RTP包的到达状态,以及与前面RTP包的时间间隔大小信息:</p>
<ul>
<li>00-Packet not received</li>
<li>01-Packet received, small delta</li>
<li>10-Packet received, large or negative delta</li>
<li>11-[Reserved]<br><code>packet chunk</code>有两种类型,<code>Run length chunk</code>(行程长度编码数据块)与<code>Status vector chunk</code>(状态矢量编码数据块),对应<code>packet chunk</code>结构的两种编码方式。packet chunk的第一个bit标识chunk类型。</li>
</ul>
<h4 id="2-1-1-Run-length-chunk"><a href="#2-1-1-Run-length-chunk" class="headerlink" title="2.1.1 Run length chunk"></a>2.1.1 Run length chunk</h4><p>Run length编码是一种数据压缩算法,其基本思想是将重复且连续出现多次的字符使用“连续出现次数+字符”来描述,例如:aaabbbcdddd通过Run length编码就可以压缩为3a3bc4d。Run length chunk中使用了Run length编码标识连续多个相同状态的包。<br>Run length chunk第一bit为0,后面跟着packet status以及run length。格式如下:</p>
<div class="highlight-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="code"><pre><span class="line"> 0 1</span><br><span class="line"> 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line">|T| S | Run Length |</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br></pre></td></tr></table></figure></div>
<ul>
<li><code>chunk type (T)</code>:1 bit,值为0</li>
<li><code>packet status symbol (S)</code>:2 bits,标识包状态</li>
<li><code>run length (L)</code>:13 bits,行程长度,标识有多少个连续包为相同状态</li>
</ul>
<p>举例如下:</p>
<div class="highlight-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="code"><pre><span class="line"> 0 1</span><br><span class="line"> 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line">|0|0 0|0 0 0 0 0 1 1 0 1 1 1 0 1|</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br></pre></td></tr></table></figure></div>
<p><code>packet status</code>为00,由前面包状态可知为<code>Packet not received</code>状态,<code>run length</code>为221(11011101),说明连续有221个包为<code>Packet not received</code>状态。</p>
<h4 id="2-1-2-Status-Vector-Chunk"><a href="#2-1-2-Status-Vector-Chunk" class="headerlink" title="2.1.2 Status Vector Chunk"></a>2.1.2 Status Vector Chunk</h4><p>第一bit为1,后面跟着symbol size以及symbol list。格式如下:</p>
<div class="highlight-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="code"><pre><span class="line"> 0 1</span><br><span class="line"> 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line">|T|S| symbol list |</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br></pre></td></tr></table></figure></div>
<ul>
<li><code>chunk type (T)</code>:1 bit,值为1</li>
<li><code>symbol size(S)</code>:1 bit,为0表示只包含<code>packet not received</code>以及<code>packet received</code>状态,每个状态使用1bit表示,这样后面14bits的<code>symbol list</code>能标识14个包的状态。为1表示使用2bits来标识包状态,这样<code>symbol list</code>中我们只能标识7个包的状态</li>
<li><code>symbol list</code>:14 bits,标识一系列包的状态, 总共能标识7或14个包的状态</li>
</ul>
<p>举例如下:</p>
<ol>
<li>例一:</li>
</ol>
<div class="highlight-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="code"><pre><span class="line"> 0 1</span><br><span class="line"> 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line">|1|0|0 1 1 1 1 1 0 0 0 1 1 1 0 0|</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br></pre></td></tr></table></figure></div>
<p><code>symbol size</code>为0,这样能标识14个包的状态。第一个包状态为<code>packet not received</code>,接着后面5个包状态为<code>packet received</code>,再接着三个包状态为<code>packet not received</code>,再接着三个包状态为<code>packet received</code>,最后两个包状态为<code>packet not received</code>。<br>2. 例二:</p>
<div class="highlight-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="code"><pre><span class="line"> 0 1</span><br><span class="line"> 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line">|1|1|0 0 1 1 0 1 0 1 0 1 0 0 0 0|</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br></pre></td></tr></table></figure></div>
<p><code>symbol size</code>为1,这样只能标识7个包的状态。第一个包为<code>packet not received</code>状态,第二个包为<code>packet received, w/o timestamp</code>状态,再接着三个包为<code>packet received</code>状态,最后两个包为<code>packet not received</code>状态。</p>
<h3 id="2-2-Receive-Delta"><a href="#2-2-Receive-Delta" class="headerlink" title="2.2 Receive Delta"></a>2.2 Receive Delta</h3><p>以250us(0.25ms)为单位,表示RTP包到达时间与前面一个RTP包到达时间的间隔,对于记录的第一个RTP包,该包的时间间隔是相对reference time的。</p>
<ul>
<li>如果在<code>packet chunk</code>记录了一个<code>Packet received, small delta</code>状态的包,那么就会在<code>receive delta</code>列表中添加一个无符号1字节长度<code>receive delta</code>,无符号1字节取值范围[0,255],由于<code>Receive Delta</code>以0.25ms为单位,故此时<code>Receive Delta</code>取值范围[0, 63.75]ms</li>
<li>如果在<code>packet chunk</code>记录了一个<code>Packet received, large or negative delta</code>状态的包,那么就会在<code>receive delta</code>列表中添加一个有符号2字节长度的<code>receive delta</code>,范围[-8192.0, 8191.75]ms</li>
<li>如果时间间隔超过了最大限制,那么就会构建一个新的TransportFeedback RTCP包,由于<code>reference time</code>长度为3字节,所以目前的包中3字节长度能够覆盖很大范围了<br>以上说明总结起来就是:对于收到的RTP包在TransportFeedback RTCP <code>receive delta</code>列表中通过时间间隔记录到达时间,如果与前面包时间间隔小,那么使用1字节表示,否则2字节,超过最大取值范围,就另起新RTCP包了。<br>对于<code>Packet received, small delta</code>状态的包来说,<code>receive delta</code>最大值63.75ms,那么一秒时间跨度最少能标识1000/63.75~=16个包。由于<code>receive delta</code>为250us的倍数,所以一秒时间跨度最多能标识4000个包。<br><code>packet chunk</code>以及<code>receive delta</code>的使用是为了尽可能减小RTCP包大小。<code>packet chunk</code>用到了不同编码方式,对于收到的RTP包才添加到达时间信息,而且是通过时间间隔的方式记录到达时间。</li>
</ul>
]]></content>
<tags>
<tag>WebRTC基础</tag>
</tags>
</entry>
<entry>
<title>WebRTC源码分析之任务队列-TaskQueueBase</title>
<url>/2022/12/06/WebRTC%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90%E4%B9%8B%E4%BB%BB%E5%8A%A1%E9%98%9F%E5%88%97-TaskQueueBase/</url>
<content><![CDATA[<h2 id="1-前言"><a href="#1-前言" class="headerlink" title="1. 前言"></a>1. 前言</h2><p>任务队列 <code>TaskQueue</code>是WebRTC中非常核心的一部分,其主要功能是将任务投递到某一个线程执行。<code>TaskQueue</code>是WebRTC中进程交互很重要的方式。<br>本文主要分析 <code>TaskQueue</code>中最重要的基类 <code>TaskQueueBase</code>。<br><code>TaskQueue</code>机制中涉及的其他类后续会继续补充。<br>WebRTC版本:M84</p>
<h2 id="2-正文"><a href="#2-正文" class="headerlink" title="2. 正文"></a>2. 正文</h2><h3 id="2-0-预说明:线程局部存储"><a href="#2-0-预说明:线程局部存储" class="headerlink" title="2.0. 预说明:线程局部存储"></a>2.0. 预说明:线程局部存储</h3><p>任务队列不可避免地涉及到多线程的知识,此处仅简单介绍一下 <code>TaskQueueBase</code>部分涉及到的相关内容以及函数。</p>
<h4 id="2-0-1-线程局部存储概念"><a href="#2-0-1-线程局部存储概念" class="headerlink" title="2.0.1. 线程局部存储概念"></a>2.0.1. 线程局部存储概念</h4><p>线程局部存储(TLS,Thread Local Storage)是线程私有的全局变量。普通的全局变量是多个线程共享的,一个线程对其修改,所有线程均可见。而线程局部存储是线程私有的,每个线程都有自己的一个副本,某个线程对其所做修改只会修改自己的副本,不会影响到其他线程的副本。</p>
<h4 id="2-0-2-pthread-key-t"><a href="#2-0-2-pthread-key-t" class="headerlink" title="2.0.2. pthread_key_t"></a>2.0.2. <code>pthread_key_t</code></h4><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="code"><pre><span class="line">ABSL_CONST_INIT <span class="type">pthread_key_t</span> g_queue_ptr_tls = <span class="number">0</span>;</span><br></pre></td></tr></table></figure></div>
<p>上述声明中 <code>pthread_key_t</code>为前一小节提到的线程局部存储类型</p>
<h4 id="2-0-3-Tls相关函数说明"><a href="#2-0-3-Tls相关函数说明" class="headerlink" title="2.0.3. Tls相关函数说明"></a>2.0.3. <code>Tls</code>相关函数说明</h4><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="type">int</span> <span class="title">pthread_key_create</span><span class="params">(<span class="type">pthread_key_t</span> *key, <span class="type">void</span> (*destructor)(<span class="type">void</span>*))</span></span>;</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">pthread_setspecific</span><span class="params">(<span class="type">pthread_key_t</span> key, <span class="type">const</span> <span class="type">void</span> *value)</span></span>;</span><br><span class="line"><span class="function"><span class="type">void</span>* <span class="title">pthread_getspecific</span><span class="params">(<span class="type">pthread_key_t</span> key)</span></span>;</span><br></pre></td></tr></table></figure></div>
<ul>
<li>调用 <code>pthread_key_create</code>函数可以创建 <code>pthread_key_t</code>变量。该函数需要提供两个参数,第一个参数是需要创建的 <code>pthread_key_t</code>变量,第二个参数是一个释放函数,在线程释放其 <code>Tls</code>的时候被调用。如果函数指针被设置成 <code>nullptr</code>,那么系统将调用默认释放函数。该函数成功创建变量时返回0,其他任何返回值均代表出现异常。</li>
<li>当线程中需要存储值的时候,可以调用 <code>pthread_setspcific</code>函数。该函数需要提供两个参数,第一个参数为前面声明的 <code>pthread_key_t</code>变量,第二个为 <code>void*</code>变量,也就意味着可以存储任何类型的值。</li>
<li>如果需要取出所存储的值,调用 <code>pthread_getspecific</code>函数。该函数的参数为前面提到的 <code>pthread_key_t</code>变量,该函数返回 <code>void *</code>类型的值,如需使用,则要进行强制类型转换。</li>
</ul>
<h4 id="2-0-4-InitializeTls"><a href="#2-0-4-InitializeTls" class="headerlink" title="2.0.4. InitializeTls"></a>2.0.4. <code>InitializeTls</code></h4><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">InitializeTls</span><span class="params">()</span></span>{</span><br><span class="line"> <span class="built_in">RTC_CHECK</span>(<span class="built_in">pthread_key_create</span>(&g_queue_ptr_tls, <span class="literal">nullptr</span>)==<span class="number">0</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div>
<p><code>RTC_CHECK()</code>为WebRTC中的断言宏,当其内部参数为 <code>false</code>时,抛出异常,直接中止当前进程。</p>
<h4 id="2-0-5-GetQueuePtrTls"><a href="#2-0-5-GetQueuePtrTls" class="headerlink" title="2.0.5. GetQueuePtrTls"></a>2.0.5. <code>GetQueuePtrTls</code></h4><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="type">pthread_key_t</span> <span class="title">GetQueuePtrTls</span><span class="params">()</span></span>{</span><br><span class="line"> <span class="type">static</span> <span class="type">pthread_once_t</span> init_once = PTHREAD_ONCE_INIT;</span><br><span class="line"> <span class="built_in">RTC_CHECK</span>(<span class="built_in">pthread_once</span>(&init_once, &InitializeTls) == <span class="number">0</span>);</span><br><span class="line"> <span class="keyword">return</span> g_queue_ptr_tls; </span><br><span class="line">}</span><br></pre></td></tr></table></figure></div>
<p>调用 <code>pthread_once()</code>函数,传入初值为 <code>PTHREAD_ONCE_INIT</code>的 <code>init_once</code>变量,结合 <code>RTC_CHECK()</code>断言保证 <code>InitializeTls()</code>函数仅执行一次。</p>
<h4 id="2-0-6-说明"><a href="#2-0-6-说明" class="headerlink" title="2.0.6. 说明"></a>2.0.6. 说明</h4><ul>
<li>上述所有函数为全局函数 不属于任何一个对象</li>
<li>上述所有函数只在 <code>WEBRTC_POSIX</code>宏定义的前提下被定义,否则不被定义</li>
<li>定义这些函数是为了供 <code>TaskQueueBase</code>类使用</li>
</ul>
<h3 id="2-1-QueuedTask类"><a href="#2-1-QueuedTask类" class="headerlink" title="2.1. QueuedTask类"></a>2.1. <code>QueuedTask</code>类</h3><blockquote>
<p>Path:<br>api\task_queue\queued_task.h</p>
</blockquote>
<h4 id="2-1-1-类声明"><a href="#2-1-1-类声明" class="headerlink" title="2.1.1. 类声明"></a>2.1.1. 类声明</h4><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="keyword">namespace</span> webrtc{</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">QueuedTask</span>{</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> <span class="keyword">virtual</span> ~<span class="built_in">QueuedTask</span>() = <span class="keyword">default</span>;</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">bool</span> <span class="title">Run</span><span class="params">()</span> </span>= <span class="number">0</span>;</span><br><span class="line">};<span class="comment">// class QueuedTask</span></span><br><span class="line">} <span class="comment">// namespace webrtc</span></span><br></pre></td></tr></table></figure></div>
<p><code>QueueTask</code>是一个抽象类,为需要异步执行的任务提供基本接口。<br>这个接口由一个单一的函数 <code>Run()</code>组成,它会在目标队列上执行,<code>Run()</code>具体实现的功能由其子类决定。</p>
<h3 id="2-2-TaskQueueBase类"><a href="#2-2-TaskQueueBase类" class="headerlink" title="2.2. TaskQueueBase类"></a>2.2. <code>TaskQueueBase</code>类</h3><blockquote>
<p>Path:<br>api\task_queue\task_queue_base.h<br>api\task_queue\task_queue_base.cc</p>
</blockquote>
<h4 id="2-2-1-类声明"><a href="#2-2-1-类声明" class="headerlink" title="2.2.1. 类声明"></a>2.2.1. 类声明</h4><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="keyword">namespace</span> webrtc{</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">RTC_LOCKABLE</span> RTC_EXPORT TaskQueueBase {</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">Delete</span><span class="params">()</span> </span>= <span class="number">0</span>;</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">PostTask</span><span class="params">(std::unique_ptr<QueuedTask> task)</span> </span>= <span class="number">0</span>;</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">PostDelayedTask</span><span class="params">(std::unique_ptr<QueuedTask> task,</span></span></span><br><span class="line"><span class="params"><span class="function"> <span class="type">uint32_t</span> milliseconds)</span> </span>= <span class="number">0</span>;</span><br><span class="line"> <span class="function"><span class="type">static</span> TaskQueueBase* <span class="title">Current</span><span class="params">()</span></span>;</span><br><span class="line"> <span class="function"><span class="type">bool</span> <span class="title">IsCurrent</span><span class="params">()</span> <span class="type">const</span> </span>{ <span class="keyword">return</span> Current == <span class="keyword">this</span>; }</span><br><span class="line"> <span class="keyword">protected</span>:</span><br><span class="line"> <span class="keyword">class</span> <span class="title class_">CurrentTaskQueueSetter</span>{</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">explicit</span> <span class="title">CurrentTaskQueueSetter</span><span class="params">(TaskQueueBase* task_queue)</span></span>;</span><br><span class="line"> <span class="built_in">CurrentTaskQueueSetter</span>(<span class="type">const</span> CurrentTaskQueueSetter&) = <span class="keyword">delete</span>;</span><br><span class="line"> CurrentTaskQueueSetter& <span class="keyword">operator</span>=(<span class="type">const</span> CurrentTaskQueueSetter&) = <span class="keyword">delete</span>;</span><br><span class="line"> ~<span class="built_in">CurrentTaskQueueSetter</span>();</span><br><span class="line"> <span class="keyword">private</span>:</span><br><span class="line"> TaskQueueBase* <span class="type">const</span> previous_;</span><br><span class="line"> };<span class="comment">// class CurrentTaskQueueSetter</span></span><br><span class="line"> <span class="keyword">virtual</span> ~<span class="built_in">TaskQueueBase</span>() = <span class="keyword">default</span>;</span><br><span class="line"> </span><br><span class="line">};<span class="comment">// class TaskQueueBase</span></span><br><span class="line">} <span class="comment">// namespace webrtc</span></span><br></pre></td></tr></table></figure></div>
<h4 id="2-2-2-函数实现及相关说明"><a href="#2-2-2-函数实现及相关说明" class="headerlink" title="2.2.2. 函数实现及相关说明"></a>2.2.2. 函数实现及相关说明</h4><h5 id="2-2-2-1-Delete"><a href="#2-2-2-1-Delete" class="headerlink" title="2.2.2.1. Delete"></a>2.2.2.1. <code>Delete</code></h5><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">Delete</span><span class="params">()</span> </span>= <span class="number">0</span>;</span><br></pre></td></tr></table></figure></div>
<p>纯虚函数,基类中无需实现。<br>调用此函数开始销毁任务队列,此函数在返回时需要确保没有任务正在运行,也没有新的任务能够在任务队列中启动。<br>同时此函数负责释放对象,释放动作可以在 <code>Delete</code>期间同步进行,也可以在 <code>Delete</code>之后异步进行。<br>销毁某个任务队列对不在此任务队列中的任务不产生任何影响,这些任务也不会因为其他的任务队列销毁而调用任何函数。<br>在任务队列上执行的任务不可以调用 <code>Delete</code>,但是可以调用其他的函数,比如 <code>PostTask</code>。</p>
<h5 id="2-2-2-2-PostTask"><a href="#2-2-2-2-PostTask" class="headerlink" title="2.2.2.2. PostTask"></a>2.2.2.2. <code>PostTask</code></h5><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">PostTask</span><span class="params">(std::unique_ptr<QueuedTask> task)</span> </span>= <span class="number">0</span>;</span><br></pre></td></tr></table></figure></div>
<p>纯虚函数,基类中无需实现。<br>此函数用于安排一个即时任务的处理,这些任务按照先进先出的顺序执行(所以称为任务队列)。<br>如果 <code>task->Run()</code>的返回值是 <code>true</code>,代表任务成功执行,任务会在下一个 <code>QueuedTask</code>开始执行之前从任务队列中被移除。</p>
<h5 id="2-2-2-3-PostDelayTask"><a href="#2-2-2-3-PostDelayTask" class="headerlink" title="2.2.2.3. PostDelayTask"></a>2.2.2.3. <code>PostDelayTask</code></h5><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">PostDelayedTask</span><span class="params">(std::unique_ptr<QueuedTask> task, </span></span></span><br><span class="line"><span class="params"><span class="function"> <span class="type">uint32_t</span> milliseconds)</span> </span>= <span class="number">0</span>;</span><br></pre></td></tr></table></figure></div>
<p>纯虚函数,基类中无需实现。<br>此函数用于安排一个延迟任务的处理,处理会在调用 <code>PostDelayedTask</code>函数后的 <code>milliseconds</code>毫秒后执行。<br>关于延迟时间的精度可以称作“尽力而为”,在某些场景下,定时可能会有一些毫秒级的误差。</p>
<h5 id="2-2-2-4-Current和-IsCurrent"><a href="#2-2-2-4-Current和-IsCurrent" class="headerlink" title="2.2.2.4. Current和 IsCurrent"></a>2.2.2.4. <code>Current</code>和 <code>IsCurrent</code></h5><p>需要说明的是,从此处开始,相关的的定义会根据所定义的宏而不同,代码块中会给出相关说明。<br>变量及函数声明:</p>
<div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="type">static</span> TaskQueueBase* <span class="title">Current</span><span class="params">()</span></span>;</span><br><span class="line"><span class="function"><span class="type">bool</span> <span class="title">IsCurrent</span><span class="params">()</span> <span class="type">const</span> </span>{ <span class="keyword">return</span> <span class="built_in">Current</span>() == <span class="keyword">this</span>; }</span><br><span class="line"></span><br><span class="line"><span class="comment">//if defined ABSL_HAVE_THREAD_LOCAL</span></span><br><span class="line">ABSL_CONST_INIT <span class="keyword">thread_local</span> TaskQueueBase* current = <span class="literal">nullptr</span>;</span><br></pre></td></tr></table></figure></div>
<p><code>Current()</code>函数定义:</p>
<div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="comment">// defined ABSL_HAVE_THREAD_LOCAL</span></span><br><span class="line"><span class="function">TaskQueueBase* <span class="title">TaskQueueBase::Current</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> current;</span><br><span class="line">}</span><br><span class="line"><span class="comment">// defined WEBRTC_POSIX</span></span><br><span class="line"><span class="function">TaskQueueBase* <span class="title">TaskQueueBase::Current</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">static_cast</span><TaskQueueBase*>(<span class="built_in">pthread_getspecific</span>(<span class="built_in">GetQueuePtrTls</span>()));</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div>
<p><code>Current()</code>函数返回当前线程保存的任务队列,返回值为一个 <code>static</code>变量。<br><code>IsCurrent()</code>函数则用于判断当前线程保存的任务队列是不是对象本身。</p>
<h4 id="2-2-3-说明"><a href="#2-2-3-说明" class="headerlink" title="2.2.3. 说明"></a>2.2.3. 说明</h4><ul>
<li><code>TaskQueueBase</code>是抽象基类,只用于提供接口,不可以被实例化</li>
<li>基于上一条说明,<code>TaskQueueBase</code>的析构函数被声明为虚函数</li>
<li>考虑到文章结构,<code>TaskQueueBase</code>的成员类会在下一节介绍</li>
</ul>
<h3 id="2-3-TaskQueue-CurrentTaskQueueSetter类"><a href="#2-3-TaskQueue-CurrentTaskQueueSetter类" class="headerlink" title="2.3. TaskQueue::CurrentTaskQueueSetter类"></a>2.3. <code>TaskQueue::CurrentTaskQueueSetter</code>类</h3><blockquote>
<p>Path:<br>api\task_queue\task_queue_base.h<br>api\task_queue\task_queue_base.cc</p>
</blockquote>
<h4 id="2-3-1-类声明"><a href="#2-3-1-类声明" class="headerlink" title="2.3.1. 类声明"></a>2.3.1. 类声明</h4><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="keyword">namespace</span> webrtc {</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">RTC_LOCKABLE</span> RTC_EXPORT TaskQueueBase {</span><br><span class="line"> <span class="comment">//...</span></span><br><span class="line"><span class="keyword">protected</span>:</span><br><span class="line"> <span class="keyword">class</span> <span class="title class_">RTC_EXPORT</span> CurrentTaskQueueSetter {</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">explicit</span> <span class="title">CurrentTaskQueueSetter</span><span class="params">(TaskQueueBase* task_queue)</span></span>;</span><br><span class="line"> <span class="built_in">CurrentTaskQueueSetter</span>(<span class="type">const</span> CurrentTaskQueueSetter&) = <span class="keyword">delete</span>;</span><br><span class="line"> CurrentTaskQueueSetter& <span class="keyword">operator</span>=(<span class="type">const</span> CurrentTaskQueueSetter&) = <span class="keyword">delete</span>;</span><br><span class="line"> ~<span class="built_in">CurrentTaskQueueSetter</span>();</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span>:</span><br><span class="line"> TaskQueueBase* <span class="type">const</span> previous_;</span><br><span class="line"> };<span class="comment">// class CurrentTaskQueueSetter</span></span><br><span class="line"> <span class="comment">//...</span></span><br><span class="line">};<span class="comment">// class TaskQueueBase</span></span><br><span class="line">}<span class="comment">// namespace webrtc</span></span><br></pre></td></tr></table></figure></div>
<h4 id="2-3-2-函数实现及相关说明"><a href="#2-3-2-函数实现及相关说明" class="headerlink" title="2.3.2.函数实现及相关说明"></a>2.3.2.函数实现及相关说明</h4><h5 id="2-3-2-1-CurrentTaskQueueSetter"><a href="#2-3-2-1-CurrentTaskQueueSetter" class="headerlink" title="2.3.2.1. CurrentTaskQueueSetter"></a>2.3.2.1. <code>CurrentTaskQueueSetter</code></h5><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="comment">// defined ABSL_HAVE_THREAD_LOCAL</span></span><br><span class="line">TaskQueueBase::CurrentTaskQueueSetter::<span class="built_in">CurrentTaskQueueSetter</span>(</span><br><span class="line"> TaskQueueBase* task_queue)</span><br><span class="line"> : <span class="built_in">previous_</span>(current) {</span><br><span class="line"> current = task_queue;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// defined WEBRTC_POSIX</span></span><br><span class="line">TaskQueueBase::CurrentTaskQueueSetter::<span class="built_in">CurrentTaskQueueSetter</span>(</span><br><span class="line"> TaskQueueBase* task_queue)</span><br><span class="line"> : <span class="built_in">previous_</span>(TaskQueueBase::<span class="built_in">Current</span>()) {</span><br><span class="line"> <span class="built_in">pthread_setspecific</span>(<span class="built_in">GetQueuePtrTls</span>(), task_queue);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div>
<p>构造函数主要的任务分为两部分:</p>
<ul>
<li>使用 <code>previous_</code>暂存当前线程的任务队列</li>
<li>将 <code>task_queue</code>存放在当前线程的 <code>Tls</code>中</li>
</ul>
<h5 id="2-3-2-2-CurrentTaskQueueSetter"><a href="#2-3-2-2-CurrentTaskQueueSetter" class="headerlink" title="2.3.2.2. ~CurrentTaskQueueSetter"></a>2.3.2.2. <code>~CurrentTaskQueueSetter</code></h5><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="comment">// defined ABSL_HAVE_THREAD_LOCAL</span></span><br><span class="line">TaskQueueBase::CurrentTaskQueueSetter::~<span class="built_in">CurrentTaskQueueSetter</span>() {</span><br><span class="line"> current = previous_;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// defined WEBRTC_POSIX</span></span><br><span class="line">TaskQueueBase::CurrentTaskQueueSetter::~<span class="built_in">CurrentTaskQueueSetter</span>() {</span><br><span class="line"> <span class="built_in">pthread_setspecific</span>(<span class="built_in">GetQueuePtrTls</span>(), previous_);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div>
<p>构造函数的任务就是将之前暂存在 <code>previous_</code>的任务队列取出,重新放回到当前线程的 <code>Tls</code>当中。</p>
<h4 id="2-3-3-说明"><a href="#2-3-3-说明" class="headerlink" title="2.3.3.说明"></a>2.3.3.说明</h4><p><code>CurrentTaskQueueSetter</code>类只在构造和析构时执行任务:</p>
<ul>
<li>构造时,用传入构造函数的任务队列更新当前线程存放的任务队列,并将更新前的任务队列暂存</li>
<li>析构时,用构造时暂存的任务队列更新当前线程存放的任务队列</li>
</ul>
<h2 id="3-总结"><a href="#3-总结" class="headerlink" title="3. 总结"></a>3. 总结</h2><p><code>TaskQueueBase</code>类作为任务队列机制的核心基类,在后续分析中经常会涉及到,了解其实现方法有助于了解整个任务队列的运行机制。<br>关于任务队列的其他类,后续会在其他文章中进行分析。</p>
]]></content>
<tags>
<tag>WebRTC源码分析</tag>
</tags>
</entry>
<entry>
<title>WebRTC源码分析之任务队列-TaskQueueStdlib</title>
<url>/2022/12/06/WebRTC%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90%E4%B9%8B%E4%BB%BB%E5%8A%A1%E9%98%9F%E5%88%97-TaskQueueStdlib/</url>
<content><![CDATA[<h2 id="1-前言"><a href="#1-前言" class="headerlink" title="1. 前言"></a>1. 前言</h2><p><code>TaskQueueStdlib</code>类是WebRTC任务队列机制的核心类,也是整个任务队列的标准库,在阅读本文之前,需要对 <code>TaskQueueBase</code>类有一定了解。<br>可以参考这篇文章<a href="https://shis-zhi.github.io/2022/12/06/WebRTC%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90%E4%B9%8B%E4%BB%BB%E5%8A%A1%E9%98%9F%E5%88%97-TaskQueueBase/">WebRTC源码分析-TaskQueue(任务队列)-TaskQueueBase</a><br>WebRTC版本:M84</p>
<h2 id="2-正文"><a href="#2-正文" class="headerlink" title="2. 正文"></a>2. 正文</h2><h3 id="2-0-预说明:TaskQueuePriorityToThreadPriority"><a href="#2-0-预说明:TaskQueuePriorityToThreadPriority" class="headerlink" title="2.0. 预说明:TaskQueuePriorityToThreadPriority"></a>2.0. 预说明:<code>TaskQueuePriorityToThreadPriority</code></h3><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="function">rtc::ThreadPriority <span class="title">TaskQueuePriorityToThreadPriority</span><span class="params">(</span></span></span><br><span class="line"><span class="params"><span class="function"> TaskQueueFactory::Priority priority)</span> </span>{</span><br><span class="line"> <span class="keyword">switch</span> (priority) {</span><br><span class="line"> <span class="keyword">case</span> TaskQueueFactory::Priority::HIGH:</span><br><span class="line"> <span class="keyword">return</span> rtc::kRealtimePriority;</span><br><span class="line"> <span class="keyword">case</span> TaskQueueFactory::Priority::LOW:</span><br><span class="line"> <span class="keyword">return</span> rtc::kLowPriority;</span><br><span class="line"> <span class="keyword">case</span> TaskQueueFactory::Priority::NORMAL:</span><br><span class="line"> <span class="keyword">return</span> rtc::kNormalPriority;</span><br><span class="line"> <span class="keyword">default</span>:</span><br><span class="line"> <span class="built_in">RTC_NOTREACHED</span>();</span><br><span class="line"> <span class="keyword">return</span> rtc::kNormalPriority;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div>
<p>此函数用于将任务队列的优先级转换为线程的优先级。<br><code>TaskQueueFactory::Priority</code>定义如下:</p>
<div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="keyword">enum class</span> <span class="title class_">Priority</span> { NORMAL = <span class="number">0</span>, HIGH, LOW };</span><br></pre></td></tr></table></figure></div>
<h3 id="2-1-TaskQueueStdlib类"><a href="#2-1-TaskQueueStdlib类" class="headerlink" title="2.1. TaskQueueStdlib类"></a>2.1. <code>TaskQueueStdlib</code>类</h3><blockquote>
<p>path:<br>rtc_base\task_queue_stdlib.h<br>rtc_base\task_queue_stdlib.cc</p>
</blockquote>
<h4 id="2-1-1-类声明"><a href="#2-1-1-类声明" class="headerlink" title="2.1.1. 类声明"></a>2.1.1. 类声明</h4><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">TaskQueueStdlib</span> <span class="keyword">final</span> : <span class="keyword">public</span> TaskQueueBase {</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">TaskQueueStdlib</span>(absl::string_view queue_name, rtc::ThreadPriority priority);</span><br><span class="line"> ~<span class="built_in">TaskQueueStdlib</span>() <span class="keyword">override</span> = <span class="keyword">default</span>;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">Delete</span><span class="params">()</span> <span class="keyword">override</span></span>;</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">PostTask</span><span class="params">(std::unique_ptr<QueuedTask> task)</span> <span class="keyword">override</span></span>;</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">PostDelayedTask</span><span class="params">(std::unique_ptr<QueuedTask> task,</span></span></span><br><span class="line"><span class="params"><span class="function"> <span class="type">uint32_t</span> milliseconds)</span> <span class="keyword">override</span></span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span>:</span><br><span class="line"> <span class="keyword">using</span> OrderId = <span class="type">uint64_t</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">struct</span> <span class="title class_">DelayedEntryTimeout</span> {</span><br><span class="line"> <span class="type">int64_t</span> next_fire_at_ms_{};</span><br><span class="line"> OrderId order_{};</span><br><span class="line"></span><br><span class="line"> <span class="type">bool</span> <span class="keyword">operator</span><(<span class="type">const</span> DelayedEntryTimeout& o) <span class="type">const</span> {</span><br><span class="line"> <span class="keyword">return</span> std::<span class="built_in">tie</span>(next_fire_at_ms_, order_) <</span><br><span class="line"> std::<span class="built_in">tie</span>(o.next_fire_at_ms_, o.order_);</span><br><span class="line"> }</span><br><span class="line"> };</span><br><span class="line"></span><br><span class="line"> <span class="keyword">struct</span> <span class="title class_">NextTask</span> {</span><br><span class="line"> <span class="type">bool</span> final_task_{<span class="literal">false</span>};</span><br><span class="line"> std::unique_ptr<QueuedTask> run_task_;</span><br><span class="line"> <span class="type">int64_t</span> sleep_time_ms_{};</span><br><span class="line"> };</span><br><span class="line"></span><br><span class="line"> <span class="function">NextTask <span class="title">GetNextTask</span><span class="params">()</span></span>;</span><br><span class="line"> <span class="function"><span class="type">static</span> <span class="type">void</span> <span class="title">ThreadMain</span><span class="params">(<span class="type">void</span>* context)</span></span>;</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">ProcessTasks</span><span class="params">()</span></span>;</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">NotifyWake</span><span class="params">()</span></span>;</span><br><span class="line"> rtc::Event started_;</span><br><span class="line"> rtc::Event stopped_;</span><br><span class="line"> rtc::Event flag_notify_;</span><br><span class="line"> rtc::PlatformThread thread_;</span><br><span class="line"> rtc::CriticalSection pending_lock_;</span><br><span class="line"> <span class="function"><span class="type">bool</span> thread_should_quit_ <span class="title">RTC_GUARDED_BY</span><span class="params">(pending_lock_)</span></span>{<span class="literal">false</span>};</span><br><span class="line"> <span class="function">OrderId thread_posting_order_ <span class="title">RTC_GUARDED_BY</span><span class="params">(pending_lock_)</span></span>{};</span><br><span class="line"> std::queue<std::pair<OrderId, std::unique_ptr<QueuedTask>>> <span class="function">pending_queue_</span></span><br><span class="line"><span class="function"> <span class="title">RTC_GUARDED_BY</span><span class="params">(pending_lock_)</span></span>;</span><br><span class="line"> std::map<DelayedEntryTimeout, std::unique_ptr<QueuedTask>> <span class="function">delayed_queue_</span></span><br><span class="line"><span class="function"> <span class="title">RTC_GUARDED_BY</span><span class="params">(pending_lock_)</span></span>;</span><br><span class="line">};</span><br></pre></td></tr></table></figure></div>
<p>可以看出 <code>TaskQueueStdlib</code>的 <code>public</code>成员除构造函数和析构函数之外只有三个函数,<code>private</code>成员都是为了实现 <code>public</code>方法而定义的,所以后续会先分析其私有成员,最后再分析三个 <code>public</code>成员函数的具体实现方式。</p>
<h4 id="2-1-2-私有成员实现"><a href="#2-1-2-私有成员实现" class="headerlink" title="2.1.2. 私有成员实现"></a>2.1.2. 私有成员实现</h4><p>此处使用 <code>using</code>语句,类似于 <code>typedef</code>,类型 <code>OrderId</code>实际上就是 <code>uint64_t</code>。</p>
<h5 id="2-1-2-1-私有数据成员"><a href="#2-1-2-1-私有数据成员" class="headerlink" title="2.1.2.1. 私有数据成员"></a>2.1.2.1. 私有数据成员</h5><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 表示该线程是否已经开始</span></span><br><span class="line">rtc::Event started_;</span><br><span class="line"><span class="comment">// 表示该线程是否已经结束</span></span><br><span class="line">rtc::Event stopped_;</span><br><span class="line"><span class="comment">// 每当有新的任务等待时,就会发出信号</span></span><br><span class="line">rtc::Event flag_notify_;</span><br><span class="line"><span class="comment">// 表示被分配用于处理任务的活动工作线程</span></span><br><span class="line">rtc::PlatformThread thread_;</span><br><span class="line"><span class="comment">// 对于存在多个线程访问的数据需要上锁</span></span><br><span class="line">rtc::CriticalSection pending_lock_;</span><br><span class="line"><span class="comment">// 表示工作线程是否需要现在关闭</span></span><br><span class="line"><span class="function"><span class="type">bool</span> thread_should_quit_ <span class="title">RTC_GUARDED_BY</span><span class="params">(pending_lock_)</span></span>{<span class="literal">false</span>};</span><br><span class="line"><span class="comment">// 保存下一个任务的序号,用于将其放入一个挂起的队列中</span></span><br><span class="line"><span class="function">OrderId thread_posting_order_ <span class="title">RTC_GUARDED_BY</span><span class="params">(pending_lock_)</span></span>{};</span><br><span class="line"><span class="comment">// 需要在工作线程中按照先进先出的队列顺序处理的待办任务列表</span></span><br><span class="line">std::queue<std::pair<OrderId, std::unique_ptr<QueuedTask>>> <span class="function">pending_queue_</span></span><br><span class="line"><span class="function"> <span class="title">RTC_GUARDED_BY</span><span class="params">(pending_lock_)</span></span>;</span><br><span class="line"><span class="comment">// 需要在工作线程中延迟一段时间再处理的待办任务列表,如果两个任务在相同的时间间隔内</span></span><br><span class="line"><span class="comment">// 发生,那么将根据先后顺序进行处理。</span></span><br><span class="line">std::map<DelayedEntryTimeout, std::unique_ptr<QueuedTask>> <span class="function">delayed_queue_</span></span><br><span class="line"><span class="function"> <span class="title">RTC_GUARDED_BY</span><span class="params">(pending_lock_)</span></span>;</span><br></pre></td></tr></table></figure></div>
<h5 id="2-1-2-2-私有成员类-amp-结构"><a href="#2-1-2-2-私有成员类-amp-结构" class="headerlink" title="2.1.2.2. 私有成员类&结构"></a>2.1.2.2. 私有成员类&结构</h5><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="keyword">struct</span> <span class="title class_">DelayedEntryTimeout</span> {</span><br><span class="line"> <span class="type">int64_t</span> next_fire_at_ms_{};</span><br><span class="line"> OederId order_{};</span><br><span class="line"> </span><br><span class="line"> <span class="type">bool</span> <span class="keyword">operator</span><(<span class="type">const</span> DelayedEntryTimeout& o) <span class="type">const</span> {</span><br><span class="line"> <span class="keyword">return</span> std::<span class="built_in">tie</span>(next_fire_at_ms_, order_) < </span><br><span class="line"> std::<span class="built_in">tie</span>(o.next_fire_at_ms_, o.order_);</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure></div>
<p><code>DelayedEntryTimeout</code>具有类似于时间戳的功能,<code>next_fire_at_ms_</code>记录了任务执行的绝对时间,<code>OrderId</code>则记录了任务对应的序号,重载了 <code><</code>操作符用于比较两个<br><code>DelayedEntryTimeout</code>,该属性较小的任务会先被执行。<br><code>DelayedEntryTimeout</code>比较规则:先比较 <code>next_fire_at_ms_</code>,如果不等直接返回对应的 <code>bool</code>值;如果相等,则再次比较 <code>order</code>,返回比较结果对应的 <code>bool</code>值。</p>
<div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="keyword">struct</span> <span class="title class_">NextTask</span> {</span><br><span class="line"> <span class="type">bool</span> final_task_{<span class="literal">false</span>};</span><br><span class="line"> std::unique_ptr<QueuedTask> run_task_;</span><br><span class="line"> <span class="type">int64_t</span> sleep_time_ms_{};</span><br><span class="line">};</span><br></pre></td></tr></table></figure></div>
<p><code>NextTask</code>用于表示下一个任务,前两个属性很容易理解,第三个属性是为了兼容 <code>DelayedTask</code>而设置的,具体的实现将会在 <code>GetNextTask</code>函数中介绍到。</p>
<h5 id="2-1-2-3-私有成员函数实现"><a href="#2-1-2-3-私有成员函数实现" class="headerlink" title="2.1.2.3. 私有成员函数实现"></a>2.1.2.3. 私有成员函数实现</h5><h6 id="2-1-2-3-1-GetNextTask"><a href="#2-1-2-3-1-GetNextTask" class="headerlink" title="2.1.2.3.1. GetNextTask"></a>2.1.2.3.1. <code>GetNextTask</code></h6><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="function">TaskQueu1Stdlib::NextTask <span class="title">TaskQueueStdlib::GetNextTask</span><span class="params">()</span> </span>{</span><br><span class="line"> NextTask result{};</span><br><span class="line"> <span class="comment">// 获取当前的绝对时间(ms)</span></span><br><span class="line"> <span class="keyword">auto</span> tick = rtc::<span class="built_in">TimeMillis</span>();</span><br><span class="line"></span><br><span class="line"> <span class="function">rtc::CritScope <span class="title">lock</span><span class="params">(&pending_lock_)</span></span>;</span><br><span class="line"> <span class="comment">// 判断是否线程需要退出</span></span><br><span class="line"> <span class="keyword">if</span> (thread_should_quit_) {</span><br><span class="line"> result.final_task_ = <span class="literal">true</span>;</span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 如果延迟任务队列非空,则首先从其中取出任务 </span></span><br><span class="line"> <span class="keyword">if</span> (delayed_queue_.<span class="built_in">size</span>() > <span class="number">0</span>) {</span><br><span class="line"> <span class="comment">// 获取延迟任务相关信息</span></span><br><span class="line"> <span class="keyword">auto</span> delayed_entry = delayed_queue_.<span class="built_in">begin</span>();</span><br><span class="line"> <span class="type">const</span> <span class="keyword">auto</span>& delay_info = delayed_entry->first;</span><br><span class="line"> <span class="keyword">auto</span>& delay_run = delayed_entry->second;</span><br><span class="line"> <span class="comment">// 判断是否应该执行延迟任务</span></span><br><span class="line"> <span class="keyword">if</span> (tick >= delay_info.next_fire_at_ms_) {</span><br><span class="line"> <span class="comment">// 如果即时任务队列非空,通过比较决定取出哪种任务</span></span><br><span class="line"> <span class="keyword">if</span> (pending_queue_.<span class="built_in">size</span>() > <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">auto</span>& entry = pending_queue_.<span class="built_in">front</span>();</span><br><span class="line"> <span class="keyword">auto</span>& entry_order = entry.first;</span><br><span class="line"> <span class="keyword">auto</span>& entry_run = entry.second;</span><br><span class="line"> <span class="comment">// 如果即时任务序号较小,则直接返回该即时任务 </span></span><br><span class="line"> <span class="keyword">if</span> (entry_order < delay_info.order_) { </span><br><span class="line"> result.run_task_ = std::<span class="built_in">move</span>(entry_run); <span class="comment">//<--1</span></span><br><span class="line"> pending_queue_.<span class="built_in">pop</span>();</span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 如果即时任务队列为空,或者1处即时任务序号较大,则取出最靠前的延迟任务</span></span><br><span class="line"> result.run_task_ = std::<span class="built_in">move</span>(delay_run); <span class="comment">//<--2</span></span><br><span class="line"> delayed_queue_.<span class="built_in">erase</span>(delayed_entry);</span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// !!如果运行到这里,此时result.task_run_为nullptr,表明没有任务需要处理</span></span><br><span class="line"> <span class="comment">// 更新sleep_time_ms_</span></span><br><span class="line"> result.sleep_time_ms_ = delay_info.next_fire_at_ms_ - tick; <span class="comment">//<--3</span></span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 延迟任务队列为空,即时任务队列非空</span></span><br><span class="line"> <span class="keyword">if</span> (pending_queue_.<span class="built_in">size</span>() > <span class="number">0</span>) { </span><br><span class="line"> <span class="keyword">auto</span>& entry = pending_queue_.<span class="built_in">front</span>(); <span class="comment">//<--4</span></span><br><span class="line"> result.run_task_ = std::<span class="built_in">move</span>(entry.second);</span><br><span class="line"> pending_queue_.<span class="built_in">pop</span>();</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div>
<p>其中涉及到 <code>map</code>和 <code>queue</code>两个容器的相关函数,可以自行查阅。<br>由于 <code>NextTask</code>结构体内部的 <code>run_task_</code>为 <code>unique_ptr</code>,所以不能直接赋值而是使用 <code>std::move</code>转移所有权。</p>
<p><code>GetNextTask</code>函数体较长,逻辑略显复杂,而且非常重要,一定要理清楚如何从任务队列中取出任务。<br>代码块里做了四处标记,便于理解整个流程(此处分析认为 <code>thread_should_quit_</code>为 <code>true</code>):</p>
<div class="highlight-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="code"><pre><span class="line">if 延迟任务队列非空</span><br><span class="line"> if 需要执行下一个延迟任务</span><br><span class="line"> if 即时任务队列非空</span><br><span class="line"> if 即时任务序号较小</span><br><span class="line"> 返回即时任务(1)</span><br><span class="line"> else </span><br><span class="line"> 返回延迟任务(2)</span><br><span class="line"> else</span><br><span class="line"> 返回延迟任务(2)</span><br><span class="line"> else</span><br><span class="line"> 更新result.sleep_time_ms_(3)</span><br><span class="line">if 即时任务队列非空</span><br><span class="line"> result.run_task_设置为即时任务(4)</span><br><span class="line">返回result</span><br></pre></td></tr></table></figure></div>
<p>这一部分可以概括为:</p>
<ul>
<li><code>result.run_task_</code>为空,<code>result.sleep_time_ms_</code>为0时:两个任务队列均为空</li>
<li><code>result.run_task_</code>为空,<code>result.sleep_time_ms_</code>不为0时:即时任务队列为空,延迟任务队列非空,但是没有可执行的延迟任务</li>
<li><code>result.run_task_</code>不为空,<code>result.sleep_time_ms_</code>为0时:即时任务队列非空,延迟任务队列为空</li>
<li><code>result.run_task_</code>不为空,<code>result.sleep_time_ms_</code>不为0时:即时任务队列非空,延迟任务队列非空,但是没有可执行的延迟任务</li>
</ul>
<h6 id="2-1-2-3-2-ThreadMain"><a href="#2-1-2-3-2-ThreadMain" class="headerlink" title="2.1.2.3.2 ThreadMain"></a>2.1.2.3.2 <code>ThreadMain</code></h6><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="comment">// static</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">TaskQueueStdlib::ThreadMain</span><span class="params">(<span class="type">void</span>* context)</span> </span>{</span><br><span class="line"> TaskQueueStdlib* me = <span class="built_in">static_cast</span><TaskQueueStdlib*>(context);</span><br><span class="line"> <span class="function">CurrentTaskQueueSetter <span class="title">set_current</span><span class="params">(me)</span></span>;</span><br><span class="line"> me-><span class="built_in">ProcessTasks</span>();</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div>
<p><code>ThreadMain</code>是任务处理线程真正的入口函数,其首先将传入的参数强制转换成 <code>TaskQueueStdlib*</code>,然后将这个任务队列注册到当前的线程中,随后开始处理任务。<br>注意此函数是一个 <code>static</code>函数。</p>
<h6 id="2-1-2-3-3-ProcessTasks"><a href="#2-1-2-3-3-ProcessTasks" class="headerlink" title="2.1.2.3.3. ProcessTasks"></a>2.1.2.3.3. <code>ProcessTasks</code></h6><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">TaskQueueStdlib::ProcessTasks</span><span class="params">()</span> </span>{</span><br><span class="line"> started_.<span class="built_in">Set</span>();</span><br><span class="line"> <span class="keyword">while</span> (<span class="literal">true</span>) {</span><br><span class="line"> <span class="keyword">auto</span> task = <span class="built_in">GetNextTask</span>();</span><br><span class="line"> <span class="keyword">if</span> (task.final_task_)</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">if</span> (task.run_task_) {</span><br><span class="line"> <span class="comment">// release()会解除智能指针对这个QueuedTask的占用,</span></span><br><span class="line"> <span class="comment">// 并将该智能指针置空</span></span><br><span class="line"> QueuedTask* release_ptr = task.run_task_.<span class="built_in">release</span>();</span><br><span class="line"> <span class="keyword">if</span> (release_ptr-><span class="built_in">Run</span>())</span><br><span class="line"> <span class="keyword">delete</span> release_ptr;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 取出下一个任务</span></span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// task.sleep_time_ms_为0时表示</span></span><br><span class="line"> <span class="keyword">if</span> (<span class="number">0</span> == task.sleep_time_ms_)</span><br><span class="line"> flag_notify_.<span class="built_in">Wait</span>(rtc::Event::kForever);</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> flag_notify_.<span class="built_in">Wait</span>(task.sleep_time_ms_);</span><br><span class="line"> }</span><br><span class="line"> stopped_.<span class="built_in">Set</span>();</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div>
<p><code>ProcessTasks</code>函数与 <code>GetNextTask</code>函数协同工作。由 <code>GetNextTask</code>函数部分的分析可知只要 <code>task.run_task_</code>为空,就说明即时任务队列为空且暂时没有需要执行的任务,但此时又面临着两种情况:</p>
<ul>
<li>如果延迟任务队列为空,那么直接睡眠直到被唤起</li>
<li>如果延迟任务队列非空,那么将会睡眠指定时间</li>
</ul>
<h6 id="2-1-2-3-4-NotifyWake"><a href="#2-1-2-3-4-NotifyWake" class="headerlink" title="2.1.2.3.4. NotifyWake"></a>2.1.2.3.4. <code>NotifyWake</code></h6><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">TaskQueueStdlib::NotifyWake</span><span class="params">()</span> </span>{</span><br><span class="line"> flag_notify_.<span class="built_in">Set</span>();</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div>
<p>任务队列中存放着待执行的任务:</p>
<ul>
<li>对于即时任务,线程会忙于执行该任务而不会等待 <code>flag_notify_</code>事件。</li>
<li>如果没有即时任务,但有一个延迟任务正在等待,那么线程将会等待 <code>flag_notify_</code>事件,也就是 <code>ProcessTasks</code>中所提及到的 <code>flag_notify_.Wait(task.sleep_time_ms_);</code></li>
<li>如果即时任务队列和延迟任务队列都为空,那么线程将无限期等待 <code>flag_notify_</code>事件,直到有一个信号显示有新的任务被添加(或者告诉线程需要终止)。</li>
</ul>
<p>任何情况下,当一个新的上述请求被添加后,会发出 <code>flag_notify_</code>信号。如果此时线程正在等待,则会被立即唤醒并且重新评估下一步需要做什么。如果线程并没有在等待,那么线程将保持信号,在下一次试图等待 <code>flag_notify_</code>事件发生时被唤醒。</p>
<p>在发出 <code>flag_notify_</code>信号来唤醒可能正在睡眠的线程之前,需要确保有任务或相关请求添加到队列中,从而避免竞争情况:线程被通知唤醒但是发现没有任务需要执行,所以会再次等待信号,然而这样的信号将有可能不会再次出现。</p>
<h4 id="2-1-3-公有成员实现"><a href="#2-1-3-公有成员实现" class="headerlink" title="2.1.3. 公有成员实现"></a>2.1.3. 公有成员实现</h4><h5 id="2-1-3-1-公有成员函数实现"><a href="#2-1-3-1-公有成员函数实现" class="headerlink" title="2.1.3.1. 公有成员函数实现"></a>2.1.3.1. 公有成员函数实现</h5><h6 id="2-1-3-1-1-TaskQueueStdlib"><a href="#2-1-3-1-1-TaskQueueStdlib" class="headerlink" title="2.1.3.1.1. TaskQueueStdlib"></a>2.1.3.1.1. <code>TaskQueueStdlib</code></h6><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="code"><pre><span class="line">TaskQueueStdlib::<span class="built_in">TaskQueueStdlib</span>(absl::string_view queue_name,</span><br><span class="line"> rtc::ThreadPriority priority)</span><br><span class="line"> : <span class="built_in">started_</span>(<span class="comment">/*manual_reset=*/</span><span class="literal">false</span>, <span class="comment">/*initially_signaled=*/</span><span class="literal">false</span>),</span><br><span class="line"> <span class="built_in">stopped_</span>(<span class="comment">/*manual_reset=*/</span><span class="literal">false</span>, <span class="comment">/*initially_signaled=*/</span><span class="literal">false</span>),</span><br><span class="line"> <span class="built_in">flag_notify_</span>(<span class="comment">/*manual_reset=*/</span><span class="literal">false</span>, <span class="comment">/*initially_signaled=*/</span><span class="literal">false</span>),</span><br><span class="line"> <span class="built_in">thread_</span>(&TaskQueueStdlib::ThreadMain, <span class="keyword">this</span>, queue_name, priority) {</span><br><span class="line"> thread_.<span class="built_in">Start</span>();</span><br><span class="line"> started_.<span class="built_in">Wait</span>(rtc::Event::kForever);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div>
<p>创建一个任务处理线程,随后开始执行入口函数并挂起当前线程。<br>当任务处理线程准备就绪之后,会唤醒当前线程。<br>当前线程负责创建一个任务处理线程并且向其投递任务,任务处理线程负责处理任务队列内的任务。</p>
<h6 id="2-1-3-1-2-Delete"><a href="#2-1-3-1-2-Delete" class="headerlink" title="2.1.3.1.2.Delete"></a>2.1.3.1.2.<code>Delete</code></h6><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">TaskQueueStdlib::Delete</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="built_in">RTC_DCHECK</span>(!<span class="built_in">IsCurrent</span>());</span><br><span class="line"></span><br><span class="line"> {</span><br><span class="line"> <span class="function">rtc::CritScope <span class="title">lock</span><span class="params">(&pending_lock_)</span></span>;</span><br><span class="line"> thread_should_quit_ = <span class="literal">true</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="built_in">NotifyWake</span>();</span><br><span class="line"></span><br><span class="line"> stopped_.<span class="built_in">Wait</span>(rtc::Event::kForever);</span><br><span class="line"> thread_.<span class="built_in">Stop</span>();</span><br><span class="line"> <span class="keyword">delete</span> <span class="keyword">this</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div>
<p>此函数用于销毁任务队列对象:</p>
<ul>
<li>首先判断当前线程是不是任务处理线程,因为销毁操作不可以在任务处理线程中进行</li>
<li>随后标记任务处理线程需要退出并唤醒线程执行相关任务</li>
<li>然后当前线程等待任务处理线程退出后唤醒主线程</li>
<li>主线程回收任务处理线程,最后释放任务队列对象</li>
</ul>
<h6 id="2-1-3-1-3-PostTask"><a href="#2-1-3-1-3-PostTask" class="headerlink" title="2.1.3.1.3. PostTask"></a>2.1.3.1.3. <code>PostTask</code></h6><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">TaskQueueStdlib::PostTask</span><span class="params">(std::unique_ptr<QueuedTask> task)</span> </span>{</span><br><span class="line"> {</span><br><span class="line"> <span class="function">rtc::CritScope <span class="title">lock</span><span class="params">(&pending_lock_)</span></span>;</span><br><span class="line"> <span class="comment">// 序号自增赋值</span></span><br><span class="line"> OrderId order = thread_posting_order_++;</span><br><span class="line"> <span class="comment">// 加入到即时任务队列中</span></span><br><span class="line"> pending_queue_.<span class="built_in">push</span>(std::pair<OrderId, std::unique_ptr<QueuedTask>>(</span><br><span class="line"> order, std::<span class="built_in">move</span>(task)));</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 唤醒任务处理线程处理任务</span></span><br><span class="line"> <span class="built_in">NotifyWake</span>();</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div>
<h6 id="2-1-3-1-4-PostDelayedTask"><a href="#2-1-3-1-4-PostDelayedTask" class="headerlink" title="2.1.3.1.4. PostDelayedTask"></a>2.1.3.1.4. <code>PostDelayedTask</code></h6><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">TaskQueueStdlib::PostDelayedTask</span><span class="params">(std::unique_ptr<QueuedTask> task,</span></span></span><br><span class="line"><span class="params"><span class="function"> <span class="type">uint32_t</span> milliseconds)</span> </span>{</span><br><span class="line"> <span class="comment">// 计算延迟任务的执行时间(绝对时间)</span></span><br><span class="line"> <span class="keyword">auto</span> fire_at = rtc::<span class="built_in">TimeMillis</span>() + milliseconds;</span><br><span class="line"></span><br><span class="line"> DelayedEntryTimeout delay;</span><br><span class="line"> <span class="comment">// 设置延迟任务的执行时间</span></span><br><span class="line"> delay.next_fire_at_ms_ = fire_at;</span><br><span class="line"></span><br><span class="line"> {</span><br><span class="line"> <span class="function">rtc::CritScope <span class="title">lock</span><span class="params">(&pending_lock_)</span></span>;</span><br><span class="line"> <span class="comment">// 序号自增赋值</span></span><br><span class="line"> delay.order_ = ++thread_posting_order_;</span><br><span class="line"> delayed_queue_[delay] = std::<span class="built_in">move</span>(task);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 唤醒任务处理线程处理任务</span></span><br><span class="line"> <span class="built_in">NotifyWake</span>();</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div>
<h2 id="3-总结"><a href="#3-总结" class="headerlink" title="3. 总结"></a>3. 总结</h2><p><code>TaskQueueStdlib</code>涉及到任务队列的核心实现方式,尤其是 <code>GetNextTask</code>和 <code>ProcessTasks</code>两个函数,需要理清相关的逻辑,从而了解整个模块的流程。</p>
]]></content>
<tags>
<tag>WebRTC源码分析</tag>
</tags>
</entry>
<entry>
<title>计算机网络之RTP/RTCP</title>
<url>/2022/12/08/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%E4%B9%8BRTP-RTCP/</url>
<content><![CDATA[<p>RTP标准实际上定义了一对协议:</p>
<ul>
<li><strong>实时传输协议</strong>(Real-time Transport Protocol,RTP),用于多媒体数据的交换</li>
<li><strong>实时传输控制协议</strong>(Real-time Transport Control Protocol,RTCP),用于周期性地发送与特定数据流相关联的控制信息<br>当在UDP上运行时,RTP数据流与相关联的RTCP控制流使用连续的传输层端口。RTP数据使用偶数端口号,RTCP控制信息使用相连的下一个(奇数)端口号。<br>RTP为Internet上端到端的实时传输提供时间信息和流同步,但并不保证服务质量,服务质量由RTCP来提供。</li>
</ul>
<h2 id="1-RTP协议格式"><a href="#1-RTP协议格式" class="headerlink" title="1. RTP协议格式"></a>1. RTP协议格式</h2><div class="highlight-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="code"><pre><span class="line"> 0 1 2 3</span><br><span class="line"> 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line">|V=2|P|X| CC |M| PT | sequence number |</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line">| timestamp |</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line">| synchronization source (SSRC) identifier |</span><br><span class="line">+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+</span><br><span class="line">| contributing source (CSRC) identifiers |</span><br><span class="line">| .... |</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br></pre></td></tr></table></figure></div>
<p>RTP报文由两部分组成:报头和有效载荷。RTP报头格式如上图所示,其中:</p>
<ol>
<li>V:RTP协议的<strong>版本号</strong>,占2位,当前协议版本号为2。</li>
<li>P:<strong>填充标志</strong>,占1位,如果P=1,则在该报文的尾部填充一个或多个额外的八位组,它们不是有效载荷的一部分。</li>
<li>X:<strong>扩展标志</strong>,占1位,如果X=1,则在RTP报头后跟有一个扩展报头。</li>
<li>CC:<strong>CSRC计数器</strong>,占4位,指示CSRC 标识符的个数。</li>
<li>M: <strong>标记</strong>,占1位,不同的有效载荷有不同的含义,对于视频,标记一帧的结束;对于音频,标记会话的开始。</li>
<li>PT: <strong>有效载荷类型</strong>,占7位,用于说明RTP报文中有效载荷的类型,如GSM音频、JPEM图像等,在流媒体中大部分是用来区分音频流和视频流的,这样便于客户端进行解析。</li>
<li><strong>序列号</strong>:占16位,用于标识发送者所发送的RTP报文的序列号,每发送一个报文,序列号增1。这个字段当下层的承载协议用UDP的时候,网络状况不好的时候可以用来检查丢包。同时出现网络抖动的情况可以用来对数据进行重新排序,在helix服务器中这个字段是从0开始的,同时音频包和视频包的<code>sequence</code>是分别记数的。</li>
<li>**时间戳(Timestamp)**:占32位,时戳反映了该RTP报文的第一个八位组的采样时刻。接收者使用时戳来计算延迟和延迟抖动,并进行同步控制。</li>
<li><strong>同步信源(SSRC)标识符</strong>:占32位,用于标识同步信源。该标识符是随机选择的,参加同一视频会议的两个同步信源不能有相同的SSRC。</li>
<li><strong>特约信源(CSRC)标识符</strong>:每个CSRC标识符占32位,可以有0~15个。每个CSRC标识了包含在该RTP报文有效载荷中的所有特约信源。</li>
</ol>
<p>如果扩展标志被置位则说明紧跟在报头后面是一个头扩展,其格式如下:</p>
<div class="highlight-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="code"><pre><span class="line"> 0 1 2 3</span><br><span class="line"> 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line">| defined by profile | length |</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line">| header extension |</span><br><span class="line">| .... |</span><br></pre></td></tr></table></figure></div>
<h2 id="2-RTCP协议"><a href="#2-RTCP协议" class="headerlink" title="2. RTCP协议"></a>2. RTCP协议</h2><h3 id="2-1-RTCP工作机制"><a href="#2-1-RTCP工作机制" class="headerlink" title="2.1. RTCP工作机制"></a>2.1. RTCP工作机制</h3><p><strong>当应用程序开始一个rtp会话时将使用两个端口:一个给RTP,一个给RTCP。</strong><br>RTP本身并不能为按顺序传送数据包提供可靠的传送机制,也不提供流量控制或拥塞控制,它依靠RTCP提供这些服务。<br>在RTP的会话之间周期的发放一些RTCP包以用来传监听服务质量和交换会话用户信息等功能。RTCP包中含有已发送的数据包的数量、丢失的数据包的数量等统计资料。<br>因此,服务器可以利用这些信息动态地改变传输速率,甚至改变有效载荷类型。RTP和RTCP配合使用,它们能以有效的反馈和最小的开销使传输效率最佳化,因而特别适合传送网上的实时数据。根据用户间的数据传输反馈信息,可以制定流量控制的策略,而会话用户信息的交互,可以制定会话控制的策略。<br>在RTCP通信控制中,RTCP协议的功能是通过不同的RTCP数据报来实现的,主要有如下几种类型:</p>
<h4 id="2-1-1-SR:发送端报告"><a href="#2-1-1-SR:发送端报告" class="headerlink" title="2.1.1. SR:发送端报告"></a>2.1.1. SR:发送端报告</h4><p>所谓发送端是指<strong>发出RTP数据报的应用程序或者终端</strong>,发送端同时也可以是接收端。</p>
<div class="highlight-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="code"><pre><span class="line"> 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line">|V=2|P| RC | PT=SR=200 | length L |</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line">| SSRC of sender |</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line">| NTP timestamp, most significant word NTS |</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line">| NTP timestamp, least significant word |</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line">| RTP timestamp RTS |</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line">| sender's packet count SPC |</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line">| sender's octet count SOC |</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line">| SSRC_1 (SSRC of first source) |</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line">|fraction lost F| cumulative number of packets lost C |</span><br><span class="line">-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line">| extended highest sequence number received EHSN |</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line">| inter-arrival jitter J |</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line">| last SR LSR |</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line">| delay since last SR DLSR |</span><br><span class="line">+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+</span><br><span class="line">| SSRC_2 (SSRC of second source) |</span><br><span class="line">: ... :</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line">| profile-specific extensions |</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br></pre></td></tr></table></figure></div>
<ol>
<li><strong>版本</strong>(V):2比特,RTCP版本。</li>
<li><strong>填充</strong>(P):1比特,如果该位置为1,则该RTCP包的尾部就包含附加的填充字节。</li>
<li><strong>接收报告计数器</strong>(RC):5比特,该SR包中的接收报告块的数目,可以为零。</li>
<li><strong>包类型</strong>(PT):8比特,SR包是200。</li>
<li><strong>长度域</strong>(Length):16比特,RTCP包的长度,包括填充的内容。</li>
<li><strong>同步源</strong>(SSRC of sender):32比特,SR包发送者的同步源标识符。与对应RTP包中的SSRC一样。</li>
<li><strong>NTP timestamp</strong>(MSW+LWS):64比特, 表示发送此报告时以挂钟时间测量的时间点。 结合来自各个接收器的接收报告中返回的时间戳,它可用于估计往返于接收器的往返传播时间。</li>
<li><strong>RTP timestamp</strong>:32比特,与NTP时间戳对应,与RTP数据包中的RTP时间戳具有相同的单位和随机初始值。</li>
<li><strong>Sender’s packet count</strong>:32比特,从开始发送包到产生这个SR包这段时间里,发送者发送的RTP数据包的总数. SSRC改变时,这个域清零。</li>
<li><strong>Sender`s octet count</strong>:32比特,从开始发送包到产生这个SR包这段时间里,发送者发送的净荷数据的总字节数(不包括头部和填充)。发送者改变其SSRC时,这个域要清零。</li>
<li><strong>SSRC_n</strong>:32比特,在此块中报告其接收的发送者的 SSRC 标识符</li>
<li><strong>丢失率</strong>(Fraction Lost):8比特,表明从上一个SR或RR包发出以来从同步源n(SSRC_n)来的RTP数据包的丢失率</li>
<li><strong>累计的包丢失数目</strong>(cumulative number of packets lost C ):24比特,从开始接收到SSRC_n的包到发送SR,从SSRC_n传过来的RTP数据包的丢失总数。</li>
<li><strong>收到的扩展最大序列号</strong>(extended highest sequence number received EHSN ):从SSRC_n收到的RTP数据包中最大的序列号</li>
<li><strong>接收抖动</strong>(Interarrival jitter):32比特,RTP数据包接受时间的统计方差估计</li>
<li><strong>上次SR时间戳</strong>(Last SR,LSR):32比特,取最近从SSRC_n收到的SR包中的NTP时间戳的中间32比特。如果目前还没收到SR包,则该域清零</li>
<li><strong>上次SR以来的延时</strong>(Delay since last SR,DLSR):32比特,上次从SSRC_n收到SR包到发送本报告的延时</li>
<li><strong>扩展字段</strong> profile-specific extensions</li>
</ol>
<h4 id="2-1-2-RR:接收端报告"><a href="#2-1-2-RR:接收端报告" class="headerlink" title="2.1.2. RR:接收端报告"></a>2.1.2. RR:接收端报告</h4><p>所谓接收端是指仅接收但不发送RTP数据报的应用程序或者终端。<br><strong>除包类型代码外,SR与RR间唯一的差别是源报告包含有一个20字节发送者信息段。</strong>活动源在发出最后一个数据包之后或前一个数据包与下一个数据包间隔期间发送SR;否则,就发送RR。<br>SR和RR包都可没有接收报告块也可以包括多个接收报告块,其发布报告表示的源不一定是在CSRC列表上的起作用的源,每个接收报告块提供从特殊源接收数据的统计。最大可有31个接收报告块嵌入在SR 或 RR包中。<br>丢失包累计数差别给出间隔期间丢包的数量,而系列号的差别给出间隔期间希望发送的包数量,两者之比等于经过间隔期间包丢失百分比。<br>从发送者信息,第三方监控器可计算载荷平均数据速率与没收到数据间隔的平均包速率,两者比值给出平均载荷大小。<br>如假设包丢失与包大小无关,那么特殊接收者收到的包数量给出此接收者收到的表观流量。</p>
<div class="highlight-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="code"><pre><span class="line"> 0 1 2 3</span><br><span class="line"> 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line">|V=2|P| RC | PT=RR=201 | length L |</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line">| SSRC of packet sender |</span><br><span class="line">+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+</span><br><span class="line">| SSRC_1 (SSRC of first source) |</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line">| fraction lost | cumulative number of packets lost |</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line">| extended highest sequence number received |</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line">| inter-arrival jitter |</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line">| last SR (LSR) |</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line">| delay since last SR (DLSR) |</span><br><span class="line">+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+</span><br><span class="line">| SSRC_2 (SSRC of second source) |</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line">: ... :</span><br><span class="line">+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+</span><br><span class="line">| profile-specific extensions |</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br></pre></td></tr></table></figure></div>
<h4 id="2-1-3-SDES:源描述"><a href="#2-1-3-SDES:源描述" class="headerlink" title="2.1.3. SDES:源描述"></a>2.1.3. SDES:源描述</h4><p>主要功能是<strong>作为会话成员有关标识信息的载体</strong>,如用户名、邮件地址、电话号码等,此外还具有<strong>向会话成员传达会话控制信息的功能</strong>。<br>SDES源描述包提供了直观的文本信息来描述会话的参加者,包括CNAME、NAME、EMAIL、PHONE、LOC等源描述项,这些为接收方获取发送方的有关信息提供了方便。SDES 包由包头与数据块组成,数据块可以没有,也可有多个。</p>
<div class="highlight-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="code"><pre><span class="line"> 0 1 2 3</span><br><span class="line"> 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line">|V=2|P| SC | PT=SDES=202 | length L |</span><br><span class="line">+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+</span><br><span class="line">| SSRC/CSRC_1 |</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line">| SDES items |</span><br><span class="line">| ... |</span><br><span class="line">+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+</span><br><span class="line">| SSRC/CSRC_2 |</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line">| SDES items |</span><br><span class="line">| ... |</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line">| CNAME=1 | length | user and domain name ...</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br></pre></td></tr></table></figure></div>
<p>V, P, PT, L:和RR包的描述一样,只不过其PT值为202<br>SC:5比特,此 SDES 数据包中包含的 SSRC/CSRC 块的数量。<br>CNAME: <strong>规范终端标识SDES项</strong>,类似SSRC标识,RTCP为RTP连接中每一个参加者赋予唯一一个CNAME标识。在发生冲突或重启程序时,由于随机分配的SSRC标识可能发生变化,CNAME项可以提供从SSRC标识到仍为常量的源标识的绑定。为方便第三方监控,CNAME应适合程序或人员定位源。不同的 SDES 项根据类型-长度-值方案进行编码。 目前,CNAME、NAME、EMAIL、PHONE、LOC、TOOL、NOTE 和 PRIV 项目在 [RFC1889] 中定义。CNAME 项在每个 SDES 数据包中都是强制性的,而这又是每个复合 RTCP 数据包的强制性部分。与 SSRC 标识符一样,CNAME 必须与其他所有会话参与者的 CNAME 不同。 但不是随机选择 CNAME 标识符,CNAME 应该允许人或程序都可以通过 CNAME 内容来定位源。</p>
<h4 id="2-1-4-BYE:通知离开"><a href="#2-1-4-BYE:通知离开" class="headerlink" title="2.1.4. BYE:通知离开"></a>2.1.4. BYE:通知离开</h4><p>主要功能是<strong>指示某一个或者几个源不再有效</strong>,即通知会话中的其他成员自己将退出会话。<br>作为可选项,BYE包可包括一个8位八进制计数,后跟文本信息,表示离开原因,如:”cameramalfunction”或”RTPloop detected”。字符串的编码与在SDES 项中所描述的相同。如字符串信息至BYE包下32位边界结束处,字符串就不以空结尾;否则,BYE包以空八进制填充。</p>
<div class="highlight-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="code"><pre><span class="line"> 0 1 2 3</span><br><span class="line"> 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line">|V=2|P| SC | PT=BYE=203 | length L |</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line">| SSRC/CSRC |</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line">: ... :</span><br><span class="line">+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+</span><br><span class="line">| length | reason for leaving (opt) ... </span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br></pre></td></tr></table></figure></div>
<h4 id="2-1-5-APP:由应用程序自己定义"><a href="#2-1-5-APP:由应用程序自己定义" class="headerlink" title="2.1.5. APP:由应用程序自己定义"></a>2.1.5. APP:由应用程序自己定义</h4><p>解决了RTCP的扩展性问题,并且为协议的实现者提供了很大的灵活性。</p>
<h2 id="3-RTP报头扩展"><a href="#3-RTP报头扩展" class="headerlink" title="3. RTP报头扩展"></a>3. RTP报头扩展</h2><h3 id="3-1-RTP报头"><a href="#3-1-RTP报头" class="headerlink" title="3.1. RTP报头"></a>3.1. RTP报头</h3><p>RTP协议中,RTP Header(报头)包括<strong>固定报头</strong>(Fixed Header)与<strong>报头扩展</strong>(Header extension,可选)。<br>RTP Fixed Header结构如下,其中前12字节是每个RTP包必须包含的。</p>
<div class="highlight-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="code"><pre><span class="line"> 0 1 2 3</span><br><span class="line"> 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line">|V=2|P|X| CC |M| PT | sequence number |</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line">| timestamp |</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line">| synchronization source (SSRC) identifier |</span><br><span class="line">+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+</span><br><span class="line">| contributing source (CSRC) identifiers |</span><br><span class="line">| .... |</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br></pre></td></tr></table></figure></div>
<p>固定报头携带的信息满足不了更复杂的需求,所以引入RTP报头扩展,可以携带更多的信息。</p>
<h3 id="3-2-RTP报头扩展"><a href="#3-2-RTP报头扩展" class="headerlink" title="3.2. RTP报头扩展"></a>3.2. RTP报头扩展</h3><p>如果RTP Fixed Header中,X字段为1,说明后面跟着RTP Header Extension。RTP Header Extension结构如下:</p>
<div class="highlight-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="code"><pre><span class="line"> 0 1 2 3</span><br><span class="line"> 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line">| defined by profile | length |</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line">| header extension |</span><br><span class="line">| .... |</span><br></pre></td></tr></table></figure></div>
<ul>
<li><code>defined by profile</code>:决定使用哪种Header Extension:<strong>one-byte</strong>或者<strong>two-byte</strong> header</li>
<li><code>length</code>:表示Header Extension的长度:<code>length x 4</code>字节</li>
</ul>
<h4 id="3-2-1-One-byte-header"><a href="#3-2-1-One-byte-header" class="headerlink" title="3.2.1. One-byte header"></a>3.2.1. One-byte header</h4><p>对于One-Byte Header,”defined by profile”字段为固定的0xBEDE。接着后面的结构如下:</p>
<div class="highlight-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="code"><pre><span class="line"> 0</span><br><span class="line"> 0 1 2 3 4 5 6 7</span><br><span class="line">+-+-+-+-+-+-+-+-+</span><br><span class="line">| ID | len |</span><br><span class="line">+-+-+-+-+-+-+-+-+</span><br></pre></td></tr></table></figure></div>
<ul>
<li>ID:4 bit长度的ID表示本地标识符</li>
<li>len:表示extension data长度,<strong>范围:0~15,为0表示长度为1字节,15表示16字节</strong></li>
</ul>
<p>如下是一个One-Byte Header的示例:</p>
<div class="highlight-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="code"><pre><span class="line"> 0 1 2 3</span><br><span class="line"> 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line">| 0xBE | 0xDE | length=3 |</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line">| ID | L=0 | data | ID | L=1 | data...</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line">...data | 0 (pad) | 0 (pad) | ID | L=3 |</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line">| data |</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br></pre></td></tr></table></figure></div>
<p>首先是<code>0xBEDE</code>固定字段开头,接着length长度为3,说明后面跟着<code>3x4</code>字节长度的header extension。</p>
<ul>
<li>对于第一个header extension:<code>L=0</code>,表示data长度为1字节。</li>
<li>对于第二个header extension:<code>L=1</code>,表示data长度为2字节。由于按4字节对齐,所以接着是值为0的填充数据。</li>
<li>对于最后一个header extension:<code>L=3</code>,表示data长度为4字节。</li>
</ul>
<h4 id="3-2-2-Two-Byte-Header"><a href="#3-2-2-Two-Byte-Header" class="headerlink" title="3.2.2. Two-Byte Header"></a>3.2.2. Two-Byte Header</h4><p><code>defined by profile</code>字段结构如下:</p>
<div class="highlight-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="code"><pre><span class="line"> 0 1</span><br><span class="line"> 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line">| 0x100 |appbits|</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br></pre></td></tr></table></figure></div>
<p>接着后面的每个扩展元素结构如下:</p>
<div class="highlight-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="code"><pre><span class="line"> 0 1</span><br><span class="line"> 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line">| ID | length |</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br></pre></td></tr></table></figure></div>
<ul>
<li>ID:本地标识符</li>
<li>length:表示extension data长度,范围1~255<br>如下是一个Two-Byte Header示例:</li>
</ul>
<div class="highlight-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="code"><pre><span class="line"> 0 1 2 3</span><br><span class="line"> 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line">| 0x10 | 0x00 | length=3 |</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line">| ID | L=0 | ID | L=1 |</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line">| data | 0 (pad) | ID | L=4 |</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line">| data |</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br></pre></td></tr></table></figure></div>
<p>首先<code>defined by profile</code>字段为0x1000,length为3,后面跟着3x4字节长度扩展。</p>
<ul>
<li>对于第一个header extension:<code>L=0</code>,数据长度为0</li>
<li>对于第二个header extension:<code>L=1</code>,data长度为1,接着是填充数据</li>
<li>对于第三个header extension:<code>L=4</code>,后面跟着4字节长度数据</li>
</ul>
]]></content>
<tags>
<tag>计算机网络</tag>
</tags>
</entry>
<entry>
<title>C++STL之关联容器</title>
<url>/2022/12/15/C++STL%E4%B9%8B%E5%85%B3%E8%81%94%E5%AE%B9%E5%99%A8/</url>
<content><![CDATA[<h2 id="1-关联容器概述"><a href="#1-关联容器概述" class="headerlink" title="1. 关联容器概述"></a>1. 关联容器概述</h2><p>关联容器主要用于查找操作,而查找操作主要通过其“关键字”进行。<br>关联容器又根据其自身的特点分为八种不同的容器,其主要的区别在于:</p>
<ol>
<li>仅包含关键字<code>key</code>还是包含键值对<code>key-value</code>,前者称为集合<code>set</code>,后者称为映射<code>map</code></li>
<li>关键字<code>key</code>是否允许重复,如果允许重复则包含<code>multi-</code></li>
<li>是否按照hash函数映射的方式组织元素,如果是,则加上<code>unordered-</code></li>
</ol>
<h2 id="2-集合set与多重集合multiset"><a href="#2-集合set与多重集合multiset" class="headerlink" title="2. 集合set与多重集合multiset"></a>2. 集合<code>set</code>与多重集合<code>multiset</code></h2><p><code>set</code>和<code>multiset</code>都定义在头文件<code><set></code>中,在构造过程中会自动按照关键字的大小初始化。如果出现多个值相同的元素,则<code>set</code>只会保留第一个元素,而<code>multiset</code>则可以同时保留多个相同值的元素。<br>默认情况下,<code>set</code>会将集合内的元素按照从小到大的顺序进行排列,也可以使用自定义或者C++STL提供的函数对象改变元素的排列方式。<br><strong>住:<code>set</code>不支持<code>at()</code>和<code>[]</code>操作。</strong></p>
<h3 id="2-1-set与multiset的构造"><a href="#2-1-set与multiset的构造" class="headerlink" title="2.1.set与multiset的构造"></a>2.1.<code>set</code>与<code>multiset</code>的构造</h3><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="type">int</span> a[] = {<span class="number">5</span>, <span class="number">3</span>, <span class="number">9</span>, <span class="number">3</span>, <span class="number">7</span>, <span class="number">2</span>, <span class="number">9</span>, <span class="number">5</span>};</span><br><span class="line"><span class="type">int</span> size = <span class="built_in">sizeof</span>(a)/<span class="built_in">sizeof</span>(<span class="type">int</span>);</span><br><span class="line"></span><br><span class="line">multiset<<span class="type">int</span>> s1;</span><br><span class="line"><span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">0</span>;i<size;i++){</span><br><span class="line"> s1.<span class="built_in">insert</span>(a[i]);</span><br><span class="line"> <span class="comment">// s1的元素排列与插入的先后顺序无关,取决于元素本身的关键字大小</span></span><br><span class="line"> <span class="comment">// 默认按照关键字从小到大排列</span></span><br><span class="line">}</span><br><span class="line"><span class="comment">// s1:2 3 3 5 5 7 9 9</span></span><br><span class="line"></span><br><span class="line">multiset<<span class="type">int</span>, less<<span class="type">int</span>>> <span class="built_in">s2</span>(s1);</span><br><span class="line"><span class="comment">// s2:2 3 3 5 5 7 9 9</span></span><br><span class="line"></span><br><span class="line"><span class="function">multiset<<span class="type">int</span>> <span class="title">s3</span><span class="params">(a,a+size)</span></span>;</span><br><span class="line"><span class="comment">// s3:2 3 3 5 5 7 9 9</span></span><br><span class="line"></span><br><span class="line">multiset<<span class="type">int</span>,greater<<span class="type">int</span>>> <span class="built_in">s4</span>(a,a+size);</span><br><span class="line"><span class="comment">// s4:2 3 3 5 5 7 9 9</span></span><br><span class="line"></span><br><span class="line">set<<span class="type">int</span>> s5;</span><br><span class="line"><span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">0</span>;i<size;i++){</span><br><span class="line"> s5.<span class="built_in">insert</span>(a[i]);</span><br><span class="line">}</span><br><span class="line"><span class="comment">// s5:2 3 5 7 9</span></span><br></pre></td></tr></table></figure></div>
<p>其中<code>less<int></code>和<code>greater<int></code>为头文件<code><functional></code>中的预定义函数对象,用来指定集合中的元素是按照升序还是降序排列。</p>
<h3 id="2-2-set与multiset的成员函数"><a href="#2-2-set与multiset的成员函数" class="headerlink" title="2.2. set与multiset的成员函数"></a>2.2. <code>set</code>与<code>multiset</code>的成员函数</h3><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="type">int</span> a[] = {<span class="number">9</span>, <span class="number">3</span>, <span class="number">9</span>, <span class="number">7</span>, <span class="number">10</span>, <span class="number">7</span>, <span class="number">3</span>};</span><br><span class="line"><span class="type">int</span> size = <span class="built_in">sizeof</span>(a)/<span class="built_in">sizeof</span>(<span class="type">int</span>);</span><br><span class="line">multiset<<span class="type">int</span>><span class="built_in">s</span>(a,a+size);</span><br><span class="line"><span class="comment">// s:3 3 7 7 9 9 10</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 输出关键字9的前一个元素,即7</span></span><br><span class="line">cout << *(--s.<span class="built_in">find</span>(<span class="number">9</span>)) << endl;</span><br><span class="line"><span class="comment">// 输出关键字8的前一个元素</span></span><br><span class="line"><span class="comment">// 由于集合中没有关键字为8的元素,所以find()返回集合结尾的后面一个位置</span></span><br><span class="line"><span class="comment">// 整个语句返回集合的最后一个元素</span></span><br><span class="line">cout << *(--s.<span class="built_in">find</span>(<span class="number">8</span>)) << endl;</span><br><span class="line"><span class="comment">// 输出关键字9的个数</span></span><br><span class="line">cout << s.<span class="built_in">count</span>(<span class="number">9</span>) << endl;</span><br><span class="line"><span class="comment">//在从小到大的数组中,</span></span><br><span class="line"><span class="comment">// lower_bound(key)返回指向 >=key 的元素的迭代器</span></span><br><span class="line"><span class="comment">// upper_bound(key)返回指向 >key 的元素迭代器</span></span><br><span class="line">cout << *s.<span class="built_in">lower_bound</span>(<span class="number">6</span>) << endl;<span class="comment">// 7</span></span><br><span class="line">cout << *s.<span class="built_in">upper_bound</span>(<span class="number">6</span>) << endl;<span class="comment">// 7</span></span><br><span class="line">cout << *s.<span class="built_in">lower_bound</span>(<span class="number">7</span>) << endl;<span class="comment">// 7</span></span><br><span class="line">cout << *s.<span class="built_in">upper_bound</span>(<span class="number">7</span>) << endl;<span class="comment">// 9</span></span><br><span class="line"><span class="comment">// equal_range(key)以pair的形式</span></span><br><span class="line"><span class="comment">// 返回关键字 =key 的下界lower和上界upper迭代器</span></span><br><span class="line"><span class="comment">// 可以理解为返回值的.first为.lower_bound()</span></span><br><span class="line"><span class="comment">// .second为.upper_bound()</span></span><br><span class="line">cout << *s.<span class="built_in">equal_range</span>(<span class="number">6</span>).first </span><br><span class="line"> << *s.<span class="built_in">equal_range</span>(<span class="number">6</span>).second << endl;<span class="comment">// 7, 7</span></span><br><span class="line">cout << *s.<span class="built_in">equal_range</span>(<span class="number">7</span>).first </span><br><span class="line"> << *s.<span class="built_in">equal_range</span>(<span class="number">7</span>).second << endl;<span class="comment">// 7, 9</span></span><br><span class="line"><span class="comment">// 删除关键字3并返回3的个数</span></span><br><span class="line">cout << s.<span class="built_in">erase</span>(<span class="number">3</span>) << endl;<span class="comment">// 2 set:7 7 9 9 10</span></span><br><span class="line"><span class="comment">// 删除一个9</span></span><br><span class="line">s.<span class="built_in">erase</span>(s.<span class="built_in">find</span>(<span class="number">9</span>));<span class="comment">// set:7 7 9 10</span></span><br><span class="line"><span class="comment">// 删除所有7</span></span><br><span class="line">s.<span class="built_in">erase</span>(s.<span class="built_in">find</span>(<span class="number">7</span>));<span class="comment">// set:9 10</span></span><br><span class="line"><span class="comment">// 删除所有元素</span></span><br><span class="line">s.<span class="built_in">clear</span>();<span class="comment">// set: (none)</span></span><br></pre></td></tr></table></figure></div>
<h2 id="3-映射map与多重映射multimap"><a href="#3-映射map与多重映射multimap" class="headerlink" title="3.映射map与多重映射multimap"></a>3.映射<code>map</code>与多重映射<code>multimap</code></h2><p>映射<code>map</code>和多重映射<code>multimap</code>中的元素是由<code>key</code>和<code>value</code>所构成的键值对组成的,这个键值对的类型<code>value_type</code>是由<code>pair</code>所定义的:</p>
<div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="keyword">typedef</span> pair<<span class="type">const</span> key,T> value_type</span><br></pre></td></tr></table></figure></div>
<p>在<code>map</code>的内部,根据关键字<code>key</code>来对元素进行排序和唯一性检查(<code>multimap</code>不进行唯一性检查)。<br>由于关键字的类型是多种多样的,例如字符串、数字或者某种自定义类型,因此要构成<code>map</code>,<strong>关键字类型必须定义元素比较的方法</strong>。<br><strong>通常情况下,对<code>map</code>中单个元素的访问速度会低于<code>unordered_map</code>,但<code>map</code>允许使用迭代器对其有序子集进行访问,也可以采用<code>[]+key</code>的形式访问元素。</strong></p>
<h3 id="3-1-multimap的构造"><a href="#3-1-multimap的构造" class="headerlink" title="3.1.multimap的构造"></a>3.1.<code>multimap</code>的构造</h3><div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="keyword">typedef</span> multimap<<span class="type">int</span>, string, less<<span class="type">int</span>>> ML;</span><br><span class="line"><span class="keyword">typedef</span> multimap<<span class="type">int</span>, string, greater<<span class="type">int</span>>> MG;</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> s1[] = {<span class="number">23</span>, <span class="number">20</span>, <span class="number">10</span>, <span class="number">28</span>};</span><br><span class="line">string s2[] = {<span class="string">"重庆"</span>, <span class="string">"上海"</span>, <span class="string">"北京"</span>, <span class="string">"成都"</span>};</span><br><span class="line"><span class="type">int</span> size = <span class="number">4</span>;</span><br><span class="line"></span><br><span class="line">ML m1;</span><br><span class="line"><span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">0</span>;i<size;i++){</span><br><span class="line"> m1.<span class="built_in">insert</span>(<span class="built_in">make_pair</span>(s1[i],s2[i]);</span><br><span class="line"><span class="comment">// (10 北京) (20 上海) (23 重庆) (28 成都)</span></span><br><span class="line">ML <span class="built_in">m2</span>(m1);</span><br><span class="line"><span class="comment">// (10 北京) (20 上海) (23 重庆) (28 成都)</span></span><br><span class="line">ML <span class="built_in">m3</span>(m2.<span class="built_in">begin</span>(),m2.<span class="built_in">end</span>());</span><br><span class="line"><span class="comment">// (10 北京) (20 上海) (23 重庆) (28 成都)</span></span><br><span class="line">MG m4;</span><br><span class="line"><span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">0</span>;i<size;i++){</span><br><span class="line"> m4.<span class="built_in">insert</span>(<span class="built_in">make_pair</span>(s1[i],s2[i]);</span><br><span class="line"><span class="comment">// (28 成都) (23 重庆) (20 上海) (10 北京)</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure></div>
<h3 id="3-2-map与multimap的成员函数"><a href="#3-2-map与multimap的成员函数" class="headerlink" title="3.2. map与multimap的成员函数"></a>3.2. <code>map</code>与<code>multimap</code>的成员函数</h3><p>映射与集合非常类似,因此其也具有<code>insert</code>,<code>lower_bound</code>,<code>upper_bound</code>,<code>equal_range</code>等成员函数。不同之处在于:在向<code>map</code>容器中添加元素时,需要先构成<code><key-value></code>键值对,再用<code>insert</code>插入;也可以通过<code>map</code>的<code>emplace</code>成员函数直接插入而无需构成键值对。</p>
<div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="keyword">typedef</span> multimap<<span class="type">int</span>, string> Map_L;</span><br><span class="line"><span class="type">int</span> a[] = {<span class="number">80</span>, <span class="number">60</span>, <span class="number">70</span>};</span><br><span class="line">string s[] = {<span class="string">"赵"</span>, <span class="string">"钱"</span>, <span class="string">"孙"</span>, <span class="string">"李"</span>, <span class="string">"赵"</span>, <span class="string">"孙"</span>};</span><br><span class="line"><span class="type">int</span> size = <span class="number">6</span>;</span><br><span class="line">Map_L mapL;</span><br><span class="line">mapL.<span class="built_in">emplace</span>(a[<span class="number">0</span>],s[<span class="number">0</span>]);</span><br><span class="line">mapL.<span class="built_in">emplace</span>(a[<span class="number">1</span>],s[<span class="number">1</span>]);</span><br><span class="line">mapL.<span class="built_in">emplace</span>(a[<span class="number">2</span>],s[<span class="number">2</span>]);</span><br><span class="line">mapL.<span class="built_in">emplace</span>(a[<span class="number">1</span>],s[<span class="number">3</span>]);</span><br><span class="line">mapL.<span class="built_in">emplace</span>(a[<span class="number">2</span>],s[<span class="number">4</span>]);</span><br><span class="line">mapL.<span class="built_in">emplace</span>(a[<span class="number">1</span>],s[<span class="number">5</span>]);</span><br><span class="line"><span class="comment">// (60,钱) (60,李) (60,孙) (70,孙) (70,赵) (80,赵)</span></span><br></pre></td></tr></table></figure></div>
<h2 id="4-unordered-set容器与unordered-multiset容器"><a href="#4-unordered-set容器与unordered-multiset容器" class="headerlink" title="4. unordered_set容器与unordered_multiset容器"></a>4. <code>unordered_set</code>容器与<code>unordered_multiset</code>容器</h2><p><code>unordered</code>表示无序,在无序关联容器中,元素并不是按照其值的比较关系来进行组织和存储的,而是使用一个哈希函数和关键字类型的<code>==</code>运算符来管理容器。<br>在无序关联容器中,元素之间并没有任何的序关系,当向容器中插入元素时,通过计算关键字的哈希值将其映射到不同的桶(bucket)中,每一个桶可以保存一个或多个元素。如果容器允许重复关键字,那么具有相同关键字的元素也会被映射到同一个桶中。</p>
<p>无序容器提供了大量管理桶的函数,如下:</p>
<div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 返回编号为n的桶中元素的个数</span></span><br><span class="line"><span class="built_in">bucket_size</span>(n)</span><br><span class="line"><span class="comment">// 返回容器中桶的数量</span></span><br><span class="line"><span class="built_in">bucket_count</span>()</span><br><span class="line"><span class="comment">// 返回关键字key对应的桶编号</span></span><br><span class="line"><span class="built_in">bucket</span>(key)</span><br><span class="line"><span class="comment">// 返回平均装填因子 = 元素数量/桶数量</span></span><br><span class="line"><span class="built_in">load_factor</span>()</span><br><span class="line"><span class="comment">// 返回最大装填因子</span></span><br><span class="line"><span class="comment">// 为保证load_factor <= max_load_factor,容器会在必要的时候增加桶数量</span></span><br><span class="line"><span class="built_in">max_load_factor</span>()</span><br><span class="line"><span class="comment">// 重建hash表,使得bucket_count >= n</span></span><br><span class="line"><span class="built_in">rehash</span>(n)</span><br><span class="line"><span class="comment">// 返回哈希函数对象</span></span><br><span class="line"><span class="built_in">hash_function</span>()</span><br></pre></td></tr></table></figure></div>
<p>此外,无序容器也提供了许多与有序容器相同的操作,因此可以很方便的将有序容器替换为无序容器。<br>需要注意的是,<strong>无序容器只支持前向迭代器,因此无法实现迭代器的自减运算;有序容器支持双向迭代器,支持迭代器的自增和自减运算</strong>。</p>
<h2 id="4-unordered-map容器和unordered-multimap容器"><a href="#4-unordered-map容器和unordered-multimap容器" class="headerlink" title="4. unordered_map容器和unordered_multimap容器"></a>4. <code>unordered_map</code>容器和<code>unordered_multimap</code>容器</h2><p><code>unordered_map</code>除了元素存储组织方式上的差别外,与<code>map</code>的操作基本一致,在此不展开介绍。</p>
<h2 id="5-补充"><a href="#5-补充" class="headerlink" title="5. 补充"></a>5. 补充</h2><p>在关联容器中,<code>set</code>的关键字是<code>const</code>的,<code>map</code>中的键值对<code><key-value></code>中的<code>key</code>也是<code>const</code>的,因此无法直接修改关键字,只能删除后再添加。</p>
]]></content>
<tags>
<tag>C++</tag>
<tag>STL</tag>
</tags>
</entry>
<entry>
<title>设计模式之单例模式(Singleton)</title>
<url>/2022/12/13/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E4%B9%8B%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F-Singleton/</url>
<content><![CDATA[<h2 id="1-单例模式定义"><a href="#1-单例模式定义" class="headerlink" title="1. 单例模式定义"></a>1. 单例模式定义</h2><p>单例模式是指在整个程序运行期间,某一个类只能产生一个实例,用于确保该类的唯一性。</p>
<p>单例模式也是为了确保程序的线程安全。</p>
<h2 id="2-单例模式特点"><a href="#2-单例模式特点" class="headerlink" title="2. 单例模式特点"></a>2. 单例模式特点</h2>]]></content>
<tags>
<tag>设计模式</tag>
</tags>
</entry>
<entry>
<title>GN工具介绍</title>
<url>/2022/12/17/GN%E5%B7%A5%E5%85%B7%E4%BB%8B%E7%BB%8D/</url>
<content><![CDATA[<h2 id="1-GN语言"><a href="#1-GN语言" class="headerlink" title="1. GN语言"></a>1. GN语言</h2><p>GN使用动态类型语言,具有以下类型:</p>
<ul>
<li>Boolean(true, false)</li>
<li>64-bit signed integers</li>
<li>Strings</li>
<li>Lists</li>
<li>Scopes(类似于dictionary)</li>
</ul>
<p>注意:<strong>GN中没有循环和用户定义的函数调用</strong></p>
<h3 id="1-1-Strings"><a href="#1-1-Strings" class="headerlink" title="1.1. Strings"></a>1.1. <code>Strings</code></h3><p>String用双引号括起来,并且使用反斜杠作为转义字符。<br>仅支持以下转义序列:</p>
<ul>
<li><code>\"</code> 表示符号<code>"</code></li>
<li><code>\$</code> 表示符号`$``</li>
<li><code>\\</code> 表示符号<code>\</code><br>除以上用法外,反斜杠的任何用法都被视为符号<code>\</code></li>
</ul>
<p><code>$</code> 支持简单变量替换,将其后紧跟的单词替换为变量的值,如下例:</p>
<div class="highlight-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="code"><pre><span class="line">a = "mypath"</span><br><span class="line">b = "$a/foo.cc" # b -> "mypath/foo.cc"</span><br><span class="line">c = "foo${a}bar.cc"# c -> "foomypathbar.cc"</span><br></pre></td></tr></table></figure></div>
<h3 id="1-2-Lists"><a href="#1-2-Lists" class="headerlink" title="1.2. Lists"></a>1.2. <code>Lists</code></h3><p>列表的长度无法获取。</p>
<p>列表支持追加元素:</p>
<div class="highlight-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="code"><pre><span class="line">a = [ "first" ]</span><br><span class="line">a += [ "second" ] # [ "first", "second" ]</span><br><span class="line">a += [ "third", "fourth" ] # [ "first", "second", "third", "fourth" ] </span><br><span class="line">b = a + [ "fifth" ] # [ "first", "second", "third", "fourth", "fifth" ]</span><br></pre></td></tr></table></figure></div>
<p>列表也支持删除元素:</p>
<div class="highlight-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="code"><pre><span class="line">a = [ "first", "second", "third", "first" ]</span><br><span class="line">b = a - [ "first" ] # [ "second", "third" ]</span><br><span class="line">a -= [ "second" ] # [ "first", "third", "fourth" ]</span><br></pre></td></tr></table></figure></div>
<p>列表的<code>-</code>运算符搜索匹配项并且删除所有的匹配项,如果未找到匹配项,则会抛出错误。<br>列表支持索引(下标从0开始):</p>
<div class="highlight-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="code"><pre><span class="line">a = [ "first", "second", "third" ]</span><br><span class="line">b = a[1] # -> "second"</span><br></pre></td></tr></table></figure></div>
<h3 id="1-3-Functions"><a href="#1-3-Functions" class="headerlink" title="1.3. Functions"></a>1.3. Functions</h3><p>简单函数与大多数其他语言类似:</p>
<div class="highlight-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="code"><pre><span class="line">print("hello,world")</span><br><span class="line">assert(is_win, "This should only be executed on Windows")</span><br></pre></td></tr></table></figure></div>
<p>部分函数采用一个由{} 包围的代码块:</p>
<div class="highlight-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="code"><pre><span class="line">static_library("mylibrary"){</span><br><span class="line"> sources = ["a.cc"]</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div>
<p>用户无法自行定义函数,但是可以通过定义模板达到类似的效果,见后文</p>
<h3 id="1-4-Scoping-and-execution"><a href="#1-4-Scoping-and-execution" class="headerlink" title="1.4. Scoping and execution"></a>1.4. Scoping and execution</h3><p>文件和<code>{}</code>块引入了新的作用域。<br>作用域是嵌套的,读取变量时会按照相反顺序搜索包含范围,直到找到匹配的名称。写入变量时,总是进入最里面的作用域。<br>除了最里面的作用域外,没有任何方法可以修改任何封闭范围,这意味着,当定义目标时,在块内执行的任何操作都不会泄漏到文件的其余部分。<br>对于<code>if/else</code>语句,即使使用了<code>{}</code>,也不会引入一个新的作用域,因此更改将在语句之外永久存在。</p>
<h2 id="2-命名"><a href="#2-命名" class="headerlink" title="2. 命名"></a>2. 命名</h2><h3 id="2-1-文件以及目录名"><a href="#2-1-文件以及目录名" class="headerlink" title="2.1. 文件以及目录名"></a>2.1. 文件以及目录名</h3><p>三种可能的形式:<br>相对路径:</p>
<div class="highlight-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="code"><pre><span class="line">"foo.cc"</span><br><span class="line">"src/foo.cc"</span><br><span class="line">"../src/foo.cc"</span><br></pre></td></tr></table></figure></div>
<p>SourceTree绝对路径:</p>
<div class="highlight-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="code"><pre><span class="line">"//net/foo.cc"</span><br><span class="line">"//base/test/foo.cc"</span><br></pre></td></tr></table></figure></div>
<p>系统绝对路径:(比较少见,多用于头文件目录)</p>
<div class="highlight-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="code"><pre><span class="line">"/usr/local/include/"</span><br><span class="line">"C:/Program Files/Windows Kits/Include"</span><br></pre></td></tr></table></figure></div>
<h3 id="2-2-标签"><a href="#2-2-标签" class="headerlink" title="2.2 标签"></a>2.2 标签</h3><p>可以参与依赖关系图的所有内容都由一个定义格式的字符串所标识,常见的标签如下所示:</p>
<div class="highlight-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="code"><pre><span class="line">"//base/test:test_support"</span><br></pre></td></tr></table></figure></div>
<p>它由一个SourceTree绝对路径,一个冒号和一个名称组成,这意味着在<code>src/base/test/BUILD.gn</code>中查找名为<code>test_support</code>的内容。</p>
<p>规范标签通常还包括正在使用的工具链的标签。通常情况下,工具链标签是隐式继承的,但可以将其包含在指定的跨工具链的依赖中,如下:</p>
<div class="highlight-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="code"><pre><span class="line">"//base/test:test_support(//build/toolchain/win:msvc)"</span><br></pre></td></tr></table></figure></div>
<p>在这种情况下,系统会在文件<code>//build/toolchain/win</code>中查找名为<code>msvc</code>的工具链定义从而了解如何编译此目标.<br>如果要引用同一构建文件中的内容,可以省略路径名,只需以冒号开头:</p>
<div class="highlight-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="code"><pre><span class="line">":base"</span><br></pre></td></tr></table></figure></div>
<p>标签可以指定为相对于当前目录</p>
<div class="highlight-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="code"><pre><span class="line">"source/plugin:myplugin"</span><br><span class="line">"../net:url_request"</span><br></pre></td></tr></table></figure></div>
<p>如果未指定名称,它将继承目录名:</p>
<div class="highlight-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="code"><pre><span class="line">"//net" = "//net:net"</span><br><span class="line">"//tools/gn" = "//tools/gn:gn"</span><br></pre></td></tr></table></figure></div>
<h2 id="3-构建配置"><a href="#3-构建配置" class="headerlink" title="3. 构建配置"></a>3. 构建配置</h2><h3 id="3-1-总体构建流程"><a href="#3-1-总体构建流程" class="headerlink" title="3.1.总体构建流程"></a>3.1.总体构建流程</h3><ol>
<li>在当前路径下寻找.gn 文件,并在目录树上遍历,直到找到一个.gn 文件,设置此目录为“源根目录”,并且解释该文件从而查找生成配置文件的名称</li>
<li>执行构建配置文件(默认的工具链)</li>
<li>加载根目录中的BUILD.gn</li>
<li>递归加载其他目录中的BUILD.gn 从而解决所有当前依赖关系。如果在指定位置未找到BUILD文件,GN将在tools/gn/secondary 内的相应位置查找</li>
<li>解决了目标的依赖关系后,将.ninja文件写入磁盘</li>
<li>解决所有目标后,写入build.ninja</li>
</ol>
<h3 id="3-2-构建配置文件"><a href="#3-2-构建配置文件" class="headerlink" title="3.2. 构建配置文件"></a>3.2. 构建配置文件</h3><p>执行的首个文件是构建配置文件,此文件名在标记为存储库根的<code>.gn</code>文件中被指定。<br><strong>只有一个构建配置文件。</strong><br>此文件设置所有其他构建文件将在其中执行的范围。在此文件中设置的任何参数、变量、默认值等都将对构建过程中的所有文件可见。</p>
<h3 id="3-3-构建参数"><a href="#3-3-构建参数" class="headerlink" title="3.3. 构建参数"></a>3.3. 构建参数</h3><p>参数可以从命令行(以及其他工具链)传入,可以声明接受哪些参数并指定默认值<br>在给定范围内多次声明给定参数是错误的。<br>通常,参数在导入的文件中声明(在生成的某些子集中间共享)或在主生成配置文件中声明(使其全局)。</p>
<h3 id="3-4-目标默认值"><a href="#3-4-目标默认值" class="headerlink" title="3.4. 目标默认值"></a>3.4. 目标默认值</h3><p>可以为给定的目标类型设置一些默认值,通常在构建配置文件中完成,用以设置一个默认配置的列表,此列表定义每个目标类型的构建标志和其他设置信息。<br>例如,声明一个静态库时,将会应用静态库的目标默认值。目标可以覆盖、修改或保留这些值</p>
<div class="highlight-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="code"><pre><span class="line"># This call is typically in the build config file (see above).</span><br><span class="line">set_defaults("static_library") {</span><br><span class="line"> configs = [ "//build:rtti_setup", "//build:extra_warnings" ]</span><br><span class="line">}</span><br><span class="line"># This would be in your directory's BUILD.gn file.</span><br><span class="line">static_library("mylib") {</span><br><span class="line"> # At this point configs is set to [ "//build:rtti_setup", "//build:extra_warnings" ]</span><br><span class="line"> # by default but may be modified.</span><br><span class="line"> configs -= "//build:extra_warnings" # Don't want these warnings.</span><br><span class="line"> configs += ":mylib_config" # Add some more configs.</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div>
<p>设置目标默认值的另一个用例是通过模板定义自己的目标类型并且希望指定某些默认值。</p>
<h2 id="4-目标"><a href="#4-目标" class="headerlink" title="4. 目标"></a>4. 目标</h2><p>目标就是构建图中的节点,通常表示即将生成的某种可执行文件或库文件。<br>目标依赖于其他目标,内置目标类型包括:</p>
<ul>
<li>action: 运行脚本以生成文件</li>
<li>action_foreach: 为每一个源文件运行一次脚本</li>
<li>component: 可以配置为另一种类型的库</li>
<li>executable: 生成可执行文件</li>
<li>group: 引用一个或多个其他目标的虚拟依存关系节点</li>
<li>shared_library: 一个.dll或.so</li>
<li>source_set: 一个轻量级虚拟静态库(通常优于真实静态库,因为它可以更快地构建)</li>
<li>static_library: 一个.lib或者.a文件 (通常你需要一个source_set)</li>
<li>test: 生成可执行文件,但将其注释为测试</li>
</ul>
<p>可以扩展此功能,使用模板创建自定义目标类型。</p>
<h3 id="4-1-配置"><a href="#4-1-配置" class="headerlink" title="4.1. 配置"></a>4.1. 配置</h3><p>配置是指定标志集(包括目录和定义)的命名对象。他们可以应用于目标并推送到相关目标。<br>定义一个配置:</p>
<div class="highlight-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="code"><pre><span class="line">config("myconfig"){</span><br><span class="line"> includes = [ "src/include" ]</span><br><span class="line"> defines = [ "ENABLE_DOOM_MELON" ]</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div>
<p>将配置应用于目标:</p>
<div class="highlight-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="code"><pre><span class="line">executable("doom_melon"){</span><br><span class="line"> configs = [ ":myconfig" ]</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div>
<p>构建配置文件通常指定设置默认配置列表的目标默认值。目标可以根据需要添加或删除到此列表。因此,在实践中,您通常会使用<code>configs += ":myconfig"</code>附加到默认值列表中。</p>
<h3 id="4-2-公共配置"><a href="#4-2-公共配置" class="headerlink" title="4.2. 公共配置"></a>4.2. 公共配置</h3><p>一个目标可以将设置应用于依赖他的其他目标。<br>最常见的例子是第三方目标,需要一些定义或包含目录才能正确编译其标头。<br>你希望这些设置既适用于第三方库本身的编译,也适用于使用该库的所有目标。<br>为了实现以上目标,可以使用要应用的设置编写配置</p>
<div class="highlight-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="code"><pre><span class="line">config("my_external_library_config") {</span><br><span class="line"> includes = "."</span><br><span class="line"> defines = [ "DISABLE_JANK" ]</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div>
<p>然后将此配置作为公共配置添加到目标。它既适用于目标,也适用于直接依赖它的目标。</p>
<div class="highlight-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="code"><pre><span class="line">shared_library("my_external_library") {</span><br><span class="line"> ...</span><br><span class="line"> # Targets that depend on this get this config applied.</span><br><span class="line"> public_configs = [ ":my_external_library_config" ]</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div>
<p>通过将你的目标添加为公共依赖项,依赖目标可以反过来将其转发到依赖树的另一个级别。<br>通过将目标设置为<code>all_dependent_config</code>,可以将配置转发给所有依赖项直到链接边界。</p>
<h2 id="5-工具链"><a href="#5-工具链" class="headerlink" title="5.工具链"></a>5.工具链</h2><p>工具链是一组构建命令,用于运行不同类型的输入文件和链接任务。<br>构建中可以有多个工具链。最简单的是将每一个都视为完全独立的构建,它们之间可以有额外的依赖关系。例如,这意味着32位Windows构建可能依赖于64位助手目标。<br>当一个目标指定对另一个目标的依赖关系时,将继承当前工具链,除非它被显式重写。</p>
<h3 id="5-1-工具链和构建配置"><a href="#5-1-工具链和构建配置" class="headerlink" title="5.1.工具链和构建配置"></a>5.1.工具链和构建配置</h3><p>当您有一个只有一个工具链的简单构建时,构建配置文件仅在构建开始时加载一次。它必须调用<code>set_default_toolchain</code>来告诉GN要使用的工具链定义的标签。此工具链定义包含用于编译器和链接器的命令。工具链定义的<code>toolchain_args</code>部分会被忽略。<br>当目标依赖于使用不同工具链的目标时,GN将使用该辅助工具链启动构建以解析目标。GN将使用工具链定义中指定的参数加载构建配置文件。由于工具链已知,因此忽略对<code>set_default_toolchain</code>工具链的调用。<br>因此,工具链配置是双向的。在默认工具链(即主构建目标)中,配置从构建配置文件流向工具链:构建配置文件查看构建状态(操作系统类型、CPU架构等),并决定使用哪个工具链(通过<code>set_default_toolchain</code>工具链)。在辅助工具链中,配置从工具链流向构建配置文件:工具链定义中的<code>toolchain_args</code>指定重新调用构建的参数。</p>
<h3 id="5-2-工具链示例"><a href="#5-2-工具链示例" class="headerlink" title="5.2. 工具链示例"></a>5.2. 工具链示例</h3><p>假设默认版本是64位版本。这是基于当前系统的默认CPU架构,或者用户在命令行上传递了<code>target_CPU="x64"</code>。构建配置文件可能如下所示,以设置默认工具链:</p>
<div class="highlight-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="code"><pre><span class="line"># Set default toolchain only has an effect when run in the context of</span><br><span class="line"># the default toolchain. Pick the right one according to the current CPU</span><br><span class="line"># architecture.</span><br><span class="line">if (target_cpu == "x64") {</span><br><span class="line"> set_default_toolchain("//toolchains:64")</span><br><span class="line">} else if (target_cpu == "x86") {</span><br><span class="line"> set_default_toolchain("//toolchains:32")</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div>
<p>如果64位目标想要依赖于32位二进制文件,它将使用<code>datadeps</code>指定依赖关系(<code>datadeps</code>类似于仅在运行时需要且不链接的<code>deps</code>,因为您无法链接32位和64位库)。</p>
<div class="highlight-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="code"><pre><span class="line">executable("my_program") {</span><br><span class="line"> ...</span><br><span class="line"> if (target_cpu == "x64") {</span><br><span class="line"> # The 64-bit build needs this 32-bit helper.</span><br><span class="line"> datadeps = [ ":helper(//toolchains:32)" ]</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line">if (target_cpu == "x86") {</span><br><span class="line"> # Our helper library is only compiled in 32-bits.</span><br><span class="line"> shared_library("helper") {</span><br><span class="line"> ...</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div>
<p>上面引用的工具链文件(<code>toolchains/BUILD.gn</code>)将定义两个工具链:</p>
<div class="highlight-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="code"><pre><span class="line">toolchain("32") {</span><br><span class="line"> tool("cc") {</span><br><span class="line"> ...</span><br><span class="line"> }</span><br><span class="line"> ... more tools ...</span><br><span class="line"> # Arguments to the build when re-invoking as a secondary toolchain.</span><br><span class="line"> toolchain_args() {</span><br><span class="line"> toolchain_cpu = "x86"</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line">toolchain("64") {</span><br><span class="line"> tool("cc") {</span><br><span class="line"> ...</span><br><span class="line"> }</span><br><span class="line"> ... more tools ...</span><br><span class="line"> # Arguments to the build when re-invoking as a secondary toolchain.</span><br><span class="line"> toolchain_args() {</span><br><span class="line"> toolchain_cpu = "x64"</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div>
<p>工具链参数明确指定了CPU架构,因此,如果目标依赖于使用该工具链的某些东西,则在重新调用构建时将设置该CPU架构。默认工具链忽略这些参数,因为在知道它们时,构建配置已经运行。通常,工具链参数和用于设置默认工具链的条件应该一致。<br>多构建设置的好处是,可以在目标中编写引用当前工具链状态的条件。构建文件将在每个工具链的不同状态下重新运行。对于上面的<code>my_program</code>示例,您可以看到它查询CPU架构,只为程序的64位构建添加了依赖项。32位构建不会获得这种依赖关系。</p>
<h3 id="5-3-声明工具链"><a href="#5-3-声明工具链" class="headerlink" title="5.3. 声明工具链"></a>5.3. 声明工具链</h3><p>工具链使用toolchain命令声明,该命令设置用于每个编译和链接操作的命令。该工具链还指定了一组参数,以在执行时传递给构建配置文件。这允许你将配置信息传递给备用工具链。</p>
<h2 id="6-模板"><a href="#6-模板" class="headerlink" title="6. 模板"></a>6. 模板</h2><p>模板是GN重用代码的主要方式。通常,模板会扩展到一个或多个其他目标类型。</p>
<div class="highlight-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="code"><pre><span class="line"># Declares static library consisting of rules to build all of the IDL files into</span><br><span class="line"># compiled code.</span><br><span class="line">template("idl") {</span><br><span class="line"> source_set(target_name) {</span><br><span class="line"> ...</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div>
<p>通常,您的模板定义将放在<code>.gni</code>文件中,用户将导入该文件以查看模板定义:</p>
<div class="highlight-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="code"><pre><span class="line">import("//tools/idl_compiler.gni")</span><br><span class="line">idl("my_interfaces") {</span><br><span class="line"> sources = [ "a.idl", "b.idl" ]</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div>
<p>声明模板会在当时范围内的变量周围创建一个闭包。当调用模板时,魔术变量<code>invoker</code>用于从调用范围中读取变量。模板通常会将其感兴趣的值复制到自己的范围中:</p>
<div class="highlight-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="code"><pre><span class="line">template("idl") {</span><br><span class="line"> source_set(target_name) {</span><br><span class="line"> sources = invoker.sources</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div>
<p>模板执行时的当前目录将是调用生成文件的目录,而不是模板源文件的目录。因此,从模板调用器传递的文件是正确的(这通常是模板中大多数文件处理的原因)。<br>但是,如果模板本身有文件(可能它生成了一个运行脚本的操作),您将希望使用绝对路径(<code>//foo/…</code>)来引用这些文件,以说明当前目录在调用过程中是不可预测的。</p>
<h2 id="7-其他功能"><a href="#7-其他功能" class="headerlink" title="7. 其他功能"></a>7. 其他功能</h2><h3 id="7-1-导入"><a href="#7-1-导入" class="headerlink" title="7.1. 导入"></a>7.1. 导入</h3><p>您可以使用<code>import</code>功能将<code>.gni</code>文件导入当前范围。这不是一个包含。导入的文件独立执行,生成的作用域复制到当前文件中。这允许缓存导入的结果,也防止了对包含的一些更“创造性”的使用。</p>
<h3 id="7-2-路径处理"><a href="#7-2-路径处理" class="headerlink" title="7.2. 路径处理"></a>7.2. 路径处理</h3><p>通常,您需要创建一个文件名或一个相对于不同目录的文件名列表。这在运行脚本时尤其常见,脚本以生成输出目录作为当前目录执行,而生成文件通常指与其包含目录相关的文件。<br>您可以使用<code>rebase_path</code>转换目录。<br>将相对于当前目录的文件名转换为相对于根生成目录的文件名的典型用法是:<code>new_paths=rebase_path("myfile.c",root_build_dir)</code></p>
<h3 id="7-3-模式"><a href="#7-3-模式" class="headerlink" title="7.3. 模式"></a>7.3. 模式</h3><p>模式用于为自定义目标类型的给定输入集生成输出文件名,并自动从<code>sources</code>变量中删除文件。</p>
<h3 id="7-4-执行脚本"><a href="#7-4-执行脚本" class="headerlink" title="7.4. 执行脚本"></a>7.4. 执行脚本</h3><p>执行脚本有两种方法。GN中的所有外部脚本都是<code>Python</code>。<br>第一种方法是作为构建步骤。作为构建的一部分,这样的脚本将接受一些输入并生成一些输出。调用脚本的目标使用<code>"action"</code>目标类型声明。<br>第二种执行脚本的方法是在生成文件执行期间同步执行。在某些情况下,这对于确定要编译的文件集或获取生成文件可能依赖的某些系统配置是必要的。生成文件可以读取脚本的标准输出并以不同的方式对其进行操作。<br>同步脚本执行由<code>exec_script</code>函数完成。因为同步执行脚本需要暂停当前构建文件的执行,直到<code>Python</code>进程完成执行,所以依赖外部脚本的速度很慢,应该最小化。<br>您可以同步读取和写入文件,这在同步运行脚本时偶尔是必要的。典型的用例是传递比当前平台的命令行限制长的文件名列表。如果可能的话,应该避免这些功能。</p>
]]></content>
<tags>
<tag>GN</tag>
</tags>
</entry>
<entry>
<title>WebRTC基础之GN</title>
<url>/2022/12/17/WebRTC%E5%9F%BA%E7%A1%80%E4%B9%8BGN/</url>
<content><![CDATA[<h2 id="1-GN相关介绍"><a href="#1-GN相关介绍" class="headerlink" title="1. GN相关介绍"></a>1. GN相关介绍</h2><p><code>GN</code>是谷歌用来生成<code>Ninja</code>配置文件的工具。<br><code>GN</code>的配置文件使用<code>Python</code>语法,WebRTC源码根目录的<code>BUILD.gn</code>文件是整个项目的配置文件,在其中引用了诸如<code>webrtc.gni</code>等配置文件,这些配置文件中定义了很多目标模板、变量等。<br><code>BUILD.gn</code>里还定义了一些构建目标,比如:</p>
<div class="highlight-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="code"><pre><span class="line">rtc_static_library("webrtc") {</span><br><span class="line"> # Only the root target and the test should depend on this.</span><br><span class="line"> visibility = [</span><br><span class="line"> "//:default",</span><br><span class="line"> "//:webrtc_lib_link_test",</span><br><span class="line"> ]</span><br><span class="line"></span><br><span class="line"> sources = []</span><br><span class="line"> complete_static_lib = true</span><br><span class="line"> suppressed_configs += [ "//build/config/compiler:thin_archive" ]</span><br><span class="line"> defines = []</span><br><span class="line"></span><br><span class="line"> deps = [</span><br><span class="line"> ":webrtc_common",</span><br><span class="line"> "api:create_peerconnection_factory",</span><br><span class="line"> "api:libjingle_peerconnection_api",</span><br><span class="line"> "api:rtc_error",</span><br><span class="line"> "api:transport_api",</span><br><span class="line"> "api/crypto",</span><br><span class="line"> "api/rtc_event_log:rtc_event_log_factory",</span><br><span class="line"> "api/task_queue",</span><br><span class="line"> "api/task_queue:default_task_queue_factory",</span><br><span class="line"> "audio",</span><br><span class="line"> "call",</span><br><span class="line"> "common_audio",</span><br><span class="line"> "common_video",</span><br><span class="line"> "logging:rtc_event_log_api",</span><br><span class="line"> "media",</span><br><span class="line"> "modules",</span><br><span class="line"> "modules/video_capture:video_capture_internal_impl",</span><br><span class="line"> "p2p:rtc_p2p",</span><br><span class="line"> "pc:libjingle_peerconnection",</span><br><span class="line"> "pc:peerconnection",</span><br><span class="line"> "pc:rtc_pc",</span><br><span class="line"> "pc:rtc_pc_base",</span><br><span class="line"> "rtc_base",</span><br><span class="line"> "sdk",</span><br><span class="line"> "video",</span><br><span class="line"> ]</span><br><span class="line"> # ...</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div>
<p>下面简单解释一下各个部分的含义:</p>
<ul>
<li><code>rtc_static_library</code>:通常会基于一些模板定义构建目标,常用的有静态库(<code>rtc_static_library</code>)、动态库(<code>rtc_shared_library</code>)和可执行程序(<code>rtc_executable</code>)</li>
<li><code>visibility</code>:用来指明这个目标可以被哪些其他目标依赖</li>
<li><code>sources</code>:用来指明这个目标的源码文件,这里没有指定任何源码,因为所有源码都包含在其他的依赖目标中。</li>
<li><code>deps</code>:用来指明依赖目标。在这部分以<code>:</code>开头的就是在同一个<code>BUILD.gn</code>中定义的目标;否则就是其他目录的<code>BUILD.gn</code>定义的目标。指定目录时可能会以<code>//</code>开头,比如<code>api</code>和<code>api/rtc_event_log</code>都是去子目录查找;指定目录如果不包含<code>:</code>,就是指和目录同名的目标,比如<code>audio</code>等价于<code>audio:audio</code>。</li>
<li><code>complete_static_lib = true</code>和<code>suppressed_configs += [ "//build/config/compiler:thin_archive" ]</code>:是定义静态库时必须的,否则编译出来的静态库不完整(不会包含所有的目标文件)。</li>
<li><code>defines</code>:用来定义宏。</li>
</ul>
<p>除了以上参数之外,还有几个参数比较常用:</p>
<ul>
<li><code>include_dirs</code>:用来指明C++头文件查找路径</li>
<li><code>libs</code>:用来指明依赖的预编译库</li>
<li><code>cflags</code>:用来指明编译选项</li>
<li><code>ldflags</code>:用来指明链接选项</li>
</ul>
<p>上面的参数如果是列表,就都可以使用<code>=</code>进行覆盖赋值或使用<code>+=</code>进行增量赋值。</p>
<h2 id="2-系统宏定义"><a href="#2-系统宏定义" class="headerlink" title="2. 系统宏定义"></a>2. 系统宏定义</h2><p>在<code>WebRTC</code>的源码中,有很多类似于<code>if defined(WEBRTC_IOS)</code>之类的宏定义检查,这是在同一个函数或者文件中。只有细微的平台差异需要使用不同的代码,因此根据不同的系统宏定义来控制只编译对应平台的代码。<br>上述宏的定义是通过<code>gn</code>配置文件来控制的,具体逻辑在源码根目录下<code>BUILD.gn</code>文件中的<code>config("common_inherited_config")代码块中</code>:</p>
<div class="highlight-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="code"><pre><span class="line">config("common_inherited_config") {</span><br><span class="line"> # ...</span><br><span class="line"> if (is_posix || is_fuchsia) {</span><br><span class="line"> defines += [ "WEBRTC_POSIX" ]</span><br><span class="line"> }</span><br><span class="line"> if (is_ios) {</span><br><span class="line"> defines += [</span><br><span class="line"> "WEBRTC_MAC",</span><br><span class="line"> "WEBRTC_IOS",</span><br><span class="line"> ]</span><br><span class="line"> }</span><br><span class="line"> if (is_linux) {</span><br><span class="line"> defines += [ "WEBRTC_LINUX" ]</span><br><span class="line"> }</span><br><span class="line"> if (is_mac) {</span><br><span class="line"> defines += [ "WEBRTC_MAC" ]</span><br><span class="line"> }</span><br><span class="line"> if (is_fuchsia) {</span><br><span class="line"> defines += [ "WEBRTC_FUCHSIA" ]</span><br><span class="line"> }</span><br><span class="line"> if (is_win) {</span><br><span class="line"> defines += [ "WEBRTC_WIN" ]</span><br><span class="line"> }</span><br><span class="line"> if (is_android) {</span><br><span class="line"> defines += [</span><br><span class="line"> "WEBRTC_LINUX",</span><br><span class="line"> "WEBRTC_ANDROID",</span><br><span class="line"> ]</span><br><span class="line"></span><br><span class="line"> if (build_with_mozilla) {</span><br><span class="line"> defines += [ "WEBRTC_ANDROID_OPENSLES" ]</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> if (is_chromeos) {</span><br><span class="line"> defines += [ "CHROMEOS" ]</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> if (rtc_sanitize_coverage != "") {</span><br><span class="line"> assert(is_clang, "sanitizer coverage requires clang")</span><br><span class="line"> cflags += [ "-fsanitize-coverage=${rtc_sanitize_coverage}" ]</span><br><span class="line"> ldflags += [ "-fsanitize-coverage=${rtc_sanitize_coverage}" ]</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> if (is_ubsan) {</span><br><span class="line"> cflags += [ "-fsanitize=float-cast-overflow" ]</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div>
<p>其中引用了<code>is_ios</code>等变量,这些变量的定义在<code>build/config/BUILDCONFIG.gn</code>中:</p>
<div class="highlight-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="code"><pre><span class="line"># =============================================================================</span><br><span class="line"># OS DEFINITIONS</span><br><span class="line"># =============================================================================</span><br><span class="line">#</span><br><span class="line"># We set these various is_FOO booleans for convenience in writing OS-based</span><br><span class="line"># conditions.</span><br><span class="line">#</span><br><span class="line"># - is_android, is_chromeos, is_ios, and is_win should be obvious.</span><br><span class="line"># - is_mac is set only for desktop Mac. It is not set on iOS.</span><br><span class="line"># - is_posix is true for mac and any Unix-like system (basically everything</span><br><span class="line"># except Fuchsia and Windows).</span><br><span class="line"># - is_linux is true for desktop Linux and ChromeOS, but not Android (which is</span><br><span class="line"># generally too different despite being based on the Linux kernel).</span><br><span class="line">#</span><br><span class="line"># Do not add more is_* variants here for random lesser-used Unix systems like</span><br><span class="line"># aix or one of the BSDs. If you need to check these, just check the</span><br><span class="line"># current_os value directly.</span><br><span class="line"></span><br><span class="line">is_android = current_os == "android"</span><br><span class="line">is_chromeos = current_os == "chromeos"</span><br><span class="line">is_fuchsia = current_os == "fuchsia"</span><br><span class="line">is_ios = current_os == "ios"</span><br><span class="line">is_linux = current_os == "chromeos" || current_os == "linux"</span><br><span class="line">is_mac = current_os == "mac"</span><br><span class="line">is_nacl = current_os == "nacl"</span><br><span class="line">is_win = current_os == "win" || current_os == "winuwp"</span><br><span class="line"></span><br><span class="line">is_posix = !is_win && !is_fuchsia</span><br></pre></td></tr></table></figure></div>
<p>将各系统与对应的系统宏定义总结如下:</p>
<ul>
<li>iOS系统:<code>WEBRTC_MAC</code>、<code>WEBRTC_IOS</code>、<code>WEBRTC_POSIX</code></li>
<li>maxOS系统:<code>WEBRTC_MAC</code>、<code>WEBRTC_POSIX</code></li>
<li>Android系统:<code>WEBRTC_LINUX</code>、<code>WEBRTC_ANDROID</code>、<code>WEBRTC_POSIX</code></li>
<li>Linux系统:<code>WEBRTC_LINUX</code>、<code>WEBRTC_POSIX</code></li>
<li>Windows系统:<code>WEBRTC_WIN</code></li>
</ul>
<h2 id="3-添加源码目录和预编译依赖库"><a href="#3-添加源码目录和预编译依赖库" class="headerlink" title="3. 添加源码目录和预编译依赖库"></a>3. 添加源码目录和预编译依赖库</h2><p>(后续更新)</p>
]]></content>
<tags>
<tag>WebRTC基础</tag>
<tag>GN</tag>
</tags>
</entry>
<entry>
<title>WebRTC基础之SDP</title>
<url>/2022/12/17/WebRTC%E5%9F%BA%E7%A1%80%E4%B9%8BSDP/</url>
<content><![CDATA[]]></content>
<tags>
<tag>WebRTC基础</tag>
</tags>
</entry>
<entry>
<title>WebRTC基础之基本流程分析</title>
<url>/2022/12/17/WebRTC%E5%9F%BA%E7%A1%80%E4%B9%8B%E5%9F%BA%E6%9C%AC%E6%B5%81%E7%A8%8B%E5%88%86%E6%9E%90/</url>
<content><![CDATA[<h2 id="1-基本流程简介"><a href="#1-基本流程简介" class="headerlink" title="1. 基本流程简介"></a>1. 基本流程简介</h2><p><img lazyload src="/images/loading.svg" data-src="/2022/12/17/WebRTC%E5%9F%BA%E7%A1%80%E4%B9%8B%E5%9F%BA%E6%9C%AC%E6%B5%81%E7%A8%8B%E5%88%86%E6%9E%90/1-1.png" alt="WebRTC基本流程"></p>
<p>WebRTC的通话流程如上:</p>
<ol>
<li>发起端创建本地PeerConnection(简称 PC)对象,并创建Offer。</li>
<li>发起端通过Signaling Server(HTTP服务)把Offer送到应答端</li>
<li>应答端创建本地PC对象,把发起端的Offer设置给PC,然后获得Answer。</li>
<li>应答端通过Signaling Server(长连接)把Answer发给发起端。</li>
<li>发起端把应答端的Answer设置给PC。</li>
<li>两端都收集本地PC的ICE Candidate(包括访问 TURN Server),通过Signaling Server(长连接)发送给对端,对端把ICE Candidate设置给本地的PC。</li>
<li>两端开始建立P2P的Socket,并收发音视频数据。</li>
</ol>
<p>下面是相关的名词解释:</p>
<h3 id="1-1-PeerConnection"><a href="#1-1-PeerConnection" class="headerlink" title="1.1. PeerConnection"></a>1.1. PeerConnection</h3><p>WebRTC的目的是浏览器带来无插件化的P2P媒体通信解决方案。这个P2P的解决方案核心类是PeerConnection,通常简称PC。</p>
<h3 id="1-2-Offer、Answer和SDP"><a href="#1-2-Offer、Answer和SDP" class="headerlink" title="1.2. Offer、Answer和SDP"></a>1.2. Offer、Answer和SDP</h3><p>Offer和Answer都属于SDP(Session Description Protocol)。<br>SDP是一种会话描述协议。电话会议、网络电话、视频流传输等都是一次会话。描述一个会话,最基础的要包含多媒体数据格式和网络传输地址,当然还要包括很多其他的配置信息。</p>
<p>关于为什么需要描述会话:<br>首先就是参与会话的各个成员能力不对等,需要考虑各成员支持的媒体格式以及通话质量。</p>
<p>关于会话的配置,也需要所有人的意见一致,这涉及一个媒体协商的过程:会话发起者先提出一些建议(Offer),其他参与者再根据Offer给出自己的选择(Answer),最终意见达成一致后才能开始会话。如果意见不一致则会报错。</p>
<h3 id="1-3-ICE"><a href="#1-3-ICE" class="headerlink" title="1.3. ICE"></a>1.3. ICE</h3><p>ICE是用于UDP媒体传输的NAT穿透协议(适当扩展也能支持TCP协议),是对上述Offer/Answer模型的扩展,会利用STUN、TURN协议完成工作。ICE会在SDP中增加传输地址记录值(IP+port+协议),然后对其进行连通性测试,测试通过之后就可以用于传输媒体数据。</p>
<h3 id="1-4-STUN"><a href="#1-4-STUN" class="headerlink" title="1.4. STUN"></a>1.4. STUN</h3><p>STUN只是NAT穿透的一套工具,而非完整解决方案,它提供了获取一个内网连接(IP+port)对应的公网连接映射关系(NAT Banding)的机制,也提供了NAT Binding保活机制。WebRTC里就用到了这两种机制。</p>
<h3 id="1-5-TURN"><a href="#1-5-TURN" class="headerlink" title="1.5. TURN"></a>1.5. TURN</h3><p>TURN协议是STUN协议的一个扩展,允许一个peer只使用一个<code>relay address</code>就可以和多个peer实现通信。<br>实现方式:为每一个peer分配一个中继地址,其他peer向A的中继地址发数据,TURN Server就会把数据转发给A。<br>实际上这并不是P2P,因为所有数据都经过了一次TURN Server的中转,但是在有些情况下,必须借助中转才能实现通信,具体会在后面展开介绍。</p>
<h3 id="1-6-ICE-Candidate"><a href="#1-6-ICE-Candidate" class="headerlink" title="1.6. ICE Candidate"></a>1.6. ICE Candidate</h3><p>每个传输地址记录值都是一个ICE Candidate,可能有以一下4种:</p>
<ul>
<li>客户端从本机网络接口上获取的地址(host)</li>
<li>STUN Server看到的该客户端的地址(server reflexive,缩写为 srflx)</li>
<li>TURN Server为该客户端分配的中继地址(relayed)</li>
<li>连通性测试种,在来自对方的数据报文里看到的地址(peer reflexive,缩写为prflx)</li>
</ul>
<p>两个客户端上述Candidate的任意组合也许都能连通,但实际上很多都不可用,而ICE的任务就是逐个尝试找出可以联通的组合。<br>网络接口地址对应的端口号是客户端自己分配的,如果有多个网络接口地址,都要一并携带。TURN Server可以同时取得srflx和relayed Candidate,而STUN Server只能取得srflx Candidate(所以coturn是一个TURN Server)。</p>
<h2 id="2-核心API和Demo代码位置"><a href="#2-核心API和Demo代码位置" class="headerlink" title="2. 核心API和Demo代码位置"></a>2. 核心API和Demo代码位置</h2><p>注:此处仅对Windows端代码进行分析</p>
<h3 id="2-1-全局初始化"><a href="#2-1-全局初始化" class="headerlink" title="2.1. 全局初始化"></a>2.1. 全局初始化</h3><p>所有平台的客户端在使用WebRTC的API之前都需要进行一些初始化操作,主要是设置实验性功能开关、初始化SSL,当然也可以启用trace、设置日志输出等。<br>Windows端的初始化代码在<code>examples/peerconnection/client/main.cc</code>中的<code>wWinMain</code>函数,也是整个程序的入口函数中。</p>
<div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="code"><pre><span class="line">rtc::WinsockInitializer winsock_init;</span><br><span class="line">rtc::Win32SocketServer w32_ss;</span><br><span class="line"><span class="function">rtc::Win32Thread <span class="title">w32_thread</span><span class="params">(&w32_ss)</span></span>;</span><br><span class="line">rtc::ThreadManager::<span class="built_in">Instance</span>()-><span class="built_in">SetCurrentThread</span>(&w32_thread);</span><br><span class="line"><span class="comment">// ...</span></span><br><span class="line">webrtc::field_trial::<span class="built_in">InitFieldTrialsFromString</span>(forced_field_trials.<span class="built_in">c_str</span>());</span><br><span class="line"><span class="comment">// ...</span></span><br><span class="line">rtc::<span class="built_in">InitializeSSL</span>();</span><br><span class="line"><span class="comment">// ...</span></span><br></pre></td></tr></table></figure></div>
<h3 id="2-2-PeerConnectionFactory"><a href="#2-2-PeerConnectionFactory" class="headerlink" title="2.2. PeerConnectionFactory"></a>2.2. PeerConnectionFactory</h3><p>在初始化之后、使用PC之前,需要先创建和初始化<code>PeerConnectionFactory</code>对象,因为PC的创建使用了工厂模式。</p>
<p>Windows端创建PC Factory的代码在<code>examples/peerconnection/client/conductor.cc</code>的<code>Conductor::InitializePeerConnection</code>函数里,这个函数会在用户呼叫对方(或收到对方呼叫信息)时调用。</p>
<div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="code"><pre><span class="line">peer_connection_factory_ = webrtc::<span class="built_in">CreatePeerConnectionFactory</span>(</span><br><span class="line"> <span class="literal">nullptr</span> <span class="comment">/* network_thread */</span>, <span class="literal">nullptr</span> <span class="comment">/* worker_thread */</span>,</span><br><span class="line"> <span class="literal">nullptr</span> <span class="comment">/* signaling_thread */</span>, <span class="literal">nullptr</span> <span class="comment">/* default_adm */</span>,</span><br><span class="line"> webrtc::<span class="built_in">CreateBuiltinAudioEncoderFactory</span>(),</span><br><span class="line"> webrtc::<span class="built_in">CreateBuiltinAudioDecoderFactory</span>(),</span><br><span class="line"> webrtc::<span class="built_in">CreateBuiltinVideoEncoderFactory</span>(),</span><br><span class="line"> webrtc::<span class="built_in">CreateBuiltinVideoDecoderFactory</span>(), <span class="literal">nullptr</span> <span class="comment">/* audio_mixer */</span>,</span><br><span class="line"> <span class="literal">nullptr</span> <span class="comment">/* audio_processing */</span>);</span><br></pre></td></tr></table></figure></div>
<h3 id="2-3-创建PeerConnection"><a href="#2-3-创建PeerConnection" class="headerlink" title="2.3. 创建PeerConnection"></a>2.3. 创建PeerConnection</h3><p>Windows端创建PC的代码在<code>examples/peerconnection/client/conductor.cc</code>的<code>Conductor::CreatePeerconnection</code>函数里,这个函数会在Demo首页peer列表元素被双击或者收到其他peer的呼叫信息后调用。</p>
<div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="code"><pre><span class="line">webrtc::PeerConnectionInterface::RTCConfiguration config;</span><br><span class="line">config.sdp_semantics = webrtc::SdpSemantics::kUnifiedPlan;</span><br><span class="line">config.enable_dtls_srtp = dtls;</span><br><span class="line">webrtc::PeerConnectionInterface::IceServer server;</span><br><span class="line">server.uri = <span class="built_in">GetPeerConnectionString</span>();</span><br><span class="line">config.servers.<span class="built_in">push_back</span>(server);</span><br><span class="line"></span><br><span class="line">peer_connection_ = peer_connection_factory_-><span class="built_in">CreatePeerConnection</span>(</span><br><span class="line"> config, <span class="literal">nullptr</span>, <span class="literal">nullptr</span>, <span class="keyword">this</span>);</span><br></pre></td></tr></table></figure></div>
<h3 id="2-4-创建Source和Track"><a href="#2-4-创建Source和Track" class="headerlink" title="2.4. 创建Source和Track"></a>2.4. 创建Source和Track</h3><p>创建PC的目的是为了收发音视频数据,收发的载体就是Track,而Track的数据来自于Source<br>Windows创建Source和Track的代码在<code>examples/peerconnection/client/conductor.cc</code>的<code>Conductor::AddTracks</code>函数里,创建完PC后就会被立即调用</p>
<p>音频Source和Track的创建:</p>
<div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="function">rtc::scoped_refptr<webrtc::AudioTrackInterface> <span class="title">audio_track</span><span class="params">(</span></span></span><br><span class="line"><span class="params"><span class="function"> peer_connection_factory_->CreateAudioTrack(</span></span></span><br><span class="line"><span class="params"><span class="function"> kAudioLabel, peer_connection_factory_->CreateAudioSource(</span></span></span><br><span class="line"><span class="params"><span class="function"> cricket::AudioOptions())))</span></span>;</span><br><span class="line"><span class="keyword">auto</span> result_or_error = peer_connection_-><span class="built_in">AddTrack</span>(audio_track, {kStreamId});</span><br></pre></td></tr></table></figure></div>
<p>只需要调用PC Factory的接口创建Source和Track,然后把Track添加到PC中即可。</p>
<p>视频Source和Track的创建:</p>
<div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="code"><pre><span class="line">rtc::scoped_refptr<CapturerTrackSource> video_device =</span><br><span class="line"> CapturerTrackSource::<span class="built_in">Create</span>();</span><br><span class="line"><span class="keyword">if</span> (video_device) {</span><br><span class="line"> <span class="function">rtc::scoped_refptr<webrtc::VideoTrackInterface> <span class="title">video_track_</span><span class="params">(</span></span></span><br><span class="line"><span class="params"><span class="function"> peer_connection_factory_->CreateVideoTrack(kVideoLabel, video_device))</span></span>;</span><br><span class="line"> main_wnd_-><span class="built_in">StartLocalRenderer</span>(video_track_);</span><br><span class="line"></span><br><span class="line"> result_or_error = peer_connection_-><span class="built_in">AddTrack</span>(video_track_, {kStreamId});</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure></div>
<p><code>rtc::scoped_refptr</code>是WebRTC中定义的智能指针类型。<code>CapturerTrackSource</code>是Windows Demo对WebRTC视频采集接口类做的封装,实现了Source接口,所以可以用于创建Track。其内部会创建一个<code>Capturer</code>对象,视频数据的传递路径也是<code>Capturer->Source->Track</code>。</p>
<h3 id="2-5-创建Offer"><a href="#2-5-创建Offer" class="headerlink" title="2.5. 创建Offer"></a>2.5. 创建Offer</h3><p>添加完Track之后,需要明确需要发送哪些数据,此时开始SDP协商,首先是发起端创建Offer,并将其作为本地SDP设置给PC对象,然后把Offer通过Signaling Server交给应答端。</p>
<p>Windows创建Offer的代码在<code>examples/peerconnection/client/conductor.cc</code>的<code>Conductor::ConnectToPeer</code>函数中,在添加完Track后就会被立即调用。</p>
<div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="code"><pre><span class="line">peer_connection_-><span class="built_in">CreateOffer</span>(</span><br><span class="line"> <span class="keyword">this</span>, webrtc::PeerConnectionInterface::<span class="built_in">RTCOfferAnswerOptions</span>());</span><br></pre></td></tr></table></figure></div>
<p>这里并没有设置任何选项表明是否接受数据,因为<code>AddTrack</code>默认情况下也会认为需要接收数据。<br>创建成功的回调为<code>Conductor::OnSuccess</code>函数,其中包含了把SDP设置给PC和把Offer发送给应答端的逻辑:</p>
<div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="code"><pre><span class="line">peer_connection_-><span class="built_in">SetLocalDescription</span>(</span><br><span class="line"> DummySetSessionDescriptionObserver::<span class="built_in">Create</span>(), desc);</span><br><span class="line"><span class="comment">// ...</span></span><br><span class="line">Json::StyledWriter writer;</span><br><span class="line">Json::Value jmessage;</span><br><span class="line">jmessage[kSessionDescriptionTypeName] =</span><br><span class="line"> webrtc::<span class="built_in">SdpTypeToString</span>(desc-><span class="built_in">GetType</span>());</span><br><span class="line">jmessage[kSessionDescriptionSdpName] = sdp;</span><br><span class="line"><span class="built_in">SendMessage</span>(writer.<span class="built_in">write</span>(jmessage));</span><br></pre></td></tr></table></figure></div>
<h3 id="2-6-创建Answer"><a href="#2-6-创建Answer" class="headerlink" title="2.6. 创建Answer"></a>2.6. 创建Answer</h3><p>应答端拿到发起端的Offer之后,先将其设置给PC对象,然后创建Answer并设置给PC对象,最后将Answer通过Signaling Server发送到发起端,发起端拿到Answer之后,也需要把Answer设置给PC对象。<br>Windows设置Offer的代码在<code>examples/peerconnection/client/conductor.cc</code>的<code>Conductor::OnMessageFromPeer</code>函数中,这个函数用于处理来自Signaling Server的消息。</p>
<div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="code"><pre><span class="line">peer_connection_-><span class="built_in">SetRemoteDescription</span>(</span><br><span class="line"> DummySetSessionDescriptionObserver::<span class="built_in">Create</span>(),</span><br><span class="line"> session_description.<span class="built_in">release</span>());</span><br></pre></td></tr></table></figure></div>
<p>Windows Demo创建Answer也没有等待Offer设置成功回调。创建Answer的代码如下:</p>
<div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="code"><pre><span class="line">peer_connection_-><span class="built_in">CreateAnswer</span>(</span><br><span class="line"> <span class="keyword">this</span>, webrtc::PeerConnectionInterface::<span class="built_in">RTCOfferAnswerOptions</span>());</span><br></pre></td></tr></table></figure></div>
<p>创建Answer成功后,设置给PC、发送给发起端的代码和创建Offer成功后的处理代码是同一个函数。<br>同样的,发起端收到Answer后,也是调用<code>setRemoteDescription</code>设置给PC对象。具体代码和应答端设置Offer是一样。</p>
<h3 id="2-7-ICE-Candidate回调和设置"><a href="#2-7-ICE-Candidate回调和设置" class="headerlink" title="2.7.ICE Candidate回调和设置"></a>2.7.ICE Candidate回调和设置</h3><p>通话双方交换并设置了SDP之后,下一步就是交换并设置ICE Candidate、建立P2P连接。</p>
<p>Windows收到本地ICE Candidate的回调函数为<code>examples/peerconnection/client/conductor.cc</code>的<code>Conductor::OnIceCandidate</code>函数,它是<code>PeerConnectionObserver</code>的回调函数之一。设置本地SDP(<code>SetLocalDescription</code>)会启动ICE Candidate收集过程,收集到之后会回调该函数。</p>
<div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="code"><pre><span class="line">Json::StyledWriter writer;</span><br><span class="line">Json::Value jmessage;</span><br><span class="line"></span><br><span class="line">jmessage[kCandidateSdpMidName] = candidate-><span class="built_in">sdp_mid</span>();</span><br><span class="line">jmessage[kCandidateSdpMlineIndexName] = candidate-><span class="built_in">sdp_mline_index</span>();</span><br><span class="line">std::string sdp;</span><br><span class="line"><span class="keyword">if</span> (!candidate-><span class="built_in">ToString</span>(&sdp)) {</span><br><span class="line"> <span class="built_in">RTC_LOG</span>(LS_ERROR) << <span class="string">"Failed to serialize candidate"</span>;</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line">}</span><br><span class="line">jmessage[kCandidateSdpName] = sdp;</span><br><span class="line"><span class="built_in">SendMessage</span>(writer.<span class="built_in">write</span>(jmessage));</span><br></pre></td></tr></table></figure></div>
<p>其中的处理也是把ICE Candidate发送给对端。对端收到ICE Candidate后,会在<code>Conductor::OnMessageFromPeer</code>函数里添加给PC对象:</p>
<div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="code"><pre><span class="line">std::unique_ptr<webrtc::SessionDescriptionInterface> session_description =</span><br><span class="line"> webrtc::<span class="built_in">CreateSessionDescription</span>(type, sdp, &error);</span><br><span class="line"><span class="keyword">if</span> (!session_description) {</span><br><span class="line"> <span class="built_in">RTC_LOG</span>(WARNING) << <span class="string">"Can't parse received session description message. "</span></span><br><span class="line"> <span class="string">"SdpParseError was: "</span></span><br><span class="line"> << error.description;</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div>
<p>Windows Demo 没有使用一个队列保存创建PC前收到的消息。如果收到消息时PC尚未创建,就会立即创建PC,所以不会有问题。</p>
<h3 id="2-8-ICE连接状态回调"><a href="#2-8-ICE连接状态回调" class="headerlink" title="2.8. ICE连接状态回调"></a>2.8. ICE连接状态回调</h3><p>交换了SDP后,通话两端就开始建立P2P连接了。对于这个过程的状态变化,可以监听PC的ICE连接状态回调。</p>
<p>Windows的回调定义为:</p>
<div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">OnIceConnectionChange</span><span class="params">(</span></span></span><br><span class="line"><span class="params"><span class="function"> PeerConnectionInterface::IceConnectionState new_state)</span> </span>{}</span><br></pre></td></tr></table></figure></div>
<p>其中状态码的定义为:</p>
<div class="highlight-container" data-rel="C++"><figure class="iseeu highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="keyword">enum</span> <span class="title class_">IceConnectionState</span> {</span><br><span class="line"> kIceConnectionNew,</span><br><span class="line"> kIceConnectionChecking,</span><br><span class="line"> kIceConnectionConnected,</span><br><span class="line"> kIceConnectionCompleted,</span><br><span class="line"> kIceConnectionFailed,</span><br><span class="line"> kIceConnectionDisconnected,</span><br><span class="line"> kIceConnectionClosed,</span><br><span class="line"> kIceConnectionMax,</span><br><span class="line">};</span><br></pre></td></tr></table></figure></div>
<p>几个常用的状态为:</p>
<ul>
<li><code>kIceConnectionConnected</code>:ICE连接建立成功的状态,此状态下可以收发音视频数据</li>
<li><code>kIceConnectionDisconnected</code>:ICE连接断开后的状态</li>
<li><code>kIceConnectionFailed</code>:ICE连接建立失败的状态</li>
</ul>
<h3 id="2-9-核心API回顾"><a href="#2-9-核心API回顾" class="headerlink" title="2.9. 核心API回顾"></a>2.9. 核心API回顾</h3><p>首先是几个重要的概念:</p>
<ul>
<li><code>Capturer</code>:负责数据采集,只有视频才有这一层抽象,其有多种实现,包括相机采集、录屏采集、视频文件采集等。</li>
<li><code>Sources</code>:数据源,数据来自于<code>Capturer</code>。它把数据交给<code>Track</code></li>
<li><code>Track</code>:媒体数据交换的载体,发送端把本地的<code>Track</code>发送给远程的接收端</li>
<li><code>Sink</code>:<code>Track</code>数据的消费者,只有视频才有这一层封装。发送端的视频本地预览、接收到收到远程视频后的渲染都是<code>Sink</code></li>
<li><code>Transceiver</code>:负责收发媒体数据(以<code>Track</code>为载体)</li>
</ul>
<p>以视频为例,数据由发送端的Capturer采集,交给Source,再交给本地的Track,然后兵分靓丽:一路由本地Sink进行预览,一路由Transceiver发送给接收端。接收端Track把数据交给Sink渲染。</p>
]]></content>
<tags>
<tag>WebRTC基础</tag>
</tags>
</entry>
<entry>
<title>Linux基础之文件管理</title>
<url>/2023/01/23/Linux%E5%9F%BA%E7%A1%80%E4%B9%8B%E6%96%87%E4%BB%B6%E7%AE%A1%E7%90%86/</url>
<content><![CDATA[<h2 id="1-前言"><a href="#1-前言" class="headerlink" title="1.前言"></a>1.前言</h2><p>Linux系统下<strong>一切皆文件</strong>,因此文件管理是Linux最基础的一部分。<br>在本文中,主要介绍文件权限、文件管理等操作,当然也不可避免地涉及到目录的相关内容。</p>
<h2 id="2-文件权限与目录配置"><a href="#2-文件权限与目录配置" class="headerlink" title="2.文件权限与目录配置"></a>2.文件权限与目录配置</h2><h3 id="2-1-用户及用户组"><a href="#2-1-用户及用户组" class="headerlink" title="2.1. 用户及用户组"></a>2.1. 用户及用户组</h3><p>提到文件权限就不可避免地涉及到用户及用户组,这里做一个简单的介绍,关于Linux的用户管理会在其他文章中介绍。</p>
<p>一般地,可以将Linux中的用户分为两类:普通用户和超级用户(root)。<br>Linux是一个多用户多任务的分时操作系统,所以需要有一位“管理员”对整个系统进行管理,这就是超级用户。超级用户拥有整个系统的最高权限,其可以无视任何用户及用户组权限对所有文件执行任何操作。因而本文的重点并不是root用户,文件权限针对的是普通用户。<br>除此之外,每个用户都属于至少一个用户组,因为在一个用户被创建时,系统会自动生成一个与该用户同名的用户组。</p>
<h3 id="2-2-文件相关信息"><a href="#2-2-文件相关信息" class="headerlink" title="2.2. 文件相关信息"></a>2.2. 文件相关信息</h3><p>使用<code>ls -al</code>命令可以查看某目录下文件和子目录的相关信息,在某目录下执行以上命令,显示如下:</p>
<p><img lazyload src="/images/loading.svg" data-src="/2023/01/23/Linux%E5%9F%BA%E7%A1%80%E4%B9%8B%E6%96%87%E4%BB%B6%E7%AE%A1%E7%90%86/1.png" alt="ls -al 结果"></p>
<p>首先可以看到任意一个文件(或目录)都有一行相关的内容对应,从左至右依次为:<strong>类型与权限位、链接到此节点的文件名个数、拥有者账号、所属群组、容量大小(默认单位Bytes)、创建(或修改)日期、文件名</strong>。下面主要针对类型与权限位进行分析。</p>
<h4 id="2-2-1-类型与权限位"><a href="#2-2-1-类型与权限位" class="headerlink" title="2.2.1. 类型与权限位"></a>2.2.1. 类型与权限位</h4><p>每行的第一个属性为文件或目录的类型与权限位,共十位。<br>其中第一位为类型位,共有五种:</p>
<ul>
<li>d:目录</li>
<li>-:文件</li>
<li>l:链接文件(link file)</li>
<li>b:设备文件里面的可供存储的周边设备</li>
<li>c:设备文件里面的序列埠设备,例如键盘鼠标</li>
</ul>
<p>一般前三种更为常见。</p>
<p>后面的九个字符每三个为一组,均为<code>rwx</code>三个参数的组合,<code>rwx</code>分别代表可读(read)、可写(write)和可执行(execute)。如果没有相应的权限,则会显示<code>-</code>。<br>第一组为文件拥有者的权限,第二组为文件所属用户组内所有用户的权限,第三组为其他用户的权限。</p>
<p>以上图中的<code>demo.cc</code>为例,<code>-rw-rw-r--</code>,表示这是一个文件,用户<code>shiszhi</code>对其只拥有读写权限,用户组<code>shiszhi</code>内所有用户对其只拥有读写权限,其余所有用户对其只拥有读权限。</p>
<p>然而对于目录来说,<code>rwx</code>的意义分别如下:</p>
<ul>
<li><p>r:表示具有读取目录结构清单的权限,只要拥有该权限们就可以查询该目录下的文件名数据等,可以使用<code>ls</code>指令将该目录的内容列表显示出来。</p>
</li>
<li><p>w:表示具有异动该目录结构清单的权限,也就是以下权限:</p>
<ul>
<li>创建新的文件与目录</li>
<li>删除已经存在的文件与目录</li>
<li>将已存在的文件或目录重命名</li>
<li>移动该目录内的文件、目录位置。</li>
</ul>
</li>
<li><p>x:表示用户能否进入该目录使其成为自己的工作目录,如果没有该权限,则无法通过<code>cd</code>命令切换到此目录。</p>
</li>
</ul>
<p>值得一提的是,以<code>.</code>开头的文件表示隐藏文件,如图中的<code>.git</code>。</p>
<h3 id="2-3-目录配置"><a href="#2-3-目录配置" class="headerlink" title="2.3. 目录配置"></a>2.3. 目录配置</h3><p>Linux目录配置的依据为Filesystem Hierarchy Standard(FHS)标准,其定义三层主目录为<code>/</code>、<code>/var</code>、<code>/usr</code>。<br>FHS对应的规定了一些目录内存放的数据类型,这里并不全部列举,仅选取几个具有代表性的目录进行说明:</p>
<ul>
<li><code>/</code>:此目录为整个系统最重要的目录,所有的目录都是由根目录衍生出来的。</li>
<li><code>/bin</code>:一些常用的指令,如<code>cat</code>、<code>chmod</code>等。</li>
<li><code>/etc</code>:系统主要的配置文件,一般来讲,只有root用户具有此目录的修改权限。</li>
<li><code>/usr</code>:usr是Unix Software Resource的缩写,所以其存放的是软件安装、执行相关的文件</li>
<li><code>/var</code>:主要存放与系统运行过程相关的文件。</li>
</ul>
<h2 id="3-文件与目录管理"><a href="#3-文件与目录管理" class="headerlink" title="3. 文件与目录管理"></a>3. 文件与目录管理</h2><h3 id="3-1-绝对路径与相对路径"><a href="#3-1-绝对路径与相对路径" class="headerlink" title="3.1. 绝对路径与相对路径"></a>3.1. 绝对路径与相对路径</h3><p>在图1中可以发现有两个特殊的目录<code>.</code>和<code>..</code>,这两个目录其实是<strong>相对路径</strong>,<code>.</code>代表当前目录,<code>..</code>代表上一层目录。<br>既然有相对路径,就有绝对路径,绝对路径是从根目录写起,是以<code>/</code>开头,如果当前路径为<code>/vars/spool/mail</code>,想要进入到<code>/vars/spool/cron</code>目录内,可以使用以下两条命令:</p>
<ul>
<li><code>cd /vars/spool/cron</code> —— 绝对路径</li>
<li><code>cd ../cron</code> —— 相对路径</li>
</ul>
<h3 id="3-2-目录的相关操作"><a href="#3-2-目录的相关操作" class="headerlink" title="3.2. 目录的相关操作"></a>3.2. 目录的相关操作</h3><p>首先需要了解几个特殊的目录:</p>
<ul>
<li><code>.</code>:本级目录</li>
<li><code>..</code>:上一级目录</li>
<li><code>-</code>:前一个工作目录</li>
<li><code>~</code>:目前用户的主文件夹</li>
<li><code>~user1</code>:用户<code>user1</code>的主文件夹</li>
</ul>
<p>关于目录主要有以下几个操作:</p>
<ul>
<li>cd(change directory):变换工作目录</li>
<li>pwd(print working directory):显示当前目录</li>
<li>mkdir(make directory):创建新目录</li>
<li>rmdir(remove directory):删除<strong>空目录</strong></li>
</ul>
<p>关于命令的相关参数可以查阅对应的手册,尤其需要关注的是<code>mkdir</code>的<code>-p</code>参数,用于递归创建目录。</p>
<h3 id="3-2-文件及目录管理的常见命令"><a href="#3-2-文件及目录管理的常见命令" class="headerlink" title="3.2. 文件及目录管理的常见命令"></a>3.2. 文件及目录管理的常见命令</h3><p>注:<em>本节只选取部分常用的选项进行介绍,其他详细内容可以自行搜索</em></p>
<h4 id="3-2-1-文件与目录的查看:ls"><a href="#3-2-1-文件与目录的查看:ls" class="headerlink" title="3.2.1. 文件与目录的查看:ls"></a>3.2.1. 文件与目录的查看:<code>ls</code></h4><div class="highlight-container" data-rel="Bash"><figure class="iseeu highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">ls</span> [options] 文件或目录名</span><br></pre></td></tr></table></figure></div>
<p><code>ls</code>可以不带任何选项直接执行,也具有一些可选选项,一般常用的选项如下:</p>
<ul>
<li><code>-a</code>:全部文件(包括隐藏文件)</li>
<li><code>-d</code>:仅列出目录本身,而不列出目录内的文件数据</li>
<li><code>-l</code>:长数据串,包含文件的属性与权限等等数据</li>
</ul>
<p>不同的选项可以叠加使用,比如上面所使用的<code>ls -al</code>。</p>
<h4 id="3-2-2-复制、删除和移动:cp、rm、mv"><a href="#3-2-2-复制、删除和移动:cp、rm、mv" class="headerlink" title="3.2.2. 复制、删除和移动:cp、rm、mv"></a>3.2.2. 复制、删除和移动:<code>cp</code>、<code>rm</code>、<code>mv</code></h4><h5 id="1-复制文件或目录:cp-copy"><a href="#1-复制文件或目录:cp-copy" class="headerlink" title="1. 复制文件或目录:cp(copy)"></a>1. 复制文件或目录:<code>cp</code>(copy)</h5><div class="highlight-container" data-rel="Bash"><figure class="iseeu highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">cp</span> [options] 来源文件(<span class="built_in">source</span>) 目标文件(destination)</span><br></pre></td></tr></table></figure></div>
<p>常见选项与参数:</p>
<ul>
<li><code>-a</code>:用于复制文件或整个目录,复制目录时,对其子目录等都递归的复制,而且还要保持文件的访问模式,所有者,时间戳等属性与原文件一样。</li>
<li><code>-i</code>:若目标文件已存在,则覆盖时会先询问</li>
<li><code>-p</code>:连同文件的属性一起复制(权限、拥有者、时间等)</li>
<li><code>-r</code>:用于目录的复制</li>
</ul>
<h5 id="2-移除文件或目录:rm-remove"><a href="#2-移除文件或目录:rm-remove" class="headerlink" title="2. 移除文件或目录:rm(remove)"></a>2. 移除文件或目录:<code>rm</code>(remove)</h5><div class="highlight-container" data-rel="Bash"><figure class="iseeu highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">rm</span> [options] 文件或目录</span><br></pre></td></tr></table></figure></div>
<p>选项与参数:</p>
<ul>
<li><code>-f</code>:忽略不存在的文件,不出现任何警告信息</li>
<li><code>-i</code>:删除前询问是否执行</li>
<li><code>-r</code>:递归删除,主要用在目录的删除</li>
</ul>
<h5 id="3-移动文件或目录(也可用来重命名):mv-move"><a href="#3-移动文件或目录(也可用来重命名):mv-move" class="headerlink" title="3. 移动文件或目录(也可用来重命名):mv(move)"></a>3. 移动文件或目录(也可用来重命名):<code>mv</code>(move)</h5><div class="highlight-container" data-rel="Bash"><figure class="iseeu highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">mv</span> [options] 来源文件(<span class="built_in">source</span>) 目标文件(destination)</span><br></pre></td></tr></table></figure></div>
<p>选项与参数:</p>
<ul>
<li><code>-f</code>:如果目标问件已存在将直接覆盖,不出现任何警告信息</li>
<li><code>-i</code>:如果目标文件已存在,则会询问是否执行</li>
<li><code>-u</code>:如果目标文件已存在并且source比较新才会更新</li>
</ul>
<h3 id="3-3-文件内容的查阅"><a href="#3-3-文件内容的查阅" class="headerlink" title="3.3. 文件内容的查阅"></a>3.3. 文件内容的查阅</h3><p>这部分只做简单的介绍,更多的参数设置可以查阅相关的文档。</p>
<h4 id="3-3-1-直接查看文件内容"><a href="#3-3-1-直接查看文件内容" class="headerlink" title="3.3.1. 直接查看文件内容"></a>3.3.1. 直接查看文件内容</h4><p>主要有<code>cat</code>、<code>tac</code>、<code>nl</code>三个命令。<br>其中,<code>cat</code>和<code>tac</code>分别为正向和反向列示,比如一个n行的文件,<code>cat</code>的显示顺序为1<del>n,而<code>tac</code>的显示顺序为n</del>1。<code>nl</code>命令则是添加行号打印。</p>
<h4 id="3-3-2-可翻页查看"><a href="#3-3-2-可翻页查看" class="headerlink" title="3.3.2. 可翻页查看"></a>3.3.2. 可翻页查看</h4><p>3.3.1内的命令都是一次性将数据显示到屏幕上,而<code>more</code>和<code>less</code>命令是逐页翻动,不同的是<code>more</code>只可以向后翻页,而<code>less</code>可以前后翻页,并且具有搜索功能,具体的可以查看相关的命令说明。</p>
<h4 id="3-3-3-数据提取"><a href="#3-3-3-数据提取" class="headerlink" title="3.3.3. 数据提取"></a>3.3.3. 数据提取</h4><p>有时只需要查看文件前几行或者后几行的内容,可以使用<code>head</code>和<code>tail</code>命令。</p>
<div class="highlight-container" data-rel="Bash"><figure class="iseeu highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">head</span> [-n number] 文件</span><br><span class="line"><span class="built_in">tail</span> [-n number] 文件</span><br></pre></td></tr></table></figure></div>
<p>如果不带选项执行,则会默认<code>number</code>为10,否则限制对应的<code>number</code>行。特别地,如果<code>number</code>为负数,比如<code>head -n -10 file</code>,其中<code>file</code>有17行,则只会显示前七行而不显示后十行。</p>
<h4 id="3-3-4-非纯文本文件:od"><a href="#3-3-4-非纯文本文件:od" class="headerlink" title="3.3.4. 非纯文本文件:od"></a>3.3.4. 非纯文本文件:<code>od</code></h4><p>此命令并不常用,不展开说明。</p>
<h4 id="3-3-5-修改文件时间或创建新文件:touch"><a href="#3-3-5-修改文件时间或创建新文件:touch" class="headerlink" title="3.3.5. 修改文件时间或创建新文件:touch"></a>3.3.5. 修改文件时间或创建新文件:<code>touch</code></h4><p><code>touch</code>命令最主要的功能是:</p>
<ul>
<li>若文件不存在,创建一个空的文件</li>
<li>若文件存在,将该文件的日期修改为当前时间</li>
</ul>
<h3 id="3-4-文件与目录的权限管理"><a href="#3-4-文件与目录的权限管理" class="headerlink" title="3.4. 文件与目录的权限管理"></a>3.4. 文件与目录的权限管理</h3><p>这里会介绍到文件及目录的默认权限,也会提到如何后续修改权限。</p>
<h4 id="3-4-1-文件与目录的权限表示"><a href="#3-4-1-文件与目录的权限表示" class="headerlink" title="3.4.1. 文件与目录的权限表示"></a>3.4.1. 文件与目录的权限表示</h4><p>在介绍后续内容之前,需要首先了解以下权限的表示方式。<br>前文提到权限分为<code>rwx</code>,类似于二进制编码,三者分别对应4,2,1。<br>根据上述的原则,可以将权限位简写为三个数字,比如<code>rw-rw-r--</code>就可以写为664。</p>
<h4 id="3-4-2-文件默认权限:umask"><a href="#3-4-2-文件默认权限:umask" class="headerlink" title="3.4.2. 文件默认权限:umask"></a>3.4.2. 文件默认权限:<code>umask</code></h4><p><code>umask</code>命令用于查看目前使用者在创建文件或目录时的权限默认值。</p>
<div class="highlight-container" data-rel="Bash"><figure class="iseeu highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">umask</span></span><br><span class="line"><span class="comment"># output:0022</span></span><br><span class="line"><span class="built_in">umask</span> -S</span><br><span class="line"><span class="comment"># output:u=rwx,g=rx,o=rx</span></span><br></pre></td></tr></table></figure></div>
<p>以上两种方式均可以查看<code>umask</code>,不同点在于前者以数字形式展示,后者以符号形式展示。<br>上述示例中,执行<code>umask</code>得到一个四位数字,其中第一位与特殊权限有关,我们不关注,后面的三组才与一般权限有关。前文提到三种权限被编为对应的数字,而此处的数字代表缺失的权限,因此0代表<code>rwx</code>,2代表<code>rx</code>(即<code>r-x</code>)。<br>这个值可以修改,需要修改<code>~/.bashrc</code>文件。</p>
<h4 id="3-4-3-文件权限的修改"><a href="#3-4-3-文件权限的修改" class="headerlink" title="3.4.3. 文件权限的修改"></a>3.4.3. 文件权限的修改</h4><h5 id="1-修改文件拥有者:chown"><a href="#1-修改文件拥有者:chown" class="headerlink" title="1.修改文件拥有者:chown"></a>1.修改文件拥有者:<code>chown</code></h5><div class="highlight-container" data-rel="Bash"><figure class="iseeu highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">chown</span> [-R] 账号 文件或目录</span><br><span class="line"><span class="built_in">chown</span> [-R] 账号:群组 文件或目录</span><br></pre></td></tr></table></figure></div>
<p>选项与参数:</p>
<ul>
<li><code>-R</code>:进行递归修改,即修改目录下所有的文件</li>
</ul>
<h5 id="2-修改文件所属群组:chgrp"><a href="#2-修改文件所属群组:chgrp" class="headerlink" title="2. 修改文件所属群组:chgrp"></a>2. 修改文件所属群组:<code>chgrp</code></h5><div class="highlight-container" data-rel="Bash"><figure class="iseeu highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">chgrp</span> [-R] 群组 文件或目录</span><br></pre></td></tr></table></figure></div>
<p>选项与参数:</p>
<ul>
<li><code>-R</code>:进行递归修改,即修改目录下所有的文件</li>
</ul>
<h5 id="3-修改文件权限:chmod"><a href="#3-修改文件权限:chmod" class="headerlink" title="3. 修改文件权限:chmod"></a>3. 修改文件权限:<code>chmod</code></h5><p>设置的方式比较多样,首先可以使用上述的数字形式表示的权限,如:</p>
<div class="highlight-container" data-rel="Bash"><figure class="iseeu highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 设置.bashrc的权限为rwxrwxrwx</span></span><br><span class="line"><span class="built_in">chmod</span> 777 .bashrc</span><br></pre></td></tr></table></figure></div>
<p>其次可以单独指定对应的权限,如:</p>
<div class="highlight-container" data-rel="Bash"><figure class="iseeu highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 设置.bashrc的权限为rwxr-xr-x</span></span><br><span class="line"><span class="built_in">chmod</span> u=rwx,go=rx .bashrc</span><br><span class="line"><span class="comment"># u->user g->group o->others a->all</span></span><br></pre></td></tr></table></figure></div>
<p>此外也可以添加或取出指定的权限,如:</p>
<div class="highlight-container" data-rel="Bash"><figure class="iseeu highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 为所有用户增加.bashrc的w权限</span></span><br><span class="line"><span class="built_in">chmod</span> a+w .bashrc</span><br></pre></td></tr></table></figure></div>
<h3 id="3-5-查看文件类型:file"><a href="#3-5-查看文件类型:file" class="headerlink" title="3.5. 查看文件类型:file"></a>3.5. 查看文件类型:<code>file</code></h3><p>通过这个指令可以查看某个文件的基本数据,例如是属于 ASCII或者是 data 文件,或者是 binary , 且其中有没有使用到动态函数库 (share library) 等等的信息。</p>
<h3 id="3-6-指令与文件的搜索"><a href="#3-6-指令与文件的搜索" class="headerlink" title="3.6. 指令与文件的搜索"></a>3.6. 指令与文件的搜索</h3><p>搜索可执行文件使用<code>which</code>。<br>搜索文件名可以使用<code>whereis</code>,<code>locate</code>,<code>updatedb</code>,<code>find</code>,具体的使用方式可以自行查阅。</p>
]]></content>
<tags>
<tag>Linux</tag>
</tags>
</entry>
</search>