<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Logres</title><description>Q.E.D. and Amen</description><link>https://blog.logres.icu/</link><language>zh_CN</language><item><title>QED and Amen</title><link>https://blog.logres.icu/posts/qed-and-amen/</link><guid isPermaLink="true">https://blog.logres.icu/posts/qed-and-amen/</guid><description>How to use this blog template.</description><pubDate>Mon, 31 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;QED and AMEN&lt;/p&gt;
</content:encoded></item><item><title>数据库范式详解</title><link>https://blog.logres.icu/posts/%E6%95%B0%E6%8D%AE%E5%BA%93%E8%8C%83%E5%BC%8F%E8%AF%A6%E8%A7%A3/</link><guid isPermaLink="true">https://blog.logres.icu/posts/%E6%95%B0%E6%8D%AE%E5%BA%93%E8%8C%83%E5%BC%8F%E8%AF%A6%E8%A7%A3/</guid><description>数据库范式详解</description><pubDate>Thu, 10 Oct 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;It has been a long time since the last time I learning database related knowledge, but I do design database for my project often. As those project do contains some complex, I may Need to learn the DB NF again.&lt;/p&gt;
&lt;h2&gt;范式&lt;/h2&gt;
&lt;p&gt;范式是数据库设计的一个重要问题，无法绕开。通过范式，我们可以最大程度减少数据库表之间的冗余，实现更高效的存储、修改。&lt;/p&gt;
&lt;p&gt;对于范式，分为6个层级，第一项讨论单一表项的规范，2NF至BCNF讨论函数依赖，4NF与6NF讨论多值依赖，而5NF(PJNF)讨论关联依赖。在具体论述范式前，需要讨论清楚一些关键词：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;主键：一组可以决定整个条目的属性&lt;/li&gt;
&lt;li&gt;主属性：主键中的属性&lt;/li&gt;
&lt;li&gt;非主属性：主键外的属性&lt;/li&gt;
&lt;li&gt;函数依赖：属性A可以唯一决定B，那么B函数依赖于A&lt;/li&gt;
&lt;li&gt;多值依赖：一组属性A决定一组属性B的一组值，且可以决定一组属性C的一组值，但BC独立。&lt;/li&gt;
&lt;li&gt;连接依赖：即A决定B、C、D等等，但BCD互相无关。如果表可以无损拆分，就存在连接依赖。（多值依赖即二元连接依赖）&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;注：多值依赖、连接依赖似乎可以从R与A两个角度来讲，当R中存在A到B、C的多值依赖，R就存在多值依赖，但若仅存在A到B的多值依赖，R就不存在多值依赖？？？连接依赖则是完全作用在R上的，由于当连接依赖仅分解出两个子集R1 R2时表现为多值依赖，所以多值依赖是特殊的连接依赖。&lt;/p&gt;
&lt;h3&gt;1NF&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;列原子化&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;即数据库的表的列仅能填写原子值，而非组合数据&lt;/p&gt;
&lt;h3&gt;2NF&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;不要展开主键的部分主属性&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;要求非主属性不存在对主属性的部分依赖&lt;/p&gt;
&lt;h3&gt;3NF&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;不要展开非主属性&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;要求非主属性不存在对主属性的传递依赖&lt;/p&gt;
&lt;p&gt;不要太关注3NF，因为其被BCNF囊括。&lt;/p&gt;
&lt;h3&gt;BCNF&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;不要倒反天罡&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;主属性不要依赖于非主属性&lt;/p&gt;
&lt;h3&gt;4NF&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;A &lt;a href=&quot;https://en.wikipedia.org/wiki/Table_(database)&quot;&gt;table&lt;/a&gt; is in 4NF &lt;a href=&quot;https://en.wikipedia.org/wiki/If_and_only_if&quot;&gt;if and only if&lt;/a&gt;, for every one of its non-trivial multivalued dependencies &lt;em&gt;X&lt;/em&gt; ↠ &lt;em&gt;Y&lt;/em&gt;, &lt;em&gt;X&lt;/em&gt; is a &lt;a href=&quot;https://en.wikipedia.org/wiki/Superkey&quot;&gt;superkey&lt;/a&gt;—that is, &lt;em&gt;X&lt;/em&gt; is either a &lt;a href=&quot;https://en.wikipedia.org/wiki/Candidate_key&quot;&gt;candidate key&lt;/a&gt; or a superset thereof.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;不要组合排列&lt;/strong&gt;  &lt;em&gt;消除非平凡的多值依赖&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;存在多值依赖例子：
{ID 爱好}
{ID 喜欢的食物}
ID-&amp;gt;&amp;gt;爱好，ID-&amp;gt;&amp;gt;喜欢的事物，此处存在非平凡多值依赖(X + Y != PK)。所以分解为
ID + 爱好 唯一确定一个条目，ID+喜欢的食物 唯一确定一个条目，满足4NF。&lt;/p&gt;
&lt;p&gt;但无需过度关注4NF，因为5NF很好地囊括了4NF。&lt;/p&gt;
&lt;h3&gt;5NF&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;A &lt;a href=&quot;https://en.wikipedia.org/wiki/Table_(database)&quot;&gt;table&lt;/a&gt; is said to be in the 5NF &lt;a href=&quot;https://en.wikipedia.org/wiki/If_and_only_if&quot;&gt;if and only if&lt;/a&gt; every non-trivial &lt;a href=&quot;https://en.wikipedia.org/wiki/Join_dependency&quot;&gt;join dependency&lt;/a&gt; in that table is implied by the &lt;a href=&quot;https://en.wikipedia.org/wiki/Candidate_key&quot;&gt;candidate keys&lt;/a&gt;. It is the final normal form as far as removing redundancy is concerned.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;移除所有冗余&lt;/strong&gt; &lt;em&gt;所有非平凡连接依赖都由主键唯一决定&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;可以无损分解就是存在连接依赖？ What about &lt;strong&gt;six form&lt;/strong&gt;?&lt;/p&gt;
&lt;p&gt;存在连接依赖例子：
{学生ID 学生姓名 课程名 分数 }
学生ID并不唯一决定课程名&lt;/p&gt;
&lt;p&gt;所有涉及连接依赖的东西，都应该拆，将所有连接依赖分解到不同的表中。所有的子表都应该由相同的主键确定。&lt;/p&gt;
&lt;h3&gt;6NF&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;A &lt;a href=&quot;https://en.wikipedia.org/wiki/Relvar&quot;&gt;relvar&lt;/a&gt; R [table] is in &lt;strong&gt;sixth normal form&lt;/strong&gt; (abbreviated 6NF) if and only if it satisfies no nontrivial join dependencies at all — where, as before, a &lt;a href=&quot;https://en.wikipedia.org/wiki/Join_dependency&quot;&gt;join dependency&lt;/a&gt; is trivial if and only if at least one of the projections (possibly U_projections) involved is taken over the set of all attributes of the relvar [table] concerned.&lt;a href=&quot;5&quot;&gt;5&lt;/a&gt;(&lt;a href=&quot;https://en.wikipedia.org/wiki/Sixth_normal_form#cite_note-FOOTNOTEDateDarwenLorentzos2003176-5&quot;&gt;https://en.wikipedia.org/wiki/Sixth_normal_form#cite_note-FOOTNOTEDateDarwenLorentzos2003176-5&lt;/a&gt;)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;表不可约&lt;/strong&gt; &lt;em&gt;不存在任何非平凡的连接依赖&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;在5NF基础上，拆分所有可拆分的表，哪怕是{ID 姓名 年龄 地址}都要拆开成{ID 姓名} {ID 年龄} {ID 地址}（ID唯一决定姓名、年龄和地址）。6NF无助于减少冗余。&lt;/p&gt;
&lt;p&gt;但是当我们希望研究历史数据时，这样拆分在加入历史数据后就不会造成冗余。&lt;/p&gt;
&lt;h3&gt;一些思索&lt;/h3&gt;
&lt;p&gt;这些依赖似乎都可以拆解为一对一决定关系、一对多决定关系的组合。&lt;/p&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Join_dependency#Formal_definition&quot;&gt;Join dependency - Wikipedia&lt;/a&gt;
&lt;a href=&quot;https://en.wikipedia.org/wiki/Multivalued_dependency&quot;&gt;Multivalued dependency - Wikipedia&lt;/a&gt;
&lt;a href=&quot;https://en.wikipedia.org/wiki/Sixth_normal_form&quot;&gt;Sixth normal form - Wikipedia&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>信息论</title><link>https://blog.logres.icu/posts/%E4%BF%A1%E6%81%AF%E8%AE%BA/</link><guid isPermaLink="true">https://blog.logres.icu/posts/%E4%BF%A1%E6%81%AF%E8%AE%BA/</guid><pubDate>Sun, 01 Sep 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;信息是一个泛泛的概念，我们了解到的有关事实的所有内容都可以被视作信息，其降低了我们对事实的不确定性。同时，信息量似乎也是可以被定性比较的，&lt;strong&gt;9月11号&lt;/strong&gt;总是比&lt;strong&gt;9月某一天&lt;/strong&gt;来的更有价值。但是，当我们需要将其作为一门科学来看待，研究时，就需要定量地来阐释它了。&lt;/p&gt;
&lt;h2&gt;信息与随机变量与熵&lt;/h2&gt;
&lt;p&gt;信息可以降低我们对事实的不确定性，所以我们的讨论基础落到随机变量上。我们有一个随机变量$X$，它的取值范围是${x_1,x_2,\cdots,x_n}$，对应的概率分布是$P(X=x_i)=p_i$。我们定义$X$的信息量为：$$I(X=x_i)=-\log p_i$$
其中$log$底数为2，表征在最优编码（使用更短的编码编码更可能的情况）场景下，使用二进制编码这一情况所需要的比特数。我们可以看到，对于一个事件发生概率越大的事件，其信息量越小，这是符合直觉的。同时，我们可以看到，对于一个随机变量$X$，其信息量的期望值为：$$E[I(X)]=-\sum_{i=1}^n p_i\log p_i$$
这也正是熵的定义。我们定义随机变量$X$的熵为：$$H(X)=-\sum_{i=1}^n p_i\log p_i$$&lt;/p&gt;
&lt;p&gt;因此，熵的含义就可以被归纳为&lt;strong&gt;为了将不确定崩塌为确定的事实所需要的平均信息量（二进制bit的数量））&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;不确定事件中的纠葛&lt;/h2&gt;
&lt;h3&gt;条件熵&lt;/h3&gt;
&lt;p&gt;很多情况下，我们所面对的不确定之间是互相纠葛的，当其中一个不确定奔溃为确定的事实后，另一个不确定性也会随之减小。我们可以用条件熵来描述这种情况。对于X,Y两个随机变量，当确定Y的情况下，X的熵为：$$H(X|y)=\sum_{i=1}^n p_{(
x_i|y)}H(x_i|Y=y)$$
这里$p_{(x_i|y)}$表示在给定Y的情况下，X取值为$x_i$的概率。我们可以看到，条件熵描述了在给定Y的情况下，X的不确定性。我们可以将条件熵的期望值定义为：$$H(X|Y)=\sum_{i=1}^n p_{(y_i)}H(X|Y=y_i)$$
这就是&lt;strong&gt;条件熵，其定义了在考虑了在确定了Y的情况下，剩下的对于X的不确定性。&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;互信息&lt;/h3&gt;
&lt;p&gt;假若初始，对X的不确定性有$H(X)$，对Y的不确定性有$H(Y)$，当我们知道了Y的情况后，X的不确定性为$H(X|Y)$，我们可以定义互信息为：$$I(X;Y)=H(X)-H(X|Y)$$
即，当了解了Y的情况后，X的不确定性减少了多少。&lt;/p&gt;
&lt;p&gt;互信息有以下性质：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;$I(X;Y)=I(Y;X)$ 互信息是对称的。&lt;/li&gt;
&lt;li&gt;互信息的最小值为0，当X,Y独立时，互信息为0。这是因为在知道了Y的情况后，对X的不确定性没有减少。&lt;/li&gt;
&lt;li&gt;互信息的最大值为$min(H(X),H(Y))$，当X,Y完全相关时，互信息为$min(H(X),H(Y))$。这是因为，互信息能够减少的不确定性，一方面不能超过Y自己本来的不确定性，另一方面不能超过X本来的不确定性。（对于Y这部分，可以认为，当Y本身不具备多少不确定性时，其也无法对X的不确定性进行详细表征）&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;联合熵&lt;/h3&gt;
&lt;p&gt;在考虑到了X,Y之间的关联关系后，我们可以定义联合熵为：$$H(X,Y)=-\sum_{i=1}^n\sum_{j=1}^m p(x_i,y_j)\log p(x_i,y_j)$$
联合熵表示了(X,Y)这一联合分布的不确定性。&lt;/p&gt;
&lt;h3&gt;关联&lt;/h3&gt;
&lt;p&gt;从下图我们可以清晰地观察到上述几个概念之间的关联。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./assets/file-20240922134908422.png&quot; alt=&quot;file-20240922134908422&quot; /&gt;&lt;/p&gt;
&lt;p&gt;联合熵$H(X,Y)$是不确定的上限，$H(X)$与$H(Y)$都需要在已知对方的条件熵下进行修正，才能通过$$H(X,Y)=H(X)+H(X|Y)$$得到联合熵。而互信息则是在联合熵的基础上，减去了条件熵，得到了在已知Y的情况下，X的不确定性减少了多少。我们有$$I(X;Y)=H(X)-H(X|Y)$$，这也是互信息的定义。
综合二者，我们可以得到：$$I(X;Y)=H(X)+H(Y)-H(X,Y)$$&lt;/p&gt;
&lt;h2&gt;信息论与随机过程&lt;/h2&gt;
&lt;p&gt;To be continue……&lt;/p&gt;
&lt;h2&gt;Ref&lt;/h2&gt;
&lt;p&gt;[1] H. Pinkard和L. Waller, &lt;em&gt;A visual introduction to information theory&lt;/em&gt;. 2022. doi: &lt;a href=&quot;https://doi.org/10.48550/arXiv.2206.07867&quot;&gt;10.48550/arXiv.2206.07867&lt;/a&gt;.&lt;/p&gt;
</content:encoded></item><item><title>数学在自然科学中不可思议的有效性</title><link>https://blog.logres.icu/posts/%E6%95%B0%E5%AD%A6%E5%9C%A8%E8%87%AA%E7%84%B6%E7%A7%91%E5%AD%A6%E4%B8%AD%E4%B8%8D%E5%8F%AF%E6%80%9D%E8%AE%AE%E7%9A%84%E6%9C%89%E6%95%88%E6%80%A7/</link><guid isPermaLink="true">https://blog.logres.icu/posts/%E6%95%B0%E5%AD%A6%E5%9C%A8%E8%87%AA%E7%84%B6%E7%A7%91%E5%AD%A6%E4%B8%AD%E4%B8%8D%E5%8F%AF%E6%80%9D%E8%AE%AE%E7%9A%84%E6%9C%89%E6%95%88%E6%80%A7/</guid><pubDate>Sat, 01 Jun 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;The Unreasonable Efectiveness of Mathematics in the Natural Sciences&lt;/em&gt; 是1959年5月11日尤金·维格纳在纽约大学Courant数学科学讲座上的演讲主题。&lt;/p&gt;
&lt;p&gt;该演讲探讨了一个非常引人思考的现象——为何数学能够有效的对自然科学进行指导？&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;em&gt;&lt;strong&gt;数学在自然科学中广泛的有用性近乎神秘，现在还没有合理的解释。&lt;/strong&gt;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&lt;strong&gt;数学概念的这种不可思议的有用性，引出了我们的物理学理论是否具有唯一性的问题。&lt;/strong&gt;&lt;/em&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;关于数学有效性的论述&lt;/h2&gt;
&lt;p&gt;数学就是精巧地操作&lt;strong&gt;概念和规则&lt;/strong&gt;的科学，概念和规则就是为此目的而创造出来的。重点在于概念的创造；物理学则建立在&lt;strong&gt;不变性&lt;/strong&gt;与&lt;strong&gt;独立性&lt;/strong&gt;之上，自然规律都是有条件的陈述，它只涉及我们关于自然界知识的一小部分。&lt;/p&gt;
&lt;p&gt;数学在物理学中，不仅仅是量化计算的工具，&lt;strong&gt;自然规律必须已经用数学语言表达出来，成为应用数学研究的一个对象&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;数学概念并非由于其概念的简单性才被物理学所选中——甚至数对的序列也远不是简单的概念——而是它们服从于巧妙的运算操作，服从于惹人注目的、鲜明的论证。&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;物理理论的有效性与唯一性的探讨&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;没有恒定性规律，物理学理论就没有事实基础；如果认识论经验规律不正确，我们将缺乏情感所必须的鼓励和自信，“自然规律”就不可能被成功地探索出来。&lt;/strong&gt; 物理学正是建立在恒定的自然规律下，基于某种认知论的经验视角下的直觉，所探究出来的。&lt;/p&gt;
&lt;p&gt;使用数学工具，能够很好地描述、预测特定条件下的物理规律，因而可以使用数学对物理规律进行建模与表征。&lt;/p&gt;
&lt;p&gt;但是，与现实数据的一致性、精确性，并不一定包含着其&lt;strong&gt;真理性&lt;/strong&gt;与&lt;strong&gt;持续有效性&lt;/strong&gt;。在物理发展过程中，无数物理规律的数学模型被更大的图景证伪，但不影响其对特定情境下数据的一致性——如牛顿定律对宏观低速下的运动描述是正确的，在高速情境下、微观情境下，却败给相对论与量子力学，而这二者却又互相矛盾。&lt;/p&gt;
&lt;p&gt;我们使用理论拟合数据，但却无法知道理论为何有效。只能寄希望于其将继续有效下去。&lt;/p&gt;
</content:encoded></item><item><title>区块链共识算法</title><link>https://blog.logres.icu/posts/%E5%8C%BA%E5%9D%97%E9%93%BE%E7%9A%84%E5%85%B1%E8%AF%86%E7%AE%97%E6%B3%95/</link><guid isPermaLink="true">https://blog.logres.icu/posts/%E5%8C%BA%E5%9D%97%E9%93%BE%E7%9A%84%E5%85%B1%E8%AF%86%E7%AE%97%E6%B3%95/</guid><description>区块链共识算法</description><pubDate>Fri, 10 May 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;两条主要路线&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;基于证明：POW、POS、PoStorage and Po What Ever!&lt;/li&gt;
&lt;li&gt;基于Committee，PBFT&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;区块链的网络模型分为&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;同步网络：有时间上界&lt;/li&gt;
&lt;li&gt;部分同步网络：有时间上界，但不知道&lt;/li&gt;
&lt;li&gt;异步网络：无上界&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;概览&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;基于证明：通过提供某种证明证明自己是leader，然后添加新的区块；添加的区块在被追加多个区块后达到确认状态&lt;/li&gt;
&lt;li&gt;基于Committee：通过投票决定下一轮的leader&lt;/li&gt;
&lt;li&gt;改进方案：&lt;/li&gt;
&lt;li&gt;安全性&lt;/li&gt;
&lt;li&gt;规模&lt;/li&gt;
&lt;li&gt;去中心化&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;POW&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;assets/Pasted%20image%2020240517184314.png&quot; alt=&quot;Pasted image 20240517184314&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;中本聪协议&lt;/h3&gt;
&lt;p&gt;略&lt;/p&gt;
&lt;h3&gt;改进方案&lt;/h3&gt;
&lt;h4&gt;Decoupling Blockchain Functions&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;assets/Pasted%20image%2020240517184336.png&quot; alt=&quot;Pasted image 20240517184336&quot; /&gt;&lt;/p&gt;
&lt;p&gt;通过分割中本聪协议中，证明leadership、打包交易块、通过追加区块进行确认，这样多合一方案造成的低效，实现性能的提升。&lt;/p&gt;
&lt;p&gt;Bitcoin-NG：分离证明leadership区块与交易区块，在一个时间区间内，先挖掘证明区块，然后多次打包交易区块，提高性能，但引发别的问题——自私挖矿攻击、双花攻击等等。&lt;/p&gt;
&lt;p&gt;Prism：分离 交易链、核心链以及确认链条，核心链中的区块指向多个交易区块，在核心链上进行POW工作，Voter链上则对Proposal进行确认。（这位更是重量级，值得细细品味）&lt;/p&gt;
&lt;p&gt;NC-Max：分离交易同步与区块的确认，使用紧致区块机制来加快区块的验证。&lt;/p&gt;
&lt;h4&gt;平行链&lt;/h4&gt;
&lt;p&gt;OHIE：同时展开k条链，每个区块包含所有链最后的区块的哈希，新的区块被计算出来后根据哈希的值按照某种算法被指定到某条链上。&lt;/p&gt;
&lt;p&gt;ChainWeb &amp;amp; CliqueChain：每个区块的头部包含其他链的默克尔根的哈希。但要达到一样的一致性，其性能与中本聪协议无异。&lt;/p&gt;
&lt;p&gt;Monoxide：考虑到将工作量分割开，避免无意义的工作，同时运行一次解谜提交多个区块，避免攻击者针对算力薄弱的子链。&lt;/p&gt;
&lt;p&gt;Wang的研究，采用核心链的难度来动态调整其他子链。~~~&lt;/p&gt;
&lt;h4&gt;DAG 区块链&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;assets/Pasted%20image%2020240517184350.png&quot; alt=&quot;Pasted image 20240517184350&quot; /&gt;&lt;/p&gt;
&lt;p&gt;GHOST Greedy Heaviest-observed Sub-tree 协议，不选择最长链，而选择最大权重链，考虑到一个子链的“叔块”的作用，以太坊实现了一种加强版的GHOST协议。&lt;/p&gt;
&lt;p&gt;在DAG中，全局序列化交易是一个复杂的工作，平行链虽然也有多个链，但总是按照相同的epoch行动，epoch内可以按照链的序号进行排序，但DAG中不存在这样的编号。&lt;/p&gt;
&lt;p&gt;Spectre：一个子块可以有多个父块，节点使用其所知的所有末尾块作为前序块。&lt;/p&gt;
&lt;p&gt;Phatom：将全局交易序列化问题转化为一个Maximum K-Cluster SubDAG 问题，NP难，其使用贪心算法解决，面对活性攻击存在隐患。&lt;/p&gt;
&lt;p&gt;Conflux：引入Parent Edge与Reference Edge，以及GHOST原则，区分主链，并将DAG切分为多个Epoch，实现全局序列化。其还引入妥协策略用于抵御活性攻击与保持高交易量与快速取人，并在一定的权重机制下进行切换。&lt;/p&gt;
&lt;p&gt;Occam：考虑Transaction的需求，动态调整难度。&lt;/p&gt;
&lt;h3&gt;区中心化的改进方案&lt;/h3&gt;
&lt;p&gt;矿池加重了区块链网络的中心化，引入自私挖矿攻击等问题。一般有两种解决思路：1. 避免节点形成矿池 2. 鼓励去中心化节点加入&lt;/p&gt;
&lt;h4&gt;避免矿池&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;设计算法使得矿池面面临收益被Worker窃取的风险&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;鼓励去中心化&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;混合工作量证明，对低算力设备更友好&lt;/li&gt;
&lt;li&gt;采用高内存占用的算法，避免ASIC矿机的竞争——以太坊ETHash&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;安全性改进方案&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;FruitChain：分离出Fruit与Block两个概念，强调Fruit对实时性的要求，使得挖矿者不得不及时提交区块，避免自私挖矿攻击。 ？？？&lt;/li&gt;
&lt;li&gt;Bobtail：当最低的7个挖出来的随机数的均值低于某个阈值，即可提交新区块，稳定了块生成时间。&lt;/li&gt;
&lt;li&gt;StrongChain：在采用正确答案时也考虑弱答案，可以进一步强调正确Branch上的算力水平，使得攻击者进行分叉攻击更为艰难。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;POS&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;assets/Pasted%20image%2020240517184406.png&quot; alt=&quot;Pasted image 20240517184406&quot; /&gt;&lt;/p&gt;
&lt;p&gt;PoS有两种路子：基于链的PoS与基于BFT0Based PoS&lt;/p&gt;
&lt;h3&gt;Chain-based PoS&lt;/h3&gt;
&lt;p&gt;使用某种基于链的算法，能够决定下一个挖矿人是不是自己。&lt;/p&gt;
&lt;p&gt;Peercoin：与比特币类似，PoW进行挖矿；但在验证方面，稍有不同，其提出Coin Age概念，持有人具有的Coin 与 持有时间的乘积，在进行质押时将成为权重。验证者将获得自己资产1%的奖励。&lt;/p&gt;
&lt;p&gt;Nxt：第一个完全基于PoS的加密货币，一个节点持有的货币越多，越有可能有资格挖下一个矿。&lt;/p&gt;
&lt;p&gt;SnowWhite：限制了加密货币的流动能力，抵御双花。每一轮基于上一个提交的区块进行哈希计算，选出一个Committee，然后再哈希计算从中选出leader，提交新块。&lt;/p&gt;
&lt;p&gt;Ouroboros：PoS中，不可预测与不可操纵的领导人选择极为关键，一般使用伪随机算法基于现有区块链状态选出leader；但Ouroboros则不同，每一轮持币者需要执行一个多方掷硬币算法，选出领导人。&lt;/p&gt;
&lt;p&gt;Ouroboros Praos：Ouroboros的改进版本，使用Verifiable Random Function进行领导人选举，更具安全性。&lt;/p&gt;
&lt;p&gt;Ouroboros Genesis：希望达成full dynamic availabilituy，即只要诚实节点的算力占大多数，就能不在意任何节点上下线。使用本地的chain selection 规则而非全局。允许节点加入，仅仅根据创世区块，而无需检查点。&lt;/p&gt;
&lt;p&gt;Ouroboros crypsinous：结合Zerocash与Ouroboros系列，保护隐私。但研究证明，只要敌对方能操控网络延迟，可用性与隐私不可兼得。&lt;/p&gt;
&lt;p&gt;Pos 侧链：为了改进Ouroboros系列的互操作性、规模性与可升级性，引入侧链，使用双向铆钉，能够实现安全的跨链价值传输。&lt;/p&gt;
&lt;p&gt;PoSAT：使用Verifiable Delay Function，避免额外的安全假设。为了生成新区块，节点需要迭代地计算先驱节点与公钥的VDF，直到发现一个抵御阈值的值。攻击者更难分叉链。&lt;/p&gt;
&lt;h3&gt;BFT-based Pos&lt;/h3&gt;
&lt;p&gt;BFT-based需要先选出几个验证者与一个leader组成委员会。一般来说质押，然后基于密码学的选举，leader提出新区块，委员会实现BFT算法，决定新区块是否有效——经过投票。&lt;/p&gt;
&lt;p&gt;Capser：以太坊的叠加层，用于规范以太坊的正规链条——指明主链，其基于投票生成检查点。&lt;/p&gt;
&lt;p&gt;Tendermint：质押，使用两阶段提交，提交者提出区块，等待超过三分之二的投票，再进入下一个阶段，同时采用了锁定机制，避免一个节点同时发起投票，引发分叉。&lt;/p&gt;
&lt;p&gt;Algorand：使用VRF选择leader与验证者。VRF是一个非交互算法，节点本地运行，获取运行结果。leader与validator实现Byzantine agreement算法，BA&lt;em&gt;来达成共识。BA&lt;/em&gt; 运行多轮，不停得选出validator进行验证与投票，一轮又一轮，直到达成共识。&lt;/p&gt;
&lt;p&gt;LaKSA：采用密码学采样在质押列表上采样，质押数越多采样概率越大。偏好可用性，节点将自己决定是否采用某个区块——其中记录了其得到的投票数量。&lt;/p&gt;
&lt;h2&gt;PoStorage And POX-BASED&lt;/h2&gt;
&lt;h3&gt;PoStorage&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;assets/Pasted%20image%2020240517184426.png&quot; alt=&quot;Pasted image 20240517184426&quot; /&gt;&lt;/p&gt;
&lt;p&gt;存储服务器通过证明文件的完整性。&lt;/p&gt;
&lt;h4&gt;Proof of Replication and Proof of Spacetime&lt;/h4&gt;
&lt;p&gt;Filecoin：Proof of Replication 表征文件被接受并被存储在良好的物理设备上；而Proof of Spacetime表征数据被存储了一个特定长度的时间段。&lt;/p&gt;
&lt;p&gt;PoRep：PoRep是一个挑战/回答协议。挖矿者根据自己的公钥存储一个伪随机的排列，并给用户返回一些信息。用户根据这些信息生成挑战，挖矿者根据数据进行零知识证明。（并不存储有价值信息） Filecoin递归计算PoRep，每一轮的输出作为下一轮输入，从而实现proof of Spacetime。&lt;/p&gt;
&lt;h4&gt;Proof of Space&lt;/h4&gt;
&lt;p&gt;SpaceMint：使用pebbling游戏，要求用户投入大量磁盘空间，磁盘空间越大，proof的质量越高，最高者成为新leader。&lt;/p&gt;
&lt;p&gt;Chia：也采用PoSpace，并采用了VDF。&lt;/p&gt;
&lt;h4&gt;Proof of Retrievability&lt;/h4&gt;
&lt;p&gt;Permacoin：在数据中插入纠错码以及随机数，作为哨兵。通过质询哨兵证明数据可取回。&lt;/p&gt;
&lt;h3&gt;Proof of X&lt;/h3&gt;
&lt;p&gt;Proof of Whatever&lt;/p&gt;
&lt;h4&gt;Proof of Elapsed Time&lt;/h4&gt;
&lt;p&gt;Hyperledger Sawtooth：&lt;strong&gt;Trusted Execution Environment&lt;/strong&gt; 。即证明执行环境，每个节点从自己的围地中生成等待时间，最短者提出新块，拥有越多处理器越能够被选中。但容易被攻击，所以又提出了PoETA。&lt;/p&gt;
&lt;h4&gt;Proof of Meaningful Work&lt;/h4&gt;
&lt;p&gt;Primecoin：将puzzle替换为寻找巨大的素数。&lt;/p&gt;
&lt;p&gt;Proof of eXercise：求解基于矩阵的计算问题，用于图像识别与数据挖掘。&lt;/p&gt;
&lt;h2&gt;COMMITTEE-BASED CONSENSUS PROTOCAL&lt;/h2&gt;
&lt;p&gt;主题思想在于通过投票达到对某个提案的共识，且一旦达成，这个提案的区块将不会被代替。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;assets/Pasted%20image%2020240517221143.png&quot; alt=&quot;Pasted image 20240517221143&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Permissioned&lt;/h3&gt;
&lt;p&gt;Raft与Paxos是基于有限的且已知的参与者的情况下的，是CFT共识协议，不支持拜占庭容错。BFT算法本来是用于确保无损节点能够对客户端产生的指令顺序达成一致，但其也很适合用于构建Permissioned区块链。&lt;/p&gt;
&lt;h4&gt;PBFT&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;assets/Pasted%20image%2020240517221300.png&quot; alt=&quot;Pasted image 20240517221300&quot; /&gt;&lt;/p&gt;
&lt;p&gt;PBFT的一致性不依赖于网络同步，但活性依赖于网络同步（CAP倾向于一致性与分区容错性）&lt;/p&gt;
&lt;p&gt;PBFT执行Primary&amp;amp;Backup理论，即某个节点是Primary(leader)，而其他节点是Backup(validator)。其流程如图3：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Request：客户端发送请求——&lt;strong&gt;客户端消息&lt;/strong&gt;(操作、时间戳、身份)&lt;/li&gt;
&lt;li&gt;Pre-prepare：leader对view number、sequence number与请求摘要签名，并广播&lt;strong&gt;Pre-prepare消息&lt;/strong&gt;(客户端请求与签名）&lt;/li&gt;
&lt;li&gt;Prepare Phase：validator检查签名、view number与sequence number，如果全部正确，对Pre-parepare消息、view number 与sequence number与自己的身份签名，然后将其作为&lt;strong&gt;Prepare消息&lt;/strong&gt;，本地保存一份，并广播到其他节点。&lt;/li&gt;
&lt;li&gt;Commit Phase：如果验证者（与leader）收到2f份Prepare消息，匹配其自己的Pre-prepare消息，就进行Commit Phase。leader与验证者将Commit Message(view number、sequence number 与身份 的签名)进行广播，如果收到多余2f+1份Commit Message，就认为Commit Phase完成，执行指令，并将结果返回客户端。&lt;/li&gt;
&lt;li&gt;Replay phase：如果客户端收到多余f+1份reply message 且结果相同，就认为达成共识了。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;PBFT能够在含有3f+1个节点的情境下保证拜占庭容错，但无法处理恶意的leader。其引入了一个view 更替机制，validator带有一个计时器，当等待时间达到一定程度，就发起view change 消息，如果下一个view的领导人受到了2f+1份view change 消息，就广播 new-view消息，转为正常模式。&lt;/p&gt;
&lt;p&gt;Byzcoin：在PBFT上引入collective signature 协议改进通信消耗，以及一个POW链，用于处理Permissionless模式。&lt;/p&gt;
&lt;p&gt;许多区块链项目引入分片与PBFT达成大规模网络，Tendermint通过投票达成最终一致性。&lt;/p&gt;
&lt;h4&gt;HoneyBadgerBFT&lt;/h4&gt;
&lt;p&gt;PBFT依赖于同步或部分同步网络模型，但FPL的存在，要求设计者作出妥协。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;FPL impossibility指出，在消息延迟但不会丢失的网络中，如果至少一个节点失败、停止，则不存在保证在所有启动条件下都能终止的共识算法。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;HoneyBadgerBFT基于完全异步的环境，其活性不依赖于对消息延迟上界的假设。所有节点采用Asyncronous Common Subset(ACS)来发送消息。如果N个节点都发出一个值，那么ACS保证每个节点发出一个向量，含有至少N-2f个正确输入值。然而ACS导致缓慢，所以节点随机选择交易，广播最无交集的交易集。……&lt;/p&gt;
&lt;p&gt;其步骤：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;每一轮，每个参与节点随机选择一批交易，发送前进行Threshold加密。&lt;/li&gt;
&lt;li&gt;在第二阶段，节点使用ACS广播交易。ACS包括Reliable Broadcast(RBC)阶段与Asynchronous Binary Byzantine Agreement(ABA)阶段。交易通过RBC广播，然后ABA生成一个bit向量，指示哪些交易发送成功。一致合集通过ABA生成。&lt;/li&gt;
&lt;li&gt;在第三阶段，每个节点解密他的部分并广播之。交易集可以在接收到至少f+1个解密消息后解码出来。最后，这些交易将形成新区块。&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;Improvements of Asynchronous BFT Consensus Protocol&lt;/h4&gt;
&lt;p&gt;BEAT：改进了HoneyBadgerBFT的效率，利用了更高效的threshold 投硬币算法，代替原来的threshold 加密算法，包含五套不同的方案可以适时使用。&lt;/p&gt;
&lt;p&gt;DispersedLedger：将共识算法解耦为数据可用性约定与基于HoneyBadgerBFT的区块检索。高带宽节点可以不用等待慢速节点。&lt;/p&gt;
&lt;p&gt;Dumbo：涵盖两个原子广播协议Dumbo1与Dumbo2。HoneyBadger要求每个节点运行N个ABA实例，非常慢，但采用Dumbo1减少ABA实例数量，Dumbo2 采用multi-valued validated Byzantine agreement(MVBA)来优化ACS。Dumbo-NG与Speeding Dumbo也朝着改进效率前进。&lt;/p&gt;
&lt;h4&gt;Other Improvements of Committee-based BFT Consensus Protocol&lt;/h4&gt;
&lt;p&gt;BFT要求多轮交互实现一致。&lt;/p&gt;
&lt;p&gt;PILI与PALA采用流水线BFT协议，基于同步(PILI)/部分同步(PALA)网络假设。&lt;/p&gt;
&lt;p&gt;HotStuff 目标在于简化领导人替换的复杂度，基于部分同步网络假设。其添加了一个decide phase在commit phase后，新领导人能够选择最高法定人数证书来趋向一致。而基于同步网络假设的Sync HotStuff不再要求所有节点在同一时刻开启、结束一轮。&lt;/p&gt;
&lt;p&gt;Flexibale BFT：Committee-based的BFT协议要求预设网络环境、Byzantine节点比例，一旦变换，活性与一致性就难以保障。Flexible Byzatine协议结构BFT协议，并设计Flexibale Byzantine Quorums。其运行一个账本容纳具有不同假设的节点，以及不同比例的拜占庭节点。其通过客户端选择不同的commit规则实现。&lt;/p&gt;
&lt;p&gt;Momose：提出了一种多thresholdBFT协议，适配多种网络环境假设，且仅有一个提交规则，其安全性可以忍受同步网络情况下2/3的节点故障，活性可以忍受同步网络条件下1/3的节点故障以；异步与部分同步下则可以忍受1/3的节点故障。&lt;/p&gt;
&lt;p&gt;Pompe：Leader节点可以通过操纵交易的顺序实现牟利，而Validator仅能验证交易的正确性，却无法验证交易顺序。Pompe解耦了交易顺序与添加记录，通过将交易添加顺序标识，leader无法再控制交易顺序。&lt;/p&gt;
&lt;p&gt;Aequitas：transaction order-fairness 成为safety与liveness的又一属性。Aequitas中，交易基于FIFO-broadcast规则进行广播，节点通过Byzantine Agreement(Set-BA)对交易顺序达成一致。交易的最终顺序可以被基于leader的模型与无leader模型验证。&lt;/p&gt;
&lt;p&gt;BFT取证，基于最大拜占庭节点数量、证明有罪的诚实节点的交易数量以及在一致性被违背的情况下可以被定为有罪的拜占庭节点数量。&lt;/p&gt;
&lt;h3&gt;Permissionless&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;assets/Pasted%20image%2020240518005659.png&quot; alt=&quot;Pasted image 20240518005659&quot; /&gt;&lt;/p&gt;
&lt;p&gt;由于传统的基于委员会的BFT协议在无限制的网络环境下无法适应：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;需要预先固定的参与数量来保证liveness与safety&lt;/li&gt;
&lt;li&gt;没有身份控制机制，会遭遇女巫攻击&lt;/li&gt;
&lt;li&gt;需要多轮交互，网络数量大的时候通信开销很大&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;方案1：先使用基于证明的算法从环境中选出节点组成committee，然后执行PBFT算法
方案2：使用分片，将节点分配到不同sharding中。&lt;/p&gt;
&lt;p&gt;挑战：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;需要合理的分配方法，将节点公平随机地分配到不同分片&lt;/li&gt;
&lt;li&gt;需要避免跨链交易，否则将带来高开销与安全隐患&lt;/li&gt;
&lt;li&gt;分片内算法需要安全与高效&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Elastico是第一个采用分片的，其要求节点进行PoW避免女巫攻击，然后按照哈希计算结果分配到对应分片中。哈希结果最低的组成final committee，其他节点组成各自committee，交换身份，实现PBFT；而final committee负责验证其他committee的交易。&lt;/p&gt;
&lt;p&gt;OmniLedger指出Elastico不安全，因为攻击者可以选择性公布哈希结果，从而进入同一个committee，而且final committee 很容易成为性能瓶颈。OmniLedger采用VRF-based领导人选举，与无偏的随机算法RandHound来讲validator分配到不同分片，且设计了Byzantine Shard Atomic Commit 协议（上锁开锁）。首先，用户需要从输入的交易所属的shard的leader处取得proof-of-acceptance，然后输入交易将被上锁。然后，用户&lt;strong&gt;流言&lt;/strong&gt;一份unlock-to-commit交易，其包含所有所有proofs与上锁的交易，负责用户将会流言一份unlock-to-abort交易终端跨链交易流程。&lt;/p&gt;
&lt;p&gt;这个过程需要用户自己做很多事情，不利于轻量级客户端的实现；齐次，validator需要对每个用户的证明签名，造成非常高的通信与计算开销。&lt;/p&gt;
&lt;p&gt;Rapid Chain：对整个网络的流言造成极大延迟，RapidChain实现了一种新型的流言协议与一个内部委员会，极大降低了延迟。遗憾的是，Rapidchain无法实现跨链交易的原子化与隔离。Dang通过使用two-phase locking与two phase commit实现了跨链交易的原子化与隔离。&lt;/p&gt;
&lt;p&gt;跨链交易是分片区块链面对的一个主要挑战。OptChain与BrokerChain尝试通过合理分配transaction到shard中。OptChain通过学习过去transaction模式，但仅适用于UTXO模型；BrokerChain应用于Model模型，但依赖于Broker，且不支持多输入多输出交易，因此非常慢。Pyramid尝试构建层次分片，某一个分片存储了其他几个分片的数据来实现，但失败了。&lt;/p&gt;
&lt;h3&gt;Other Miscellaneous Protocol&lt;/h3&gt;
&lt;h4&gt;Tx-based Chain&lt;/h4&gt;
&lt;p&gt;Tx-based Chain基于trasaction而非block。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;assets/Pasted%20image%2020240518014133.png&quot; alt=&quot;Pasted image 20240518014133&quot; /&gt;&lt;/p&gt;
&lt;p&gt;IOTA中的Tangle就是一个DAG区块链，其直接关联交易，略过区块。交易的添加不需要挖矿与费用。添加交易时，一个节点需要验证两个已有交易，然后关联自己的新交易上去，者使得其没有严格的confirm时间，越多后续交易关联到前序交易，前序交易可信度越高。Byteball是一个类似的项目。&lt;/p&gt;
&lt;h4&gt;Hashgraph&lt;/h4&gt;
&lt;p&gt;Swirlds hashgraph 一致算法是一个完全基于异步网络模型的BFT协议，采用DAG结构。其独特地依赖于gossip about gossip 以及 virtual vote 来实现一致。Hashgraph采用event结构而非block，每个event含有两个指向父event的指针，一个是其自己的最后一个event，另一个则是从别的节点接收到的event。节点对他们接收到的event签名，然后通过gossip about gossip 转给别人。在此过程中，节点可以更新其自己的hashgraph。如果所有节点具有一样的event，那么那个event的祖先以及对应的边也将存在。event的传递代表着节点对那个event的投票，故可以从hashgraph计算出总的票数，实现virtual vote。 这是完全异步以及节省带宽的。&lt;/p&gt;
&lt;h4&gt;Hyperledger Fabric&lt;/h4&gt;
&lt;p&gt;大多数BFT共识协议基于排序-指向模型，然而Fabric采用指向-排序-验证模型。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;执行阶段：客户端发送交易提案到特定节点——endorsers。endorsers模拟执行，并给出结果（writeset 与 readset），然后生成endorsement消息到客户端，包含其对执行结果的签名与其他信息。&lt;/li&gt;
&lt;li&gt;排序阶段：交易被送至排序服务节点，如果客户端收集到了足够的endorsement，排序服务讲打包交易，并生成特定的区块序列，并广播到peer节点，通过gossip协议。&lt;/li&gt;
&lt;li&gt;验证阶段：peer节点验证endorsement，如果无效，transaction将被丢弃，否则，进行读写检查，最后，更新阶段，讲区块添加到本地区块链上。&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;Delegated Proof of Stake(DPoS)&lt;/h4&gt;
&lt;p&gt;DPoS不随机选择leader与validator，而是通过投票决定谁来生成下一个区块。&lt;/p&gt;
&lt;p&gt;Bitshars：持币人通过投票选择节点作为 目击者。 目击者负责生成、验证与广播区块。目击者需要质押，并可以在成功生成区块后获得奖励。&lt;/p&gt;
&lt;p&gt;EOS：得到足够投票的节点可以成为区块生产者，每一轮21个节点被选举，合作生成新区块。EOS融合了asynchrounous Byzantine Fault Tolerance 技术来达到快速交易确认。&lt;/p&gt;
&lt;p&gt;在DPoS中，节点间需要建立信任关系，降低了去中心化程度。&lt;/p&gt;
&lt;h4&gt;Non-anonymous Proof-based Protocols&lt;/h4&gt;
&lt;p&gt;Ethereum Proof-of-Authority就是为企业准备的permissioned区块链。每个节点都需要一个公开的身份，所有区块被其生产者签名。节点生成有意义区块的收益与坏行为的惩罚都是透明的，其中添加新成员、删除节点、选择管理员与验证者都通过投票进行。&lt;/p&gt;
&lt;p&gt;GoChain 采用proof of repuation。只有具备一定公信力的节点可以作为认证节点，只有认证节点可以生成、签署新区块以及验证新区块。&lt;/p&gt;
&lt;h4&gt;Redactable Blockchain&lt;/h4&gt;
&lt;p&gt;可编辑区块链用于&lt;/p&gt;
&lt;p&gt;部分研究采用变色龙哈希算法代替传统的单向算法，变色龙哈希算法具备陷门，如果没有钥匙，变色龙哈希算法无碰撞，而如果掌握钥匙，就能简易地生成一样哈希的区块，从而修改内容。&lt;/p&gt;
&lt;p&gt;另一部分研究则采用投票实现可逆。&lt;/p&gt;
&lt;h2&gt;比较&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;assets/Pasted%20image%2020240518163510.png&quot; alt=&quot;Pasted image 20240518163510&quot; /&gt;&lt;/p&gt;
&lt;p&gt;基于证明的方法与基于委员会的方法。
&lt;img src=&quot;assets/Pasted%20image%2020240518154245.png&quot; alt=&quot;Pasted image 20240518154245&quot; /&gt;&lt;/p&gt;
&lt;p&gt;前者基于中本聪的共识协议，允许任意节点在任意时刻加入协议，除了一些特殊的设计——proof-of-authority 与proof-of-reputation。优势在于无主权完全去重心化，有助于达到强匿名性。然而这往往导致低吞吐量与更长的确认延迟。同时，基于证明的协议也进能够达成概率一致性，无法保证达成100%参与者的一致。每一轮都是概率的，但多轮的执行可以达成更高的确信度，这又进一步延长了确认延迟。&lt;/p&gt;
&lt;p&gt;基于委员会的协议则从传统的分布式一致性算法，如PFBT演进而来。一个固定数量的参与方组成委员会，达成一致。参与方的数量是固定的，身份是明确的，匿名是不可能的。通常来说，能够保证结果得到多数同意，得到确定的一致性。一旦一个交易被添加到区块链上，他就被确认了。相比于基于证明的方法，具有更高吞吐量与更低的确认延迟。&lt;/p&gt;
&lt;p&gt;为了达成去中心化与高吞吐的统一，出现了三条演进路线：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;保持基于证明的领导人选举，但采用新的结构来实现并发，如DAG、平行链。&lt;/li&gt;
&lt;li&gt;扩展基于委员会的协议来允许节点加入，如分片。&lt;/li&gt;
&lt;li&gt;混合模型，基于BFT的PoS模型，节点的加入无需验证，但需要证明持有足够的stake。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;PoW与PoS是两种主要的基于证明的机制，PoS不需要大量计算，而通过持有加密货币来参与共识协议，但相比于PoW丧失了部分去中心性质。
&lt;img src=&quot;assets/Pasted%20image%2020240518163521.png&quot; alt=&quot;Pasted image 20240518163521&quot; /&gt;&lt;/p&gt;
&lt;p&gt;基于对网络模型的假设，基于委员会的共识协议可以分为三类：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;同步：允许n/2的拜占庭节点&lt;/li&gt;
&lt;li&gt;部分同步：n/3的拜占庭节点&lt;/li&gt;
&lt;li&gt;异步：n/3的拜占庭节点
同时，异步网络将无法达成决定性的协议，因为FLP impossibility的存在。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;POTENTIAL FUTURE RESAERCH&lt;/h2&gt;
&lt;h3&gt;Efficient Transactions Processing&lt;/h3&gt;
&lt;p&gt;最小化冲突、重复的交易，同时允许并发区块，避免并发区块中冲突、重复交易对效率的损害。&lt;/p&gt;
&lt;p&gt;提高跨片交易的处理。想办法避免跨片交易的发生。&lt;/p&gt;
&lt;h3&gt;Performance Improvement of Committee-based BFT Protocols&lt;/h3&gt;
&lt;p&gt;采用阈值签名、集体签名，避免all to all gossip。&lt;/p&gt;
&lt;p&gt;将Committee方案移植到permissionless环境，非Committe组成节点的资源将被浪费，可以考虑将其应用到解耦功能与异步处理上。&lt;/p&gt;
&lt;h3&gt;Cross-chain Interoperability&lt;/h3&gt;
&lt;p&gt;区块链在未来连成一体是大趋势。互操作性将是一个重要的指标。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;侧链：侧脸允许多个区块链通过two-way pag实现跨链操作。一个区块链可以成为另一个区块链的侧脸。数据与资产可以安全高效地在不同区块链间交换。&lt;/li&gt;
&lt;li&gt;异构实现多个分片的互操作性。比如，以太坊中除了多方分片链，存在一个信标链，用于童鞋不同的分片中的链。&lt;/li&gt;
&lt;/ol&gt;
</content:encoded></item><item><title>浅谈面向对象</title><link>https://blog.logres.icu/posts/%E6%B5%85%E8%B0%88%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1/</link><guid isPermaLink="true">https://blog.logres.icu/posts/%E6%B5%85%E8%B0%88%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1/</guid><description>多年的编程后，终于明白了面向对象的含义。</description><pubDate>Mon, 07 Aug 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;一提及面向对象，“封装、继承、多态”立刻就浮现出来。在学习OO时，我们总是不假思索地接受了这些概念，却没能更进一步地思考，什么才是面向对象。&lt;/p&gt;
&lt;h2&gt;Motivation&lt;/h2&gt;
&lt;p&gt;首先要明确的是，编程范式，到底是人们对程序进行建模抽象的手段，哪怕不依赖面向对象、函数式编程，单单依靠计算机最基础的过程式——也即图灵机模型，也可以解决大多数问题。但正如我们不可能通过二进制的形式来直接书写文字、编辑音乐一样，我们需要一些更符合思维逻辑的方式来编写程序，否则当程序规模超过人们掌控范畴时，将难以理解与维护。&lt;/p&gt;
&lt;h2&gt;What&lt;/h2&gt;
&lt;p&gt;函数式编程将编程逻辑变为自顶向下的函数拆分，通过数学的逻辑演算与不变性来保障了程序的正确性；而面向对象则是从更为通俗的分工合作来构建程序的：我们有许多&lt;strong&gt;实体&lt;/strong&gt;，其间通过&lt;strong&gt;消息传递&lt;/strong&gt;来实现交互，进而构建出一个大型程序。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;实体&lt;/strong&gt;的概念使我们不需要完全了解其内部实现——其最内部往往还是需要按照面向过程来构建，即指令序列，保证了程序模块/部分的可替换性。而&lt;strong&gt;消息传递&lt;/strong&gt;的概念则是我们协调各个实体的工具。&lt;/p&gt;
&lt;p&gt;还需要明确的一点是，面向对象，是一种模式，语言可以提供便于实现该模式的特性，但模式绝不与语言特性绑定。一个很好的例子就是，linux操作系统内核中的各个模块，其间相互仅暴露有限的接口，用于消息传递，而其开发使用的却是不提供面向对象特性的C语言。另一方面，消息传递不一定通过本地的函数调用，现代互联网的微服务架构中，使用HTTP、RPC互相连接的微服务网络，又何尝不是一个庞大的面向对象系统呢？&lt;/p&gt;
&lt;h2&gt;History&lt;/h2&gt;
&lt;p&gt;了解了什么是面向对象的实质，我们便不得不好奇，所谓的“封装、继承、多态”是从何而来。&lt;/p&gt;
&lt;h3&gt;Two way&lt;/h3&gt;
&lt;p&gt;面向对象语言的发展统共可以分出两条脉络：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Simula语言，以及一众追随其发展而出现的C++、Java、C#等通过继承结构实现代码复用与&lt;strong&gt;归一化&lt;/strong&gt;的语言。&lt;/li&gt;
&lt;li&gt;Alan Kay设计的的Smalltalk，以及Swift、Obejective-C等并不使用继承结构的语言。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;前者以演进的继承层次结构，将对象分门别类，使得子类默认复用父类代码，而子类也应当能够完成父类能够完成的事情。这种实现，直觉上十分符合现实，但是却由于缺乏函数式般的严谨，而容易导致谬误——“正方形是不是长方形？”，直觉上是，但在接口的一致性方面，正方形不是长方形。这就加重了开发人员进行设计时的心智负担；而默认启用的继承，也使得类之间继承关系的繁杂，多继承时的字段矛盾，也常常带来问题——这就是老生常谈的&lt;strong&gt;组合优于继承&lt;/strong&gt;的由来。其主要问题在于，将&lt;strong&gt;实现的继承&lt;/strong&gt;与&lt;strong&gt;接口的继承&lt;/strong&gt;进行了耦合。有时，我们仅需要两个功能类似的函数，而不在意其族谱；有时，我们想代码复用，但并非以方法的形式！&lt;/p&gt;
&lt;p&gt;再就是多态。我们知道，严格的继承结构，限制了我们能够给怎样的对象发送消息，而静态类型的语言，往往又有参数、引用类型上的限制，这就使得这种结构下的语言不得不想方设法，使用重写、泛型、重载等方式来解决这些问题，于是，我们可以说，多态不过是继承的副作用。&lt;/p&gt;
&lt;p&gt;从后者的角度看，问题就截然不同了。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“I made up the term ‘object-oriented’, and I can tell you I didn’t have C++ in mind.” ~ Alan Kay, OOPSLA ‘97&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;在Alan Kay看来，C++并不是其心中面向对象编程的样子。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“OOP to me means only messaging, local retention and protection and hiding of state-process, and extreme late-binding of all things.”~ Alan Kay&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;类、继承关系绝非OOP的关注点，&lt;strong&gt;消息传递&lt;/strong&gt;才是。在Smalltalk中，任何元素都可以是对象。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1 + 1

means send message + = 1 with param 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;即发送消息是十分自然的事情，不需要知道对象是什么，有什么接口，达成了更深程度的解耦。当然，这种随意的消息传递，比起模块开发内部，还是更适用于庞大的网络系统，毕竟这将为模块开发带来十分庞大的不确定性，不利于编译器检查。&lt;/p&gt;
&lt;h3&gt;Design Pattern&lt;/h3&gt;
&lt;p&gt;想要成为OO大师，设计模式是必经之路。试问谁在学习OO时没被23种设计模式所惊艳过呢？但其实绝大多数设计模式，其实本身仅仅是在弥补语言中的设计漏洞，譬如策略模式，不过是使用一个单独接口来实现函数引用；适配器模式，亦不过是为了绕过严格的某个类下的某个接口这种莫名严苛的规范。设计模式于OOP，与其说是设计的艺术，不如说是语言的补丁。&lt;/p&gt;
&lt;h2&gt;Continue Talk&lt;/h2&gt;
&lt;p&gt;在两种OO发展中，实体都具有不容撼动的地位，而消息的传递则各有各的做法。首先需要明确的是，大多编程语言都是&lt;strong&gt;原生支持单核单线程的&lt;/strong&gt;，多数的并发编程都需要依赖显式引入的库函数。因此，在OO语言中的消息传递往往表现为对某个对象的函数调用。那么，我能调用哪些函数就显得尤为重要了。&lt;/p&gt;
&lt;p&gt;在C++类语言中，我能调用的函数往往局限为某个类下的某个函数签名——即类、函数类型、函数名缺一不可；而在Smalltalk下，又似乎百无禁忌，什么都可以。前者限制过头，开发束手束脚；后者过于自由，愿景美好，但难以实现。&lt;/p&gt;
&lt;p&gt;前者依靠多样的设计模式，缝缝补补，也算是撑到今天，然而也不免自我怀疑，吸收了函数式编程，以及interface这种纯接口的继承。&lt;/p&gt;
&lt;h2&gt;Statu quo&lt;/h2&gt;
&lt;p&gt;僵化的类型继承体系给开发者带来了过大的负担，使得开发人员不得不求助于各种技巧绕过类型继承限制。如今，新的开发语言已然开始抛弃继承体系，转而使用组合来完成代码复用，接口继承实现归一化；旧的语言也纷纷引入interface、protocol等特性来避免严苛的类型限制。&lt;/p&gt;
&lt;h2&gt;补充&lt;/h2&gt;
&lt;p&gt;类型继承机制对开发的限制在静态语言上能够得到比较深入的体现，而在动态类型下，其鸭子类型的特性，近乎天然地满足了OOP对无限制消息传递的可能性。在python与js中，其继承与其说是C++类的继承，不如说仅仅是实现上的继承，而接口继承的性质体现的却不是那么强烈，直至近来mypy、Ts将类型检查引入动态语言。&lt;/p&gt;
&lt;h2&gt;Reference&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.csdn.net/myan/article/details/5928531&quot;&gt;function/bind的救赎（上）&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.zhihu.com/question/20275578/answer/26577791&quot;&gt;面向对象编程的弊端是什么&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.zhihu.com/question/305042684/answer/550196442&quot;&gt;怎么从本质上理解面向对象的编程思想？&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://medium.com/javascript-scene/the-forgotten-history-of-oop-88d71b9b2d9f&quot;&gt;The Forgotten History of OOP&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>Priceton University BlockChain Note 2 比特币的共识算法</title><link>https://blog.logres.icu/posts/priceton-university-blockchain-note/priceton-university-blockchain-note-2/</link><guid isPermaLink="true">https://blog.logres.icu/posts/priceton-university-blockchain-note/priceton-university-blockchain-note-2/</guid><pubDate>Sun, 27 Nov 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&amp;lt;!--more--&amp;gt;&lt;/p&gt;
&lt;h2&gt;比特币系统&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;点对点网络&lt;/li&gt;
&lt;li&gt;挖矿机制&lt;/li&gt;
&lt;li&gt;系统软件&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;分布式共识&lt;/h2&gt;
&lt;p&gt;比特币在分布式货币系统内通过 激励 与 抛弃结束点 以某种角度解决的分布式系统的问题。&lt;/p&gt;
&lt;h2&gt;比特币的分布式算法&lt;/h2&gt;
&lt;h3&gt;无ID&lt;/h3&gt;
&lt;p&gt;和大多数分布式算法不同，区块链的实现是无ID的。ID有助于标识节点，参与算法流程；给出更强的假设，即50%以上的节点是善意的；但在比特币系统中，由于是P2P网络，ID难以实现且匿名的需求也与ID相悖，故在比特币系统中不存在ID的概念。&lt;/p&gt;
&lt;h3&gt;关键理念——隐式共识&lt;/h3&gt;
&lt;p&gt;每一轮一个节点提议其认可的下一个区块，并广播之，其他区块通过该区块作为自己下次提出区块的父节点或忽略它来实现隐式的反馈。&lt;/p&gt;
&lt;p&gt;算法具体实现流程:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;新的交易广播给所有节点&lt;/li&gt;
&lt;li&gt;节点收集交易并打包成区块&lt;/li&gt;
&lt;li&gt;每一轮一个随机节点将其区块进行广播&lt;/li&gt;
&lt;li&gt;其他节点仅当该区块的所有交易有效（合法/非双花）时才接受&lt;/li&gt;
&lt;li&gt;接受的节点以该区块的作为其下次产出区块的父节点&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;有效性&lt;/h3&gt;
&lt;p&gt;从攻击角度而言：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;伪造攻击：签名无法伪造&lt;/li&gt;
&lt;li&gt;拒绝服务：攻击者无法阻止交易的广播，总有节点能够接受交易&lt;/li&gt;
&lt;li&gt;双花：虽然该算法无法保证交易一定有效，但依靠比特币趋向于延长最长链的机制，在多次确认（延长区块后），被攻击的风险将指数下降&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;POF&lt;/h2&gt;
&lt;p&gt;比特币不对节点的善意与否加以假设，因而使用了POF（Proof of Work）机制搭配激励机制来保证系统的安全性。&lt;/p&gt;
&lt;h3&gt;激励机制&lt;/h3&gt;
&lt;p&gt;比特币的激励机制分为两种：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;挖矿奖励（Block Ward）：矿工通过在区块头部追加挖矿奖励来获得激励，仅在该区块存在于长期链的情况下才有效，因而可以降低节点产生问题节点的风险&lt;/li&gt;
&lt;li&gt;交易费（Transaction Fee）：交易发起人产生部分输入与输出的差额来给予矿工奖励，以激励其优先打包特定的交易&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;约束机制&lt;/h3&gt;
&lt;p&gt;我们仍然存在一些问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;谁有权力打包区块？&lt;/li&gt;
&lt;li&gt;如何防止女巫攻击？&lt;/li&gt;
&lt;li&gt;如何防止打包混乱？&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;在激励机制下，所有人都有足够的动机来抢占发行下一个区块的权力，因而我们需要对其加以限制，防止无限量的区块提案以及各种试图通过多开节点来试图获取更高提案权的行为。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;工作量证明机制&lt;/strong&gt;要求矿工在提出的区块中包含一个Nonce字段，并要求整个区块的hash落在整个hash取值空间的特定子空间中，这样的区块称为有效区块。这个nonce无法被预测，仅能通过大量的尝试性计算来找到。比特币算法通过调整目标空间大小来控制区块产生的速度，一半控制在10min左右。&lt;/p&gt;
&lt;p&gt;POW机制对区块提出者的算力提出考验，女巫攻击产生的大量节点并不能凭空提供算力；而艰难的计算也能控制区块产生的速度，因而有效地防止了打包混乱；且计算的随机性也解决了如何随机分配打包权的问题。&lt;/p&gt;
&lt;h2&gt;杂论&lt;/h2&gt;
&lt;p&gt;比特币是一个自举的系统，通过币的价值通过激励机制鼓励节点创建健康的挖矿、打包生态，而健康的生态给予了比特币安全性的保障（即免于遭受51%攻击），而出于对比特币的信任，其币价将持续上涨。&lt;/p&gt;
&lt;p&gt;51%攻击无法创造伪造的交易，也无法阻止交易的广播，但能够扭曲区块链的走向，造成双花等问题。&lt;/p&gt;
</content:encoded></item><item><title>Priceton University BlockChain Note 3 比特币的机制</title><link>https://blog.logres.icu/posts/priceton-university-blockchain-note/priceton-university-blockchain-note-3/</link><guid isPermaLink="true">https://blog.logres.icu/posts/priceton-university-blockchain-note/priceton-university-blockchain-note-3/</guid><pubDate>Sun, 27 Nov 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;比特币交易机制&lt;/h2&gt;
&lt;p&gt;传统的银行机制中一般使用基于账户的模型，即系统需要在记录每一笔交易的同时计算每个账户的实时余额。在比特币的区块链场景下，我们仅记录了交易本身，这将导致我们不得不回溯历史上的所有交易来确定交易时某个账户（公钥）的余额。&lt;/p&gt;
&lt;p&gt;幸运的是，在比特币系统中我们并不使用账户作为交易的输入。交易在区块链中使用哈希指针串联起来，每一笔交易的输出（以比特币脚本形式存在）都作为下一笔交易的输入的一部分（包含上一次交易的哈希以及公钥签名）。因而在比特币系统中实际的财产以为花费交易（UTXO）形式存在。&lt;/p&gt;
&lt;p&gt;在需要进行交易时，用户需要提供其UTXO的哈希以及公钥签名以解锁UTXO，生成新的交易。&lt;/p&gt;
&lt;h2&gt;比特币脚本&lt;/h2&gt;
&lt;p&gt;比特币脚本是一种基于栈的非图灵完备语言，其包含数学计算、分支、数据逻辑处理以及密码学等指令码，但不包含循环，以避免死循环造成的攻击。&lt;/p&gt;
&lt;p&gt;Pay to Script Hash 提供一种较为简单的创建交易的方法，付款方仅需对收款方指定的脚本哈希进行转账，该脚本在收到输入后将被反序列化并执行，从而实现对付款方透明的复杂机制。因而收款方可以在使用这笔输出时再构建当初指定的脚本码，作为输入的一部分来使用该笔输出。&lt;/p&gt;
&lt;p&gt;PS: OP_CHECKMULTISIG 验证多重签名指令需要事先指定n个公钥，并指定参数t，当且仅当超过t个公钥提供签名时才判断为成功。&lt;/p&gt;
&lt;h2&gt;比特币脚本应用&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;托管账户：使用多重签名加入第三方充当仲裁，避免某一方恶意行为；相比于传统的第三方仲裁机制，在未发生冲突时完全不需要仲裁方介入&lt;/li&gt;
&lt;li&gt;绿色地址：使用拥有公信力的绿色地址，付款方事先寄存货币，付款时由绿色地址直接转账。其公信力保证不会发生双花问题，因而可以在交易提交后直接认为交易成立。&lt;/li&gt;
&lt;li&gt;高效的微支付：在需要多次支付小额货币的情况下，将会产生高额的交易费用。因而付款方可以每次签名一笔带有多重签名的交易，将其递交给收款方，收款方先不提交交易，而是待到结算时才进行提交。这样存在两个问题：1. 收款方可以签名并提交所有付款方给与的交易，造成双花问题；2. 收款方可以不提交交易而导致付款方的交易卡顿——可以使用&quot;退款交易&quot;与lock_time字段来解决。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;比特币区块&lt;/h2&gt;
&lt;p&gt;比特币的区块链结构如下所示&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./Priceton-University-BlockChain-Note-3/%E6%AF%94%E7%89%B9%E5%B8%81%E5%8C%BA%E5%9D%97%E7%BB%93%E6%9E%84.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;区块链包含两种哈希数据结构：Merkle树和哈希链。Merkle树用于验证区块中的交易，哈希链用于验证区块链的完整性。二者通过哈希链节点中的Merkle根来连接。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  # 区块头
  &quot;hash&quot;:&quot;&quot;, # 区块哈希
  &quot;ver&quot;:2, # 版本号
  &quot;prev_block&quot;:&quot;&quot;, # 前一个区块哈希
  &quot;time&quot;:0, # 时间戳
  &quot;bits&quot;:0, # 难度
  &quot;nonce&quot;:0, # 随机数

  # 函数体
  &quot;mrkl_root&quot;:&quot;&quot;, # Merkle根
  &quot;n_tx&quot;:0, # 交易数量
  &quot;size&quot;:0, # 区块大小
  &quot;tx&quot;:[], # 交易列表

  # 哈希链
  &quot;mrkl_tree&quot;:[] # Merkle树
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;比特币网络&lt;/h2&gt;
&lt;p&gt;比特币网络是一个点对点网络，节点可以自由加入退出，从一个已知的节点获得其余节点地址，从而形成任意形态的拓扑结构。&lt;/p&gt;
&lt;p&gt;在广播交易时，收到交易的节点仅当该交易满足以下条件才进行转发：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;交易对于现有区块链是有效的&lt;/li&gt;
&lt;li&gt;交易的脚本类型满足白名单&lt;/li&gt;
&lt;li&gt;交易未见到过&lt;/li&gt;
&lt;li&gt;与已有的交易不冲突&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;其中2、3、4仅为健康检查，只要满足1即可进行广播。而4这一原则则由节点自己决定保留哪一条交易。&lt;/p&gt;
&lt;p&gt;矿工广播新区块时的行为类似，其验证包含验证区块链头与体中每一笔交易的有效性，且&lt;strong&gt;仅能转发接续在最长链后的区块&lt;/strong&gt;。注意到由于拓扑结构的不同与网络延迟的存在，区块的提交可能会产生竞争，即暂时的分叉，但很快新的区块将被挖掘，其决定了最长链（一般来说很少发生因为竞争产生长度超过2的分叉），而其他分叉将被丢弃。&lt;/p&gt;
&lt;p&gt;网络中，Fully-validating nodes长期连接着，保存了整个区块链的数据，接受并转发所有节点的交易。区块链数据大部分保存在磁盘中，但UTXO的集合仍然足够小以至于能够装入内存，进行快速的交易验证;而SPV客户端则不存储整个区块链，而仅仅存储区块链头部，并请求所需的交易数据来验证交易，并信任Fully-validating nodes。&lt;/p&gt;
&lt;h2&gt;比特币的局限与改进&lt;/h2&gt;
&lt;h3&gt;比特币局限&lt;/h3&gt;
&lt;p&gt;比特币设计初期并未考虑到会成为如此大规模的网络，因此其设计存在一些局限性，如：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;区块产生速度慢，每10分钟产生一个区块，而且区块大小有限制，因此交易吞吐量有限&lt;/li&gt;
&lt;li&gt;比特币总量有限，因此其价值受到供需的影响，而且随着时间的推移，其价值会越来越低&lt;/li&gt;
&lt;li&gt;使用固定的加密算法，随着密码学研究的推进，可能存在安全性问题&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;对此，存在两种改进方案：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;硬分叉&lt;/li&gt;
&lt;li&gt;软分叉&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;硬分叉&lt;/h3&gt;
&lt;p&gt;为了弥补比特币的不足，有时需要对比特币的客户端软件进行修改，但由于分布式的原因，难以做到全体节点一致进行升级，这就会造成网络上存在多种版本的情况。&lt;/p&gt;
&lt;p&gt;在一种情况下，比特币协议修改过大，旧的节点将不承认新节点产生的区块，此时虽然网络互通，区块在所有节点上传递，但旧节点将忽略新类型区块，从而分叉出自己的区块链，同理，新节点则运行在新区块链上。这种使旧节点不支持新区块的修改，就是硬分叉。&lt;/p&gt;
&lt;h3&gt;软分叉&lt;/h3&gt;
&lt;p&gt;与之对应，软分叉则是在保持旧节点承认新区块的条件下进行升级，其表现形式往往是进一步开发比特币区块中未使用的字段。这样，即使网络同时存在新旧节点，旧节点也能接受新区块，而新节点则拒绝旧区块，长期来看，只要超过51%的节点升级为新节点，新区块形成的区块链将会成为最长链，旧区块链将会被淘汰。&lt;/p&gt;
&lt;p&gt;但是，软分叉的行为其实是在一步步收紧区块条件，最终仍旧可能导致不得不硬分叉的情况。&lt;/p&gt;
</content:encoded></item><item><title>Priceton University BlockChain Note 4 比特币交易</title><link>https://blog.logres.icu/posts/priceton-university-blockchain-note/priceton-university-blockchain-note-4/</link><guid isPermaLink="true">https://blog.logres.icu/posts/priceton-university-blockchain-note/priceton-university-blockchain-note-4/</guid><pubDate>Sun, 27 Nov 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;比特币的存储与使用&lt;/h2&gt;
&lt;p&gt;使用比特币需要用到公链信息（即USTX）以及私钥，公链信息可以自由获取，故比特币的存储与使用也就等价于私钥的存储与使用。&lt;/p&gt;
&lt;p&gt;我们希望做到：可用性、安全性、便利性&lt;/p&gt;
&lt;p&gt;方案：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;文件存储：非常便利，但是易遗失，故可用性与安全性会遭到威胁&lt;/li&gt;
&lt;li&gt;钱包软件：对每个USTX使用不同的地址，由软件统一管理&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;地址为二进制串，为了进行便利的传递，一般采用base58编码或QR二维码进行编码。&lt;/p&gt;
&lt;h2&gt;冷热钱包&lt;/h2&gt;
&lt;p&gt;冷热钱包即钱包的离线与联网，使用时一般将小部分比特币存储在热钱包里，大部分以冷钱包的形式存储在冷钱包中。&lt;/p&gt;
&lt;p&gt;在这样的存储模式中，我们希望实现两点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;One USTX One address&lt;/li&gt;
&lt;li&gt;热钱包向冷钱包转账&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;第二点的实现较为简单，热钱包持有冷钱包公钥即可。但为了在此情况下实现One USTX One address，就需要进行更多考虑。我们可以提前建立一定量的密钥对，将公钥记录在热钱包，每次需要转账就使用一对。但这样使用较繁琐，可能需要频繁开启冷钱包，补充新的密钥对。但还存在更好的解决方案——分层钱包。&lt;/p&gt;
&lt;h3&gt;分层钱包&lt;/h3&gt;
&lt;p&gt;分层钱包利用了密钥对生成算法的机制，不直接生成密钥对，而是先生成用于生成公钥的信息与用于生成私钥的信息。使用这两份信息可以独立导出互相对应的公钥与密钥。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./Priceton-University-BlockChain-Note-4/%E5%88%86%E5%B1%82%E5%AF%86%E9%92%A5.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;冷钱包则可以存储在物理设备、纸质文件又或者自己记住。此外，还存在某些防篡改软件，可以存储与使用，但是无法取出。&lt;/p&gt;
&lt;h2&gt;密钥分拆与共享&lt;/h2&gt;
&lt;p&gt;有时候我们希望能够实现多个人共同控制一个地址，我们可以从两个层面进行实现：单密钥USTX与拆分私钥；多重签名USTX与独立私钥。&lt;/p&gt;
&lt;h3&gt;拆分密钥&lt;/h3&gt;
&lt;p&gt;拆分密钥时，我们可以通过某些数学方法，将密钥进行处理，获取n个处理后的密钥，仅当拥有超过t(t &amp;lt; n)个密钥后才能进行签名。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./Priceton-University-BlockChain-Note-4/%E5%AF%86%E9%92%A5%E5%88%86%E7%89%87.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;该算法核心思路为使用不同次数的函数，利用其1-t个点确认一条直线的特性，将密钥设置为其与y轴的交点来。通过使用多阶函数，我们可以改变t，而通过取出更多的点，我们可以改变n。&lt;/p&gt;
&lt;h3&gt;多重签名密钥&lt;/h3&gt;
&lt;p&gt;多重签名在前面的章节进行过描述，即对USTX使用多重签名检查脚本，从而实现单个USTX需要多个签名。&lt;/p&gt;
&lt;h2&gt;轻钱包与交易所&lt;/h2&gt;
&lt;h3&gt;轻钱包&lt;/h3&gt;
&lt;p&gt;除了自己搭建节点、广播交易外，我们还可以通过轻钱包来实现交易。轻钱包一般运行在浏览器上，后端连接至专门的服务提供网站，我们一般需要给予其信任。&lt;/p&gt;
&lt;h3&gt;交易所&lt;/h3&gt;
&lt;p&gt;交易所则是一个中心化的服务，它们可以提供交易、兑换、支付等服务。我们可以存储数字货币与法定货币，并在交易所的账户中进行自由的兑换、支付等服务。需要注意的是，当我在交易所买入比特币，区块链上的交易并没有发生，而是交易所的账户中的比特币增加了，而我的账户中的法定货币减少了。&lt;/p&gt;
&lt;p&gt;交易所十分便利，但存在多种风险：挤兑、庞氏骗局以及黑客入侵。&lt;/p&gt;
&lt;p&gt;为了消除顾客对前两种风险的担忧，交易所需要进行证明：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;自己具有足够的资金&lt;/li&gt;
&lt;li&gt;活期存款记录&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;对于1，交易所可以发起向自己的支付交易来证明自己的资金，然后再用发送方的密钥加密挑战串来证明是自己进行的支付。对于2，则需要交易所构建包括所有用户的默克尔树，并对每个用户提供零知识证明。&lt;/p&gt;
&lt;h2&gt;支付服务&lt;/h2&gt;
&lt;p&gt;支付服务为传统商户接受数字货币提供了便利，其从用户手中收取数字货币，转而向商户支付法定货币，并从中收取部分手续费。&lt;/p&gt;
&lt;h2&gt;交易手续费&lt;/h2&gt;
&lt;p&gt;比特币交易按照一定规则收取手续费&lt;/p&gt;
&lt;p&gt;$$ 如果交易大小小于1000字节且输出的比特币都大于等于0.01以及具有足够高的优先级，否则每1000字节0.0001比特币$$&lt;/p&gt;
&lt;p&gt;$$ Priority = ( \Sigma(年龄 \times 输入金额)/(交易大小) ) $$&lt;/p&gt;
&lt;p&gt;$$ 交易大小约等于 148 \times 输入 + 34 \times 输出 + 10 $$&lt;/p&gt;
&lt;h2&gt;货币兑换市场&lt;/h2&gt;
&lt;p&gt;比特币兑换市场进行比特币与法定货币的兑换。&lt;/p&gt;
&lt;p&gt;从需求角度看：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;比特币作为支付手段&lt;/li&gt;
&lt;li&gt;比特币作为投资手段&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;从供给角度看：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;流通货币&lt;/li&gt;
&lt;li&gt;交易所等机构产生的M2——活期存款等&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;当供给需求平衡，就将获取到一个稳定的汇率，在此进行比特币市场模型分析：&lt;/p&gt;
&lt;p&gt;T = 每秒需要用比特币进行交易的总额 dollar / sec
D = 比特币用作交易媒介的周期 sec
S = 比特币总供给 Bitcoin
P = 比特币价格 Bitcoin / dollar&lt;/p&gt;
&lt;p&gt;得到方程：&lt;/p&gt;
&lt;p&gt;$ \frac{T}{P} = \frac{S}{D}$&lt;/p&gt;
&lt;p&gt;$ P = \frac{DT}{S}$&lt;/p&gt;
&lt;p&gt;因而，当流通货币减少，比特币价格将上升；当流通货币增加，比特币价格将下降。此外，比特币作为投资手段也存在其对应模型，此处不作分析。&lt;/p&gt;
</content:encoded></item><item><title>Priceton University BlockChain Note 5 挖矿二三事</title><link>https://blog.logres.icu/posts/priceton-university-blockchain-note/priceton-university-blockchain-note-5/</link><guid isPermaLink="true">https://blog.logres.icu/posts/priceton-university-blockchain-note/priceton-university-blockchain-note-5/</guid><pubDate>Sun, 27 Nov 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;矿工的工作&lt;/h2&gt;
&lt;p&gt;矿工即通过打包区块来获取打包收益的节点，在比特币生态中扮演十分重要的角色，完成以下任务：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;存储与广播区块链&lt;/li&gt;
&lt;li&gt;验证新的交易&lt;/li&gt;
&lt;li&gt;通过哈希计算进行共识投票（打包新区块）&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;在POF模式下，矿工需要按照特定的规则计算哈希，找到满足要求的nounce字段，来提供一个合法的区块，需要注意的是，在计算过程中可变的部分除了nounce之外，还有挖矿奖励交易中的一个变量——extra variable。通过便利extra variable与nounce的组合，计算嵌套的sha256哈希（两层），矿工将获得奖励。&lt;/p&gt;
&lt;p&gt;需要额外注意的是，比特币协议按照全网算力动态变化难度，使得矿工的计算难度也随之变化，这样可以保证区块的生成速度在预期的10分钟左右。&lt;/p&gt;
&lt;h2&gt;挖矿设备&lt;/h2&gt;
&lt;p&gt;随着全网算力的演进，挖矿设备也在不断的发展，经历了多个世代：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;CPU挖矿&lt;/li&gt;
&lt;li&gt;GPU挖矿&lt;/li&gt;
&lt;li&gt;FPGA挖矿&lt;/li&gt;
&lt;li&gt;ASIC挖矿&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;能源消耗&lt;/h2&gt;
&lt;p&gt;按照兰道尔原理，任何不可逆计算都需要消耗kTln2每比特的能量，所以，计算哈希不可避免地需要消耗能量，这样看似无用的做工引起了环保人士的不满。&lt;/p&gt;
&lt;p&gt;从上界的角度看，我们可以从矿工挖矿的收益计算；从下界角度看，我们可以以全网算力所对应的能耗来计算。但同时，我们应当记住，传统的支付系统也需要消耗许多能量——ATM的电耗、人工的消耗等等，这些消耗和比特币的消耗一样，都只是为了维持系统自身的运转。&lt;/p&gt;
&lt;h2&gt;矿池&lt;/h2&gt;
&lt;p&gt;随着全网算力越来越高，独立矿工能够挖到矿的概率越来越小，而单个比特币的价值又非常之高，导致矿工的收入在数学期望上看满足矿工的需求，当时过度的方差却又导致了矿工的收入不稳定，这样的情况下，矿工们就会选择加入矿池，通过矿池的分配机制来提高收益。&lt;/p&gt;
&lt;p&gt;矿池吸纳矿工为矿池挖矿，而后按照矿工的贡献分配收益。那么这里存在两个问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;如何衡量贡献&lt;/li&gt;
&lt;li&gt;如何分配收益&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;如何衡量贡献——份额&lt;/h3&gt;
&lt;p&gt;矿池管理员首先打包区块，然后将计算任务分配给矿工，矿工计算出结果后，将结果提交给矿池。然而大多数矿工在都无法计算出区块（前n位为0的哈希），故矿池管理员设置难度较低的计算任务（前t位(t &amp;lt; n)），矿工每次计算出一个这样的哈希即为计算出了一个份额，最后按照份额进行利益分配。&lt;/p&gt;
&lt;h3&gt;收益分配方案&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Pay per share：按照份额进行支付，无论矿池是否计算出区块&lt;/li&gt;
&lt;li&gt;Proprtional：计算出区块后再进行分成&lt;/li&gt;
&lt;li&gt;Luke-jr：矿工持续挖矿，直到其收益达到一个阈值再进行提现&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;矿池的存在，分摊了单个矿工的风险，但也使得算力集中化，乃至于矿池主可以借用矿工的算力发动一些攻击；而且由于仅需完成矿池的任务，矿工也就不再需要运行维护完整的区块链了，降低了区块链的稳定性。&lt;/p&gt;
&lt;h2&gt;挖矿策略与激励&lt;/h2&gt;
&lt;p&gt;尽管大部分矿工挖矿时遵循默认的挖矿策略，但是我们还是要指出，事实上矿工在挖矿过程中面临相当多的决策：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;选择交易
&lt;ul&gt;
&lt;li&gt;默认行为：所有提供了最小交易费的交易&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;接续哪个区块
&lt;ul&gt;
&lt;li&gt;默认行为：最长链&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;如何处理竞争区块
&lt;ul&gt;
&lt;li&gt;默认行为：最初接收的块&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;何时广播区块
&lt;ul&gt;
&lt;li&gt;默认行为：立即公布&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;因而存在许多攻击方案：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;分叉攻击：利用50%以上的算力进行分叉，但是会导致区块链信用降低，一般只有区块链整体的敌对方才会进行。
&lt;ul&gt;
&lt;li&gt;应对方式：检查点，防止太长的分叉&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;隐藏区块攻击：发现新区块后不立即公布，而是先进行下一个块的挖掘，抢占优势
&lt;ul&gt;
&lt;li&gt;应对方式：该方式仅在优先两个区块的情况下才能稳定进行，否则都面临竞争风险，而优先两个块的成功率仅有所占算力比的平方，成功率较低&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;惩罚性分叉：针对某些类型的交易不予以接收
&lt;ul&gt;
&lt;li&gt;应对方式：难以为继，容易陷入孤立块&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;轻量级攻击：用低于50%的算力发起攻击，使其他人承担攻击者算力平方的损失风险，从而改变其余人的倾向&lt;/li&gt;
&lt;/ol&gt;
</content:encoded></item><item><title>Priceton University BlockChain Note 1 密码学基础</title><link>https://blog.logres.icu/posts/priceton-university-blockchain-note/priceton-university-blockchain-note-1/</link><guid isPermaLink="true">https://blog.logres.icu/posts/priceton-university-blockchain-note/priceton-university-blockchain-note-1/</guid><pubDate>Sun, 27 Nov 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;想要研究清楚Web3, 区块链与加密货币的底层知识自然少不了。恰逢看到了普林斯顿《比特币与加密货币技术》的公开课，遂学之记之。&lt;/p&gt;
&lt;p&gt;&amp;lt;!--more--&amp;gt;&lt;/p&gt;
&lt;h2&gt;哈希函数&lt;/h2&gt;
&lt;h3&gt;哈希函数定义&lt;/h3&gt;
&lt;p&gt;哈希函数定义：输入为任意字符串，输出为固定长度且具有高计算效率的函数。&lt;/p&gt;
&lt;h3&gt;哈希函数的性质&lt;/h3&gt;
&lt;h4&gt;collision-free&lt;/h4&gt;
&lt;p&gt;定义：粗略来说，即无法找到&lt;strong&gt;x!=y使得h(x)=h(y)&lt;/strong&gt;，但经过足够的计算总是找的到的。但至少不存在针对该哈希函数的高效方法。
应用：&lt;strong&gt;辨识&lt;/strong&gt;，即对于hash(x)=hash(y)，我们可以假定x=y。从而可以用hash(x)来代替x，节省空间，例如使用hash判断两个文件是否相等。&lt;/p&gt;
&lt;h4&gt;hiding&lt;/h4&gt;
&lt;p&gt;定义：简单说，即无法从hash(x)推导出x。但我们会发现，若在x的选取范围非常小的情况下，仅需暴力尝试-彩虹表即可列出所有输出及其对应输入。于是，我们一般使用一个随机序列r来对x进行加密，即hash(x+r)。r选取自一个高最小熵的分布，难以遍历。故正式定义：若r选取自一个高最小熵的集合，则给定H（r|x)，无法找到x。这里|表示拼接。&lt;/p&gt;
&lt;p&gt;应用：&lt;strong&gt;承诺&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
def commit(message,key):
    return (hash(message+key),key)
