-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathatom.xml
496 lines (281 loc) · 126 KB
/
atom.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
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>Howie's Notes</title>
<subtitle>Machine Learning and Cyber Security</subtitle>
<link href="https://howiezhao.github.io/atom.xml" rel="self"/>
<link href="https://howiezhao.github.io/"/>
<updated>2021-06-01T14:32:40.000Z</updated>
<id>https://howiezhao.github.io/</id>
<author>
<name>Howie Zhao</name>
</author>
<generator uri="https://hexo.io/">Hexo</generator>
<entry>
<title>Windows 10 中莫名其妙的“端口被占用”问题解决</title>
<link href="https://howiezhao.github.io/2021/06/01/win10-port-is-already-in-use-problem/"/>
<id>https://howiezhao.github.io/2021/06/01/win10-port-is-already-in-use-problem/</id>
<published>2021-06-01T13:22:03.000Z</published>
<updated>2021-06-01T14:32:40.000Z</updated>
<content type="html"><![CDATA[<p>大约是自 Windows 10 1709 更新之后(或许是 1809?2004?),莫名其妙的存在类似“端口被占用”这样的问题,比如像下面这样的报错信息:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Error: listen EACCES: permission denied 0.0.0.0:3000</span><br><span class="line">...</span><br></pre></td></tr></table></figure><p>或者启动 SS 时提示:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Shadowsocks Error: Port already in use</span><br><span class="line">...</span><br></pre></td></tr></table></figure><p>又或者启动 Docker 时提示:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Error starting userland proxy: Bind for 0.0.0.0:50051: unexpected error Permission denied.</span><br></pre></td></tr></table></figure><p>电脑重启几次之后,以上报错可能会消失,一切又恢复正常。</p><span id="more"></span><p>这表面上像是端口 <code>3000</code> 被占用,实则不然,因为当你在 PowerShell 中输入 <code>netstat -ano | findstr "3000"</code> 查看端口占用信息时,它却无任何输出。实际上这个错误对应的 Windows 错误码是 <a href="https://docs.microsoft.com/zh-cn/windows/win32/winsock/windows-sockets-error-codes-2">10013(WSAEACCES)</a> :权限被拒绝。</p><p>出现这个错误的原因是 Windows 10 的补丁 <a href="https://support.microsoft.com/zh-cn/topic/2018-%E5%B9%B4-2-%E6%9C%88-13-%E6%97%A5-kb4074588-%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E5%86%85%E9%83%A8%E7%89%88%E6%9C%AC-16299-248-b4e2ca66-dd7a-6fd5-a8f3-dc6683d4922b">KB4074588</a> 中存在一个已知问题:</p><blockquote><p>安装此更新后,应用程序可能无法保留或绑定到以前工作的端口。</p></blockquote><p>这些端口会被 Hyper-V 拿来保留备用,处于备用的端口无法被其他程序使用,用户登录后,系统会随机保留一些端口。使用 <code>netsh interface ipv4 show excludedportrange protocol=tcp</code> 可以查看被保留的端口段,每次重启都有可能是不同的端口。</p><p>因此,当你的系统启用 Hyper-V 或安装 Docker(安装 Docker Desktop 会启用 Hyper-V)之后,这个问题可能就会出现。</p><p>这个问题的解决方法(<a href="https://en.wikipedia.org/wiki/Workaround">workaround</a>)有两种,最粗暴的方法就是如上所述多重启几次,让它的随机端口改变,但以后仍有可能会遇到同样的问题。另一种方法是排除掉需要使用的端口,具体来说:</p><ol><li>禁用 Hyper-V</li><li>添加需要排除的端口范围,如:<code>netsh int ipv4 add excludedportrange protocol=tcp startport=50051 numberofports=1</code></li><li>重新启用 Hyper-V</li></ol><hr><p>参考:</p><ol><li><a href="https://github.com/shadowsocks/shadowsocks-windows/issues/1835">https://github.com/shadowsocks/shadowsocks-windows/issues/1835</a></li><li><a href="https://github.com/docker/for-win/issues/3171">https://github.com/docker/for-win/issues/3171</a></li></ol>]]></content>
<summary type="html"><p>大约是自 Windows 10 1709 更新之后(或许是 1809?2004?),莫名其妙的存在类似“端口被占用”这样的问题,比如像下面这样的报错信息:</p>
<figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Error: listen EACCES: permission denied 0.0.0.0:3000</span><br><span class="line">...</span><br></pre></td></tr></table></figure>
<p>或者启动 SS 时提示:</p>
<figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Shadowsocks Error: Port already in use</span><br><span class="line">...</span><br></pre></td></tr></table></figure>
<p>又或者启动 Docker 时提示:</p>
<figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Error starting userland proxy: Bind for 0.0.0.0:50051: unexpected error Permission denied.</span><br></pre></td></tr></table></figure>
<p>电脑重启几次之后,以上报错可能会消失,一切又恢复正常。</p></summary>
<category term="Bug" scheme="https://howiezhao.github.io/categories/Bug/"/>
</entry>
<entry>
<title>IntelliJ 平台 IDE 中 Ctrl+Shift+F 快捷键无效问题解决</title>
<link href="https://howiezhao.github.io/2021/05/02/intellij-platform-ctrl-shift-f-not-working/"/>
<id>https://howiezhao.github.io/2021/05/02/intellij-platform-ctrl-shift-f-not-working/</id>
<published>2021-05-02T11:49:22.000Z</published>
<updated>2021-05-02T12:27:51.000Z</updated>
<content type="html"><![CDATA[<p>在 IntelliJ 平台 IDE 中(如 PyCharm、IntelliJ IDEA 等),Ctrl+Shift+F 快捷键通常指在项目所有文件中搜索对应的关键词,这是一个常用的功能,然而近期笔者发现此快捷键莫名其妙的失效了(其他快捷键都工作良好),这很可能是与别的程序的快捷键相冲突导致的,经排查,是与 Windows 10 中微软拼音的简繁转换快捷键冲突。</p><p>解决步骤依次为:<strong>设置</strong> ——> <strong>时间和语言</strong> ——> <strong>语言</strong> ——> <strong>中文(简体,中国)</strong>——> <strong>选项</strong> ——> <strong>微软拼音</strong> ——> <strong>选项</strong> ——> <strong>按键</strong> ——> <strong>简体/繁体中文输入切换</strong>,可以将其改为另一个键或者直接将其关闭。</p><p>此解决方案可能并不适应于每个人,具体还是要找到那个冲突的快捷键。</p>]]></content>
<summary type="html"><p>在 IntelliJ 平台 IDE 中(如 PyCharm、IntelliJ IDEA 等),Ctrl+Shift+F 快捷键通常指在项目所有文件中搜索对应的关键词,这是一个常用的功能,然而近期笔者发现此快捷键莫名其妙的失效了(其他快捷键都工作良好),这很可能是与别的程序的</summary>
<category term="Bug" scheme="https://howiezhao.github.io/categories/Bug/"/>
</entry>
<entry>
<title>升级到 WSL 2</title>
<link href="https://howiezhao.github.io/2021/05/02/update-to-wsl-2/"/>
<id>https://howiezhao.github.io/2021/05/02/update-to-wsl-2/</id>
<published>2021-05-02T09:31:15.000Z</published>
<updated>2022-06-18T03:35:48.000Z</updated>
<content type="html"><![CDATA[<p>从 2020 年末开始,微软逐步向 Windows 10 用户推送了 WSL 2 更新,WSL 2 相比 WSL 1 是一个巨大的变化,最显著的改变在于 WSL 2 开始采用 Hyper-V 虚拟机来运行 Linux,这会解决之前 WSL 的很多问题,但也可以看作是 WSL 项目的失败,本文所描述的主体为 WSL 2,有关 WSL 1 的相关内容请参考 <a href="/2018/08/21/wsl-problem/" title="WSL 相关问题解决">WSL 相关问题解决</a>。</p><span id="more"></span><h2 id="升级到-WSL-2"><a href="#升级到-WSL-2" class="headerlink" title="升级到 WSL 2"></a>升级到 WSL 2</h2><p>要查看当前安装的 WSL 版本,可通过 <code>wsl --list --verbose</code> 命令查看,其输出如下:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"> NAME STATE VERSION</span><br><span class="line">* Ubuntu Running 1</span><br></pre></td></tr></table></figure><p>VERSION 下面的结果为其版本号,上述为 WSL 1。</p><p>具体升级步骤如下:</p><ol><li>在 <strong>启用或关闭 Windows 功能</strong> 中勾选 <strong>虚拟机平台</strong>,或直接在管理员权限的 PowerShell 中输入 <code>Enable-WindowsOptionalFeature -Online -FeatureName "VirtualMachinePlatform"</code>;</li><li>下载并安装 <a href="https://wslstorestorage.blob.core.windows.net/wslblob/wsl_update_x64.msi">Linux 内核更新包</a> ;</li><li>输入 <code>wsl --set-version Ubuntu 2</code> 将已安装发行版设置为 WSL 2。</li></ol><h2 id="直接安装-WSL-2"><a href="#直接安装-WSL-2" class="headerlink" title="直接安装 WSL 2"></a>直接安装 WSL 2</h2><p>如果你之前没有安装过 WSL 1,可以通过在管理员权限的 PowerShell 中输入以下命令来直接安装 WSL 2:</p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">wsl <span class="literal">--update</span></span><br><span class="line">wsl <span class="literal">--shutdown</span></span><br><span class="line">wsl <span class="literal">--install</span> <span class="literal">--distribution</span> Ubuntu</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html"><p>从 2020 年末开始,微软逐步向 Windows 10 用户推送了 WSL 2 更新,WSL 2 相比 WSL 1 是一个巨大的变化,最显著的改变在于 WSL 2 开始采用 Hyper-V 虚拟机来运行 Linux,这会解决之前 WSL 的很多问题,但也可以看作是 WSL 项目的失败,本文所描述的主体为 WSL 2,有关 WSL 1 的相关内容请参考 <a href="/2018/08/21/wsl-problem/" title="WSL 相关问题解决">WSL 相关问题解决</a>。</p></summary>
<category term="CheatSheet" scheme="https://howiezhao.github.io/categories/CheatSheet/"/>
<category term="Windows" scheme="https://howiezhao.github.io/tags/Windows/"/>
<category term="Linux" scheme="https://howiezhao.github.io/tags/Linux/"/>
<category term="WSL" scheme="https://howiezhao.github.io/tags/WSL/"/>
</entry>
<entry>
<title>聊聊并发编程</title>
<link href="https://howiezhao.github.io/2021/04/13/talk-about-concurrent-programming/"/>
<id>https://howiezhao.github.io/2021/04/13/talk-about-concurrent-programming/</id>
<published>2021-04-13T02:43:43.000Z</published>
<updated>2022-02-16T15:54:13.000Z</updated>
<content type="html"><![CDATA[<h2 id="并发与并行"><a href="#并发与并行" class="headerlink" title="并发与并行"></a>并发与并行</h2><p>并发与并行,这是一个老生常谈的问题,简单来说,<strong>并发</strong>(concurrent)指的是在某个<strong>时间段</strong>内有多个程序在执行,而<strong>并行</strong>(parallel)指的是在某个<strong>时间点</strong>有多个程序在执行。</p><p>这也就是说,在某种程度上而言,<strong>并发是伪并行</strong>,真正的并行需要多个核心。由于现代计算机的核心往往是有限的,但通常运行的程序远大于核心数,所以在计算机科学中,<strong>并发</strong>往往是我们探讨的重点。</p><p>Go 语言的创造者之一 Rob Pike 就曾经指出:</p><blockquote><p>并发用于制定方案,用来解决可能(但未必)并行的问题。</p></blockquote>]]></content>
<summary type="html"><h2 id="并发与并行"><a href="#并发与并行" class="headerlink" title="并发与并行"></a>并发与并行</h2><p>并发与并行,这是一个老生常谈的问题,简单来说,<strong>并发</strong>(concurrent)指的是在</summary>
<category term="CheatSheet" scheme="https://howiezhao.github.io/categories/CheatSheet/"/>
</entry>
<entry>
<title>为什么我不会放弃 Python 投向 Go</title>
<link href="https://howiezhao.github.io/2021/04/10/why-im-not-leaving-python-for-go-zh/"/>
<id>https://howiezhao.github.io/2021/04/10/why-im-not-leaving-python-for-go-zh/</id>
<published>2021-04-10T07:22:25.000Z</published>
<updated>2021-04-10T12:26:15.000Z</updated>
<content type="html"><![CDATA[<p>本文翻译自 <a href="https://uberpython.wordpress.com/2012/09/23/why-im-not-leaving-python-for-go/">https://uberpython.wordpress.com/2012/09/23/why-im-not-leaving-python-for-go/</a> ,原文标题为 <em>Why I’m not leaving Python for Go</em>,译文如下:</p><hr><p>首先,Go 似乎是一门很棒的语言。它有一个<a href="https://tour.golang.org/welcome/1">很棒的教程</a>,我乐此不疲地去看,发现:</p><ul><li>Go 很快。</li><li>在设计上支持并发。</li><li>类型化(对 JIT 和 IDE 来说很重要),但不像 C 或 C++ 的<a href="http://c-faq.com/decl/spiral.anderson.html">螺旋形</a>那样繁琐和丑陋。</li><li>鸭子类型的接口。</li><li><a href="http://golang.org/doc/effective_go.html#defer">延迟(defer)</a>机制真的很精巧。</li></ul><p>但是有一个问题我不能接受。这是个遗憾,因为我很想以并发的名义进行信仰的飞跃。这个问题就是<strong>用返回值来进行错误处理</strong>。这简直像 70 年代的风格。</p><span id="more"></span><h3 id="冗长而重复的错误处理"><a href="#冗长而重复的错误处理" class="headerlink" title="冗长而重复的错误处理"></a>冗长而重复的错误处理</h3><p><a href="https://blog.golang.org/error-handling-and-go">go 的设计师认为这是一种优点。</a></p><blockquote><p>在 Go 语言中,错误处理非常重要。语言的设计和规范鼓励开发人员显式地检查错误(与其他语言抛出异常然后 catch 住是不同的)。在某些情况下,这会使 Go 代码变得<strong>冗长</strong>,但是幸运的是,可以使用一些技术来减少<strong>重复性</strong>错误处理。</p></blockquote><p>这是我在 C 语言中无法忍受的事情之一。<strong>每一行都需要一个 if 语句</strong>来防止程序做疯狂的事情。这是上述链接中的一个官方的规范示例,或许是“最小的重复性错误处理”:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> err := datastore.Get(c, key, record); err != <span class="literal">nil</span> {</span><br><span class="line"> <span class="keyword">return</span> &appError{err, <span class="string">"Record not found"</span>, <span class="number">404</span>}</span><br><span class="line">}</span><br><span class="line"><span class="keyword">if</span> err := viewTemplate.Execute(w, record); err != <span class="literal">nil</span> {</span><br><span class="line"> <span class="keyword">return</span> &appError{err, <span class="string">"Can't display record"</span>, <span class="number">500</span>}</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在 Go 中调用函数的正确方法是将其包装在 if 语句中。即使 <a href="http://golang.org/pkg/fmt/#Println">Println</a> 也会返回一个错误值,我敢肯定,这个星球上的大多数人都不会检查。这让我想到了...</p><h3 id="错误悄悄忽略-——-滴答作响的定时炸弹"><a href="#错误悄悄忽略-——-滴答作响的定时炸弹" class="headerlink" title="错误悄悄忽略 —— 滴答作响的定时炸弹"></a>错误悄悄忽略 —— 滴答作响的定时炸弹</h3><p>引用 Tim Peters 的话:</p><blockquote><p>错误绝不能悄悄忽略,<br>除非它明确需要如此。</p></blockquote><p>Go 不仅停留在冗长而重复的错误处理上。它还使忽略错误变得容易和诱人。在以下程序中,即使我们未能保护总统(presidential)工作人员,我们也会触发世界末日的装置(trigger the doomsday device)。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> http.Get(<span class="string">"http://www.nuke.gov/seal_presidential_bunker"</span>)</span><br><span class="line"> http.Get(<span class="string">"http://www.nuke.gov/trigger_doomsday_device"</span>)</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>多可惜。哎呀。</p><p>从理论上讲,我们可以要求程序员不要忽略返回的错误。通过静态分析或约定。在实践中,仅在最关键的错误编程任务中才能忍受这一痛苦。也许这就是 Go 的目的。</p><h3 id="panic-recover"><a href="#panic-recover" class="headerlink" title="panic/recover"></a>panic/recover</h3><p>只要标准库很少使用它们,panic 和 recover 就不够好。为什么一个数组越界比格式字符串损坏或连接中断更容易引起 panic?Go 想完全避免异常,但意识到它们不能 —— 这里和那里都加了几个异常,让我很困惑,不知道哪个错误什么时候发生。</p><h3 id="或许下次吧"><a href="#或许下次吧" class="headerlink" title="或许下次吧"></a>或许下次吧</h3><p>所以我说这句话的时候非常遗憾,因为 Go 有很多惊人的想法和功能,但是如果没有现代的错误处理 —— 我是不会<strong>去</strong>的。</p><p>我还在等待那个开源、并发、<a href="https://web.archive.org/web/20120922025809/http://shootout.alioth.debian.org/u64q/code-used-time-used-shapes.php">左下角的语言</a>出现。有什么建议,欢迎大家多多指教。</p>]]></content>
<summary type="html"><p>本文翻译自 <a href="https://uberpython.wordpress.com/2012/09/23/why-im-not-leaving-python-for-go/">https://uberpython.wordpress.com/2012/09/23/why-im-not-leaving-python-for-go/</a> ,原文标题为 <em>Why I’m not leaving Python for Go</em>,译文如下:</p>
<hr>
<p>首先,Go 似乎是一门很棒的语言。它有一个<a href="https://tour.golang.org/welcome/1">很棒的教程</a>,我乐此不疲地去看,发现:</p>
<ul>
<li>Go 很快。</li>
<li>在设计上支持并发。</li>
<li>类型化(对 JIT 和 IDE 来说很重要),但不像 C 或 C++ 的<a href="http://c-faq.com/decl/spiral.anderson.html">螺旋形</a>那样繁琐和丑陋。</li>
<li>鸭子类型的接口。</li>
<li><a href="http://golang.org/doc/effective_go.html#defer">延迟(defer)</a>机制真的很精巧。</li>
</ul>
<p>但是有一个问题我不能接受。这是个遗憾,因为我很想以并发的名义进行信仰的飞跃。这个问题就是<strong>用返回值来进行错误处理</strong>。这简直像 70 年代的风格。</p></summary>
<category term="Translation" scheme="https://howiezhao.github.io/categories/Translation/"/>
</entry>
<entry>
<title>对称加密</title>
<link href="https://howiezhao.github.io/2021/03/11/symmetric-encryption/"/>
<id>https://howiezhao.github.io/2021/03/11/symmetric-encryption/</id>
<published>2021-03-11T07:26:13.000Z</published>
<updated>2021-03-14T13:16:09.000Z</updated>
<content type="html"><![CDATA[<p>本文讨论现代密码学中的对称加密。</p><p>对称加密技术有两种宽泛的类型:<strong>块密码</strong>(block cipher)和<strong>流密码</strong>(stream cipher)。块密码用在多种因特网协议的加密中,包括 PGP、<a href="/2018/05/11/https/" title="HTTPS 运行机制">SSL</a> 和 <a href="/2018/05/11/ipsec/" title="IPSec 运行机制">IPsec</a> 等;流密码用在无线 LAN 的安全性中。</p><span id="more"></span><h2 id="块密码"><a href="#块密码" class="headerlink" title="块密码"></a>块密码</h2><p>在块密码中,要加密的报文被处理为 $k$ 比特的块。为了加密一个块,该密码采用了一对一映射,将 $k$ 比特块的明文映射为 $k$ 比特块的密文。</p><p>假设 $k=3$,因此块密码将 3 比特输入(明文)映射为 3 比特输出(密文)。下表给出了一种可能的映射:</p><table><thead><tr><th>输入/输出</th><th>比特块</th><th>比特块</th><th>比特块</th><th>比特块</th><th>比特块</th><th>比特块</th><th>比特块</th><th>比特块</th></tr></thead><tbody><tr><td>输入</td><td>000</td><td>001</td><td>010</td><td>011</td><td>100</td><td>101</td><td>110</td><td>111</td></tr><tr><td>输出</td><td>110</td><td>111</td><td>101</td><td>100</td><td>011</td><td>010</td><td>000</td><td>001</td></tr></tbody></table><p>可以验证,报文 <code>010110001111</code> 被加密成了 <code>101000111001</code>。</p><p>注意到上述映射只是许多可能映射中的一种,观察到一个映射只不过是所有可能输入的排列,共有 $2^3$(=8)种可能的输入(排列在“输入”栏中),这 8 种输入能够排列为 $8!=40320$ 种不同方式,因此共有 40320 种可能的映射。我们能够将这些映射的每种视为一个密钥,即如果 A 和 B 都知道该映射(密钥),它们能够加密和解密在它们之间发送的报文。</p><p>对这种密码的蛮力攻击即通过使用所有映射来尝试解密密文,为了挫败蛮力攻击,块密码通常使用大得多的块,由 64 比特甚至更多比特组成。注意到对于通常的 $k$ 比特块密码,可能映射数量是 $2^k!$,对于即使不大的 $k$ 值(如 $k=64$),这也是一个天文数字。</p><p>像上面这种在所有输入和输出之间提供了预先决定的映射的方法称为<strong>全表块密码</strong>,尽管全表块密码对于不大的 $k$ 值能够产生健壮的对称密钥加密方案,但这要求加密双方各维护一张具有 $2^k$ 个输入值的表,这是一个难以实现的任务。此外,如果加密双方要改变密钥,它们将不得不各自重新生成该表。</p><p>取而代之的是,块密码通常使用函数模拟随机排列表。下图显示了当 $k=64$ 时,这种函数的一个例子。</p><p><img src="/images/block-cipher-example.jpg" alt="block cipher example"></p><p>该函数首先将 64 比特块划分为 8 个块,每个块由 8 比特组成。每个 8 比特块由一个“8 比特到 8 比特”表处理,这是个可管理的长度。例如,第一个块由标志为 $T_1$ 的表来处理。接下来,这 8 个输出块被重新装配成一个 64 比特的块。该输出被回馈到 64 比特的输入,开始了第二次循环。经 n 次这样的循环后,该函数提供了一个 64 比特的密文块。这种循环的目的是使得每个输入比特影响最后输出比特的大部分(即使不是全部)。(如果仅使用一次循环,一个给定的输入比特将仅影响 64 输出比特中的 8 比特。)这种块密码算法的密钥将是 8 张排列表(假定置乱函数是公共已知的)。</p><p>目前有一些流行的块密码,包括 DES、3DES 和 AES 等。这些标准都使用了函数(而不是预先决定的表),连同上图中的线(虽然对每种密码来说更为复杂和具体)。这些算法也都使用了比特串作为密钥。一个算法的密钥决定了特定“小型表”映射和该算法内部的排列。</p><h3 id="密码块链接"><a href="#密码块链接" class="headerlink" title="密码块链接"></a>密码块链接</h3><p>对于相同的块,块密码当然将产生相同的密文,这是不安全的,为了解决这个问题,可以在密文中混合某些随机性,使得相同的明文块产生不同的密文块。</p><p>在介绍其思想之前,先明确一些术语:令 $m(i)$ 表示第 $i$ 个明文块,$c(i)$ 表示第 $i$ 个密文块,并且 $a\oplus b$ 表示两个比特串 $a$ 和 $b$ 的异或(XOR),另外,将具有密钥 $S$ 的块密码加密算法表示为 $K_S$。</p><p>其基本思想如下:</p><p>发送方为第 $i$ 块生成一个随机的 $k$ 比特数 $r(i)$,并且计算 $c(i)=K_S(m(i)\oplus r(i))$。注意到每块选择一个新的 $k$ 比特随机数。则发送方发送 $c(1)$、$r(1)$、$c(2)$、$r(2)$、$c(3)$ 和 $r(3)$ 等等。因为接收方接收到 $c(i)$ 和 $r(i)$,它能够通过计算 $m(i)=K_S(c(i)\oplus r(i))$ 而恢复每个明文块。</p><p>尽管 $r(i)$ 是以明文发送的,但嗅探者无法获得明文 $m(i)$,因为它不知道密钥 $K_S$。如果两个明文块 $m(i)$ 和 $m(j)$ 是相同的,对应的密文块 $c(i)$ 和 $c(j)$ 将是不同的(只要随机数 $r(i)$ 和 $r(j)$ 不同,这种情况出现的概率将很高)。</p><p>举例来说,参照上表的 3 比特块密码,假设明文是 <code>010010010</code>,若直接加密,没有包括随机性,则密文为 <code>101101101</code>;若使用上述技术加密,假设生成了随机块 $r(1)=001$,$r(2)=111$ 和 $r(3)=100$,则生成的密文为 $c(1)=100$,$c(2)=010$ 和 $c(3)=000$。</p><p>引入随机性解决了一个问题而产生了另一个问题,即发送方必须传输以前两倍的比特。实际上,对每个加密比特,它现在必须再发送一个随机比特,使需要的带宽加倍。为了有效利用该技术,块密码通常使用了一种称为<strong>密码块链接</strong>(Cipher Block Chaining,<strong>CBC</strong>)的技术。其基本思想是 <em>仅随第一个报文发送一个随机值,然后让发送方和接收方使用计算的编码块代替后继的随机数</em>。具体而言,CBC 运行过程如下:</p><ol><li>在加密报文(或数据流)之前,发送方生成一个随机的 $k$ 比特串,称为<strong>初始向量</strong>(Initialization Vector,<strong>IV</strong>)。将该初始向量表示为 $c(0)$。发送方以 <em>明文方式</em> 将 IV 发送给接收方。</li><li>对第一个块,发送方计算 $m(1)\oplus c(0)$,即计算第一块明文与 IV 的异或。然后通过块密码算法运行得到的结果以得到对应的密文块,即 $c(1)=K_S(m(1)\oplus c(0))$。发送方向接收方发送加密块 $c(1)$。</li><li>对于第 $i$ 个块,发送方根据 $c(i)=K_S(m(i)\oplus c(i-1))$ 生成第 $i$ 个密文块。</li></ol><p>当接收方接收到 $c(i)$ 时,它用 $K_S$ 解密之以获得 $s(i)=m(i)\oplus c(i-1)$;因为接收方已经知道 $c(i-1)$,则从 $m(i)=s(i)\oplus c(i-1)$ 获得明文块。</p><p>举例来说,参照上表的 3 比特块密码,假设明文是 <code>010010010</code>,$IV=c(0)=001$,则 $c(1)=K_S(m(1)\oplus c(0))=100$,$c(2)=K_S(m(2)\oplus c(1))=K_S(010\oplus 100)=000$,$c(3)=K_S(m(3)\oplus c(2))=K_S(010\oplus 000)=101$。</p><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ol><li><a href="https://book.douban.com/subject/30280001/">James F. Kurose,Keith W. Ross.计算机网络:自顶向下方法[M].北京:机械工业出版社,2018:390-392.</a></li></ol>]]></content>
<summary type="html"><p>本文讨论现代密码学中的对称加密。</p>
<p>对称加密技术有两种宽泛的类型:<strong>块密码</strong>(block cipher)和<strong>流密码</strong>(stream cipher)。块密码用在多种因特网协议的加密中,包括 PGP、<a href="/2018/05/11/https/" title="HTTPS 运行机制">SSL</a> 和 <a href="/2018/05/11/ipsec/" title="IPSec 运行机制">IPsec</a> 等;流密码用在无线 LAN 的安全性中。</p></summary>
<category term="CheatSheet" scheme="https://howiezhao.github.io/categories/CheatSheet/"/>
<category term="密码学" scheme="https://howiezhao.github.io/tags/%E5%AF%86%E7%A0%81%E5%AD%A6/"/>
</entry>
<entry>
<title>DHCP 运行机制</title>
<link href="https://howiezhao.github.io/2021/03/07/dhcp/"/>
<id>https://howiezhao.github.io/2021/03/07/dhcp/</id>
<published>2021-03-07T08:46:17.000Z</published>
<updated>2022-02-22T15:44:31.000Z</updated>
<content type="html"><![CDATA[<p><strong>DHCP</strong>(Dynamic Host Configuration Protocol,<strong>动态主机配置协议</strong>)允许主机自动获取(被分配)一个 IP 地址。网络管理员能够配置 DHCP,以使某给定主机每次与网络连接时能得到一个相同的 IP 地址,或者某主机将被分配一个<strong>临时的 IP 地址</strong>(temporary IP address),每次与网络连接时该地址也许是不同的。除了主机 IP 地址分配外,DHCP 还允许一台主机得知其他信息,例如它的子网掩码、它的第一跳路由器地址(常称为<strong>默认网关</strong>)与它的本地 DNS 服务器的地址。</p><p>由于 DHCP 具有将主机连接进一个网络的网络相关方面的自动能力,故它又常被称为<strong>即插即用协议</strong>(plug-and-play protocol)或<strong>零配置(zeroconf)协议</strong>。</p><span id="more"></span><p>DHCP 是一个客户 — 服务器协议。客户通常是新到达的主机,它要获得包括自身使用的 IP 地址在内的网络配置信息。在最简单场合下,每个子网将具有一台 DHCP 服务器。如果在某子网中没有服务器,则需要一个 DHCP 中继代理(通常是一台路由器),这个代理知道用于该网络的 DHCP 服务器的地址。</p><h2 id="工作过程"><a href="#工作过程" class="headerlink" title="工作过程"></a>工作过程</h2><p>对于一台新到达的主机而言,DHCP 协议是一个 4 个步骤的过程:</p><ol><li><strong>DHCP 服务器发现</strong>:客户在 UDP 分组中向端口 67 发送 <strong>DHCP 发现报文</strong>(DHCP discover message),该 UDP 分组封装在一个 IP 数据报中,其中使用广播目的地址 <code>255.255.255.255</code> 并且使用“本主机”源 IP 地址 <code>0.0.0.0</code>。DHCP 客户将该 IP 数据报传递给链路层,链路层然后将该帧广播到所有与该子网连接的节点。</li><li><strong>DHCP 服务器提供</strong>:DHCP 服务器收到一个 DHCP 发现报文时,用 <strong>DHCP 提供报文</strong>(DHCP offer message)向客户做出响应,该报文向该子网的所有节点广播,仍然使用 IP 广播地址 <code>255.255.255.255</code>。因为在子网中可能存在几个 DHCP 服务器,该客户也许会发现它处于能在几个提供者之间进行选择的优越位置。每台服务器提供的报文包含有收到的发现报文的事务 ID、向客户推荐的 IP 地址、网络掩码以及 <strong>IP 地址租用期</strong>(address lease time),即 IP 地址有效的时间量。服务器租用期通常设置为几小时或几天。</li><li><strong>DHCP 请求</strong>:新到达的客户从一个或多个 <em>服务器提供</em> 中选择一个,并向选中的 <em>服务器提供</em> 用 <strong>DHCP 请求报文</strong>(DHCP request message)进行响应,回显配置的参数。</li><li><strong>DHCP ACK</strong>:服务器用 <strong>DHCP ACK 报文</strong>(DHCP ACK message)对 DHCP 请求报文进行响应,证实所要求的参数。</li></ol><p>一旦客户收到 DHCP ACK 后,交互便完成了,并且该客户能够在租用期内使用 DHCP 分配的 IP 地址。因为客户可能在该租用期超时后还希望使用这个地址,所以 DHCP 还提供了一种机制以允许客户更新它对一个 IP 地址的租用。</p><h2 id="缺陷"><a href="#缺陷" class="headerlink" title="缺陷"></a>缺陷</h2><p>从移动性角度看,每当节点连到一个新子网,要从 DHCP 得到一个新的 IP 地址,当一个移动节点在子网之间移动时,就不能维持与远程应用之间的 TCP 连接。</p><p>此外,学校机房、网吧等地方不使用 DHCP 而使用静态 IP 是为了方便监控。</p><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ol><li><a href="https://book.douban.com/subject/30280001/">James F. Kurose,Keith W. Ross.计算机网络:自顶向下方法[M].北京:机械工业出版社,2018:222-225.</a></li><li><a href="https://www.zhihu.com/question/20971480">为什么学校、网吧机房基本都不使用DHCP?</a></li></ol>]]></content>
<summary type="html"><p><strong>DHCP</strong>(Dynamic Host Configuration Protocol,<strong>动态主机配置协议</strong>)允许主机自动获取(被分配)一个 IP 地址。网络管理员能够配置 DHCP,以使某给定主机每次与网络连接时能得到一个相同的 IP 地址,或者某主机将被分配一个<strong>临时的 IP 地址</strong>(temporary IP address),每次与网络连接时该地址也许是不同的。除了主机 IP 地址分配外,DHCP 还允许一台主机得知其他信息,例如它的子网掩码、它的第一跳路由器地址(常称为<strong>默认网关</strong>)与它的本地 DNS 服务器的地址。</p>
<p>由于 DHCP 具有将主机连接进一个网络的网络相关方面的自动能力,故它又常被称为<strong>即插即用协议</strong>(plug-and-play protocol)或<strong>零配置(zeroconf)协议</strong>。</p></summary>
<category term="CheatSheet" scheme="https://howiezhao.github.io/categories/CheatSheet/"/>
<category term="计算机网络" scheme="https://howiezhao.github.io/tags/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/"/>
</entry>
<entry>
<title>ICMP 运行机制</title>
<link href="https://howiezhao.github.io/2021/03/06/icmp/"/>
<id>https://howiezhao.github.io/2021/03/06/icmp/</id>
<published>2021-03-06T10:30:51.000Z</published>
<updated>2022-02-22T15:44:31.000Z</updated>
<content type="html"><![CDATA[<p><strong>ICMP</strong>(Internet Control Message Protocol,<strong>因特网控制报文协议</strong>)被主机和路由器用来彼此沟通网络层的信息。ICMP 最典型的用途是差错报告。</p><p>ICMP 报文有一个类型字段和一个编码字段,并且包含引起该 ICMP 首次生成的 IP 数据报的首部和前 8 个字节(以便发送方能确定引发该差错的数据报)。下表是相关的 ICMP 报文类型:</p><span id="more"></span><table><thead><tr><th>ICMP 类型</th><th>编码</th><th>描述</th></tr></thead><tbody><tr><td>0</td><td>0</td><td>回显回答(对 <code>ping</code> 的回答)</td></tr><tr><td>3</td><td>0</td><td>目的网络不可达</td></tr><tr><td>3</td><td>1</td><td>目的主机不可达</td></tr><tr><td>3</td><td>2</td><td>目的协议不可达</td></tr><tr><td>3</td><td>3</td><td>目的端口不可达</td></tr><tr><td>3</td><td>6</td><td>目的网络未知</td></tr><tr><td>3</td><td>7</td><td>目的主机未知</td></tr><tr><td>4</td><td>0</td><td>源抑制(拥塞控制)</td></tr><tr><td>8</td><td>0</td><td>回显请求</td></tr><tr><td>9</td><td>0</td><td>路由器通告</td></tr><tr><td>10</td><td>0</td><td>路由器发现</td></tr><tr><td>11</td><td>0</td><td>TTL 过期</td></tr><tr><td>12</td><td>0</td><td>IP 首部损坏</td></tr></tbody></table><p>注意到 ICMP 报文并不仅是用于通知差错情况。</p><h2 id="ping"><a href="#ping" class="headerlink" title="ping"></a><code>ping</code></h2><p><code>ping</code> 程序发送一个 ICMP 类型 8 编码 0 的报文到指定主机。看到回显(echo)请求,目的主机发回一个类型 0 编码 0 的 ICMP 回显回答。</p><p>当 <code>ping</code> 一个不存在的 IP 时,会收到目的主机不可达(类型 3 编码 1)的响应。</p><h2 id="Traceroute"><a href="#Traceroute" class="headerlink" title="Traceroute"></a>Traceroute</h2><p>Traceroute 在 Linux 中一般为 <code>tracepath</code>,在 Windows 中一般为 <code>tracert</code>。该程序允许我们跟踪从一台主机到世界上任意一台主机之间的路由。其用 ICMP 报文来实现。</p><p>为了判断源和目的地之间所有路由器的名字和地址,源主机中的 Traceroute 向目的地主机发送一系列普通的 IP 数据报。这些数据报的每个携带了一个具有不可达 UDP 端口号的 UDP 报文段。第一个数据报的 TTL 为 1,第二个的 TTL 为 2,第三个的 TTL 为 3,依此类推。该源主机也为每个数据报启动定时器。当第 n 个数据报到达第 n 台路由器时,第 n 台路由器观察到这个数据报的 TTL 正好过期。根据 IP 协议规则,路由器丢弃该数据报并发送一个 ICMP 告警报文给源主机(类型 11 编码 0)。该告警报文包含了路由器的名字和它的 IP 地址。当该 ICMP 报文返回源主机时,源主机从定时器得到往返时延,从 ICMP 报文中得到第 n 台路由器的名字与 IP 地址。</p><p>这些数据报之一将最终沿着这条路到达目的主机。因为该数据报包含了一个具有不可达端口号的 UDP 报文段,该目的主机将向源发送一个端口不可达的 ICMP 报文(类型 3 编码 3)。当源主机收到这个特别的 ICMP 报文时,知道它不需要再发送另外的探测分组。</p><p>标准的 Traceroute 程序实际上用相同的 TTL 发送 3 个一组的分组,因此 Traceroute 输出对每个 TTL 提供了 3 个结果。</p><p>一个 Traceroute 运行示例如下:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">tracert 185.199.109.153</span><br><span class="line"></span><br><span class="line">通过最多 30 个跃点跟踪</span><br><span class="line">到 cdn-185-199-109-153.github.com [185.199.109.153] 的路由:</span><br><span class="line"></span><br><span class="line"> 1 26 ms 5 ms 51 ms Hiwifi.lan [192.168.199.1]</span><br><span class="line"> 2 * * * 请求超时。</span><br><span class="line"> 3 9 ms 16 ms 6 ms 1.240.35.58.broad.xw.sh.dynamic.163data.com.cn [58.35.240.1]</span><br><span class="line"> 4 11 ms 11 ms 10 ms 124.74.22.29</span><br><span class="line"> 5 9 ms 6 ms 14 ms 61.152.24.42</span><br><span class="line"> 6 27 ms 41 ms * 202.97.50.154</span><br><span class="line"> 7 21 ms 23 ms 30 ms 202.97.74.1</span><br><span class="line"> 8 93 ms 102 ms 102 ms 202.97.94.10</span><br><span class="line"> 9 85 ms * * ae-3.r30.tokyjp05.jp.bb.gin.ntt.net [129.250.3.23]</span><br><span class="line"> 10 * 83 ms 78 ms ae-2.r00.tokyjp08.jp.bb.gin.ntt.net [129.250.6.127]</span><br><span class="line"> 11 85 ms * 81 ms ae-2.fastly.tokyjp08.jp.bb.gin.ntt.net [117.103.177.66]</span><br><span class="line"> 12 72 ms 70 ms 80 ms cdn-185-199-109-153.github.com [185.199.109.153]</span><br><span class="line"></span><br><span class="line">跟踪完成。</span><br></pre></td></tr></table></figure><p>可以看到输出有 6 列:</p><ul><li>第一列:前面描述的 n 值,即路径上的路由器编号</li><li>第二、三、四列:3 次实验的往返时延</li><li>第五列:路由器的名字</li><li>第六列:路由器地址</li></ul><p>如果源从任何给定路由器接收到的报文少于 3 条(由于网络中的丢包),Traceroute 在该路由器号码后面放一个星号,并向那台路由器报告少于 3 次往返时间。</p><h2 id="其他"><a href="#其他" class="headerlink" title="其他"></a>其他</h2><p>ICMP 通常被认为是 IP 的一部分,但从体系结构上讲它位于 IP 之上,因为 ICMP 报文是承载在 IP 分组中的。这就是说,ICMP 报文是作为 IP 有效载荷承载的,就像 TCP 与 UDP 报文段作为 IP 有效载荷被承载那样。</p>]]></content>
<summary type="html"><p><strong>ICMP</strong>(Internet Control Message Protocol,<strong>因特网控制报文协议</strong>)被主机和路由器用来彼此沟通网络层的信息。ICMP 最典型的用途是差错报告。</p>
<p>ICMP 报文有一个类型字段和一个编码字段,并且包含引起该 ICMP 首次生成的 IP 数据报的首部和前 8 个字节(以便发送方能确定引发该差错的数据报)。下表是相关的 ICMP 报文类型:</p></summary>
<category term="CheatSheet" scheme="https://howiezhao.github.io/categories/CheatSheet/"/>
<category term="计算机网络" scheme="https://howiezhao.github.io/tags/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/"/>
</entry>
<entry>
<title>ARP 运行机制</title>
<link href="https://howiezhao.github.io/2021/03/06/arp/"/>
<id>https://howiezhao.github.io/2021/03/06/arp/</id>
<published>2021-03-06T09:03:40.000Z</published>
<updated>2022-02-22T15:44:31.000Z</updated>
<content type="html"><![CDATA[<p>与 DNS 类似,<strong>ARP</strong>(Address Resolution Protocol,<strong>地址解析协议</strong>)提供了将 <strong>IP 地址</strong>转换为<strong>链路层地址</strong>的机制。这两种解析器之间的一个重要区别是,DNS 为在因特网中任何地方的主机解析主机名,而 ARP 只为在同一个子网上的主机和路由器<strong>接口</strong>解析 IP 地址。</p><span id="more"></span><h2 id="MAC-地址"><a href="#MAC-地址" class="headerlink" title="MAC 地址"></a>MAC 地址</h2><p>事实上,并不是主机或路由器具有链路层地址,而是它们的适配器(即网络接口)具有链路层地址。因此,具有多个网络接口的主机或路由器将具有与之相关联的多个链路层地址,就像它也具有与之相关联的多个 IP 地址一样。</p><p>链路层地址有各种不同的称呼:<strong>LAN 地址</strong>(LAN address)、<strong>物理地址</strong>(physical address)或 <strong>MAC 地址</strong>(MAC address)。我们一般将链路层地址称为 MAC 地址。</p><p>对于大多数局域网(包括以太网和 802.11 无线局域网)而言,MAC 地址长度为 6 字节,共有 $2^{48}$ 个可能的 MAC 地址。这些 6 个字节地址通常用十六进制表示法,地址的每个字节被表示为一对十六进制数。如 <code>1A-23-F9-CD-06-9B</code>。尽管 MAC 地址被设计为永久的,但用软件改变一块适配器的 MAC 地址现在是可能的。</p><p>没有两块适配器具有相同的 MAC 地址。IEEE 在管理着 MAC 地址空间。特别是,当一个公司要生产适配器时,它支付象征性的费用购买组成 $2^{24}$ 个地址的一块地址空间。IEEE 分配这块 $2^{24}$ 个地址的方式是:固定一个 MAC 地址的前 24 比特,让公司自己为每个适配器生成后 24 比特的唯一组合。具体的分配列表可参考<a href="http://standards-oui.ieee.org/oui/oui.txt">其官网</a>。</p><p>适配器的 MAC 地址具有<strong>扁平结构</strong>(这与层次结构相反),而且不论适配器到哪里用都不会变化。与之形成对照的是,IP 地址具有<strong>层次结构</strong>(即一个网络部分和一个主机部分),而且当主机移动时,主机的 IP 地址需要改变,即改变它所连接到的网络。可以将适配器的 MAC 地址比喻成一个人的身份证号,而将 IP 地址比喻成一个人的住址。</p><p>当某适配器要向某些目的适配器发送一个帧时,发送适配器将目的适配器的 MAC 地址插入到该帧中,并将该帧发送到局域网上。对于使用 6 字节地址的局域网(例如以太网和 802.11)来说,<strong>MAC 广播地址</strong>(broadcast address)是 48 个连续的 1 组成的字符串(即以十六进制表示法表示的 <code>FF-FF-FF-FF-FF-FF</code>)。</p><h2 id="ARP-工作过程"><a href="#ARP-工作过程" class="headerlink" title="ARP 工作过程"></a>ARP 工作过程</h2><p>每台主机或路由器在其内存中具有一个 <strong>ARP 表</strong>(ARP table),这张表包含 IP 地址到 MAC 地址的映射关系。例如:</p><table><thead><tr><th>IP 地址</th><th>MAC 地址</th><th>TTL</th></tr></thead><tbody><tr><td>222.222.222.221</td><td>88-B2-2F-54-1A-0F</td><td>13:45:00</td></tr><tr><td>222.222.222.223</td><td>5C-66-AB-90-75-B1</td><td>13:52:00</td></tr></tbody></table><p>该 ARP 表也包含一个寿命(TTL)值,它指示了从表中删除每个映射的时间。注意到这张表不必为该子网中的每台主机和路由器都包含一个表项;某些可能从来没有进入到该表中,某些可能已经过期。从一个表项放置到某 ARP 表中开始,一个表项通常的过期时间是 20 分钟。</p><p>如果 ARP 表中当前没有目的主机的表项,在这种情况下,发送方用 ARP 协议来解析这个地址。首先,发送方构造一个称为 <strong>ARP 分组</strong>(ARP packet)的特殊分组。一个 ARP 分组有几个字段,包括发送和接收 IP 地址及 MAC 地址。ARP 查询分组和响应分组都具有相同的格式。ARP 查询分组的目的是询问子网上所有其他主机和路由器,以确定对应于要解析的 IP 地址的那个 MAC 地址。</p><p>查询 ARP 报文是在广播帧中发送的,而响应 ARP 报文在一个标准帧中发送。</p><p>ARP 表是即插即用的,这就是说,一个 ARP 表是自动建立的,即它不需要系统管理员来配置。并且如果某主机与子网断开连接,它的表项最终会从留在子网中的节点的表中删除。</p><h2 id="其他"><a href="#其他" class="headerlink" title="其他"></a>其他</h2><p>ARP 是一个链路层协议还是一个网络层协议?一个 ARP 分组封装在链路层帧中,因而在体系结构上位于链路层之上。然而,一个 ARP 分组具有包含链路层地址的字段,因而可认为是链路层协议,但它也包含网络层地址,因而也可认为是网络层协议。所以,可能最好把 ARP 看成是跨越链路层和网络层边界两边的协议,即不完全符合简单的分层协议栈。现实世界协议就是这样复杂!</p><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ol><li><a href="https://book.douban.com/subject/30280001/">James F. Kurose,Keith W. Ross.计算机网络:自顶向下方法[M].北京:机械工业出版社,2018:303-308.</a></li></ol>]]></content>
<summary type="html"><p>与 DNS 类似,<strong>ARP</strong>(Address Resolution Protocol,<strong>地址解析协议</strong>)提供了将 <strong>IP 地址</strong>转换为<strong>链路层地址</strong>的机制。这两种解析器之间的一个重要区别是,DNS 为在因特网中任何地方的主机解析主机名,而 ARP 只为在同一个子网上的主机和路由器<strong>接口</strong>解析 IP 地址。</p></summary>
<category term="CheatSheet" scheme="https://howiezhao.github.io/categories/CheatSheet/"/>
<category term="计算机网络" scheme="https://howiezhao.github.io/tags/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/"/>
</entry>
<entry>
<title>Session、Cookie 和 Token</title>
<link href="https://howiezhao.github.io/2021/02/25/session-cookie-token/"/>
<id>https://howiezhao.github.io/2021/02/25/session-cookie-token/</id>
<published>2021-02-25T08:01:02.000Z</published>
<updated>2022-02-16T15:54:13.000Z</updated>
<content type="html"><![CDATA[<h2 id="Session"><a href="#Session" class="headerlink" title="Session"></a>Session</h2><p>Session 是一种抽象的概念,它不止存在于 HTTP 中,在计算机的众多领域都有它的身影,比如在 SSH、tmux 中都有 Session 的出现。</p><p>Session 被直译为<strong>会话</strong>,顾名思义,它指客户端和服务器之间的连接会话。</p><h2 id="Cookie"><a href="#Cookie" class="headerlink" title="Cookie"></a>Cookie</h2><p>Cookie 是一种具体的技术,它常被用在 HTTP 中,在其他领域很少见到 Cookie 的身影。</p><p>Cookie 可以理解为是 Session 这种概念的一种实现,通过 Cookie 可以确定客户端和服务器之间的连接会话,从而使无状态的 HTTP 协议有状态。</p><p>除 Cookie 之外,还可以使用带参数的 URL 来实现 Session 这种概念。</p><h2 id="Token"><a href="#Token" class="headerlink" title="Token"></a>Token</h2><p>Token 直译为<strong>令牌</strong>,顾名思义,它常被用以客户端和服务器之间的身份认证。</p><p>Cookie 不仅可以用来实现上述的这种身份认证,也可以用来实现类似购物车这种场景。</p>]]></content>
<summary type="html"><h2 id="Session"><a href="#Session" class="headerlink" title="Session"></a>Session</h2><p>Session 是一种抽象的概念,它不止存在于 HTTP 中,在计算机的众多领域都有它的身影,比如在</summary>
<category term="CheatSheet" scheme="https://howiezhao.github.io/categories/CheatSheet/"/>
</entry>
<entry>
<title>vCard 是什么</title>
<link href="https://howiezhao.github.io/2021/02/04/what-is-vcard/"/>
<id>https://howiezhao.github.io/2021/02/04/what-is-vcard/</id>
<published>2021-02-04T09:00:58.000Z</published>
<updated>2021-02-05T09:43:14.000Z</updated>
<content type="html"><![CDATA[<p>vCard 是广为使用的电子名片的文件格式标准,常用于手机通讯录文件中,其后缀名常为 <code>.vcf</code>。</p><p>一个典型的 vCard 文件内容如下所示:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">BEGIN:VCARD</span><br><span class="line">VERSION:2.1</span><br><span class="line">N;CHARSET=UTF-8;ENCODING=QUOTED-PRINTABLE:testa</span><br><span class="line">FN;CHARSET=UTF-8;ENCODING=QUOTED-PRINTABLE:testa</span><br><span class="line">TEL;CELL:12345678910</span><br><span class="line">END:VCARD</span><br><span class="line">BEGIN:VCARD</span><br><span class="line">VERSION:2.1</span><br><span class="line">N;CHARSET=UTF-8;ENCODING=QUOTED-PRINTABLE:testb</span><br><span class="line">FN;CHARSET=UTF-8;ENCODING=QUOTED-PRINTABLE:testb</span><br><span class="line">TEL;CELL:12345678910</span><br><span class="line">END:VCARD</span><br></pre></td></tr></table></figure><p>每个 vCard 条目通过 <code>BEGIN:VCARD</code> 与 <code>END:VCARD</code> 包裹,其中 <code>VERSION</code> 字段表示 vCard 版本号,<code>N</code> 和 <code>FN</code> 字段分别表示 <em>姓名</em> 和 <em>名</em>,<code>TEL</code> 字段表示电话号码。</p><p>GitHub 上的 <a href="https://github.com/metowolf/vCards">vCards</a> 项目提供了类似中国黄页的功能。</p>]]></content>
<summary type="html"><p>vCard 是广为使用的电子名片的文件格式标准,常用于手机通讯录文件中,其后缀名常为 <code>.vcf</code>。</p>
<p>一个典型的 vCard 文件内容如下所示:</p>
<figure class="highlight text"><table><tr></summary>
<category term="CheatSheet" scheme="https://howiezhao.github.io/categories/CheatSheet/"/>
</entry>
<entry>
<title>CDN</title>
<link href="https://howiezhao.github.io/2021/01/21/cdn/"/>
<id>https://howiezhao.github.io/2021/01/21/cdn/</id>
<published>2021-01-21T04:26:07.000Z</published>
<updated>2022-07-30T06:14:48.000Z</updated>
<content type="html"><![CDATA[<p><strong>CDN</strong> 全称 Content Distribution Network,即<strong>内容分发网络</strong>。CDN 管理分布在多个地理位置上的服务器,在它的服务器中存储各种类型的 Web 内容(包括文档、图片、音频和视频等)的副本,并且所有试图将每个用户请求定向到一个将提供最好的用户体验的 CDN 位置。</p><p>CDN 可以是<strong>专用 CDN</strong>(private CDN),即它由内容提供商自己所拥有;例如,谷歌的 CDN 分发 YouTube 视频和其他类型的内容。也可以是<strong>第三方 CDN</strong>(third-party CDN),它代表多个内容提供商分发内容;<a href="https://www.akamai.com/zh">Akamai</a>、<a href="https://www.limelight.com/">Limelight</a> 和 <a href="https://www.cloudflare.com/zh-cn/cdn">Cloudflare CDN</a> 都运行第三方 CDN。</p><span id="more"></span><p>相比于使用 CDN,建立单一的大规模数据中心存在以下三个问题:</p><ul><li>如果客户远离数据中心,服务器到客户的分组将跨越许多通信链路并很可能通过许多 ISP,其中某些 ISP 可能位于不同的大洲。如果这些链路之一提供的吞吐量小于内容消耗速率,端到端吞吐量也将小于该消耗速率,给用户带来恼人的停滞时延。出现这种事件的可能性随着端到端的路径中链路数量的增加而增加。</li><li>流行的内容很可能经过相同的通信链路发送许多次。这不仅浪费了网络带宽,内容提供商自己也将为向因特网反复发送 <em>相同</em> 的字节而向其 ISP 运营商(连接到数据中心)支付费用。</li><li>单个数据中心代表一个单点故障,如果数据中心或其通向因特网的链路崩溃,它将不能够分发 <em>任何</em> 内容了。</li></ul><p>CDN 通常采用两种不同的服务器安置原则:</p><ul><li><strong>深入</strong>:该原则是通过在遍及全球的接入 ISP 中部署服务器集群来深入到 ISP 的接入网中。由 Akamai 首创。其目标是靠近端用户,通过减少端用户和 CDN 集群之间(内容从这里收到)链路和路由器的数量,从而改善了用户感受的时延和吞吐量。因为这种高度分布式设计,维护和管理集群的任务成为挑战。</li><li><strong>邀请做客</strong>:该原则是通过在少量(例如 10 个)关键位置建造大集群来 <em>邀请到 ISP 做客</em>。该原则由 Limelight 和许多其他 CDN 公司所采用。不是将集群放在接入 ISP 中,这些 CDN 通常将它们的集群放置在因特网交换点(IXP)。与深入设计原则相比,邀请做客设计通常产生较低的维护和管理开销,可能以对端用户的较高时延和较低吞吐量为代价。</li></ul><p>一旦 CDN 的集群准备就绪,它就可以跨集群复制内容。CDN 可能不希望将每个内容的副本放置在每个集群中,因为某些内容很少访问或仅在某些国家中流行。事实上,许多 CDN 没有将内容推入它们的集群,而是使用一种简单的<strong>拉策略</strong>:如果客户向一个未存储该内容的集群请求某内容,则该集群检索该内容(从某中心仓库或者从另一个集群),向客户传输内容时的同时在本地存储一个副本。类似于 Web 缓存器,当某集群存储器变满时,它删除不经常请求的内容。</p><p>当用户主机中的一个浏览器指令检索一个特定的视频(由 URL 标识)时,CDN 必须截获该请求,以便能够:</p><ol><li>确定此时适合用于该客户的 CDN 服务器集群;</li><li>将客户的请求重定向到该集群的某台服务器。</li></ol><p>大多数 CDN 利用 DNS 来截获和重定向请求,其步骤如下:</p><ol><li>用户访问目标网页;</li><li>当用户点击链接时,该用户主机发送了一个对于目标 URL 的 DNS 请求;</li><li>用户的本地 DNS 服务器(LDNS)将该 DNS 请求中继到 <em>目标服务器的权威 DNS 服务器</em>,<em>目标服务器的权威 DNS 服务器</em> 并不返回一个 IP 地址,而是向 LDNS 返回一个 <em>第三方 CDN 的主机名</em>;</li><li>从这时起,DNS 请求进入了 <em>第三方 CDN 的专用 DNS 基础设施</em>。用户的 LDNS 则发送第二个请求,此时是对 <em>第三方 CDN 的主机名</em> 的 DNS 请求,<em>第三方 CDN 的 DNS 系统</em> 最终向 LDNS 返回 <em>第三方 CDN 服务器</em> 的 IP 地址。所以正是在这里,在 <em>第三方 CDN 的 DNS 系统</em> 中,指定了 CDN 服务器,客户将能够从这台服务器接收到它的内容;</li><li>LDNS 向用户主机转发 <em>第三方 CDN 节点</em> 的 IP 地址;</li><li>一旦客户收到 <em>第三方 CDN 节点</em> 的 IP 地址,它与具有该 IP 地址的服务器创建了一条直接的 TCP 连接,并且发出对该网页的 HTTP GET 请求。</li></ol><p>任何 CDN 部署,其核心是<strong>集群选择策略</strong>(cluster selection strategy),即动态地将客户定向到 CDN 中的某个服务器集群或数据中心的机制。如上所述,经过客户的 DNS 查找,CDN 得知了该客户的 LDNS 服务器的 IP 地址。在得知该 IP 地址之后,CDN 需要基于该 IP 地址选择一个适当的集群。CDN 一般采用专用的集群选择策略。</p><p>一种简单的策略是指派客户到<strong>地理上最为临近</strong>(geographically closest)的集群。使用商用地理位置数据库,每个 LDNS IP 地址都映射到一个地理位置。当从一个特殊的 LDNS 接收到一个 DNS 请求时,CDN 选择地理上最为接近的集群,即离 LDNS 最少几千米远的集群。这样的解决方案对于众多用户来说能够工作得相当好。但对于某些客户,该解决方案可能执行的效果差,因为就网络路径的长度或跳数而言,地理最临近的集群可能并不是最近的集群。此外,一种所有基于 DNS 的方法都内在具有的问题是,某些端用户配置使用位于远地的 LDNS,在这种情况下,LDNS 位置可能远离客户的位置。此外,这种简单的策略忽略了时延和可用带宽随因特网路径时间而变化,总是为特定的客户指派相同的集群。</p><p>为了基于 <em>当前</em> 流量条件为客户决定最好的集群,CDN 能够对其集群和客户之间的时延和丢包性能执行周期性的<strong>实时测量</strong>(real-time measurement)。例如,CDN 能够让它的每个集群周期性地向位于全世界的所有 LDNS 发送探测分组(例如,ping 报文或 DNS 请求)。这种方法的一个缺点是许多 LDNS 被配置为不会响应这些探测。</p>]]></content>
<summary type="html"><p><strong>CDN</strong> 全称 Content Distribution Network,即<strong>内容分发网络</strong>。CDN 管理分布在多个地理位置上的服务器,在它的服务器中存储各种类型的 Web 内容(包括文档、图片、音频和视频等)的副本,并且所有试图将每个用户请求定向到一个将提供最好的用户体验的 CDN 位置。</p>
<p>CDN 可以是<strong>专用 CDN</strong>(private CDN),即它由内容提供商自己所拥有;例如,谷歌的 CDN 分发 YouTube 视频和其他类型的内容。也可以是<strong>第三方 CDN</strong>(third-party CDN),它代表多个内容提供商分发内容;<a href="https://www.akamai.com/zh">Akamai</a>、<a href="https://www.limelight.com/">Limelight</a> 和 <a href="https://www.cloudflare.com/zh-cn/cdn">Cloudflare CDN</a> 都运行第三方 CDN。</p></summary>
<category term="CheatSheet" scheme="https://howiezhao.github.io/categories/CheatSheet/"/>
<category term="计算机网络" scheme="https://howiezhao.github.io/tags/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/"/>
</entry>
<entry>
<title>UDP 协议分析</title>
<link href="https://howiezhao.github.io/2021/01/12/udp/"/>
<id>https://howiezhao.github.io/2021/01/12/udp/</id>
<published>2021-01-12T07:25:26.000Z</published>
<updated>2021-01-21T15:21:53.000Z</updated>
<content type="html"><![CDATA[<p>UDP 全称 User Datagram Protocol,即<strong>用户数据报协议</strong>,它为调用它的应用程序提供了一种不可靠、无连接的服务。</p><p>UDP 无非就是对网络层协议增加了一点(多路)复用/(多路)分解服务而已。</p><p>使用 UDP 的上层协议有:</p><ul><li><a href="/2020/12/29/dns/" title="DNS 运行机制">DNS</a></li><li>SNMP</li></ul><p>选择使用 UDP 的原因:</p><ul><li>关于发送什么数据以及何时发送的应用层控制更为精细。</li><li>无须连接建立。</li><li>无连接状态。</li><li>分组首部开销小。每个 TCP 报文段都有 20 字节的首部开销,而 UDP 仅有 8 字节的开销。</li></ul><p>使用 UDP 的应用是可能实现可靠数据传输的,这可通过在应用程序自身中建立可靠性机制来完成。<br>可参考 <a href="https://github.com/skywind3000/kcp">KCP</a>、<a href="https://www.chromium.org/quic">QUIC</a>。</p><span id="more"></span><h2 id="UDP-报文段结构"><a href="#UDP-报文段结构" class="headerlink" title="UDP 报文段结构"></a>UDP 报文段结构</h2><p>UDP 报文段结构如图所示:</p><p><img src="/images/udp-segment-structure.jpg" alt="UDP 报文段结构"></p><p>通过端口号可以使目的主机将应用数据交给运行在目的端系统中的相应进程(即执行分解功能)。<br>长度字段指示了在 UDP 报文段中的字节数(首部加数据)。<br>接收方使用检验和来检查在该报文段中是否出现了差错。</p><h2 id="UDP-检验和"><a href="#UDP-检验和" class="headerlink" title="UDP 检验和"></a>UDP 检验和</h2><p>发送方的 UDP 对报文段中的所有 16 比特字的和进行反码运算,求和时遇到的任何溢出都被回卷。得到的结果被放在 UDP 报文段中的检验和字段。</p><p>在接收方,全部的 16 比特字(包括检验和)加在一起。如果和为 <code>1111111111111111</code>,则未引入差错,反之,则已经出现了差错。</p><p>在既无法确保逐链路的可靠性,又无法确保内存中的差错检测的情况下,如果端到端数据传输服务要提供差错检测,UDP就必须在端到端基础上在运输层提供差错检测。这是一个在系统设计中被称颂的<strong>端到端原则</strong>(end-end principle)的例子,该原则表述为因为某种功能(在此时为差错检测)必须基于端到端实现:“在与较高级别提供这些功能的代价相比,在较低级别上设置的功能可能是冗余的或几乎没有价值的。”</p><p>虽然 UDP 提供差错检测,但它对差错恢复无能为力。UDP 的某种实现只是丢弃受损的报文段;其他实现是将受损的报文段交给应用程序并给出警告。</p>]]></content>
<summary type="html"><p>UDP 全称 User Datagram Protocol,即<strong>用户数据报协议</strong>,它为调用它的应用程序提供了一种不可靠、无连接的服务。</p>
<p>UDP 无非就是对网络层协议增加了一点(多路)复用&#x2F;(多路)分解服务而已。</p>
<p>使用 UDP 的上层协议有:</p>
<ul>
<li><a href="/2020/12/29/dns/" title="DNS 运行机制">DNS</a></li>
<li>SNMP</li>
</ul>
<p>选择使用 UDP 的原因:</p>
<ul>
<li>关于发送什么数据以及何时发送的应用层控制更为精细。</li>
<li>无须连接建立。</li>
<li>无连接状态。</li>
<li>分组首部开销小。每个 TCP 报文段都有 20 字节的首部开销,而 UDP 仅有 8 字节的开销。</li>
</ul>
<p>使用 UDP 的应用是可能实现可靠数据传输的,这可通过在应用程序自身中建立可靠性机制来完成。<br>可参考 <a href="https://github.com/skywind3000/kcp">KCP</a>、<a href="https://www.chromium.org/quic">QUIC</a>。</p></summary>
<category term="CheatSheet" scheme="https://howiezhao.github.io/categories/CheatSheet/"/>
<category term="计算机网络" scheme="https://howiezhao.github.io/tags/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/"/>
</entry>
<entry>
<title>DNS 运行机制</title>
<link href="https://howiezhao.github.io/2020/12/29/dns/"/>
<id>https://howiezhao.github.io/2020/12/29/dns/</id>
<published>2020-12-29T11:25:33.000Z</published>
<updated>2022-02-24T15:49:51.000Z</updated>
<content type="html"><![CDATA[<p><strong>DNS</strong>(Domain Name System,<strong>域名系统</strong>)的主要任务是进行<strong>主机名</strong>(hostname,也称<strong>域名</strong>)到 <strong>IP 地址</strong>转换的目录服务,它由两部分组成:</p><ul><li>一个由<strong>分层</strong>的 <strong>DNS 服务器</strong>(DNS server)实现的<strong>分布式数据库</strong></li><li>一个使得主机能够查询分布式数据库的<strong>应用层协议</strong></li></ul><p>DNS 服务器通常是运行 <a href="https://www.isc.org/bind/">BIND</a>(Berkeley Internet Name Domain)软件的 UNIX 机器。DNS 协议运行在 UDP 之上,使用 <strong>53</strong> 号端口。</p><span id="more"></span><p>除了进行主机名到 IP 地址的转换外,DNS 还提供了一些重要的服务:</p><ul><li>主机别名(host aliasing):有着复杂主机名的主机能拥有一个或着多个别名</li><li>邮件服务器别名(mail server aliasing)</li><li>负载分配(load distribution):DNS 也用于在冗余的服务器(如冗余的 Web 服务器等)之间进行负载分配。这种负载均衡的缺点为由于 DNS 缓存(如 LDNS 中缓存)的存在,其实际作用很有限。</li></ul><h2 id="DNS-服务器"><a href="#DNS-服务器" class="headerlink" title="DNS 服务器"></a>DNS 服务器</h2><p>DNS 服务器大致分为 3 种:</p><ul><li><strong>根 DNS 服务器</strong>:根 DNS 服务器提供 TLD 服务器的 IP 地址。有 400 多个根 DNS 服务器遍及全世界。这些根 DNS 服务器由 13 个不同的组织管理。根 DNS 服务器的全部清单连同管理它们的组织及其 IP 地址可以在<a href="https://root-servers.org/">这里</a>找到。</li><li><strong>顶级域(Top-Level Domain,TLD)DNS 服务器</strong>:TLD 服务器提供了权威 DNS 服务器的 IP 地址。对于每个顶级域(如 <code>com</code>、<code>org</code>、<code>net</code>、<code>edu</code> 和 <code>gov</code>)和所有国家的顶级域(如 <code>uk</code>、<code>fr</code>、<code>ca</code> 和 <code>jp</code>),都有 TLD 服务器(或服务器集群)。所有顶级域的列表可参见<a href="https://zh.wikipedia.org/wiki/%E4%BA%92%E8%81%94%E7%BD%91%E9%A1%B6%E7%BA%A7%E5%9F%9F%E5%88%97%E8%A1%A8">维基百科</a>。</li><li><strong>权威 DNS 服务器</strong>:在因特网上具有公共可访问主机(如 Web 服务器和邮件服务器)的每个组织机构必须提供公共可访问的 DNS 记录,这些记录将这些主机的名字映射为 IP 地址。一个组织机构的权威 DNS 服务器收藏了这些 DNS 记录。一个组织机构能够选择实现它自己的权威 DNS 服务器以保存这些记录;另一种方法是,该组织能够支付费用,让这些记录存储在某个服务提供商的一个权威 DNS 服务器中。</li></ul><p>还有另一类重要的 DNS 服务器,称为<strong>本地 DNS 服务器</strong>(Local DNS server,<strong>LDNS</strong>)。严格来说,一个本地 DNS 服务器并不属于该服务器的层次结构,但它对 DNS 层次结构是至关重要的。每个 ISP(如一个居民区的 ISP 或一个机构的 ISP)都有一台本地 DNS 服务器(也叫默认名字服务器)。当主机与某个 ISP 连接时,该 ISP 提供一台主机的 IP 地址,该主机具有一台或多台其本地 DNS 服务器的 IP 地址(通常通过 DHCP)。主机的本地 DNS 服务器通常“临近”本主机。当主机发出 DNS 请求时,该请求被发往本地 DNS 服务器,它起着代理的作用,并将该请求转发到 DNS 服务器层次结构中,大致过程如下所述:</p><ol><li>客户端向它的本地 DNS 服务器发送一个关于 <code>tieba.baidu.com</code> 的 DNS 查询报文</li><li>本地 DNS 服务器将该报文转发到根 DNS 服务器</li><li>根 DNS 服务器注意到其 <code>com</code> 前缀并向本地 DNS 服务器返回负责 <code>com</code> 的 TLD 的 IP 地址列表</li><li>本地 DNS 服务器则再次向这些 TLD 服务器之一发送查询报文</li><li>TLD 服务器注意到 <code>baidu.com</code> 前缀,并用权威 DNS 服务器的 IP 地址进行响应,该权威 DNS 服务器是 <code>dns.baidu.com</code></li><li>本地 DNS 服务器直接向 <code>dns.baidu.com</code> 重发查询报文,<code>dns.baidu.com</code> 用 <code>tieba.baidu.com</code> 的 IP 地址进行响应</li></ol><p>注意到在本例中,为了获得一台主机名的映射,共发送了 8 份 DNS 报文:4 份查询报文和 4 份回答报文!下文将介绍利用 <strong>DNS 缓存</strong>(DNS caching)减少这种查询流量的方法。</p><p>上述例子利用了<strong>递归查询</strong>(recursive query)和<strong>迭代查询</strong>(iterative query)。从请求主机到本地 DNS 服务器的查询是递归的,其余的查询是迭代的。从理论上讲,任何 DNS 查询既可以是迭代的也能是递归的。</p><h2 id="DNS-协议"><a href="#DNS-协议" class="headerlink" title="DNS 协议"></a>DNS 协议</h2><p>共同实现 DNS 分布式数据库的所有 DNS 服务器存储了<strong>资源记录</strong>(Resource Record,<strong>RR</strong>),RR 提供了主机名到 IP 地址的映射。每个 DNS 回答报文包含了一条或多条资源记录。</p><p>资源记录是一个包含了下列字段的 4 元组:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">(Name, Value, Type, TTL)</span><br></pre></td></tr></table></figure><p>TTL(Time To Live)是该记录的生存时间,它决定了资源记录应当从缓存中删除的时间。Name 和 Value 的值取决于 Type:</p><ul><li>若 Type = A,则 Name 是主机名,Value 是该主机名对应的 IP 地址。因此,一条类型为 A 的资源记录提供了标准的主机名到 IP 地址的映射。例如 <code>(relay1.bar.foo.com, 145.37.93.126, A)</code> 就是一条类型 A 记录。</li><li>若 Type = NS,则 Name 是个域(如 <code>foo.com</code>),而 Value 是个知道如何获得该域中主机 IP 地址的权威 DNS 服务器的主机名。这个记录用于沿着查询链来路要 DNS 查询。例如 <code>(foo.com, dns.foo.com, NS)</code> 就是一条类型为 NS 的记录。</li><li>若 Type = CNAME,则 Value 是别名为 Name 的主机对应的规范主机名。该记录能够向查询的主机提供一个主机名对应的规范主机名,例如 <code>(foo.com, relay1.bar.foo.com, CNAME)</code> 就是一条 CNAME 类型的记录。</li><li>若 Type = MX,则 Value 是个别名为 Name 的邮件服务器的规范主机名。举例来说,<code>(foo.com, mail.bar.foo.com, MX)</code> 就是一条 MX 记录。MX 记录允许邮件服务器主机名具有简单的别名。值得注意的是,通过使用 MX 记录,一个公司的邮件服务器和其他服务器(如它的 Web 服务器)可以使用相同的别名。为了获得邮件服务器的规范主机名,DNS 客户应当请求一条 MX 记录;而为了获得其他服务器的规范主机名,DNS 客户应当请求 CNAME 记录。</li></ul><p>如果一台 DNS 服务器是用于某特定主机名的权威 DNS 服务器,那么该 DNS 服务器会有一条包含用于该主机名的类型 A 记录(即使该 DNS 服务器不是其权威 DNS 服务器,它也可能在缓存中包含有一条类型 A 记录)。如果服务器不是用于某主机名的权威服务器,那么该服务器将包含一条类型 NS 记录,该记录对应于包含主机名的域;它还将包括一条类型 A 记录,该记录提供了在 NS 记录的 Value 字段中的 DNS 服务器的 IP 地址。</p><p>DNS 只有查询和回答这两种报文,并且,查询和回答报文有着相同的格式,如下图所示:</p><p><img src="/images/dns-message.jpg" alt="dns message"></p><p>前 12 个字节是<strong>首部区域</strong>,其中有如下几个字段:</p><ul><li>标识符:是一个 16 比特的数,用于标识该查询。这个标识符会被复制到对查询的回答报文中,以便让客户用它来匹配发送的请求和接收到的回答。</li><li>标志:含有若干标志。1 比特的“<strong>查询/回答</strong>”标志位指出报文是查询报文(0)还是回答报文(1)。当某 DNS 服务器是所请求名字的权威 DNS 服务器时,1 比特的“<strong>权威的</strong>”标志位被置在回答报文中。如果客户在该 DNS 服务器没有某记录时希望它执行递归查询,将设置 1 比特的“<strong>希望递归</strong>”标志位。如果该 DNS 服务器支持递归查询,在它的回答报文中会对 1 比特的“<strong>递归可用</strong>”标志位置位。</li><li>剩余 4 个有关数量的字段,这些字段指出了在首部后的 4 类数据区域出现的数量。</li></ul><p><strong>问题区域</strong>包含着正在进行的查询信息。该区域包括:</p><ol><li>名字字段,包含正在被查询的主机名字;</li><li>类型字段,指出有关该名字的正被询问的问题类型,例如主机地址是与一个名字相关联(类型 A)还是与某个名字的邮件服务器相关联(类型 MX)。</li></ol><p>在来自 DNS 服务器的回答中,<strong>回答区域</strong>包含了对最初请求的名字的资源记录。在回答报文的回答区域中可以包含多条 RR,因此一个主机名能够有多个 IP 地址。</p><p><strong>权威区域</strong>包含了其他权威服务器的记录。</p><p><strong>附加区域</strong>包含了其他有帮助的记录。例如,对于一个 MX 请求的回答报文的回答区域包含了一条资源记录,该记录提供了邮件服务器的规范主机名。该附加区域包含一个类型 A 记录,该记录提供了用于该邮件服务器的规范主机名的 IP 地址。</p><p>上面的讨论只是关注如何从 DNS 数据库中取数据,这些数据最初是怎么进入数据库中的呢?<strong>注册登记机构</strong>(registrar)是一个商业实体,它验证域名的唯一性,并将域名输入 DNS 数据库,对提供的服务收取少量费用。<strong>因特网名字和地址分配机构</strong>(Internet Corporation for Assigned Names and Numbers,<strong>ICANN</strong>)向各种注册登记机构授权。</p><h2 id="DNS-缓存"><a href="#DNS-缓存" class="headerlink" title="DNS 缓存"></a>DNS 缓存</h2><p>实际上,为了改善时延性能并减少在因特网上到处传输的 DNS 报文数量,DNS 广泛使用了缓存技术。</p><p>DNS 缓存的原理非常简单。在一个请求链中,当某 DNS 服务器接收一个 DNS 回答(例如,包含某主机名到 IP 地址的映射)时,它能将映射缓存到本地存储器中。如果在 DNS 服务器中缓存了一台主机名/IP 地址对,另一个对相同主机名的查询到达该 DNS 服务器时,该 DNS 服务器就能够提供所要求的 IP 地址,即使它不是该主机名的权威服务器。由于主机和主机名与 IP 地址间的映射并不是永久的,DNS 服务器在一段时间后(通常设置为两天)将丢弃缓存的信息。</p><p>本地 DNS 服务器也能够缓存 TLD 服务器的 IP 地址,因而允许本地 DNS 绕过查询链中的根 DNS 服务器。事实上,因为缓存,除了少数 DNS 查询以外,根服务器被绕过了。</p><h2 id="安全性"><a href="#安全性" class="headerlink" title="安全性"></a>安全性</h2><p>第一种针对 DNS 服务的攻击是分布式拒绝服务(DDoS)带宽洪泛攻击,对 DNS 的潜在更为有效的 DDoS 攻击将是向顶级域名服务器(例如向所有处理 <code>.com</code> 域的顶级域名服务器)发送大量的 DNS 请求,以上两种 DDoS 攻击的严重性都可以通过本地 DNS 服务器中的缓存技术可将部分地被缓解。</p><p>DNS 能够潜在地以其他方式被攻击。在中间人攻击中,攻击者截获来自主机的请求并返回伪造的回答,这被称为 <strong>DNS 劫持</strong>。在 <strong>DNS 毒害</strong>(DNS 投毒、DNS 污染)攻击中,攻击者向一台 DNS 服务器发送伪造的回答,诱使服务器在它的缓存中接收伪造的记录。这些攻击中的任一种,都能够将毫无疑虑的 Web 用户重定向到攻击者的 Web 站点。然而,这些攻击难以实现,因为它们要求截获分组或扼制住服务器。</p>]]></content>
<summary type="html"><p><strong>DNS</strong>(Domain Name System,<strong>域名系统</strong>)的主要任务是进行<strong>主机名</strong>(hostname,也称<strong>域名</strong>)到 <strong>IP 地址</strong>转换的目录服务,它由两部分组成:</p>
<ul>
<li>一个由<strong>分层</strong>的 <strong>DNS 服务器</strong>(DNS server)实现的<strong>分布式数据库</strong></li>
<li>一个使得主机能够查询分布式数据库的<strong>应用层协议</strong></li>
</ul>
<p>DNS 服务器通常是运行 <a href="https://www.isc.org/bind/">BIND</a>(Berkeley Internet Name Domain)软件的 UNIX 机器。DNS 协议运行在 UDP 之上,使用 <strong>53</strong> 号端口。</p></summary>
<category term="CheatSheet" scheme="https://howiezhao.github.io/categories/CheatSheet/"/>
<category term="计算机网络" scheme="https://howiezhao.github.io/tags/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/"/>
</entry>
<entry>
<title>POP3、IMAP 和 SMTP</title>
<link href="https://howiezhao.github.io/2020/12/29/pop3-imap-smtp/"/>
<id>https://howiezhao.github.io/2020/12/29/pop3-imap-smtp/</id>
<published>2020-12-29T06:52:46.000Z</published>
<updated>2022-02-22T15:44:31.000Z</updated>
<content type="html"><![CDATA[<p>因特网电子邮件系统由 3 个主要部分组成:</p><ul><li><strong>用户代理</strong>(user agent):允许用户阅读、回复、转发、保存和撰写报文,如微软的 Outlook 和 Apple Mail 等</li><li><strong>邮件服务器</strong>(mail server):每个接收方在其中的某个邮件服务器上有一个邮箱(mailbox)</li><li><strong>SMTP</strong></li></ul><p>一个典型的邮件发送过程是:从发送方的用户代理开始,传输到发送方的邮件服务器(通过 SMTP 或 HTTP),再传输到接收方的邮件服务器(通过 SMTP),然后在这里被分发到接收方的邮箱中。如果发送方的邮件服务器不能将邮件交付给接收方的邮件服务器,发送方的邮件服务器在一个<strong>报文队列</strong>(message queue)中保持该报文并在以后尝试再次发送。</p><span id="more"></span><h2 id="SMTP"><a href="#SMTP" class="headerlink" title="SMTP"></a>SMTP</h2><p><strong>SMTP</strong>(Simple Mail Transfer Protocol,<strong>简单邮件传输协议</strong>)是因特网电子邮件中主要的应用层协议。它使用 TCP 可靠数据传输服务,从发送方的邮件服务器向接收方的邮件服务器发送邮件。每台邮件服务器既运行 SMTP 的客户端也运行 SMTP 的服务器端。</p><p>SMTP 一般不使用中间邮件服务器发送邮件。</p><p>SMTP 是如何将一个报文从发送邮件服务器传送到接收邮件服务器的呢?首先,客户 SMTP(运行在发送邮件服务器主机上)在 <strong>25</strong> 号端口建立一个到服务器 SMTP(运行在接收邮件服务器主机上)的 TCP 连接。如果服务器没有开机,客户会在稍后继续尝试连接。一旦连接建立,服务器和客户执行某些应用层的握手。在 SMTP 握手的阶段,SMTP 客户指示发送方的邮件地址(产生报文的那个人)和接收方的邮件地址。一旦握手完成,客户发送该报文。该客户如果有另外的报文要发送到该服务器,就在该相同的 TCP 连接上重复这种处理;否则,它指示 TCP 关闭连接。以下是一个例子(S 代表 SMTP 服务器,C 代表 SMTP 客户):</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">S: 220 hamburger.edu</span><br><span class="line">C: HELO crepes.fr</span><br><span class="line">S: 250 Hello crepes.fr, pleased to meet you</span><br><span class="line">C: MAIL FROM: <[email protected]></span><br><span class="line">S: 250 [email protected] ... Sender ok</span><br><span class="line">C: RCPT TO: <[email protected]></span><br><span class="line">S: 250 [email protected] ... Recipient ok</span><br><span class="line">C: DATA</span><br><span class="line">S: 354 Enter mail, end with "." on a line by itself</span><br><span class="line">C: Do you like ketchup?</span><br><span class="line">C: How about pickles?</span><br><span class="line">C: .</span><br><span class="line">S: 250 Message accepted for delivery</span><br><span class="line">C: QUIT</span><br><span class="line">S: 221 hamburger.edu closing connection</span><br></pre></td></tr></table></figure><p>在上例中,该客户发送了 5 条命令:<code>HELO</code>(是 HELLO 的缩写)、<code>MAIL FROM</code>、<code>RCPT TO</code>、<code>DATA</code> 以及 <code>QUIT</code>。</p><p>当然,你也可以使用 Telnet 直接与一个 SMTP 服务器对话,如下:</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">telnet smtp.office365.com 25</span><br></pre></td></tr></table></figure><h3 id="与-HTTP-的对比"><a href="#与-HTTP-的对比" class="headerlink" title="与 HTTP 的对比"></a>与 HTTP 的对比</h3><p>SMTP 与 HTTP 有一些共同的特征:</p><ul><li>这两个协议都用于从一台主机向另一台主机传送文件</li><li>当进行文件传送时,持续的 HTTP 和 SMTP 都使用持续连接</li></ul><p>然而,两者之间也有一些重要的区别:</p><p>首先,HTTP 主要是一个<strong>拉协议</strong>(pull protocol),即在方便的时候,某些人在 Web 服务器上装载信息,用户使用 HTTP 从该服务器拉取这些信息。特别是 TCP 连接是由想接收文件的机器发起的。另一方面,SMTP 基本上是一个<strong>推协议</strong>(push protocol),即发送邮件服务器把文件推向接收邮件服务器。特别是,这个 TCP 连接是由发送该文件的机器发起的。</p><p>第二个区别是由于问世较早, SMTP 要求每个报文(包括它们的体)采用 7 比特 ASCII 码格式。如果某报文包含了非 7 比特 ASCII 字符(如具有重音的法文字符)或二进制数据(如图形文件),则该报文必须按照 7 比特 ASCII 码进行编码。HTTP 数据则不受这种限制。</p><p>第三个重要区别是如何处理一个既包含文本又包含图形(也可能是其他媒体类型)的文档。HTTP 把每个对象封装到它自己的 HTTP 响应报文中,而 SMTP 则把所有报文对象放在一个报文之中。</p><h3 id="邮件报文格式"><a href="#邮件报文格式" class="headerlink" title="邮件报文格式"></a>邮件报文格式</h3><p>如同 HTTP 协议,邮件报文也有相应的邮件首部行,每个首部行包含了可读的文本,是由关键词后跟冒号及其值组成的。某些关键词是必须的,另一些则是可选的。每个首部 <em>必须</em> 含有一个 <code>From:</code> 首部行和一个 <code>To:</code> 首部行;一个首部 <em>也许</em> 包含一个 <code>Subject:</code> 首部行以及其他可选的首部行。</p><p>一个典型的报文首部看看起来如下:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">From: [email protected]</span><br><span class="line">To: [email protected]</span><br><span class="line">Subject: Searching for the meaning of life.</span><br></pre></td></tr></table></figure><h2 id="POP3"><a href="#POP3" class="headerlink" title="POP3"></a>POP3</h2><p><strong>POP3</strong>(Post Office Protocol——Version 3,<strong>第三版的邮局协议</strong>)是一个极为简单的邮件访问协议。</p><p>当用户代理(客户)打开了一个到邮件服务器(服务器)端口 <strong>110</strong> 上的 TCP 连接后,POP3 就开始工作了。随着建立 TCP 连接,POP3 按照 3 个阶段进行工作:</p><ol><li><strong>特许</strong>(authorization):用户代理发送(以明文形式)用户名和口令以鉴别用户</li><li><strong>事务处理</strong>:用户代理取回报文;同时在这个阶段用户代理还能进行如下操作,对报文做删除标记,取消报文删除标记,以及获取邮件的统计信息</li><li><strong>更新</strong>:它出现在客户发出了 quit 命令之后,目的是结束该 POP3 会话;这时,该邮件服务器删除那些被标记为删除的报文</li></ol><p>在 POP3 的事务处理过程中,用户代理发出一些命令,服务器对每个命令做出回答。回答可能有两种:</p><ul><li><code>+OK</code>:(有时后面还跟有服务器到客户的数据),被服务器用来指示前面的命令是正常的</li><li><code>-ERR</code>:被服务器用来指示前面的命令出现了某些差错</li></ul><p>特许阶段有两个主要的命令:<code>user <user name></code> 和 <code>pass <password></code>。你可以使用 Telnet 进行测试,如下:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">telnet outlook.office365.com 110</span><br><span class="line">+OK The Microsoft Exchange POP3 service is ready.</span><br><span class="line">user bob</span><br><span class="line">+OK</span><br><span class="line">pass hungry</span><br><span class="line">+OK user successfully logged on</span><br></pre></td></tr></table></figure><p>在特许阶段以后,用户代理仅使用 4 个命令 <code>list</code>、<code>retr</code>、<code>dele</code> 和 <code>quit</code>。</p><p>使用 POP3 的用户代理通常被用户配置为“<strong>下载并删除</strong>”或者“<strong>下载并保留</strong>”方式。POP3 用户代理发出的命令序列取决于用户代理程序被配置为这两种工作方式的哪一种。使用下载并删除方式,用户代理发出 <code>list</code> —— <code>retr</code> —— <code>dele</code> 命令,如下(C 代表客户,S 代表服务器):</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">C: list</span><br><span class="line">S: 1 498</span><br><span class="line">S: 2 912</span><br><span class="line">S: .</span><br><span class="line">C: retr 1</span><br><span class="line">S: (blah blah ...</span><br><span class="line">S: .................</span><br><span class="line">S: ..........blah)</span><br><span class="line">S: .</span><br><span class="line">C: dele 1</span><br><span class="line">C: retr 2</span><br><span class="line">S: (blah blah ...</span><br><span class="line">S: .................</span><br><span class="line">S: ..........blah)</span><br><span class="line">S: .</span><br><span class="line">C: dele 2</span><br><span class="line">C: quit</span><br><span class="line">S: +OK POP3 server signing off</span><br></pre></td></tr></table></figure><p>使用下载并保留方式,用户代理下载某邮件后,该邮件仍保留在邮件服务器上。</p><h2 id="IMAP"><a href="#IMAP" class="headerlink" title="IMAP"></a>IMAP</h2><p><strong>IMAP</strong>(Internet Mail Access Protocol,<strong>因特网邮件访问协议</strong>)是一个相比 POP3 更强大的邮件访问协议。</p><p>相比 POP3,IMAP 协议为用户提供了创建文件夹以及将邮件从一个文件夹移动到另一个文件夹的命令;还为用户提供了在远程文件夹中查询邮件的命令,按指定条件去查询匹配的邮件;它还提供了允许用户代理获取报文某些部分的命令。</p><h2 id="其他"><a href="#其他" class="headerlink" title="其他"></a>其他</h2><p>相比于 SMTP,POP3 和 IMAP 等邮件访问协议本质上属于拉协议,在基于 Web 的电子邮件中,则可以通过 HTTP 进行邮件的收发,此时,用户代理就是普通的浏览器。</p>]]></content>
<summary type="html"><p>因特网电子邮件系统由 3 个主要部分组成:</p>
<ul>
<li><strong>用户代理</strong>(user agent):允许用户阅读、回复、转发、保存和撰写报文,如微软的 Outlook 和 Apple Mail 等</li>
<li><strong>邮件服务器</strong>(mail server):每个接收方在其中的某个邮件服务器上有一个邮箱(mailbox)</li>
<li><strong>SMTP</strong></li>
</ul>
<p>一个典型的邮件发送过程是:从发送方的用户代理开始,传输到发送方的邮件服务器(通过 SMTP 或 HTTP),再传输到接收方的邮件服务器(通过 SMTP),然后在这里被分发到接收方的邮箱中。如果发送方的邮件服务器不能将邮件交付给接收方的邮件服务器,发送方的邮件服务器在一个<strong>报文队列</strong>(message queue)中保持该报文并在以后尝试再次发送。</p></summary>
<category term="CheatSheet" scheme="https://howiezhao.github.io/categories/CheatSheet/"/>
<category term="计算机网络" scheme="https://howiezhao.github.io/tags/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/"/>
</entry>
<entry>
<title>P2P 与 BitTorrent</title>
<link href="https://howiezhao.github.io/2020/12/15/p2p-bittorrent/"/>
<id>https://howiezhao.github.io/2020/12/15/p2p-bittorrent/</id>
<published>2020-12-15T08:18:58.000Z</published>
<updated>2022-02-24T15:49:51.000Z</updated>
<content type="html"><![CDATA[<p>P2P 是一种<strong>体系结构</strong>,就好像 C/S 体系结构一样,但不同的是,P2P 对总是打开的基础设施服务器有最小的(或者没有)依赖,成对间歇连接的主机(称为<strong>对等方</strong>)彼此直接通信。</p><p>基于 P2P 这种体系结构产生了许多应用,譬如<strong>文件分发应用</strong>和<strong>分布式散列表</strong>(DHT),而文件分发应用中一个典型的例子就是 BitTorrent 协议。</p><p>如果要类比 P2P 与 BitTorrent 之间的关系,就好像 C/S 体系结构与 FTP 协议之间的关系。</p><span id="more"></span><h2 id="P2P-与-C-S"><a href="#P2P-与-C-S" class="headerlink" title="P2P 与 C/S"></a>P2P 与 C/S</h2><p>以文件分发应用为例,在 C/S 文件分发中,该服务器必须向每个对等方发送该文件的一个副本,即服务器承受了极大的负担,并且消耗了大量的服务器带宽。在 P2P 文件分发中,每个对等方能够向任何其他对等方重新分发它已经收到的该文件的任何部分,从而在分发过程中协助该服务器。</p><p>对于 C/S 体系结构,随着对等方数量的增加,分发时间呈线性增长并且没有界。然而,对于 P2P 体系结构,最小分发时间不仅总是小于 C/S 体系结构的分发时间,并且对于任意的对等方数量,总是小于一对一的 C/S 体系结构的分发时间。因此,具有 P2P 体系结构的应用程序能够是自扩展的。这种扩展性的直接成因是:<strong>对等方除了是比特的消费者外还是它们的重新分发者</strong>。</p><h2 id="BitTorrent"><a href="#BitTorrent" class="headerlink" title="BitTorrent"></a>BitTorrent</h2><p>如前所述,BitTorrent 是最为流行的 P2P 文件分发协议,通常也简称为 BT,最初由 Bram Cohen 所研发。</p><p>用 BitTorrent 的术语来讲,参与一个特定文件分发的所有对等方的集合被称为一个<strong>洪流(torrent)</strong>。在一个洪流中的对等方彼此下载等长度的<strong>文件块</strong>(chunk),典型的块长度为 256 KB。当一个对等方首次加入一个洪流时,它没有块。随着时间的流逝,它累积了越来越多的块。当它下载块时,也为其他对等方上载了多个块。一旦某对等方获得了整个文件,它也许(自私地)离开洪流,或(大公无私地)留在该洪流中并继续向其他对等方上载块。同时,任何对等方可能在任何时候仅具有块的子集就离开该洪流,并在以后重新加入该洪流中。</p><p>每个洪流具有一个基础设施节点,称为 <strong>tracker</strong>(追踪器)。当一个对等方加入某洪流时,它向 tracker 注册自己,并周期性地通知 tracker 它仍在该洪流中。以这种方式,tracker 跟踪参与在洪流中的对等方。</p><p>当一个新的对等方 A 加入该洪流时,tracker 随机地从参与对等方的集合中选择对等方的一个子集,将这个子集中每个对等方的 IP 地址发送给 A。A 持有对等方的这张列表,试图与该列表上的所有对等方创建并行的 TCP 连接。我们称所有这样与 A 成功地创建一个 TCP 连接的对等方为<strong>邻近对等方</strong>。一个对等方的临近对等方将随时间而波动。</p><p>在任何给定的时刻,A 将具有块的子集并知道它的邻居具有哪些块。利用这些信息,A 将做出两个重要决定:</p><ul><li>它应当从它的邻居请求哪些块呢?</li><li>它应当向哪些向它请求块的邻居发送块?</li></ul><p>在决定请求哪些块的过程中,A 使用一种称为<strong>最稀缺优先</strong>(rarest first)的技术。这种技术的思路时,针对它没有的块在它的邻居中决定最稀缺的块(最稀缺的块就是那些在它的邻居中副本数量最少的块),并首先请求那些最稀缺的块。这样,最稀缺块得到更为迅速的重新发放,其目标是(大致地)均衡每个块在洪流中的副本数量。</p><p>为了决定它响应哪个请求,BitTorrent 使用了一种被称为<strong>一报还一报</strong>(tit-for-tat)的交换激励机制。其基本想法是,A 根据当前能够以 <em>最高速率</em> 向它提供数据的邻居,给出其优先权。特别是,A 对于它的每个邻居都持续地测量接收到比特的速率,并确定以最高速率流入的 4 个邻居。每过 10 秒,它重新计算该速率并可能修改这 4 个对等方的集合。用 BitTorrent 术语来说,这 4 个对等方被称为<strong>疏通</strong>(unchoked)。重要的是,每过 30 秒,它也要随机地选择另外一个邻居并向其发送块。我们将这个被随机选择的对等方称为 B。现在站在 B 的角度上看,因为 A 正在向 B 发送数据,它可能成为 B 前 4 位上载者之一,这样的话 B 将开始向 A 发送数据。如果 B 向 A 发送数据的速率足够高,B 接下来也能成为 A 的前 4 位上载者。换言之,每过 30 秒 A 将随机地选择一名新的对换伴侣并开始与那位伴侣进行对换。如果这两名对等方都满足此对换,它们将对方放入其前 4 位列表中并继续与对方进行对换,直到该对等方之一发现了一个更好的伴侣为止。这种效果是对等方能够以趋向于找到彼此的协调的速率上载。随机选择邻居也允许新的对等方得到块,因此它们能够具有对换的东西。除了这 5 个对等方(<em>前</em> 4 个对等方和一个试探的对等方)的所有其他相邻对等方均被 <em>阻塞</em>,即它们不能从 A 接收到任何块。</p><h2 id="BitTorrent-客户端"><a href="#BitTorrent-客户端" class="headerlink" title="BitTorrent 客户端"></a>BitTorrent 客户端</h2><p>就如同 FTP 协议有众多的 FTP 客户端一样,BitTorrent 协议也有众多的 BitTorrent 客户端。最早的 BitTorrent 客户端应该是由 BitTorrent 发明人 Bram Cohen 开发的,很巧,软件的名字也叫 <a href="https://www.bittorrent.com/zh-cn/">BitTorrent</a>,现在已经成长为一家公司,也收购了著名的开源 BitTorrent 客户端 <a href="https://www.utorrent.com/intl/zh_cn/">µTorrent</a>。</p><p>出于种种原因,现在并不推荐使用以上两款软件,BitTorrent 发明人 Bram Cohen 也已经离开了那家公司,更推荐使用开源跨平台的 <a href="https://www.qbittorrent.org/">qBittorrent</a>。</p><!--迅雷--><h2 id="其他"><a href="#其他" class="headerlink" title="其他"></a>其他</h2><p>如前所述,tracker 服务器是一个 BitTorrent 网络的重点,大部分客户端都支持自定义 tracker 列表,GitHub 上就有一些精选的 tracker 列表:</p><ul><li><a href="https://github.com/ngosang/trackerslist">https://github.com/ngosang/trackerslist</a></li><li><a href="https://github.com/XIU2/TrackersListCollection">https://github.com/XIU2/TrackersListCollection</a></li></ul><p>为了降低对 tracker 服务器的依赖,BitTorrent 中也使用到了前面提到的分布式散列表(DHT)。</p><!--[IKnowWhatYouDownload](https://iknowwhatyoudownload.com/en/peer/)-->]]></content>
<summary type="html"><p>P2P 是一种<strong>体系结构</strong>,就好像 C&#x2F;S 体系结构一样,但不同的是,P2P 对总是打开的基础设施服务器有最小的(或者没有)依赖,成对间歇连接的主机(称为<strong>对等方</strong>)彼此直接通信。</p>
<p>基于 P2P 这种体系结构产生了许多应用,譬如<strong>文件分发应用</strong>和<strong>分布式散列表</strong>(DHT),而文件分发应用中一个典型的例子就是 BitTorrent 协议。</p>
<p>如果要类比 P2P 与 BitTorrent 之间的关系,就好像 C&#x2F;S 体系结构与 FTP 协议之间的关系。</p></summary>
<category term="CheatSheet" scheme="https://howiezhao.github.io/categories/CheatSheet/"/>
<category term="计算机网络" scheme="https://howiezhao.github.io/tags/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/"/>
</entry>
<entry>
<title>HackRF 初体验</title>
<link href="https://howiezhao.github.io/2020/01/22/hackrf/"/>
<id>https://howiezhao.github.io/2020/01/22/hackrf/</id>
<published>2020-01-22T12:25:55.000Z</published>
<updated>2020-02-05T12:51:43.000Z</updated>
<content type="html"><![CDATA[<p><a href="https://greatscottgadgets.com/hackrf/">HackRF</a> 是由 Great Scott Gadgets 设计和制造的开源 SDR 硬件,其可以发送或接收 1 MHz 到 6 GHz 的无线电信号。目前 HackRF 的具体版本为 HackRF One。你可以通过其<a href="https://greatscottgadgets.com/wheretobuy/">官网上列出的购买网址</a>购买它,也可以在万能的淘宝上购买。<img src="https://greatscottgadgets.com/images/h1-preliminary1-445.jpeg" alt="HackRF One"></p><span id="more"></span><h2 id="软件"><a href="#软件" class="headerlink" title="软件"></a>软件</h2><p>你需要安装如下软件以使用 HackRF One:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">sudo add-apt-repository -y ppa:bladerf/bladerf</span><br><span class="line">sudo add-apt-repository -y ppa:myriadrf/drivers</span><br><span class="line">sudo add-apt-repository -y ppa:myriadrf/gnuradio</span><br><span class="line">sudo add-apt-repository -y ppa:gqrx/gqrx-sdr</span><br><span class="line">sudo apt update</span><br><span class="line"></span><br><span class="line">sudo apt install gqrx-sdr hackrf</span><br></pre></td></tr></table></figure><h2 id="常用命令"><a href="#常用命令" class="headerlink" title="常用命令"></a>常用命令</h2><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">hackrf_info # 查看 Hack RF 连接信息</span><br><span class="line"></span><br><span class="line">hackrf_transfer # 基于文件进行发送和接收 SDR</span><br><span class="line">hackrf_transfer -h # 查看 hackrf_transfer 帮助信息</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">录制信号</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">-r:将数据存储到文件中</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">-f:中心频率,单位 Hz</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">-s:采样率,单位 Hz(4/8/10/12.5/16/20 MHz,默认 10 MHz)</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">-n:采样数量(默认值是无限的)</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">-a:设置功放(1 表示开启,0 表示关闭)</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">-g:设置 Rx VGA 增益(0 到 62 dB 之间,每次增加 2 dB)</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">-l:设置 Rx LNA 增益(0 到 40 dB 之间,每次增加 8 dB)</span></span><br><span class="line">hackrf_transfer -r capture.raw -f 315000000 -l 8/16/24 -g 20/40 [-s 2000000 -n 10000000 -a 1]</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">重放信号</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">-t:从文件中读取数据</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">-x:设置 Tx VGA 增益(0 到 47 dB 之间,每次增加 1 dB)</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">-R:重复发送模式(默认为关闭)</span></span><br><span class="line">hackrf_transfer -t capture.raw -f 315000000 -x 40 [-s 2000000 -a 1]</span><br></pre></td></tr></table></figure><p>一般无线钥匙工作频段都在 315 Mhz、433.92 Mhz。<br>Tx Mode:发射模式<br>Rx Mode:接收模式</p>]]></content>
<summary type="html"><p><a href="https://greatscottgadgets.com/hackrf/">HackRF</a> 是由 Great Scott Gadgets 设计和制造的开源 SDR 硬件,其可以发送或接收 1 MHz 到 6 GHz 的无线电信号。目前 HackRF 的具体版本为 HackRF One。你可以通过其<a href="https://greatscottgadgets.com/wheretobuy/">官网上列出的购买网址</a>购买它,也可以在万能的淘宝上购买。<img src="https://greatscottgadgets.com/images/h1-preliminary1-445.jpeg" alt="HackRF One"></p></summary>
<category term="CheatSheet" scheme="https://howiezhao.github.io/categories/CheatSheet/"/>
</entry>
<entry>
<title>使用 Sphinx 生成项目文档</title>
<link href="https://howiezhao.github.io/2020/01/22/sphinx-document/"/>
<id>https://howiezhao.github.io/2020/01/22/sphinx-document/</id>
<published>2020-01-22T12:21:54.000Z</published>
<updated>2022-07-30T06:14:48.000Z</updated>
<content type="html"><![CDATA[<p><a href="http://www.sphinx-doc.org/en/master/">Sphinx</a> 是一个用 Python 编写的文档生成工具,其使用 <a href="/2018/09/17/restructuredtext/" title="reStructuredText 小记">reStructuredText</a> 作为标记语言,目前广泛应用于 Python 相关项目的文档生成。比如 <a href="https://docs.python.org/zh-cn/3/">Python 官方文档</a>就是基于 Sphinx 生成的,此外 <a href="https://www.kernel.org/doc/html/latest/index.html">Linux 内核文档</a>的生成也于 2016 年从 <a href="http://www.doxygen.nl/">Doxygen</a> 转向 Sphinx,要了解更多使用 Sphinx 的项目可参考其<a href="https://www.sphinx-doc.org/en/master/examples.html">官方列表</a>。</p><span id="more"></span><p>使用 <code>pip install sphinx</code> 即可安装 Sphinx。</p><h2 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h2><p>按照最佳实践,项目文档一般是在项目的 <code>docs</code> 目录中,所以不妨先创建一个 <code>docs</code> 目录并进入,之后的一切有关 sphinx 的命令都在此目录中运行。</p><p>安装完 Sphinx 并进入 <code>docs</code> 目录后,输入 <code>sphinx-quickstart</code> 即可创建一个文档项目。该引导程序会询问你一些问题,并根据你给出的回答对生成的文档项目进行相关配置,当然,这些配置都可以在生成后的 <code>conf.py</code> 文件中进行重新设置。以下是它可能会问到的一些问题:</p><ul><li>分隔“source”和“build”目录(y/n)[n]:默认不分隔即可</li><li>项目名称:项目名称将显示在左侧导航栏顶部</li><li>作者姓名:作者姓名将显示在页面底部</li><li>项目版本:可不填</li><li>项目语言[en]:默认为英文,要切换为中文请输入 <code>zh_CN</code></li></ul><p>执行完成后,会在当前目录下生成如下文件/文件夹:</p><ul><li><code>_duild/</code>:存放构建之后的文件</li><li><code>_static/</code>:存放静态文件</li><li><code>_templates/</code>:存放模板文件</li><li><code>conf.py</code>:sphinx 的配置文件</li><li><code>index.rst</code>:文档主页</li><li><code>Makefile</code>:Linux 下 <code>make</code> 构建工具的配置文件</li><li><code>make.bat</code>:Windows 下的构建命令脚本</li></ul><p>编写完文档后,使用 <code>make html</code> 命令即可将其构建为 HTML 文件,其中,<code>html</code> 被称为构建器(builder),当然,你也可以使用别的构建器,比如 <code>latex</code>、<code>epub</code> 等。</p><p>输入 <code>make help</code> 可查看 make 支持的相关命令。</p><h2 id="主题"><a href="#主题" class="headerlink" title="主题"></a>主题</h2><p>Sphinx 生成的 HTML 文件默认使用的主题为 <a href="https://github.com/bitprophet/alabaster">Alabaster</a>(个人觉得挺好看的,<a href="https://requests.readthedocs.io/projects/cn/zh_CN/latest/">Requests</a> 和 <a href="https://flask.palletsprojects.com/en/1.1.x/">Flask</a> 项目文档的主题都是基于此主题修改的),除此之外,Sphinx 还内置了一些别的主题,具体可见其<a href="https://www.sphinx-doc.org/en/master/usage/theming.html#builtin-themes">官方文档中列出的</a>(个人觉得其余的主题不如 Alabaster 好看),当然,你也可以使用第三方主题。</p><p>第三方主题中最常见的非 <a href="https://github.com/readthedocs/sphinx_rtd_theme">sphinx_rtd_theme</a> 莫属,<a href="https://docs.scrapy.org/en/latest/">Scrapy</a> 项目的文档就使用的它,要使用 sphinx_rtd_theme,需要先执行 <code>pip install sphinx_rtd_theme</code> 命令下载它,然后修改 <code>conf.py</code> 配置文件中的 <code>html_theme</code> 变量为 <code>'sphinx_rtd_theme'</code> 并在 <code>extensions</code> 列表中添加 <code>'sphinx_rtd_theme'</code>。</p><p>要探索更多的第三方主题,可参考 <a href="https://sphinx-themes.org/">Sphinx Themes</a> 网站上收录的。</p><h2 id="扩展"><a href="#扩展" class="headerlink" title="扩展"></a>扩展</h2><p>Sphinx 支持扩展,安装完 Sphinx 后就已经内置了一些扩展,除此之外,你也可以下载第三方扩展。</p><h3 id="内置扩展"><a href="#内置扩展" class="headerlink" title="内置扩展"></a>内置扩展</h3><h4 id="sphinx-ext-autodoc"><a href="#sphinx-ext-autodoc" class="headerlink" title="sphinx.ext.autodoc"></a>sphinx.ext.autodoc</h4><h4 id="sphinx-ext-coverage"><a href="#sphinx-ext-coverage" class="headerlink" title="sphinx.ext.coverage"></a>sphinx.ext.coverage</h4><h4 id="sphinx-ext-viewcode"><a href="#sphinx-ext-viewcode" class="headerlink" title="sphinx.ext.viewcode"></a>sphinx.ext.viewcode</h4><h4 id="sphinx-ext-napoleon"><a href="#sphinx-ext-napoleon" class="headerlink" title="sphinx.ext.napoleon"></a>sphinx.ext.napoleon</h4><h4 id="sphinx-ext-graphviz"><a href="#sphinx-ext-graphviz" class="headerlink" title="sphinx.ext.graphviz"></a>sphinx.ext.graphviz</h4><h4 id="sphinx-ext-todo"><a href="#sphinx-ext-todo" class="headerlink" title="sphinx.ext.todo"></a>sphinx.ext.todo</h4><h3 id="第三方扩展"><a href="#第三方扩展" class="headerlink" title="第三方扩展"></a>第三方扩展</h3><h4 id="recommonmark"><a href="#recommonmark" class="headerlink" title="recommonmark"></a>recommonmark</h4><h4 id="nbsphinx"><a href="#nbsphinx" class="headerlink" title="nbsphinx"></a>nbsphinx</h4><h4 id="sphinx-autodoc-typehints"><a href="#sphinx-autodoc-typehints" class="headerlink" title="sphinx-autodoc-typehints"></a>sphinx-autodoc-typehints</h4><h4 id="sphinx-gallery"><a href="#sphinx-gallery" class="headerlink" title="sphinx-gallery"></a>sphinx-gallery</h4><h2 id="Read-the-Docs"><a href="#Read-the-Docs" class="headerlink" title="Read the Docs"></a>Read the Docs</h2>]]></content>
<summary type="html"><p><a href="http://www.sphinx-doc.org/en/master/">Sphinx</a> 是一个用 Python 编写的文档生成工具,其使用 <a href="/2018/09/17/restructuredtext/" title="reStructuredText 小记">reStructuredText</a> 作为标记语言,目前广泛应用于 Python 相关项目的文档生成。比如 <a href="https://docs.python.org/zh-cn/3/">Python 官方文档</a>就是基于 Sphinx 生成的,此外 <a href="https://www.kernel.org/doc/html/latest/index.html">Linux 内核文档</a>的生成也于 2016 年从 <a href="http://www.doxygen.nl/">Doxygen</a> 转向 Sphinx,要了解更多使用 Sphinx 的项目可参考其<a href="https://www.sphinx-doc.org/en/master/examples.html">官方列表</a>。</p></summary>
<category term="CheatSheet" scheme="https://howiezhao.github.io/categories/CheatSheet/"/>
</entry>
<entry>
<title>Proxmark 入坑指南</title>
<link href="https://howiezhao.github.io/2019/12/01/proxmark/"/>
<id>https://howiezhao.github.io/2019/12/01/proxmark/</id>
<published>2019-12-01T05:37:30.000Z</published>
<updated>2022-07-30T06:14:48.000Z</updated>
<content type="html"><![CDATA[<p>Proxmark 是 RFID 界的瑞士军刀,请注意,其官网为 <a href="http://www.proxmark.org/">proxmark.org</a>,其他诸如 <a href="https://proxmark.com/">proxmark.com</a> 的均为第三方商业网站,目前的最新硬件版本是 Proxmark 3。</p><span id="more"></span><h2 id="产品"><a href="#产品" class="headerlink" title="产品"></a>产品</h2><ul><li>Proxmark 3:这是 Proxmark 3 的原始版本,现已过时,不建议购买,产品图如下:</li></ul><p><img src="https://proxmark.com/user/pages/03.proxmark-3-hardware/06.proxmark-3/PM3-Trans.png" alt="Proxmark 3"></p><ul><li>Proxmark 3 RDV 2:这是 Proxmark 3 的一次升级,又称为 Revision Two(修订第二版),产品图如下:</li></ul><p><img src="https://proxmark.com/user/pages/03.proxmark-3-hardware/04.proxmark-3-rdv-2/PM3-RDV2-Trans.png" alt="Proxmark 3 RDV 2"></p><ul><li>Proxmark 3 Easy:这是 Proxmark 3 RDV 2 的廉价版本,专门通过淘宝在中国销售,也是目前淘宝上主要的 Proxmark 3 版本,产品图如下:</li></ul><p><img src="https://proxmark.com/user/pages/03.proxmark-3-hardware/05.proxmark-3-easy/PM3-Easy-Trans.png" alt="Proxmark 3 Easy"></p><ul><li>Proxmark 3 EVO:这是 Proxmark 3 RDV 2 的一次进化,又称为 Evolution(进化),产品图如下:</li></ul><p><img src="https://proxmark.com/user/pages/03.proxmark-3-hardware/03.proxmark-3-evo/PM3-Evo-Trans.png" alt="Proxmark 3 EVO"></p><ul><li>Proxmark 3 RDV 4:这是 Proxmark 3 的最新升级版本,产品图如下:</li></ul><p><img src="https://proxmark.com/user/pages/03.proxmark-3-hardware/02.proxmark-3-rdv4/PM3-RDV4-Trans.png" alt="Proxmark 3 RDV 4"></p><h2 id="购买"><a href="#购买" class="headerlink" title="购买"></a>购买</h2><p>Proxmark 只有 2 个官方分销商:<a href="https://lab401.com/">Lab401</a> 和 <a href="https://hackerwarehouse.com/">Hacker Warehouse</a>,当然,你也可以从万能的淘宝上购买。</p><p>请注意,淘宝上的版本号并不规范,据我所知,淘宝上的大部分产品都是基于 Proxmark 3 Easy 的国内再次改造版,比如,它们所谓的<strong>“一体机”</strong>实际上是 Proxmark 3 Easy 和 <a href="https://github.com/iceman1001/ChameleonMini-rebooted">ChameleonMini(变色龙)</a>的合体版。</p>]]></content>
<summary type="html"><p>Proxmark 是 RFID 界的瑞士军刀,请注意,其官网为 <a href="http://www.proxmark.org/">proxmark.org</a>,其他诸如 <a href="https://proxmark.com/">proxmark.com</a> 的均为第三方商业网站,目前的最新硬件版本是 Proxmark 3。</p></summary>
<category term="CheatSheet" scheme="https://howiezhao.github.io/categories/CheatSheet/"/>
<category term="硬件" scheme="https://howiezhao.github.io/tags/%E7%A1%AC%E4%BB%B6/"/>
</entry>
<entry>
<title>Scheme的五法十诫</title>
<link href="https://howiezhao.github.io/2019/08/07/scheme-commandments-and-laws/"/>
<id>https://howiezhao.github.io/2019/08/07/scheme-commandments-and-laws/</id>
<published>2019-08-07T13:42:23.000Z</published>
<updated>2021-02-05T11:43:26.000Z</updated>
<content type="html"><![CDATA[<p>节选自《The Little Schemer》</p><h2 id="Scheme-五法"><a href="#Scheme-五法" class="headerlink" title="Scheme 五法"></a>Scheme 五法</h2><h3 id="Scheme-五法之第一法-——-car-之法则"><a href="#Scheme-五法之第一法-——-car-之法则" class="headerlink" title="Scheme 五法之第一法 —— car 之法则"></a>Scheme 五法之第一法 —— car 之法则</h3><p>基本元件 <em>car</em> 仅定义为针对非空列表。</p><h3 id="Scheme-五法之第二法-——-cdr-之法则"><a href="#Scheme-五法之第二法-——-cdr-之法则" class="headerlink" title="Scheme 五法之第二法 —— cdr 之法则"></a>Scheme 五法之第二法 —— cdr 之法则</h3><p>基本元件 <em>cdr</em> 仅定义为针对非空列表。任意非空列表的 <em>cdr</em> 总是另一个列表。</p><h3 id="Scheme-五法之第三法-——-cons-之法则"><a href="#Scheme-五法之第三法-——-cons-之法则" class="headerlink" title="Scheme 五法之第三法 —— cons 之法则"></a>Scheme 五法之第三法 —— cons 之法则</h3><p>基本元件 <em>cons</em> 需要两个参数。第二个参数必须是一个列表。结果是一个列表。</p><h3 id="Scheme-五法之第四法-——-Null-之法则"><a href="#Scheme-五法之第四法-——-Null-之法则" class="headerlink" title="Scheme 五法之第四法 —— Null? 之法则"></a>Scheme 五法之第四法 —— Null? 之法则</h3><p>基本元件 <em>null?</em> 仅定义为针对列表。</p><h3 id="Scheme-五法之第五法-——-eq-之法则"><a href="#Scheme-五法之第五法-——-eq-之法则" class="headerlink" title="Scheme 五法之第五法 —— eq? 之法则"></a>Scheme 五法之第五法 —— eq? 之法则</h3><p>基本元件 <em>eq?</em> 需要两个参数。每个参数都必须是一个非数字的原子。</p><span id="more"></span><h2 id="Scheme十诫"><a href="#Scheme十诫" class="headerlink" title="Scheme十诫"></a>Scheme十诫</h2><h3 id="第一诫"><a href="#第一诫" class="headerlink" title="第一诫"></a>第一诫</h3><p>当对一个原子列表 <em>lat</em> 进行递归调用时,询问两个有关 <em>lat</em> 的问题:*(null? lat)* 和 <strong>else</strong>。</p><p>当对一个数字 <em>n</em> 进行递归调用时,询问两个有关 <em>n</em> 的问题:*(zero? n)* 和 <strong>else</strong>。</p><p>当对一个S-表达式列表 <em>l</em> 进行递归调用时,询问三个有关 <em>l</em> 的问题:*(null? lat)<em>、</em>(atom? (car l))* 和 <strong>else</strong>。</p><h3 id="第二诫"><a href="#第二诫" class="headerlink" title="第二诫"></a>第二诫</h3><p>使用 <em>cons</em> 来构建列表。</p><h3 id="第三诫"><a href="#第三诫" class="headerlink" title="第三诫"></a>第三诫</h3><p>构建一个列表的时候,描述第一个典型元素,之后 <em>cons</em> 该元素到一般性递归(natural recursion)上。</p><h3 id="第四诫"><a href="#第四诫" class="headerlink" title="第四诫"></a>第四诫</h3><p>在递归时总是改变至少一个参数。当对一个原子列表 <em>lat</em> 进行递归调用时,使用 *(cdr lat)*。当对数字 <em>n</em> 进行递归调用时,使用 *(sub1 n)*。当对一个S-表达式 <em>l</em> 进行递归调用时,只要是 <em>(null? l)</em> 和 <em>(atom? (car l))</em> 都不为 true,那么就同时使用 <em>(car l)</em> 和 *(cdr l)*。</p><p>在递归时改变的参数,必须向着不断接近结束条件而改变。改变的参数必须在结束条件中得以测试:<br> 当使用 <em>cdr</em> 时,用 <em>null?</em> 测试是否结束;<br> 当使用 <em>sub1</em> 时,用 <em>zero?</em> 测试是否结束。</p><h3 id="第五诫"><a href="#第五诫" class="headerlink" title="第五诫"></a>第五诫</h3><p>当用 ➕ 构建一个值时,总是使用 0 作为结束代码行的值,因为加上 0 不会改变加法的值。</p><p>当用 ✖ 构建一个值时,总是使用 1 作为结柬代码行的值,因为乘以 1 不会改变乘法的值。</p><p>当用 <em>cons</em> 构建一个值时,总是考虑把 0 作为结束代码行的值。</p><h3 id="第六诫"><a href="#第六诫" class="headerlink" title="第六诫"></a>第六诫</h3><p>简化工作只在功能正确之后开展。</p><h3 id="第七诫"><a href="#第七诫" class="headerlink" title="第七诫"></a>第七诫</h3><p>对具有相同性质的 <em>subparts</em>(子部件)进行递归调用:</p><ul><li>列表的子列表。</li><li>算术表达式的子表达式。</li></ul><h3 id="第八诫"><a href="#第八诫" class="headerlink" title="第八诫"></a>第八诫</h3><p>使用辅助函数来抽象表示方式。</p><h3 id="第九诫"><a href="#第九诫" class="headerlink" title="第九诫"></a>第九诫</h3><p>用函数来抽象通用模式。</p><h3 id="第十诫"><a href="#第十诫" class="headerlink" title="第十诫"></a>第十诫</h3><p>构建函数,一次收集多个值。</p>]]></content>
<summary type="html"><p>节选自《The Little Schemer》</p>
<h2 id="Scheme-五法"><a href="#Scheme-五法" class="headerlink" title="Scheme 五法"></a>Scheme 五法</h2><h3 id="Scheme-五法之第一法-——-car-之法则"><a href="#Scheme-五法之第一法-——-car-之法则" class="headerlink" title="Scheme 五法之第一法 —— car 之法则"></a>Scheme 五法之第一法 —— car 之法则</h3><p>基本元件 <em>car</em> 仅定义为针对非空列表。</p>
<h3 id="Scheme-五法之第二法-——-cdr-之法则"><a href="#Scheme-五法之第二法-——-cdr-之法则" class="headerlink" title="Scheme 五法之第二法 —— cdr 之法则"></a>Scheme 五法之第二法 —— cdr 之法则</h3><p>基本元件 <em>cdr</em> 仅定义为针对非空列表。任意非空列表的 <em>cdr</em> 总是另一个列表。</p>
<h3 id="Scheme-五法之第三法-——-cons-之法则"><a href="#Scheme-五法之第三法-——-cons-之法则" class="headerlink" title="Scheme 五法之第三法 —— cons 之法则"></a>Scheme 五法之第三法 —— cons 之法则</h3><p>基本元件 <em>cons</em> 需要两个参数。第二个参数必须是一个列表。结果是一个列表。</p>
<h3 id="Scheme-五法之第四法-——-Null-之法则"><a href="#Scheme-五法之第四法-——-Null-之法则" class="headerlink" title="Scheme 五法之第四法 —— Null? 之法则"></a>Scheme 五法之第四法 —— Null? 之法则</h3><p>基本元件 <em>null?</em> 仅定义为针对列表。</p>
<h3 id="Scheme-五法之第五法-——-eq-之法则"><a href="#Scheme-五法之第五法-——-eq-之法则" class="headerlink" title="Scheme 五法之第五法 —— eq? 之法则"></a>Scheme 五法之第五法 —— eq? 之法则</h3><p>基本元件 <em>eq?</em> 需要两个参数。每个参数都必须是一个非数字的原子。</p></summary>
<category term="Notes" scheme="https://howiezhao.github.io/categories/Notes/"/>
<category term="函数式编程" scheme="https://howiezhao.github.io/tags/%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B/"/>
</entry>
</feed>