def verify(message,commit,key):
    return hash(message+key)==commit
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们可以使用key对message进行加密，然后公开我们的key与commit作为承诺。当我们公开自己的承诺内容后，其他人可以用commit与key对message进行验证。这一过程可以应用在&lt;strong&gt;零知识证明&lt;/strong&gt;中，即证明者可以证明自己知道某个值，而不需要公开这个值。其中collision-free保证了我们不能找到两个message同时满足承诺，即message的确定性；而hiding保证了在我们公开message之前，其他人无法通过commit与key推导出message，即message的保密性。&lt;/p&gt;
&lt;h4&gt;puzzle-friendly&lt;/h4&gt;
&lt;p&gt;定义：若k选取自一个高最小熵的分布，则无法找到x，使得Hash(x|k)=y。这里y是一个已知的值，又或者一个已知的集合。 注意：hiding中的无法找到x是指无法解析出被用于计算commit的原始消息，而puzzle-friendly则强调无法找到满足条件的x。因为即使x满足条件，也未必就是原始信息（哈希函数总存在碰撞）。&lt;/p&gt;
&lt;p&gt;应用：POW&lt;/p&gt;
&lt;h2&gt;哈希指针与数据结构&lt;/h2&gt;
&lt;p&gt;结合哈希函数与计算机编程概念中的指针，我们便得到了哈希指针，及一系列以其为基础的数据结构。&lt;/p&gt;
&lt;h3&gt;哈希指针&lt;/h3&gt;
&lt;p&gt;哈希指针，我们可以定义为一个指针与其指向内容哈希值的二元组。通过指针寻找内容，而通过哈希验证内容，便能实现指针指向内容的不可篡改性。&lt;/p&gt;
&lt;h3&gt;数据结构&lt;/h3&gt;
&lt;p&gt;哈希指针可以在任何数据结构中替换指针，只要不存在环。&lt;/p&gt;
&lt;h4&gt;Block Chain&lt;/h4&gt;
&lt;p&gt;区块链正是哈希指针的典型应用之一。&lt;/p&gt;
&lt;p&gt;简单来说，区块链是一个链表，每个节点包含一个哈希指针，指向其前一个节点。由于第n个区块中的哈希指针保存了第n-1个区块的哈希值，故我们可以检测出n-1区块是否被修改；而由于该哈希值又参与到了第n个区块的哈希计算中，被保存于第n+1个区块，从而我们亦可以检测出第n-1个区块的哈希值是否被修改。以此类推，攻击者若想篡改区块链中的任意一个区块，必须同时篡改其后所有区块的哈希值，这一过程往往是不可行的（POW要求十分庞大的工作量）&lt;/p&gt;
&lt;h4&gt;Merkle Tree&lt;/h4&gt;
&lt;p&gt;默克尔树是哈希指针的另一个典型应用。&lt;/p&gt;
&lt;p&gt;默克尔树是一个二叉树，每个节点包含一个哈希指针，指向其左右子节点。由于每个节点的哈希值又参与到了其父节点的哈希计算中，故我们可以检测出其子节点是否被修改。因此我们仅需记住根节点的哈希指针的哈希值，就可以验证树中任何一个节点的内容是否被篡改。&lt;/p&gt;
&lt;p&gt;默克尔也提供了另一种零知识证明的手段：我们希望证明某个节点属于某棵默克尔树，仅需提供从该节点开始到根节点的全部兄弟节点即可，而无需对比整棵树。&lt;/p&gt;
&lt;p&gt;变种的排序默克尔树则可以通过展示排序上连续的两个节点，证明本该存在二者中间的节点不存在。&lt;/p&gt;
&lt;h2&gt;数字签名&lt;/h2&gt;
&lt;h3&gt;数字签名的特性&lt;/h3&gt;
&lt;p&gt;数字签名具有如下特性：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;仅能由你签署，但是可以被所有人验证&lt;/li&gt;
&lt;li&gt;签名与特定文档相关联，不可以被剪切&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;数字签名实现&lt;/h3&gt;
&lt;h4&gt;数字签名实现-API层面&lt;/h4&gt;
&lt;p&gt;数字签名所需的API如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def generate_keypair(key_size):
    # 生成公钥和私钥
    return public_key, private_key

def sign(message, private_key):
    # 用私钥签署消息
    return signature

def verify(message, signature, public_key):
    # 用公钥验证消息
    return True or False
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;按照该API来描述数字签名的特性即:&lt;/p&gt;
&lt;p&gt;$$verify(message, sign(message, private_key), public_key) == True$$&lt;/p&gt;
&lt;p&gt;以及&lt;/p&gt;
&lt;p&gt;$$无法依靠PK与Sig制造出伪造的签名$$&lt;/p&gt;
&lt;h4&gt;数字签名实现-实践细节&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;确保随机性&lt;/li&gt;
&lt;li&gt;对消息的hash签名&lt;/li&gt;
&lt;li&gt;对哈希指针签名，即是对其指向内容的签名&lt;/li&gt;
&lt;li&gt;公私钥算法:Bitcoin&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;公钥作为个人身份&lt;/h2&gt;
&lt;p&gt;由于数字签名机制的存在，公钥（或其哈希）往往被用作一种身份认证。拥有密匙对中的sk即拥有pk所代表的身份。这一机制天生去中心化，因而具有隐秘性。但连续的行为可能由于其模式被推导出sk与本人的关系，这将降低其隐秘性。&lt;/p&gt;
&lt;h2&gt;简单的加密货币&lt;/h2&gt;
&lt;p&gt;加密货币系统需要解决的问题:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;交易验证:使用哈希指针回溯交易&lt;/li&gt;
&lt;li&gt;双花问题:使用区块链记录所有交易历史&lt;/li&gt;
&lt;/ol&gt;
</content:encoded></item><item><title>详解python描述器</title><link>https://blog.logres.icu/posts/python%E6%8F%8F%E8%BF%B0%E5%99%A8/python%E6%8F%8F%E8%BF%B0%E5%99%A8/</link><guid isPermaLink="true">https://blog.logres.icu/posts/python%E6%8F%8F%E8%BF%B0%E5%99%A8/python%E6%8F%8F%E8%BF%B0%E5%99%A8/</guid><pubDate>Sun, 13 Nov 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Python中我们常常使用A.B这样的表达式对对象（包括类）的成员变量或函数进行访问。我们一般假定这样的访问将返回成员本身，但有多少人考虑过这样语法结构对应的底层过程呢？今天，我们就从这个.说开去，聊聊Load_Attr的运作流程，与描述符、@staticmethod、@classmethod以及@property等描述符的实现机制。&lt;/p&gt;
&lt;h2&gt;描述符&lt;/h2&gt;
&lt;h3&gt;描述符概述&lt;/h3&gt;
&lt;p&gt;在聊后续内容之前，我们需要先介绍一下Python中的描述符协议。我们都知道，Python中除了显示继承来实现接口外，只要实现了特定的魔法方法就算是实现了某种协议，可以适配某些语法结构与外部类。如实现了__next__与__iter__方法的类就算是实现了迭代器协议，其就成为一个迭代器，可以用于for循环进行迭代。&lt;strong&gt;描述符&lt;/strong&gt;也是一个协议，其包含__get__、__set__与__del__三个方法。&lt;/p&gt;
&lt;h3&gt;描述符的函数签名&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;def __get__(self, instance, owner):
  &quot;&quot;&quot;Parameter:
  self: the descriptor instance
  instance: the instance of the owner class
  owner: the class of the instance
  &quot;&quot;&quot;
  pass

def __set__(self, instance, value):
  &quot;&quot;&quot;Parameter:
  self: the descriptor instance
  instance: the instance of the owner class
  value: the value to be set
  &quot;&quot;&quot;
  pass

def __delete__(self, instance):
  &quot;&quot;&quot;Parameter:
  self: the descriptor instance
  instance: the instance of the owner class
  &quot;&quot;&quot;
  pass

## 对于类的补充，类中找到描述符时，instance为NULL，type为自身
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;描述符分类&lt;/h3&gt;
&lt;p&gt;虽然实现其中任意一个就能称为描述符，但是根据实现程度的不同亦有不同的分类。我们将描述符分为两类：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;数据描述符：实现了__set__与__get__方法的描述符。&lt;/li&gt;
&lt;li&gt;非数据描述符：未实现__set__方法的描述符。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;描述符用途&lt;/h3&gt;
&lt;p&gt;迭代器协议用于迭代，那么描述符协议用于什么呢？没错，正是A.B结构的访问机制。当A.B来获取B时，若B实现__get__就回访会B的__get__函数的返回值，而不一定是B本身；同样，使用A.B=C来进行赋值以及del进行删除时，将分别对应__set__与__del__方法。当然，A.B的机制绝非如此三言二语能描述清楚，在Load_Attr中将进行详细描述。&lt;/p&gt;
&lt;h2&gt;Load_Attr&lt;/h2&gt;
&lt;p&gt;A.B这样的语法结构，将经由A.__getattribute__(B)这样一个魔法方法来执行，其在Python字节码层面对应的是Load_Attr这条指令。其行为取决于A的类型（对象或类），但也十分相似。大体上，对象的访问流程如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;在对象的type，也就是类的中查找数据描述符，若找到则
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;访问&lt;/strong&gt;:返回其__get__方法的返回值&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;赋值&lt;/strong&gt;:调用__set__方法。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;在对象的__dict__里查找实例属性，也就是实例生成后附上的属性
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;访问&lt;/strong&gt;:返回属性&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;赋值&lt;/strong&gt;:修改属性&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;在对象type里查找非数据描述符，若找到则
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;访问&lt;/strong&gt;:返回其__get__方法的返回值&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;赋值&lt;/strong&gt;:不进行这一步&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;在对象type里查找类属性，也就是直接定义在类中的属性
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;访问&lt;/strong&gt;:返回属性&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;赋值&lt;/strong&gt;:为实例添加这一属性，并赋值&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;若以上都未找到，则抛出AttributeError；但若是类实现了__getattr__方法，则调用其并获得返回值&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;类的访问流程则较为简单，直接在自己中查找数据描述符、非数据描述符、类属性即可。&lt;/p&gt;
&lt;h2&gt;使用场景&lt;/h2&gt;
&lt;h3&gt;隔离访问&lt;/h3&gt;
&lt;p&gt;面向对象语言强调的是对象的自治，对于对象的成员，我们理应使用对应的方法来访问，如Java中一般借由给成员属性设置private，再提供对应getter、setter方法来避免直接访问。但在python中缺缺乏访问描述符，因此我们只能通过其他手段来实现，描述符就很好地提供了这一机制。&lt;/p&gt;
&lt;p&gt;当然，为每一个属性定义一个类是十分繁琐的，因此我们可以使用内置的property类来实现。property类的构造函数接受三个参数，分别是：fget,fset,fdel，对应描述器的三个方法。&lt;/p&gt;
&lt;p&gt;Property类实现如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Property:
    def __init__(self, fget=None, fset=None, fdel=None):
        self.fget = fget
        self.fset = fset
        self.fdel = fdel

    def __get__(self, instance, owner):
        if instance is None:
            return self
        if self.fget is None:
            raise AttributeError(&quot;unreadable attribute&quot;)
        return self.fget(instance)

    def __set__(self, instance, value):
        if self.fset is None:
            raise AttributeError(&quot;can&apos;t set attribute&quot;)
        self.fset(instance, value)

    def __delete__(self, instance):
        if self.fdel is None:
            raise AttributeError(&quot;can&apos;t delete attribute&quot;)
        self.fdel(instance)

    def getter(self, fget):
        return type(self)(fget, self.fset, self.fdel)

    def setter(self, fset):
        return type(self)(self.fget, fset, self.fdel)

    def deleter(self, fdel):
        return type(self)(self.fget, self.fset, fdel)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;结合装饰器机制，我们可以简便地将某个属性实现为描述器,仅需提供描述器所需的三个方法即可。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
class A:
  def __init__(self):
    self.a = 1
  @property
  def a(self):  # 对应fget，但一般不称为get_a，而是直接称为a，便于外部访问
    return self.a
  @get_a.setter
  def a(self, value):
    self.a = value
  @get_a.deleter
  def a(self):
    del self.a

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;类方法与静态方法&lt;/h3&gt;
&lt;p&gt;@classmethod以及@staticmethod装饰器的实现原理就是描述符。&lt;/p&gt;
&lt;h3&gt;函数绑定&lt;/h3&gt;
&lt;p&gt;方法调用时，我们不需要传递self参数，这是因为python会自动将self传递给方法。这是因为python会将方法转换为描述符，然后调用描述符的__get__方法，将self作为参数传递给方法。&lt;/p&gt;
</content:encoded></item><item><title>谈一谈python的协程机制</title><link>https://blog.logres.icu/posts/%E8%B0%88%E4%B8%80%E8%B0%88python%E7%9A%84%E5%8D%8F%E7%A8%8B%E6%9C%BA%E5%88%B6/</link><guid isPermaLink="true">https://blog.logres.icu/posts/%E8%B0%88%E4%B8%80%E8%B0%88python%E7%9A%84%E5%8D%8F%E7%A8%8B%E6%9C%BA%E5%88%B6/</guid><description>从Python Async函数的角度，谈一谈python的协程机制</description><pubDate>Mon, 01 Aug 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;由于GIL锁的存在，Python的多线程并不能起到利用多核处理器的作用，对于CPU密集型任务无法提升效率；然而，即使作为IO密集任务的并发机制，线程附带的上下文切换开销也导致了效率的低下。这种场景下，python使用await/async关键字提供的协程机制，提供了一种低开销的异步IO机制。&lt;/p&gt;
&lt;h2&gt;演进&lt;/h2&gt;
&lt;p&gt;Python尝试对协程的支持是从Python3.4开始的，其底层机制为Python中的生成器。await关键字包装了yield from&lt;/p&gt;
&lt;h2&gt;机制&lt;/h2&gt;
&lt;p&gt;异步IO的实现基于几个核心概念：事件循环、协程、Future、Task，以及用于支持协程的关键字await。&lt;/p&gt;
&lt;h3&gt;await&lt;/h3&gt;
&lt;p&gt;在谈论协程的实现之前，我们需要先了解一下await这个关键字的机制。await其实是对yield from这一关键字的封装与修改，相较于yield from， await放弃了对基于生成器的协程的支持，转而提出了一个可等待对象(awaitable)的概念。可等待对象需要实现可等待协议，即实现__await__方法，并返回一个生成器。于是，我们可以应用await的场景便分为两种，一种是coroutine，另一种是可等待对象。&lt;/p&gt;
&lt;p&gt;await关键字分为两步进行：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;返回coroutine(也是一种特殊的生成器，需要增加注解来标识)本身或调用可等待对象的__await__方法&lt;/li&gt;
&lt;li&gt;使用yield from，反复调用生成器的send方法，若yield出对象，则保存函数帧，将yield出的对象返回给更上一层函数；否则，将返回值或StopIteration异常的值作为返回值继续执行。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;如此，await实现了协程的暂停与恢复，以及对可等待对象的支持。&lt;/p&gt;
&lt;h3&gt;事件循环&lt;/h3&gt;
&lt;p&gt;回想刚开始coding的时光，我们编写的程序总是从main开始，然后执行一些操作，而后很快地，以return收尾。但在现实应用场景中，提供服务的软件往往需要不停地运行，如Web服务器、嵌入式设备等。这些程序需要一种机制来处理事件，这就是事件循环。&lt;/p&gt;
&lt;p&gt;协程这种需要调度多个任务的场景，亦需要一个事件循环。事件循环由什么元素组成呢？一般来说，需要一个任务队列——schedule_list。如果不考虑阻塞事件，显然，我们只需要按序执行完其中的所有任务，就结束了。但显然，schedule_list中的事件会触发许多与外部产生交互的事件，如网络请求、文件读写等。按照同步的方式，我们可以一一等待之，但这将不可避免地耗费许多时间，让CPU乃至线程空等。在多线程机制下，一般会让CPU调度其他线程来执行；而在协程机制下，我们就需要手动转移控制流。这一机制的实现，就依赖于协程的暂停与恢复，以及内核提供的IO与定时器库了。&lt;/p&gt;
&lt;h3&gt;协程&lt;/h3&gt;
&lt;p&gt;一般的同步代码中，函数调用将自己保存本地变量，然后再进入子过程栈帧，最后退出，再恢复之；而异步IO中需要营造出“假同步”的表象，便需要我们自己维护协程的状态，包括局部变量、栈帧、堆栈、指令计数器等等。&lt;/p&gt;
&lt;p&gt;仔细想来，在python中，确乎存在这种执行到一半，暂时退出，下次再执行的机制——生成器。&lt;/p&gt;
&lt;p&gt;仔细看下面这个函数，当执行g = generator时，并不会立即执行generator函数，而是会返回一个generator对象，这个函数有两个重要接口——next()和send()。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def generator():
    yield 1
    f = yield 2
    yield 3
    return 0

g = generator()

g.next() # 1
g.next() # 2
g.send(4) # 3
try:
    next(g) # StopIteration
except StopIteration as e:
    print(e.value) # 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;每次调用iterator的next()方法，都会返回下一个yield表达式的值。（首次调用时常常需要先执行一次next或send(None),使其执行到第一个yeild处，便于传参）&lt;/p&gt;
&lt;p&gt;generator机制能够提供一种lazy的iterator，即读取时才生成必要数据，减少内存占用；但在此处，却也为协程提供了一种绝好的机制，使用next/send调度协程，协程使用yeild/yeild from/await方法交出控制权，并指明等待对象——Future，以达到转移控制流的效果。&lt;/p&gt;
&lt;h3&gt;Future&lt;/h3&gt;
&lt;p&gt;先从学术角度谈谈Future。Future是协程通信机制中的一种，其作为一个跨越于多个协程之间的对象，由等待方进行监听，由被等待方进行触发。监听的实现一般依靠调用Future对象的set_callback方法，将回调函数传入；而被等待方则调用Future对象的set_done方法，不仅结束Future对象，还对回调函数进行调用——回调内容往往是将等待方加入事件循环的调度队列中。而对回调函数的调用则是延迟调用，即将回调函数也加入事件循环，等待事件循环的调度。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;在一般的被动式（响应式）编程方案中，我们一般通过传入回调函数来实现异步IO，这种方式的缺点是：当我们的响应函数嵌套太深，回调函数调用路径过长，将导致代码难以维护，同时也使得运行时在回调处理时间过长。延迟调用能很好地避免这方面问题&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;从基础定义上来看，Future是一个静态对象，我们可以将其set_done方法绑定到操作系统内核中提供的异步IO接口，或自己实现的epoll事件、定时器队列上，如此，当任务所等待的外部事件完成后，便可借由future再度回到任务队列中，等待调度。&lt;/p&gt;
&lt;h3&gt;Task&lt;/h3&gt;
&lt;p&gt;Task是对协程的一层封装，是事件循环调度任务的基本单位。除了持有一个coroutien，对外提供coroutine的接口外，Task亦继承了Future。为什么呢？我们需要考虑到，task除了需要等待外部事件外，有时还存在task对task的依赖，即taskA完成后taskB才能执行。于是，我们需要task本身也成为一个可等待对象。&lt;/p&gt;
&lt;h2&gt;实战——事件循环&lt;/h2&gt;
&lt;p&gt;通过前面的论述，我们已经充分了解了一个事件循环的基本构成，接下来通过实现一个简单的事件循环来加深对事件循环的理解。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
import time
import socket
from functools import partial
import select

def coroutine(name):
    loop = yield None
    yield None
    print(name+&quot;A&quot;)
    yield loop.sleep(3)
    print(name+&quot;B&quot;)
    sock = socket.socket()
    sock.connect((&quot;www.baidu.com&quot;,80))
    sock.send(&quot;GET / HTTP/1.1\r\n&quot;.encode(&quot;utf-8&quot;))
    sock.send((&quot;Host: &quot;+&quot;www.baidu.com&quot;+&quot;\r\n&quot;).encode(&quot;utf-8&quot;))
    sock.send((&quot;\n&quot;).encode(&quot;utf-8&quot;))
    future = Future()
    # 将自己注入回去
    loop.add_handler(sock.fileno(), lambda: future.set_done(&quot;done&quot;), select.EPOLLIN)
    yield future
    data = sock.recv(1024)
    print(name)
    print(data)
    task = Task(coroutine2(name+&quot;-2&quot;),loop)
    loop.add_task(task)
    yield task
    print(name+&quot;Over&quot;)
    return &quot;Over&quot;

def coroutine2(name):
    loop = yield None
    yield None
    print(name+&quot;A&quot;)
    yield loop.sleep(3)
    print(name+&quot;Over&quot;)
    return &quot;Over&quot;


class TimerEvent:
    def __init__(self, secs, callback):
        self.deadline = time.time() + secs
        self.callback = callback

    def call(self):
        self.callback()


class Future:

    def __init__(self):
        self.callbacks = []
        self.value = None

    def add_callback(self, callback):
        self.callbacks.append(callback)

    def set_done(self, value):
        self.value = value
        for callback in self.callbacks:
            callback()

    def get_result(self):
        return self.value


class Task(Future):
    def __init__(self, gen,loop):
        Future.__init__(self)
        self.gen = gen
        self.loop = loop
        self.gen.send(None)
        self.gen.send(loop)

    def step(self):
        try:
            yielded = next(self.gen)
            if isinstance(yielded, Future):
                yielded.add_callback(partial(self.loop.task_list.append, self))
            else:
                self.loop.task_list.append(self)
        except StopIteration as e:
            self.set_done(e.value)

        

class EventLoop:
    &quot;&quot;&quot;
    事件循环
    &quot;&quot;&quot;
    def __init__(self,*coroutines):
        self.task_list = [Task(c,self) for c in coroutines]
        self.poll = select.epoll()
        self.io_event_list = dict()
        self.timer_event_list = []

    def run(self):
        &quot;&quot;&quot;
        运行事件循环
        &quot;&quot;&quot;
        #默认等待时间，用于决定epoll等待时间
        default_wait_time = 10
        while True:
            #无待处理事件，结束循环
            if not self.task_list and not self.timer_event_list and not self.io_event_list:
                break
            now = time.time()                                                                                                                                                                               
            for timer_event in self.timer_event_list[:]:
                if timer_event.deadline &amp;lt;= now:
                    timer_event.call()
                    self.timer_event_list.remove(timer_event)

            while self.task_list:
                task = self.task_list.pop(0)
                task.step()
            # 修改一下下次的睡眠时间，防止睡眠时间过长，错过定时器
            min_left_time = default_wait_time
            for timer_event in self.timer_event_list:
                left_time = timer_event.deadline - now
                if left_time &amp;lt;= 0:
                    left_time = 0
                if left_time&amp;lt;min_left_time:
                    min_left_time = left_time
            next_wait_time = min(default_wait_time, min_left_time)

            # 等待IO事件（阻塞）
            events = self.poll.poll(next_wait_time)
            while events:
                fd,event = events.pop()
                self.io_event_list[fd]() # 调用回调函数，即Future的set_done方法
                self.poll.unregister(fd)
                self.io_event_list.pop(fd)

    def add_handler(self,fd, handler, events):
        self.io_event_list[fd] = handler
        self.poll.register(fd, events)
    
    def sleep(self, secs):
        future = Future()
        self.timer_event_list.append(TimerEvent(secs, partial(future.set_done, None)))
        return future
    
    def add_task(self,task:Task):
        self.task_list.append(task)

if __name__ == &quot;__main__&quot;:
    loop = EventLoop(coroutine(&quot;1&quot;),coroutine(&quot;2&quot;),coroutine(&quot;3&quot;))
    loop.run()

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这段示例代码中，通过使用最为基础的生成器，以及yield与send方法，模拟出了一个带有定时器事件、IO事件、任务事件的事件循环。当然，此处还未使用到wait、__wait__等机制，仅仅是在最简单的逻辑层面进行了一个实现，读者不妨本地运行试试，帮助理解。&lt;/p&gt;
&lt;p&gt;PS: 代码中使用了epoll，这是Linux特有的IO机制，在Windows下需要替换为select。&lt;/p&gt;
&lt;h2&gt;实战——实现一个Sleep&lt;/h2&gt;
&lt;p&gt;python协程的使用案例网络上的例子相当之多，此处我不打算再重复一遍，而是通过手动实现一个sleep功能，来帮助深入理解协程及其事件循环的机理。&lt;/p&gt;
&lt;p&gt;下面是一段再寻常不过的异步代码，但是其中的sleep的实现却涉及到事件循环的Timer、以及直接yield future。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;async def main():
    await asyncio.sleep(1)
    print(&apos;Hello&apos;)

asyncio.run(main())
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;首先我们要知道，python原生的协程中是不能使用yield关键字的，其只能使用await(yield from)。于是乎，我们无法在async def中通过生成future再抛出的方式来实现sleep。剩下的方案便剩下求助于基于生成器的协程。asyncio中便是如此实现的。&lt;/p&gt;
&lt;p&gt;下面为python中对sleep函数的实现。可以看到，由于使用的是基于生成器的协程，可以直接yield一个None，以及使用yield from一个future。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@coroutine
def sleep(delay, result=None, *, loop=None):
    &quot;&quot;&quot;Coroutine that completes after a given time (in seconds).&quot;&quot;&quot;
    
    class helper:
        
        def __await__(self):
            yield None
        
        __iter__ = __await__
    
    if delay==1:
        yield from helper()
        return result

    if loop is None:
        loop = events.get_event_loop()
    future = loop.create_future()
    h = future._loop.call_later(delay,
                                futures._set_result_unless_cancelled,
                                future, result)
    try:
        return (yield from future)
    finally:
        h.cancel()
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;值得注意的是，在设置h的时候，我们使用了future._loop.call_later，这个函数的作用是设置一个定时器，当定时器触发时，将future的结果设置为result。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;但是，我们知道，现在在python中使用@coroutine注解是一种depreciated的行为，并且python计划在python3.11版本移除这个注解，那么届时该如何实现sleep呢？&lt;/p&gt;
&lt;p&gt;回顾我们对await关键字的讨论，其后面可以跟三种东西: coroutines、awaitables。既然我们无法在coroutines中使用yield，那么我们只能使用awaitables。&lt;/p&gt;
&lt;p&gt;如下，我们在协程test2中定义了一个helper类，其中实现了__await__方法，这个方法返回一个生成器，其中按照delay yield一个设置了定时器的future。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import asyncio
import selectors
from asyncio import futures,events
from types import coroutine

async def test2():
    class helper:
        
        def __init__(self,delay):
            self.delay = delay
        
        def __await__(self,loop=None):
            delay = self.delay
            result = &quot;&quot;
            if delay==1:
                yield None
                return result

            if loop is None:
                loop = events.get_event_loop()
            future = loop.create_future()
            h = future._loop.call_later(delay,
                                        futures._set_result_unless_cancelled,
                                        future, result)
            try:
                return (yield from future)
            finally:
                h.cancel()

        #__iter__ = __await__
    await helper(2)

selector = selectors.SelectSelector()
loop = asyncio.SelectorEventLoop(selector)
asyncio.set_event_loop(loop)
loop.run_until_complete(test2())
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如此便能够实现自己的sleep，类似的，对于epoll的事件注册也可以通过这种形式来完成。&lt;/p&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;Python使用原有的生成器机制，实现了一种类似状态机的无栈协程，并通过Future对象在任务、外部事件间建立依赖关系，从而实现有序的控制流。&lt;/p&gt;
&lt;h3&gt;拓展&lt;/h3&gt;
&lt;p&gt;本文对await、Future、Task、EventLoop的机制进行了分析，并通过代码对其进行模拟实现。本文中的事件循环是基于普通生成器的，而Python3.5后就对普通生成器与协程进行了区分，并提供async与await关键字作为协程接入事件循环的接口。Python自身也提供了标准的事件循环相关库——asyncio。 此外，python还提供了许多协程相关机制，如async for/async with等。本文就暂且略过，留给读者自行探索。&lt;/p&gt;
&lt;h3&gt;应用场景&lt;/h3&gt;
&lt;p&gt;协程机制为受限于GIL的python提供了除多进程外的又一高效并发方式，但与多进程不同的是，协程仅在IO密集型任务中才能发挥其优势，而在CPU密集型任务中，协程的优势并不明显，还是应当使用基于Process的多进程来实现。&lt;/p&gt;
</content:encoded></item><item><title>C++的继承机制</title><link>https://blog.logres.icu/posts/cpp%E7%9A%84%E5%A4%9A%E6%80%81%E6%9C%BA%E5%88%B6/cpp%E7%9A%84%E5%A4%9A%E6%80%81%E6%9C%BA%E5%88%B6/</link><guid isPermaLink="true">https://blog.logres.icu/posts/cpp%E7%9A%84%E5%A4%9A%E6%80%81%E6%9C%BA%E5%88%B6/cpp%E7%9A%84%E5%A4%9A%E6%80%81%E6%9C%BA%E5%88%B6/</guid><description>对C++的继承机制的研究</description><pubDate>Fri, 10 Jun 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;面对C++，我总会回想起那个睡觉迟到而被老师电话提醒的遥远下午……&lt;/p&gt;
&lt;p&gt;&amp;lt;!--more--&amp;gt;&lt;/p&gt;
&lt;p&gt;本以为自己跟这门语言将再无瓜葛，却又再次相逢。近日，好友学习C++，遇到许多底层机制，向我问起，我仅能在脑中模模糊糊得寻得&lt;em&gt;虚函数&lt;/em&gt;、&lt;em&gt;虚基类表&lt;/em&gt;等意义不明的词语。但答不上总觉着有些对不起那个叫我起床的老师，索性半回忆半学习，将这C++的继承机制整个明白吧！&lt;/p&gt;
&lt;h2&gt;概述&lt;/h2&gt;
&lt;p&gt;C++是我学习的第一门OO语言，也是机制最为繁复的OO语言，后来学习的Java、Python等都用解释器、虚拟机把机制封装起来，而C++还是老老实实地遵循C的道路，直接编译成机器码，因而其内部机制也更为透明。&lt;/p&gt;
&lt;p&gt;C++的继承机制上，难点有二。其一是，如何实现延迟绑定，即在运行时确定调用方法，以实现子类多态；其二是，如何解决C++的菱形继承带来的语义冲突的问题。&lt;/p&gt;
&lt;h2&gt;延迟绑定——虚函数&lt;/h2&gt;
&lt;p&gt;要讨论C++如何用虚函数实现延迟绑定，须要先了解延迟绑定是什么，以及它在子类多态中的作用。&lt;/p&gt;
&lt;h3&gt;延迟绑定&lt;/h3&gt;
&lt;p&gt;何谓延迟绑定？我们知道，在C语言中，我们代码中的函数调用在编译时将被转换为汇编语言，指向函数的相对地址，并在重定位时变更为函数代码（汇编码）的绝对地址，如此，在CPU执行机器指令时才能更新pc计数器，找到函数的汇编码所在。&lt;/p&gt;
&lt;p&gt;这样的机制，足够C语言使用了，但是在C++中，为了满足面向对象编程的&lt;strong&gt;子类多态&lt;/strong&gt;，简单的编译时绑定地址就不够用了。&lt;/p&gt;
&lt;hr /&gt;
&lt;h4&gt;补充：子类多态&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;class Button {
    void clicked(){
        cout &amp;lt;&amp;lt; &quot;Button clicked&quot; &amp;lt;&amp;lt; endl;
    }
};

class ButtonParentheses : public Button {
    void clicked(){
        cout &amp;lt;&amp;lt; &quot;(Button clicked)&quot; &amp;lt;&amp;lt; endl;
    }
};

class ButtonSquareBrackets : public Button {
    void clicked(){
        cout &amp;lt;&amp;lt; &quot;[Button clicked]&quot; &amp;lt;&amp;lt; endl;
    }
};

int main(){
    Button* b = new ButtonParentheses();
    b-&amp;gt;clicked();
    return 0;
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上述代码定义了一个按钮类，以及两个子类，其中一个是括号按钮，另一个是方括号按钮。面向OO，我们希望持有一个括号按钮，而不关心其实现，只关心按钮的行为，以此实现在不改变调用者代码的情况下，通过替换引用的指向改变行为。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;猜猜上述代码会调用输出什么？是&quot;Button clicked&quot;，还是&quot;(Button clicked)&quot;？答案是&quot;Button clicked&quot;。可是我们明明指向的是ButtonParentheses类型对象啊！问题在于绑定时机。虽然Button&amp;amp; b = new ButtonParentheses();指定了引用的对象，但实际上，编译器由于编译器不了解从Button&amp;amp; b = new ButtonParentheses();开始的控制流，后续的所有对b的引用，都只能假定他是一个Button类型对象——正如其引用类型所描述的&lt;strong&gt;Button&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;如何破局？很简单，既然在编译时不能确定引用的对象的类型，就留待运行时再确定。这就是延迟绑定，也即动态联编。&lt;/p&gt;
&lt;h3&gt;虚函数&lt;/h3&gt;
&lt;p&gt;虚函数语法：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Button{
    virtual void clicked(){
        cout &amp;lt;&amp;lt; &quot;Button clicked&quot; &amp;lt;&amp;lt; endl;
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如上，我们再clicked方法前加上了virtual前缀，再次执行之前的代码时，即可获得预期的结果了。那么，怎么做到的呢？有什么东西在运行时帮我们进行了调用函数的匹配吗？可惜，没有。C++被编译完后同C一样，不过一堆汇编，没什么其他进程来辅助我们的程序了，因而，所有的机制，都蕴含在C++的汇编代码中，但是，汇编过于晦涩，不妨从C++对象的内存模型入手。&lt;/p&gt;
&lt;h4&gt;一般情况&lt;/h4&gt;
&lt;p&gt;在C++中，对象的内存模型是这样的：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./assets/%E6%97%A0%E8%99%9A%E5%87%BD%E6%95%B0.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;没错，我们的小b对象中是不包含任何函数指针的，所有的调用都在编译时进行绑定，直接替换为对代码段中Button:Clicked()函数的调用。&lt;/p&gt;
&lt;h4&gt;虚函数的情况&lt;/h4&gt;
&lt;p&gt;虚继承中，引入了虚表的概念，虚表是一个指针，指向一个虚函数表，虚函数表是一个数组，数组中的每一个元素都是一个指针，指向一个虚函数。这么讲可能有些凌乱，我们看图说话。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./assets/%E8%99%9A%E5%87%BD%E6%95%B0.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;其他函数暂且不提，我们只关心虚函数Clicked的情况。如上图所示，每个对象的最开始位置存在一个Vptr，指向&lt;strong&gt;虚函数表&lt;/strong&gt;。每个类都有自己的虚函数表，其中存放着该类所有虚函数所对应的函数在代码段的地址，换句话说——函数指针。&lt;/p&gt;
&lt;p&gt;那么，基于该结构的函数调用呢？在虚函数机制下，编译器并非简单地将函数调用按照引用类型绑定函数的地址上，而是&lt;strong&gt;调用虚表中偏移量为x的函数指针所指向的函数&lt;/strong&gt;。调用一个函数指针的函数，这在汇编中是可行的，因而我们可以相信编译器能够实现这件事。&lt;/p&gt;
&lt;p&gt;那么剩下的就是确认偏移量x了，这要求我们先了解虚函数表的构造流程。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;父类构造自己的虚函数表，将自己的虚函数按顺序填入&lt;/li&gt;
&lt;li&gt;子类则继承（复制）父类的虚函数表，并且在子类的虚函数表中尾部添加自己的虚函数。&lt;/li&gt;
&lt;li&gt;子类对父类方法的重写，即在虚函数表中将子类的重写方法的地址替换掉对应位置的父类方法的地址。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这样，我们会得到这样一个视图。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./assets/%E8%99%9A%E8%A1%A8%E6%9E%84%E5%BB%BA.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;如图，基类Button自己构建自己的虚函数表，而ButtonParentheses则继承了Button的虚函数表，即黄色框中的内容。同时，由于ButtonParentheses子类重写了Clicked方法，于是基类虚表中的第一个函数指针（红色标识）被替换成了子类的Clicked方法的地址。最后，子类将自己额外的虚函数添入虚函数表的末尾（天蓝色）。注意到：基类的方法在自己的与子类的虚表中位置相对于虚表起始位置的偏移量是固定的，哪怕是子类重写了基类的方法。&lt;/p&gt;
&lt;p&gt;于是，我们就可以按照基类的方法便宜确定所有子类中基础虚函数方法的偏移，从而实现调用，这个固定的布局就成为了一种约定。&lt;/p&gt;
&lt;p&gt;总结：&lt;strong&gt;这个偏移处的方法指向谁不知道，但肯定是基类原本方法（未重写），或子类方法（重写）。&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;进一步讨论&lt;/h3&gt;
&lt;p&gt;在了解虚函数、虚表概念后，我们思考一下现在持有指向子类对象的基类引用的函数调用流程。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;从this指针访问对象内存，获取vptr指针&lt;/li&gt;
&lt;li&gt;利用vptr指针，从对象中获取所需访问的方法的函数指针&lt;/li&gt;
&lt;li&gt;结合this指针，与函数指针，调用函数&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;十分自然而简单，但是在多继承环境下，以上条件并不成立。同上，我们先建立一个情景。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
class ButtonA {
    int a;
    virtual void AMethod() {
        printf(&quot;a = %d\n&quot;,a);
    }
};

class ButtonB{
    int b;
    virtual void BMethod() {
        printf(&quot;b = %d\n&quot;,b);
    }
    virtual void BMethod2() {
        printf(&quot;b2 = %d\n&quot;,b);
    }
};

class ButtonC: public ButtonA, public ButtonB {
    int c;
    virtual void AMethod() {
        printf(&quot;a = %d\n&quot;,c);
    }

    virtual void B2Method() {
        printf(&quot;b = %d\n&quot;,c);
    }

    virtual void CMethod() {
        printf(&quot;c = %d\n&quot;,c);
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;现在我们有ButtonA、ButtonB两个类，以及一个继承了ButtonA、ButtonB的ButtonC类。描绘一下ButtonC的内存结构。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./assets/%E5%A4%9A%E7%BB%A7%E6%89%BF%E8%99%9A%E5%87%BD%E6%95%B0.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这结构一下子就比之前单继承复杂多了，我们慢慢分析：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;引用指向对象时，可能指向对象内存中的不同部分，使用ButtonA、ButtonC指针将指向最开始，而ButtonB则指向ButtonB虚指针处。这是使得调用ButtonB的方法时，this指针加上偏移能正确访问到ButtonB的成员。&lt;/li&gt;
&lt;li&gt;ButtonC类内存中仍旧含有ButtonA、ButtonB的内存结构，且在尾部补充自己的int c成员，值得注意的是，ButtonC本身仍旧没有自己的虚指针，而是与ButtonA共用，且将自己的新虚函数追加到末尾，而ButtonB则单独持有一个虚指针，他们共同指向ButtonC的虚函数表，但指向的位置不同。&lt;/li&gt;
&lt;li&gt;虚函数表中出现了Top_offset与Type两个新字段。其实单继承中也有，不过用不到，所以忽略，此处画出。Type指向ButtonC的类型信息，为C++提供了运行时类型信息。Top_offset跟虚函数表末尾的THUNK有关。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;现在再分析一下调用流程：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;若使用ButtonA、ButtonC引用，this指向最开始处，按照ButtonA的虚指针与偏移访问虚函数；若使用ButtonB引用，this指向ButtonB的虚指针处，按照ButtonB的虚指针与偏移访问虚函数&lt;/li&gt;
&lt;li&gt;若访问虚函数，未被重写，this+函数执行正常调用；若被重写：
&lt;ol&gt;
&lt;li&gt;ButtonA/C的情况，依旧按照偏移访问即可。&lt;/li&gt;
&lt;li&gt;ButtonB的情况，THUNK指向的虚函数，将this减去Top_offset，获得一个指向初始处的指针，再按照一的情况调用。这是为了防止ButtonC重写后访问了ButtonB的成员之外的字段。&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;PS:其实并没有什么判断重写，所有情况下都是this减去Top_offset，因为ButtonA/C的Top_offset都是0，所以表现得与没减一样。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;菱形继承——虚继承&lt;/h2&gt;
&lt;p&gt;C++中除了一般的继承外，还有一种虚继承。用虚继承干什么呢？主要就是为了解决菱形继承问题。&lt;/p&gt;
&lt;p&gt;菱形继承相比大家都很熟悉了，但还是声明一遍定义:两个子类继承同一个基类，同时两个子类又作为基类继承给同一个子类。这种继承形如菱形，故又称为菱形继承。&lt;/p&gt;
&lt;p&gt;打个比方，假如我们继着ButtonParentheses与ButtonSquareBrackets类产生一个新的类ButtonParenthesesSquareBrackets类：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./assets/%E8%8F%B1%E5%BD%A2%E7%BB%A7%E6%89%BF.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;上述继承就构成了一个菱形继承。&lt;/p&gt;
&lt;p&gt;菱形继承不挺好的嘛，有啥问题呢？这还须回到内存模型来解释。&lt;/p&gt;
&lt;h3&gt;非虚继承的内存模型&lt;/h3&gt;
&lt;p&gt;讨论内存模型前，我们需要丰富一下Button的场景。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Button {
public:
    int color;

public:

    virtual void clicked() {
        cout &amp;lt;&amp;lt; &quot;Button clicked&quot; &amp;lt;&amp;lt; endl;
    }
    virtual void doubleClicked() {
        cout &amp;lt;&amp;lt; &quot;Button double clicked&quot; &amp;lt;&amp;lt; endl;
    }
};

class ButtonParentheses : virtual public Button{
public:
    int parentheses_size;

public:
    virtual void clicked() {
        cout &amp;lt;&amp;lt; &quot;(Button clicked)&quot; &amp;lt;&amp;lt; endl;
    }
    virtual void change_parentheses_size() {
        cout &amp;lt;&amp;lt; &quot;size_changed&quot; &amp;lt;&amp;lt; endl;
    }
};

class ButtonSquareBrackets : virtual public Button{
public:
    int square_brackets_size;
public:

    virtual void clicked() {
        cout &amp;lt;&amp;lt; &quot;[Button clicked]&quot; &amp;lt;&amp;lt; endl;
    }
    virtual void change_square_brackets_size() {
        cout &amp;lt;&amp;lt; &quot;size_changed&quot; &amp;lt;&amp;lt; endl;
    }
};

class ButtonParenthesesSquareBrackets : public ButtonParentheses, public ButtonSquareBrackets{
public:
    int name;
public:
    virtual void clicked() {
        cout &amp;lt;&amp;lt; &quot;([Button clicked])&quot; &amp;lt;&amp;lt; endl;
    }
};

int main() {
    Button* a = new Button();
    Button* b = new ButtonParentheses();
    Button* c = new ButtonParenthesesSquareBrackets();
    b-&amp;gt;clicked();
    return 0;
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;现在，我们有一个Button类，其拥有一个按钮颜色属性，其拥有一个按钮点击方法，其拥有一个按钮双击方法。两个子类，分别为ButtonParentheses和ButtonSquareBrackets，二者都重写了基类的clicked方法，且分别有一个自己的括号/中括号尺寸属性以及对应的修改函数。最后，一个新的类ButtonParenthesesSquareBrackets，继承了ButtonParentheses和ButtonSquareBrackets，并重写了clicked方法。&lt;/p&gt;
&lt;p&gt;这个场景下，其内存模型如何呢？先分析没有虚继承的情况下的内存模型：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./assets/%E6%97%A0%E8%99%9A%E7%BB%A7%E6%89%BF.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;注：ButtonParenthesesSquareBrackets使用的是ButtonParentheses和Button共用的虚指针，其实就是一个简单的单继承与多继承的结合。&lt;/p&gt;
&lt;p&gt;可以看出，在非虚继承的情况下，继承就是在子类对象中保存了完整的基类对象，这就直接导致了一个严重的问题：&lt;/p&gt;
&lt;p&gt;在ButtonParenthesesSquareBrackets中，存在着两个沿着两条路径继承下来的Button。一方面导致了内存空间的浪费，另一方面也导致指代的歧义，访问color时，到底访问的是哪一个Button的color属性呢？&lt;/p&gt;
&lt;h3&gt;虚继承的内存模型&lt;/h3&gt;
&lt;p&gt;虚继承是如何解决这个问题的呢？&lt;/p&gt;
&lt;p&gt;虚函数机制引入了虚表，把直接调用变为间接调用，解决了子类多态的问题；同样，我们可以引入新的中间结构，把对基类的基类的引用转为间接，就可以引用同一个基类的基类，避免重复了。&lt;/p&gt;
&lt;p&gt;不同的编译器有不同的解决方案：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;引入新的表——虚基类表&lt;/li&gt;
&lt;li&gt;拓展虚表&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这里仅讨论拓展虚表的情况，引入新表思路是类似的。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Button {
public:
    int color;

public:

    virtual void clicked() {
        cout &amp;lt;&amp;lt; &quot;Button clicked&quot; &amp;lt;&amp;lt; endl;
    }
    virtual void doubleClicked() {
        cout &amp;lt;&amp;lt; &quot;Button double clicked&quot; &amp;lt;&amp;lt; endl;
    }
};

class ButtonParentheses : virtual public Button{
public:
    int parentheses_size;

public:
    virtual void change_parentheses_size() {
        cout &amp;lt;&amp;lt; &quot;size_changed&quot; &amp;lt;&amp;lt; endl;
    }
};

class ButtonSquareBrackets : virtual public Button{
public:
    int square_brackets_size;
public:

    virtual void change_square_brackets_size() {
        cout &amp;lt;&amp;lt; &quot;size_changed&quot; &amp;lt;&amp;lt; endl;
    }
};

class ButtonParenthesesSquareBrackets : public ButtonParentheses, public ButtonSquareBrackets{
public:
    int name;
public:
    virtual void clicked() {
        cout &amp;lt;&amp;lt; &quot;([Button clicked])&quot; &amp;lt;&amp;lt; endl;
    }
};

int main() {
    Button* a = new Button();
    Button* b = new ButtonParentheses();
    Button* c = new ButtonParenthesesSquareBrackets();
    b-&amp;gt;clicked();
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;首先，我们之前的模型中，两个子类都重写了父类的clicked方法，这种重复无法靠虚继承解决，因此我们将其删去。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./assets/%E8%8F%B1%E5%BD%A2%E8%99%9A%E7%BB%A7%E6%89%BF.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;现在我们得到了使用虚继承后的各个类的内存模型。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;对于Button，虚继承对其无影响&lt;/li&gt;
&lt;li&gt;对于ButtonParentheses，可以观测到，现在其不再复用Button的vptr，而是自己独有一个vpter，且Button的成员被置于末尾。&lt;/li&gt;
&lt;li&gt;对于ButtonParenthesesSquareBrackets，其是简单多继承与虚继承的结合，首先按照简单多继承存放ButtonParentheses、ButtonSquareBrackets、自身成员，但在最后补上了Button的成员，且其复用的还是第一个继承的ButtonParentheses的vptr。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;观测虚表：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;虚表中多出了Virtual_base_offset字段，其余与原始类型一致。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;解释Virtual_base_offset字段&lt;/strong&gt;：在这样的菱形继承中，编译器将生成虚表的表，即VTT，用于记录每个类的局部虚表起始的位置。于是，现在使用ButtonParentheses类型引用调用clicked方法时，将会将this指针加上Virtual_base_offset，将this移动到Button类的vptr处，调用Button的clicked方法。但同时，由于ButtonParenthesesSquareBrackets类重写了clicked方法，因此此处的THUNK又会将this指针移动到最开始处，调用ButtonParenthesesSquareBrackets的clicked方法，由此实现了虚基类的复用。&lt;/p&gt;
&lt;p&gt;Question：如果ButtonP与ButtonSB重写了clicked方法呢？
Answer: 仍旧使用THUNK将其引导至ButtonPSB重写处，若ButtonPSB未重写，则按照引用调用各自重写的方法，若引用为Button或ButtonPSB，则调用ButtonP的（ButtonP是第一个继承对象，与ButtonPSB共享vptr）。&lt;/p&gt;
&lt;p&gt;至此，我们完成了对虚继承的讨论。&lt;/p&gt;
&lt;p&gt;疑惑：在上述代码中，ButtonPSB对ButtonP与ButtonSB的继承无论是否声明为virtual，其结果都是相同的？这个问题仍然留待探索。&lt;/p&gt;
&lt;h3&gt;皮一下&lt;/h3&gt;
&lt;p&gt;最后，提一个问题，Java等语言是如何避免菱形继承的呢？&lt;/p&gt;
&lt;p&gt;很简单，不允许多继承，自然就不会产生菱形继承了。&lt;/p&gt;
&lt;h2&gt;参考文献&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://shaharmike.com/cpp/vtable-part1/&quot;&gt;https://shaharmike.com/cpp/vtable-part1/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://shaharmike.com/cpp/vtable-part2/&quot;&gt;https://shaharmike.com/cpp/vtable-part2/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://shaharmike.com/cpp/vtable-part3/&quot;&gt;https://shaharmike.com/cpp/vtable-part3/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://shaharmike.com/cpp/vtable-part4/&quot;&gt;https://shaharmike.com/cpp/vtable-part4/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://codeantenna.com/a/7RsvVrjPen&quot;&gt;https://codeantenna.com/a/7RsvVrjPen&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://zhuanlan.zhihu.com/p/41309205&quot;&gt;https://zhuanlan.zhihu.com/p/41309205&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
</content:encoded></item><item><title>浅谈协程</title><link>https://blog.logres.icu/posts/%E6%B5%85%E8%B0%88%E5%8D%8F%E7%A8%8B/</link><guid isPermaLink="true">https://blog.logres.icu/posts/%E6%B5%85%E8%B0%88%E5%8D%8F%E7%A8%8B/</guid><description>聊一聊协程概念</description><pubDate>Tue, 07 Jun 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;期末赋闲，网上冲浪，花了点时间了解了&lt;strong&gt;协程&lt;/strong&gt;这么一个奇妙的玩意，没想到就一路从&lt;strong&gt;并发&lt;/strong&gt;查到了&lt;strong&gt;python的生成器&lt;/strong&gt;，索性写一篇随笔，讲讲自己的所见所得。&lt;/p&gt;
&lt;p&gt;&amp;lt;!--more--&amp;gt;&lt;/p&gt;
&lt;h2&gt;说说并发&lt;/h2&gt;
&lt;p&gt;进程、线程、协程，都是为了解决并发问题而存在的，所以，不如在讨论前先把并发说道清楚。&lt;/p&gt;
&lt;p&gt;有计算机基础的同学一定熟悉这张图。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./assets/Von_Neumann_architecture.png?30&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;没错，就是现代计算机的基础——冯诺依曼架构。&lt;/p&gt;
&lt;p&gt;上面的Memory对应内存，中间的控制单元与计算单元对应CPU、GPU，Input与Output则是硬盘、网卡等IO设备。&lt;/p&gt;
&lt;p&gt;在最朴素的冯诺依曼架构中，我们将数据存入/读出内存是需要&lt;strong&gt;CPU参与协调&lt;/strong&gt;的，所以程序执行到输入，是必然要阻塞的。此时的并发：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;没有多核，并发并不能做到并行，但并发仍旧是有意义的，我们可以时分复用，&lt;strong&gt;使多个程序表现得像同时执行一样&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;而有多核之后，更是能够&lt;strong&gt;真正的并行，充分利用CPU&lt;/strong&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;进程有了，那么怎么调度呢？我们知道，贸然打断一个进程，执行其他进程，是有可能产生数据访问冲突的，进程A正在读写磁盘的某一数据呢，B横插一脚，改了改数据，这还聊得，因而我们需要给资源上锁来保证自己的资源不会在自己被挂起时随意访问。当然，最初的处理方案可没&lt;strong&gt;锁&lt;/strong&gt;这么费时费力的方案，而是一种更简单明了的思路——既然“贸”然打断会造成问题，我等到你觉得可以被打断再打断不就好了？这就是&lt;strong&gt;协作式多任务处理&lt;/strong&gt;，也就是如今协程的雏形。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;协作式多任务（Cooperative Multitasking），是一种多任务方式，多任务是使电脑能同时处理多个程序的技术，相对于抢占式多任务（Preemptive multitasking），协作式多任务要求每一个运行中的程序，定时放弃自己的执行权利，告知操作系统可让下一个程序执行。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;既然协作式多任务处理可行，为什么还需要发展出&lt;strong&gt;抢占式多任务处理&lt;/strong&gt;呢？&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;抢占式多任务处理（Preemption）是计算机操作系统中，一种实现多任务处理（multi task）的方式，相对于协作式多任务处理而言。协作式环境下，下一个进程被调度的前提是当前进程主动放弃时间片；抢占式环境下，操作系统完全决定进程调度方案，操作系统可以剥夺耗时长的进程的时间片，提供给其它进程。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;每个任务赋予唯一的一个优先级（有些操作系统可以动态地改变任务的优先级）&lt;/li&gt;
&lt;li&gt;假如有几个任务同时处于就绪状态，优先级最高的那个将被运行；&lt;/li&gt;
&lt;li&gt;只要有一个优先级更高的任务就绪，它就可以中断当前优先级较低的任务的执行；&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;这很好解释。如果把进程使用CPU比作如厕，那么抢占就像是随时把人拉出来，抢个坑位；而非抢占则是等一个人觉得这一阶段差不多了，先出来，让给后边人，之后再补上后边的——假设一个人上厕所可以分几个阶段进行（&lt;s&gt;喷射&lt;/s&gt;）。抢占环境下，虽然大家都不痛快，但是好歹都能上厕所；那非抢占式呢？假如来个不文明的，占着茅坑不&lt;s&gt;拉屎&lt;/s&gt;，后边的人不就憋死了吗。当然，人还是讲文明的，但是写的烂的程序可不管，自顾自地死循环。&lt;/p&gt;
&lt;p&gt;于是，虽然需要进行上锁等额外步骤，抢占式多任务处理仍然成为了操作系统的主流设计思路。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Windows 3.x 支持的是协作式多任务，但从Win95开始，抢占式多任务处理成为普遍的设计。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;说说IO&lt;/h2&gt;
&lt;p&gt;上述讨论中，我们了解到：一个CPU核心同时仅能执行一个任务（进程/线程），并发的多任务在多个核上通过调度策略——协作式/抢占式多任务处理进行调度，分享计算资源。&lt;/p&gt;
&lt;p&gt;CPU的资源分配是解决了，那么其他资源呢？内存早就由页表分配给了每个程序，剩下的就是IO了。&lt;/p&gt;
&lt;p&gt;IO指输入输出，泛指一切输入输出设备，包括硬盘、网卡、外设等等。&lt;/p&gt;
&lt;p&gt;我们知道，早年的IO操作是需要CPU全程参与的，但数据传输其实本身是较为简单的，导致CPU大部分时间还是在看着数据慢慢地从IO移动到内存，又或移出，太浪费CPU的时间了。索性给个够用就行的芯片吧！&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./assets/DMA.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;于是，有了DMA技术，即&lt;strong&gt;直接内存访问(Direct Memory Access)&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;直接内存访问（Direct Memory Access，DMA）是计算机科学中的一种内存访问技术。它允许某些电脑内部的硬件子系统（电脑外设），可以独立地直接读写系统内存，而不需中央处理器（CPU）介入处理 。在同等程度的处理器负担下，DMA是一种快速的数据传送方式。很多硬件的系统会使用DMA，包含硬盘控制器、绘图显卡、网卡和声卡。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;通过DMA控制器，处理数据移动的计算任务交给了DMA Controller，CPU则可以在这个时间做别的事了；而且，由于IO不需要CPU参与，我们甚至可以并发IO，而不用担心CPU核不够用。&lt;/p&gt;
&lt;p&gt;此时，某任务进入IO操作了，且需要数据进行后续处理，操作系统就可以将其挂起，先去执行别的任务，待其IO完毕再调度它；更进一步，这就使得异步成为可能，任务（进程/线程）可以启动读取IO，然后忙自己的，等到数据到位，再进行处理——即事件驱动模型——可以参考笔者的网络编程IO模型小记。&lt;/p&gt;
&lt;h2&gt;回到进程、线程与协程&lt;/h2&gt;
&lt;p&gt;进程自不必多说，是操作系统对程序运行时的程序、占有的资源的抽象，是讨论任务调度的基础。&lt;/p&gt;
&lt;p&gt;但是，进程的调度需要涉及进程的上下文切换，耗时耗力，很容易成为性能瓶颈，且进程间通信（IPC）耗费亦大；于是，更符合现代软件开发，共用内存内容、文件描述符等资源的线程就成为了进程的补充，在一个程序内更轻量地并行任务。&lt;/p&gt;
&lt;p&gt;进程与线程的调度都是操作系统提供支持的，换句话说，抢占式的多任务处理，这也符合&lt;strong&gt;谈谈并行&lt;/strong&gt;中的论述。&lt;/p&gt;
&lt;p&gt;那么协程呢？先看看wiki定义。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;协程（英语：coroutine）是计算机程序的一类组件，推广了协作式多任务的子例程，允许执行被挂起与被恢复。相对子例程而言，协程更为一般和灵活，但在实践中使用没有子例程那样广泛。协程更适合于用来实现彼此熟悉的程序组件，如协作式多任务、异常处理、事件循环、迭代器、无限列表和管道。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我再对现在广泛采用的协程给出一个定义：由用户自行支持的协作式多任务调度机制。&lt;/p&gt;
&lt;p&gt;没错，在抢占式当道的现在，协程是不能被操作系统看见的，不然就被强行抢占了，故仅能存在于用户空间，比如执行在某一线程里。&lt;/p&gt;
&lt;p&gt;等等？前面不是说协作式就像上厕所啥的，有人占着茅坑就全完蛋吗？怎么现在又行了？&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;别急，听我慢慢讲。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;行不行是分场合的，在整个计算机上，运行的程序千奇百怪，很可能一个老鼠屎坏了一锅粥，而且由于程序是用户安装执行的，我们没法要求用户鉴别垃圾软件，因此使用抢占式有利于操作系统处理垃圾进程。但在一个进程内呢？程序是一个团队开发的，各个线程就像一家人，就不能有点基本信任吗？何必害的每个人都不爽呢。因此，在协程角度，我们将摒除垃圾协程的任务交给开发者，也就不需要抢占了，另外，由于每个协程都能在自己认为合适的时刻被中断，许多用于保证&lt;strong&gt;事务完整性&lt;/strong&gt;的锁也就不必要了，降低了切换开销。&lt;/p&gt;
&lt;p&gt;作为一种任务调度机制，不能为操作系统所支持，也就无法享受多核并发的优势，可以说是糟透了，但是我们仍然可以&lt;strong&gt;使程序表现得像一起执行一样&lt;/strong&gt;——此话怎讲？这就与现在的异步IO脱不开关系了。&lt;/p&gt;
&lt;p&gt;DMA的异步IO下，IO与任务流解绑，不仅是CPU可以从阻塞的任务中抽身，连任务本身都不需要为IO所阻塞，先启动IO任务，然后忙别的就行。&lt;/p&gt;
&lt;p&gt;这种机制下，一个线程中的多个协程，便可以先开启自己所需的IO，然后接力到下一个协程，让他开始IO，最后，待到自己的IO完成，再进行处理即可，完全不涉及到操作系统中的上下文切换，在一个线程内就完成了任务——尤其适合&lt;strong&gt;IO密集型任务&lt;/strong&gt;，如代理服务器等，计算仅涉及协议检验，大部分时间都在将数据从In网卡搬运到Out网卡；但是对于计算密集型，由于缺乏多核调度能力，还是需要依赖多线程执行。&lt;/p&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;无法控制的进程行为导致协作式多任务处理不可行&lt;/li&gt;
&lt;li&gt;同一程序内的协程受到开发者的管理，为协作式多任务处理提供基础&lt;/li&gt;
&lt;li&gt;缺乏操作系统支持，无法充分利用CPU资源，但在DMA与异步IO支持下，IO并行成为可能，为协程提供了应用场景。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;协程在某种程度上将控制流串行化，避免了多线程异步中繁琐的回调，使得控制逻辑更为清晰，之后，有机会的话，我还想从控制流与逻辑流的角度来讲一讲协程。&lt;/p&gt;
&lt;h2&gt;参考文献&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://zhuanlan.zhihu.com/p/147608872&quot;&gt;https://zhuanlan.zhihu.com/p/147608872&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://zh.wikipedia.org/zh-cn/%E7%9B%B4%E6%8E%A5%E8%A8%98%E6%86%B6%E9%AB%94%E5%AD%98%E5%8F%96&quot;&gt;https://zh.wikipedia.org/zh-cn/直接記憶體存取&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://zh.wikipedia.org/zh-cn/%E6%8A%A2%E5%8D%A0%E5%BC%8F%E5%A4%9A%E4%BB%BB%E5%8A%A1%E5%A4%84%E7%90%86&quot;&gt;https://zh.wikipedia.org/zh-cn/抢占式多任务处理&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://zh.m.wikipedia.org/zh-hans/%E5%86%AF%C2%B7%E8%AF%BA%E4%BC%8A%E6%9B%BC%E7%BB%93%E6%9E%84&quot;&gt;https://zh.m.wikipedia.org/zh-hans/冯·诺伊曼结构&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://zh.wikipedia.org/zh-cn/%E5%8D%8F%E4%BD%9C%E5%BC%8F%E5%A4%9A%E4%BB%BB%E5%8A%A1&quot;&gt;https://zh.wikipedia.org/zh-cn/协作式多任务&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
</content:encoded></item><item><title>Computer Networking-A Top-Down Approach 学习笔记</title><link>https://blog.logres.icu/posts/computer-networking-a-top-down-approach-%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/</link><guid isPermaLink="true">https://blog.logres.icu/posts/computer-networking-a-top-down-approach-%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/</guid><description>对Computer Networking-A Top-Down Approach学习笔记</description><pubDate>Wed, 12 May 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;此处对《计算机网络——自顶向下方法》的学习做一个小总结。&lt;/p&gt;
&lt;h2&gt;一、计算机网络模型&lt;/h2&gt;
&lt;h3&gt;TCP/IP模型&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;应用层&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;运输层&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;网络层&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;数据链路层&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;物理层&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;OSI模型&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;应用层&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;表示层&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;会话层&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;运输层&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;网络层&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;数据链路层&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;物理层&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;二、网络传输技术详解&lt;/h2&gt;
&lt;hr /&gt;
&lt;h2&gt;三、详解TCP/IP模型&lt;/h2&gt;
&lt;h3&gt;一、概述&lt;/h3&gt;
&lt;h4&gt;协议&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;协议&lt;/strong&gt;，即一系列公开的标准，是网络传输的核心。离开协议，不同的计算机间将无法互相理解，即使物理上相连也无法顺利进行通信。&lt;/p&gt;
&lt;h4&gt;数据单元&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;数据单元&lt;/strong&gt;为网络传输过程中的单位数据包，在不同的协议层中，单位数据包不同，故&lt;strong&gt;数据单元&lt;/strong&gt;对应不同的协议层有不同的称呼。&lt;/p&gt;
&lt;h4&gt;协议栈&lt;/h4&gt;
&lt;p&gt;用户数据自发送方送往接收方，在发送方经过应用层、运输层、网络层等层层协议，一层一层地封装成不同层次的数据单元，包装成一个层层包裹的数据块，当抵达接收方时，则按照物理层、数据链路层，这一与发送时相反的方向进行解包。其表现形式与栈类似，故将计算机网络中协议的组合，称为&lt;strong&gt;协议栈&lt;/strong&gt;。&lt;/p&gt;
&lt;h3&gt;二、应用层&lt;/h3&gt;
&lt;h4&gt;原始数据&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;原始数据&lt;/strong&gt;即为用户操作中直接产生的数据，包括消息、多媒体流等等。此处的&lt;strong&gt;原始数据&lt;/strong&gt;是相对&lt;strong&gt;网络数据&lt;/strong&gt;而言的。当原始数据被应用程序解读时，往往需要符合一定的格式，但是其不涉及网络传输的部分，我们仍旧认为其是原始数据。&lt;/p&gt;
&lt;h4&gt;应用层协议&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;应用层协议&lt;/strong&gt;将用户的应用层数据封装成应用层数据单元，再交付给接下来的网络层协议。联网应用程序的多种多样也催生了多种多样的应用层协议。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;HTTP协议&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SMTP协议&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DNS协议&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;BitTorrent协议&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;其协议规定的数据单元头部将规定如何还原网络数据为原始数据。再对原始数据进行处理。&lt;/p&gt;
&lt;h4&gt;应用层数据单元&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;应用层数据单元&lt;/strong&gt;由原始数据与应用层头部组成。是应用层协议中数据的最小单位。&lt;/p&gt;
&lt;h4&gt;应用层作用&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;应用层&lt;/strong&gt;向上为应用程序提供网络服务，而向下，则委托运输层完成取决于选定的运输层协议的任务（TCP/UDP）。&lt;/p&gt;
&lt;h3&gt;三、运输层&lt;/h3&gt;
&lt;h4&gt;运输层数据&lt;/h4&gt;
&lt;p&gt;对于运输层来说，由应用层交付的整个应用层数据单元（应用层头部与原始数据）被称之为&lt;strong&gt;运输层数据&lt;/strong&gt;。运输层并不关心应用层对数据的处理，仅仅完成自己的任务。&lt;/p&gt;
&lt;h4&gt;多路复用与多路分解&lt;/h4&gt;
&lt;p&gt;计算机可以同时与多个对象进行通信，故用不同的端口与不同对象交互，以避免混乱。对应用层，运输层提供套接字，应用层的进程只需要申请一个套接字，便可以该套接字进行网络通信。而在运输层，操作系统则将套接字与端口绑定（可能多个套接字一个端口、可能一对一）。
UDP下：对于网络中传来的报文段，操作系统则依靠源端口号、目的端口号二元组判断应该将该报文段交付给哪一个端口。
TCP下：则依靠源IP、目的IP、源端口号、目的端口号四元组判断应交付给哪一个套接字。
&lt;strong&gt;多路复用&lt;/strong&gt;即将不同套接字的数据处理发出的过程。
&lt;strong&gt;多路分解&lt;/strong&gt;即将收到的报文段分发给适当的套接字的过程。&lt;/p&gt;
&lt;h4&gt;运输层协议&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;运输层协议&lt;/strong&gt;仅包括两者：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;TCP&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;UDP&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;1.TCP&lt;/h5&gt;
&lt;p&gt;&lt;strong&gt;TCP&lt;/strong&gt;（Transmission Control Protocol），也称&lt;strong&gt;传输控制协议&lt;/strong&gt;。是两种传输协议的一种。其为应用层提供&lt;strong&gt;面向连接的，可靠的数据传输服务&lt;/strong&gt;。除去基本传输服务外，它也提供&lt;strong&gt;差错检验、数据流控制、拥塞控制等&lt;/strong&gt;服务。此外，还应注意到，TCP协议采用了分组交换策略，因此，TCP协议下的分组由传输层进行，使得报文段大小小于MSS&amp;lt;MTU，这样在IP段便不再需要分组。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;面向连接&lt;/strong&gt;：进行基于TCP的数据交换前，发送方与接收方需要先进行&lt;strong&gt;三次握手&lt;/strong&gt;，以确认连接关系。而结束交换后，则需要进行&lt;strong&gt;四次挥手&lt;/strong&gt;，以彻底断开连接。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可靠&lt;/strong&gt;：在连接的基础上，TCP依靠TCP头部中的Seq与Ack字段，使得发送方与接收方可以互相确认已成功传输的分组，并依照GBN或SR机制进行重传，以保证数据的成功传输。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;差错检验&lt;/strong&gt;：在TCP头部增设16位的校验和。其值为全部数据进行按16位回卷求和后的反码。在发送前计算与接收后分别计算该值，可以检测数据传输过程中是否发生错误。若发生，则进行修正或丢弃数据包。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;数据流控制&lt;/strong&gt;:接收方通过TCP头部中的剩余窗口字段，告知发送方其剩余容纳能力。发送方则保证其已发送的分组数与即将发出的分组数之和小于该值。控制方式：调整发送窗口的大小。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;拥塞控制&lt;/strong&gt;:基于不断调整自我发送窗口大小的机制，TCP协议得以避免在丢包、超时时不断重传，加重网络拥堵。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;拥塞控制详解：
TCP的窗口大小的管理模式有三种：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;慢启动
慢启动模式下，发送窗口大小以每1MSS为起点，成2、4、8、16的指数速度增长。直到1.抵达ssthresh阈值；2.发生超时或丢包。&lt;/li&gt;
&lt;li&gt;拥塞避免
拥塞避免模式下，发送窗口大小每RTT一MSS的速度线性增长。直到发生超时或丢包。&lt;/li&gt;
&lt;li&gt;快速恢复
快速恢复模式下，发送方先快速重发引发快速恢复分组。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;转换过程：运输层开始工作后进入&lt;strong&gt;慢启动&lt;/strong&gt;模式，当达到阈值ssthresh则转入&lt;strong&gt;拥塞避免&lt;/strong&gt;模式；若超时，则将ssthresh设为当前窗口大小的一半，重新开始&lt;strong&gt;慢启动&lt;/strong&gt;模式；丢包则执行&lt;strong&gt;快速恢复&lt;/strong&gt;，将ssthresh与窗口都设为当下窗口的一半，再进入&lt;strong&gt;拥塞避免&lt;/strong&gt;模式。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h5&gt;2.UDP&lt;/h5&gt;
&lt;p&gt;&lt;strong&gt;UDP&lt;/strong&gt;（User Datagram Protocol），用户数据报协议。是一种&lt;strong&gt;不面向连接的、不可靠的数据传输服务&lt;/strong&gt;。其仅提供&lt;strong&gt;差错检验&lt;/strong&gt;功能。UDP不采用分组策略，故UDP的分组在IP中进行。&lt;/p&gt;
&lt;h5&gt;3.比较&lt;/h5&gt;
&lt;p&gt;TCP 可靠 为拥塞避免所限制 连接较为繁复 速度较慢 应用：文件传输、邮件传输
UDP 不可靠 不受限制 无连接 速度快 应用：流媒体、DNS&lt;/p&gt;
&lt;p&gt;PS：运输层头部不包含IP地址，仅含（源、目的）端口号&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;4.网络层（概述与数据平面）&lt;/h3&gt;
&lt;h4&gt;网络层分组&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;网络层分组&lt;/strong&gt;称为&lt;strong&gt;数据报&lt;/strong&gt;，由运输层报文段加上网络层首部组成。&lt;/p&gt;
&lt;h4&gt;网络层协议&lt;/h4&gt;
&lt;p&gt;网络层协议，即IPInternet Protocol（网际互连协议）。向运输层提供&lt;strong&gt;尽力而为的传输服务&lt;/strong&gt;，即不保证可靠、按时，但是努力送达。TCP与UDP都由IP封装而成。相比于TCP、UDP为端到端服务，IP协议则控制数据包在网络中的传递。&lt;/p&gt;
&lt;h4&gt;IP服务模型&lt;/h4&gt;
&lt;h5&gt;数据平面&lt;/h5&gt;
&lt;p&gt;&lt;strong&gt;路由器&lt;/strong&gt;根据其内的路由表，将从输入端口传输而来的&lt;strong&gt;数据报&lt;/strong&gt;依照其目的IP地址，将其转发至对应的输出端口，完成数据包的传递，一一接力，最终抵达终点。数据平面完成&lt;strong&gt;转发&lt;/strong&gt;功能。&lt;/p&gt;
&lt;h5&gt;控制平面&lt;/h5&gt;
&lt;p&gt;&lt;strong&gt;控制平面&lt;/strong&gt;负责计算路由表，给数据平面的数据交换提供依据。&lt;/p&gt;
&lt;h6&gt;控制平面-传统方法&lt;/h6&gt;
&lt;p&gt;在传统方法中，路由选择协议由每台路由器自行执行，以确定与更新路由表。&lt;/p&gt;
&lt;h6&gt;控制平面-SDN方法&lt;/h6&gt;
&lt;p&gt;在SDN（Soft Defined Network）中，一定范围的网络内，所有路由器由远程控制服务器统一控制，故可高效快捷地实现路由表计算功能，同时可支持许多其他附加功能（按其余首部匹配转发终点）。&lt;/p&gt;
&lt;h5&gt;路由器工作原理&lt;/h5&gt;
&lt;h6&gt;分组交换&lt;/h6&gt;
&lt;p&gt;为实现传输的有效性，IP协议采用了分组交换策略，使得数据报大小大于46，小于1500，称为MTU。&lt;/p&gt;
&lt;h6&gt;组成&lt;/h6&gt;
&lt;p&gt;数据平面：（常为硬件组成）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;输入端口&lt;/li&gt;
&lt;li&gt;输出端口&lt;/li&gt;
&lt;li&gt;交换结构&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;控制平面：（常为软件构成）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;路由选择处理器&lt;/li&gt;
&lt;/ul&gt;
&lt;h6&gt;输入端口&lt;/h6&gt;
&lt;p&gt;输入端口负责：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;将分组从物理层解包到数据链路层&lt;/li&gt;
&lt;li&gt;将分组经过数据链路层校验后拼凑成网络层数据报&lt;/li&gt;
&lt;li&gt;根据头部数据信息（目的IP地址等），将得到的网络层数据报发往对应的输出端口&lt;/li&gt;
&lt;/ol&gt;
&lt;h6&gt;交换结构&lt;/h6&gt;
&lt;p&gt;交换结构负责：&lt;/p&gt;
&lt;p&gt;将各个输入端口的数据报分发给正确的输出端口&lt;/p&gt;
&lt;h6&gt;输出端口&lt;/h6&gt;
&lt;p&gt;输出端口执行输入端口相反的工作：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;接收来自交换结构的数据报&lt;/li&gt;
&lt;li&gt;将数据报添加数据链路层头部&lt;/li&gt;
&lt;li&gt;转换成物理信号发出&lt;/li&gt;
&lt;/ol&gt;
&lt;h6&gt;路由选择处理器&lt;/h6&gt;
&lt;p&gt;执行控制平面功能，在传统模式下，为数据平面提供路由表；而在SDN模式下，则负责与远程控制器通信，更新维护转发表项。&lt;/p&gt;
&lt;h4&gt;IP&lt;/h4&gt;
&lt;h5&gt;IPv4&lt;/h5&gt;
&lt;p&gt;要点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;IPv4采用32位地址进行编址。&lt;/li&gt;
&lt;li&gt;其首部长为20字节。&lt;/li&gt;
&lt;li&gt;分片：由于数据报大小受链路层限制，故必要时需对网络层数据包进行分片。&lt;/li&gt;
&lt;/ol&gt;
&lt;h5&gt;编址&lt;/h5&gt;
&lt;h6&gt;基本概念&lt;/h6&gt;
&lt;p&gt;IP地址与接口一一关联（具有多个接口的路由器具有多个IP地址）。IPv4地址为32位，4个字节，常采用点分十制记法进行书写，即以10进制数字表示每个字节，其间由点分开。&lt;/p&gt;
&lt;p&gt;PS:特殊地址：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;0.0.0.0/8 在本地网络中代表自己，仅允许被用作源，用作目的地时，网络包将会被&lt;/li&gt;
&lt;li&gt;255.255.255.255/32 表示受限的广播（局域网广播）&lt;/li&gt;
&lt;li&gt;127.0.0.0/8 用于回环（访问自身）&lt;/li&gt;
&lt;/ul&gt;
&lt;h6&gt;子网&lt;/h6&gt;
&lt;p&gt;IP地址的分配遵循&lt;strong&gt;子网&lt;/strong&gt;原则，即与一个端口关联的主机IP地址，具有一定长度相同的前缀。分配网络地址时，一个组织往往分配具有一定长度相同前缀的IP地址。在路由器中，又额外添加&lt;strong&gt;子网掩码&lt;/strong&gt;这一项来指出子网前缀在IP地址中的长度，这样一来，对于路由表而言，地址匹配工作将大大降低。另一方面，为保证路由器的灵活性，设定&lt;strong&gt;最长前缀匹配原则&lt;/strong&gt;，使得一台路由器可以处理不在其相关联子网中的某一IP地址。而具有相同前缀的多个子网连接在同一较大（前缀较短）子网下，又可以进一步合并子网，称之为&lt;strong&gt;路由聚合&lt;/strong&gt;，使得网络结构更加层次化。&lt;/p&gt;
&lt;p&gt;PS:子网掩码，诸如111111110000000~0之类的，前1后零组成的序列，1的部分对应的IP地址的位置表示子网号，而0的部分表示主机号。&lt;/p&gt;
&lt;h6&gt;前缀细则&lt;/h6&gt;
&lt;ol&gt;
&lt;li&gt;分类编址：网络前缀仅采用8、16或24bit。这使得分配合适IP地址数量变得困难。&lt;/li&gt;
&lt;li&gt;无类别域间路由选择（Classless Interdomain Routing，CIDR），自由选择前缀长度。&lt;/li&gt;
&lt;/ol&gt;
&lt;h5&gt;获取IP地址&lt;/h5&gt;
&lt;h6&gt;组织获取IP地址块&lt;/h6&gt;
&lt;p&gt;组织获得IP地址的方法：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;向ISP申请&lt;/li&gt;
&lt;li&gt;向管理局直接申请&lt;/li&gt;
&lt;/ol&gt;
&lt;h6&gt;成员获取IP地址-DHCP&lt;/h6&gt;
&lt;p&gt;&lt;strong&gt;动态主机配置协议&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;主机发送DHCP发现报文&lt;/li&gt;
&lt;li&gt;DHCP服务器发送DHCP提供报文&lt;/li&gt;
&lt;li&gt;主机发送DHCP请求&lt;/li&gt;
&lt;li&gt;服务器返回DHCP ACK报文&lt;/li&gt;
&lt;/ol&gt;
&lt;h5&gt;NAT&lt;/h5&gt;
&lt;p&gt;网络地址转换（NAT）&lt;/p&gt;
&lt;p&gt;NAT路由器通过改写本地设备分组的IP地址与端口（NAT转换表），使得外界看起来像是与一台公网设备进行交互一般。&lt;/p&gt;
&lt;p&gt;其余技术： NAT穿越；通用即插即用协议。&lt;/p&gt;
&lt;h5&gt;IPv6&lt;/h5&gt;
&lt;p&gt;要点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;40字节首部&lt;/li&gt;
&lt;li&gt;取消路由器分片&lt;/li&gt;
&lt;/ol&gt;
&lt;h6&gt;隧道&lt;/h6&gt;
&lt;p&gt;为使得IPv6融入IPv4网络，采用隧道法。即在IPv4间，将IPv6数据报装入IPv4数据报进行运输。而在IPv6设备看来，IPv4则仿佛不存在一般。&lt;/p&gt;
&lt;h4&gt;SDN&lt;/h4&gt;
&lt;h5&gt;SDN结构体系&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;SDN网络应用&lt;/li&gt;
&lt;li&gt;SDN控制器&lt;/li&gt;
&lt;li&gt;SDN数据平面&lt;/li&gt;
&lt;/ul&gt;
&lt;h6&gt;SDN网络应用&lt;/h6&gt;
&lt;p&gt;借由SDN控制器提供的API编写的网路控制应用，不需要考虑底层的硬件。网络管理员依靠网络应用可以较为轻松地设置网络。&lt;/p&gt;
&lt;h6&gt;SDN控制器&lt;/h6&gt;
&lt;p&gt;SDN控制器，向下，统筹SDN数据平面的硬件设备，负责与路由器交换数据，下达指令；向上，为网络应用提供网络相关信息，接收指令。&lt;/p&gt;
&lt;h6&gt;数据平面&lt;/h6&gt;
&lt;p&gt;数据平面由众多路由器构成，接收来自控制器的&lt;strong&gt;匹配加动作转发表&lt;/strong&gt;，并以此为依据进行数据报的转发工作。&lt;/p&gt;
&lt;h5&gt;SDN功能&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;简单转发：依照IP地址进行转发，行为同传统结构一致&lt;/li&gt;
&lt;li&gt;防火墙：对特定IP进行隔离&lt;/li&gt;
&lt;li&gt;负载均衡：将数据报均衡地分配给多个同IP地址的服务器，均衡各个服务器的负载。&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;常见SDN协议&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;OpenFlow&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h3&gt;5. 网络层（控制平面）&lt;/h3&gt;
&lt;h4&gt;控制平面分类&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;每路由器控制&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;每路由控制模式下，路由器间互相通信取得网络连接数据信息，每台路由器独立运行路由选择算法，确认路由表。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;逻辑集中式控制&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;逻辑集中控制模式下，路由器间不互相通信，而仅与远程控制器交换数据，并执行下发的指令和路由表。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;路由选择算法&lt;/h4&gt;
&lt;h5&gt;分类&lt;/h5&gt;
&lt;h6&gt;集中or分散&lt;/h6&gt;
&lt;ol&gt;
&lt;li&gt;集中式路由选择算法（链路状态算法，LS）：需要知晓全局网络连接数据&lt;/li&gt;
&lt;li&gt;分散式路由选择算法（距离向量算法，DV）：通过临近结点间交互，逐级迭代&lt;/li&gt;
&lt;/ol&gt;
&lt;h6&gt;静态or动态&lt;/h6&gt;
&lt;ol&gt;
&lt;li&gt;静态路由选择算法：人工更新数据&lt;/li&gt;
&lt;li&gt;动态路由选择算法：协议自动更新&lt;/li&gt;
&lt;/ol&gt;
&lt;h6&gt;负载敏感or迟钝&lt;/h6&gt;
&lt;ol&gt;
&lt;li&gt;负载敏感：根据拥塞与否决定线路&lt;/li&gt;
&lt;li&gt;负载迟钝：不在意负载&lt;/li&gt;
&lt;/ol&gt;
&lt;h6&gt;LS算法详解&lt;/h6&gt;
&lt;p&gt;通过&lt;strong&gt;链路状态广播&lt;/strong&gt;，所有路由器都将获得整个网络的完整视图。其后，每台路由器依照&lt;strong&gt;Dijkstra算法&lt;/strong&gt;，计算出合适的路由表，并以此为依据转发数据报。&lt;/p&gt;
&lt;h6&gt;DV算法详解&lt;/h6&gt;
&lt;p&gt;特点：&lt;strong&gt;迭代、异步和分布式算法&lt;/strong&gt;。
&lt;strong&gt;分布式&lt;/strong&gt;：路由器从邻居接收信息进行计算，将计算结果再次发出。
&lt;strong&gt;迭代&lt;/strong&gt;：更新过程将持续到无信息交换。
&lt;strong&gt;异步&lt;/strong&gt;：不要求同时进行。&lt;/p&gt;
&lt;p&gt;依照&lt;strong&gt;Bellman-Ford算法&lt;/strong&gt;，每台路由器互相发送计算结果，并在接收消息后更新自己的数据，在多轮迭代后，其路由表将收束到最小开销。&lt;/p&gt;
&lt;h4&gt;英特网网络结构&lt;/h4&gt;
&lt;p&gt;英特网全网被划分为多个&lt;strong&gt;自洽系统（AS）&lt;/strong&gt;（ISP自由划分），这样有助于：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;ISP自我管理而无需对外公开&lt;/li&gt;
&lt;li&gt;控制规模。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;在AS内，采用&lt;strong&gt;自洽系统内路由选择协议&lt;/strong&gt;
在AS间，则采用&lt;strong&gt;边界网关协议&lt;/strong&gt;&lt;/p&gt;
&lt;h5&gt;开放最短路优先（OSPF）&lt;/h5&gt;
&lt;p&gt;OSPF算法是一种LS算法。特点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;周期性（30分钟或遇到变化）广播。&lt;/li&gt;
&lt;li&gt;仅与信任的AS路由器交换信息&lt;/li&gt;
&lt;li&gt;支持AS内部的层次结构（即多个子AS）&lt;/li&gt;
&lt;/ol&gt;
&lt;h5&gt;编辑网关协议（BGP）&lt;/h5&gt;
&lt;p&gt;BGP负责跨越AS的网络连接选择路径，但是其目的不在于给定最终主机的路由路线，而只是将数据报引导至对应的AS。为实现这一点，其使得路由器可以：&lt;/p&gt;
&lt;h6&gt;1. 从邻居AS获得前缀可达信息&lt;/h6&gt;
&lt;p&gt;BGP协议下，每台路由器都将广播自己的前缀可达信息。
AS间，依靠外部BGP（eBGP连接），网关路由器向邻居AS的网关路由器发送，
AS内，依靠内部BGP（iBGP连接），内部路由器向全体广播。&lt;/p&gt;
&lt;h6&gt;2. 确定到达给定前缀的最好路由&lt;/h6&gt;
&lt;p&gt;BGP中传递的信息将包含AS-PATH，这一属性类似A-B-X（A、B、X为AS）的结构传递可达路线信息。在决定路由时：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;依照管理员设定的&lt;strong&gt;本地偏好&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;选取AS-PATH最短路由&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;热土豆&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;BGP标识符&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h6&gt;PS：BGP还用于IP任播&lt;/h6&gt;
&lt;h4&gt;因特网控制报文协议 ICMP&lt;/h4&gt;
&lt;p&gt;ICMP用于交换网络层的相关信息，例如遇到无法转发的数据报时，则丢弃并回发&lt;strong&gt;目的网络不可达&lt;/strong&gt;。&lt;/p&gt;
&lt;h4&gt;简单网络管理协议 SNMP&lt;/h4&gt;
&lt;p&gt;SDN体系中，远程控制器与路由硬件设备间的通信，就建立在SNMP上。&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;6. 链路层&lt;/h3&gt;
&lt;h4&gt;链路层同网络层关系&lt;/h4&gt;
&lt;p&gt;网络层规定了数据报如何从一个网络传输到另一个网络；而链路层则规定了链路层帧在设备到设备中的传输。&lt;/p&gt;
&lt;h4&gt;链路层概述&lt;/h4&gt;
&lt;p&gt;在主机和路由器中，被称为&lt;strong&gt;网络适配器&lt;/strong&gt;的硬件芯片实现了链路层的功能：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;成帧：将数据报封装成链路层帧&lt;/li&gt;
&lt;li&gt;链路接入：由媒体访问控制协议协调帧的传输&lt;/li&gt;
&lt;li&gt;可靠交付：依靠重传实现可靠的帧传递&lt;/li&gt;
&lt;li&gt;差错检测和纠正：依靠校验位检验帧中的错误以及进行可能的纠正（奇偶校验、校验和、循环冗余检测）&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;多路访问链路和协议&lt;/h4&gt;
&lt;p&gt;网络链路分为&lt;strong&gt;点对点链路&lt;/strong&gt;和&lt;strong&gt;广播链路&lt;/strong&gt;。
在广播链路中会出现&lt;strong&gt;多路访问问题&lt;/strong&gt;（碰撞）。
解决方案：&lt;/p&gt;
&lt;h5&gt;1.信道划分协议&lt;/h5&gt;
&lt;h6&gt;时分多路复用&lt;/h6&gt;
&lt;h6&gt;频分多路复用&lt;/h6&gt;
&lt;h6&gt;码多分址&lt;/h6&gt;
&lt;h5&gt;2.随机接入协议（）&lt;/h5&gt;
&lt;h6&gt;载波侦听多路访问（CSMA）&lt;/h6&gt;
&lt;h6&gt;碰撞检测的载波侦听多路访问（CSMA/CD）&lt;/h6&gt;
&lt;h5&gt;3.轮流协议&lt;/h5&gt;
&lt;h4&gt;MAC&lt;/h4&gt;
&lt;h5&gt;MAC地址&lt;/h5&gt;
&lt;p&gt;每台主机以及路由器的每一个接口都配备有唯一的6字节的MAC地址。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;不同于IP地址，MAC地址为扁平结构，不具备层次性&lt;/li&gt;
&lt;li&gt;MAC地址与设备绑定，不随接入网位置变化&lt;/li&gt;
&lt;/ol&gt;
&lt;h5&gt;地址解析协议（ARP）&lt;/h5&gt;
&lt;p&gt;地址解析协议（ARP）负责将IP地址转换为MAC地址，使得主机与路由器可以由网络层的IP地址得知接下来要传输的设备的MAC地址。&lt;/p&gt;
&lt;p&gt;ARP的原理为：
发起方构造ARP分组，包含源IP、MAC与目的IP，广播到子网全部设备上。符合目的IP地址条件的设备便会进行回复，这样，发起方便获得了所求的MAC地址。
设备中存有ARP表，记录了IP与MAC的对应关系，并定期更新。&lt;/p&gt;
&lt;h4&gt;以太网&lt;/h4&gt;
&lt;p&gt;以太网是时下最为流行的局域有线网技术，是链路层网络，仅设计MAC地址。
与其同一等级的包括环令牌、FDDI、ATM。&lt;/p&gt;
&lt;h5&gt;交换机工作原理&lt;/h5&gt;
&lt;p&gt;交换机的工作不涉及网络层，故交换机无IP地址。
此外，交换机虽具备网络适配器，却无MAC地址。
故，交换机对于网络中的路由器和主机而言，是透明的。&lt;/p&gt;
&lt;p&gt;同时，交换机采用星型拓扑结构，不存在碰撞，故无需使用MAC协议。&lt;/p&gt;
&lt;p&gt;交换机内部存有交换机表，储存了交换机接口和MAC地址的对应关系。
在工作过程中，交换机会自行学习，记录收到的帧的MAC地址，并将其和接口关联。
同时，按交换机表转发帧，如若没有，则对接收端口外的所有端口进行广播。&lt;/p&gt;
&lt;h5&gt;交换机VS路由器&lt;/h5&gt;
&lt;p&gt;交换机较路由器易于配置，但是缺乏安全性，也不适用于大规模网络。故在生产实践过程中，应按需求决定使用。&lt;/p&gt;
&lt;h5&gt;交换机的其余用途&lt;/h5&gt;
&lt;ol&gt;
&lt;li&gt;交换机接口分组，构建虚拟局域网&lt;/li&gt;
&lt;li&gt;构建大型数据中心网络&lt;/li&gt;
&lt;li&gt;利用其思想，链路虚拟化，如&lt;strong&gt;多协议标签交换&lt;/strong&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;总和&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;接入网设备，利用DHCP协议，生产UDP报文段，经IP与以太网获得IP地址。&lt;/li&gt;
&lt;li&gt;设备利用DNS的UDP报文获取目的域名的IP地址。&lt;/li&gt;
&lt;li&gt;多级路由器通过期间链路传递报文，抵达服务器。&lt;/li&gt;
&lt;li&gt;服务器做出回应。。。。。。（TCP与应用层协议）&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h3&gt;7.无线网络和移动网络&lt;/h3&gt;
&lt;h4&gt;无线网络概述&lt;/h4&gt;
&lt;h5&gt;硬件实现&lt;/h5&gt;
&lt;p&gt;无线网络中的设备往往通过&lt;strong&gt;基站&lt;/strong&gt;接入到互联网中，称之为&lt;strong&gt;基础设施模式&lt;/strong&gt;；此外，还存在不接入互联网的小型网络，称之为&lt;strong&gt;自组织网络&lt;/strong&gt;。&lt;/p&gt;
&lt;h5&gt;无线网络特性&lt;/h5&gt;
&lt;p&gt;相较于传统有线网络，无线网络在传输数据过程中：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;有较大的损耗&lt;/li&gt;
&lt;li&gt;容易出现碰撞（碰撞检测延迟、隐藏终端问题）&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;对于问题二，常用&lt;strong&gt;码分多址，CDMA&lt;/strong&gt;来解决。&lt;/p&gt;
&lt;h6&gt;CMDA&lt;/h6&gt;
&lt;p&gt;CMDA协议下，从同一个信号（碰撞信号的混合信号）中，可以由不同的解码方式，将各个型号恢复出来。&lt;/p&gt;
&lt;h5&gt;无线LAN-WiFi:802.11&lt;/h5&gt;
&lt;p&gt;基站：接入点（AP）
MAC协议：CSMA/CA
信道划分：11给个信道&lt;/p&gt;
&lt;h5&gt;个人域网络：蓝牙 ZigBee&lt;/h5&gt;
&lt;h5&gt;蜂窝移动网络&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;2G：使用无线电话的设施建设&lt;/li&gt;
&lt;li&gt;3G：仍旧使用无线电话设施，但上层将电话信号和数据信号分开处理&lt;/li&gt;
&lt;li&gt;4G，LTE：构建基于IP的全新蜂窝网络，电话信号也由IP设施进行传输&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;移动网络概述&lt;/h4&gt;
&lt;h5&gt;移动性分类&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;在相同无线接入网中移动，程度：低&lt;/li&gt;
&lt;li&gt;在不同接入网间移动，移动时关闭，程度：中&lt;/li&gt;
&lt;li&gt;在不同间移动且保持通信，程度：高。&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;移动处理原理&lt;/h5&gt;
&lt;p&gt;设备存在一个&lt;strong&gt;永久居所&lt;/strong&gt;，称为&lt;strong&gt;归属网络&lt;/strong&gt;。归属网络中的管理器，为&lt;strong&gt;归属代理&lt;/strong&gt;。而现接入网络，称之为&lt;strong&gt;外部网络&lt;/strong&gt;，其管理器称之为&lt;strong&gt;外部代理&lt;/strong&gt;。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;间接路由选择：设备的外部网络时刻告知其归属网络设备的位置，外部通信时，设备发出数据直接交付，接收数据先法治归属网络，再转发至设备所在的外部网络。值得一提，其过程是&lt;strong&gt;透明&lt;/strong&gt;的。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;直接路由选择：外部通信过程中不在需要转发，而是通过询问，使得通信方得到设备的外部网络，再直接通信。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;例子：移动IP；GSM蜂窝网（锚MSC）&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;8.计算机网络安全&lt;/h3&gt;
&lt;h4&gt;安全通信的要求&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;机密性：仅通信双方能理解报文内容，要求&lt;strong&gt;加密&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;报文完整性：报文不被篡改，需要&lt;strong&gt;校验&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;端点鉴别：通信双方能鉴别对方身份&lt;/li&gt;
&lt;li&gt;运行安全性：不受蠕虫、病毒影响&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;密码学知识概述（机密性的探讨）&lt;/h4&gt;
&lt;p&gt;原文：明文。
加密方式：加密算法。
结果：密文。&lt;/p&gt;
&lt;p&gt;体系：对称密匙系统と公开密匙系统&lt;/p&gt;
&lt;h5&gt;对称密匙系统&lt;/h5&gt;
&lt;p&gt;对称密匙系统中，通信双方使用相同的密匙对通信内容进行加密解密。&lt;/p&gt;
&lt;p&gt;古有：凯撒密码&lt;/p&gt;
&lt;p&gt;现在通用体系：3DES算法和AES算法&lt;/p&gt;
&lt;h5&gt;公开密匙体系&lt;/h5&gt;
&lt;p&gt;每一方持有私匙和公匙，将公匙公开，他人用公匙加密报文后，仅有收件方使用私匙才能正确解密，得到原文。&lt;/p&gt;
&lt;p&gt;现在通用体系：RSA&lt;/p&gt;
&lt;h5&gt;PS：公开密匙体系较为耗费算力，常用RSA加密对称钥匙后转用堆成密匙系统通信&lt;/h5&gt;
&lt;h4&gt;报文完整性&lt;/h4&gt;
&lt;p&gt;解决方案：在报文后附加MD5对正文的散列计算结果。在发送与接收时分别计算，若吻合，则传输无误。&lt;/p&gt;
&lt;h5&gt;数字签名&lt;/h5&gt;
&lt;p&gt;借由公开密匙系统，发送方可用私匙加密其原文（可能由某些算法进行加密后的密文）依照某些散列函数提取的片段，附加在报文上。当接收方用公匙解开片段，并再次用散列函数计算片段后，便可依照吻合与否判断报文来源。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ps：报文鉴别码：MAC&lt;/strong&gt;&lt;/p&gt;
&lt;h5&gt;公匙认证&lt;/h5&gt;
&lt;p&gt;为使得接收者可以获得正确的公匙，设立专门机构，管理公匙。并颁发公匙证书。接收者可由公匙证书在机构处查询公匙。&lt;/p&gt;
&lt;h4&gt;端点鉴别（略）&lt;/h4&gt;
&lt;p&gt;...&lt;/p&gt;
</content:encoded></item></channel></rss>