<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>洛的藏书阁</title>
  
  
  <link href="https://luolibrary.com/atom.xml" rel="self"/>
  
  <link href="https://luolibrary.com/"/>
  <updated>2024-05-03T09:28:28.925Z</updated>
  <id>https://luolibrary.com/</id>
  
  <author>
    <name>shejialuo</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>GSoC 2024 - Implement consistency check for refs</title>
    <link href="https://luolibrary.com/2024/05/03/GSoC-2024-Implement-consistency-check-for-refs/"/>
    <id>https://luolibrary.com/2024/05/03/GSoC-2024-Implement-consistency-check-for-refs/</id>
    <published>2024-05-03T09:02:53.000Z</published>
    <updated>2024-05-03T09:28:28.925Z</updated>
    
    <content type="html"><![CDATA[<p>I am fortunate to participate in <a href="https://summerofcode.withgoogle.com/programs/2024/projects/ukm4PTEF">GSoC 2024</a> contributing to <a href="https://git-scm.com/">Git</a>. This opportunity not only allows me to engage with one of the most influential open-source projects but also enables me to work under the guidance of experienced developers in the field.</p><p>I would like to extend my deepest gratitude to my two mentors, <a href="https://github.com/pks-t">Patrick</a> and <a href="mailto:karthik188@gmail.com">Karthik</a>, who are both esteemed developers at <a href="https://about.gitlab.com/">GitLab</a>. Their expertise and guidance are invaluable, and I am incredibly fortunate to have the opportunity to learn from such experienced professionals in the field.</p><p>The primary purpose of this page is to serve as an archive for my weekly reports throughout the GSoC journey. These reports will detail my progress, challenges encountered, solutions devised, and learnings acquired each week. The intention behind maintaining this archive is twofold:</p><ol><li><strong>Transparency</strong>: To keep a transparent record of my contributions and progress throughout the project. This will allow my mentors, peers, and anyone interested in the project to track its development and my growth as a developer.</li><li><strong>Reflection</strong>: To serve as a reflective journal for myself. Documenting my journey will help me analyze my approaches, celebrate milestones, and learn from the challenges I face.</li></ol><p>You may access all related pages by visiting the <a href="https://luolibrary.com/categories/GSoC-2024/">GSoC 2024 category</a>.</p>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;p&gt;I am fortunate to participate in &lt;a href=&quot;https://summerofcode.withgoogle.com/programs/2024/projects/ukm4PTEF&quot;&gt;GSoC 2024&lt;/a&gt;</summary>
        
      
    
    
    
    <category term="GSoC 2024" scheme="https://luolibrary.com/categories/GSoC-2024/"/>
    
    
    <category term="GSoC" scheme="https://luolibrary.com/tags/GSoC/"/>
    
  </entry>
  
  <entry>
    <title>2023年终总结</title>
    <link href="https://luolibrary.com/2024/01/31/2023%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/"/>
    <id>https://luolibrary.com/2024/01/31/2023%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/</id>
    <published>2024-01-31T15:41:28.000Z</published>
    <updated>2024-01-31T15:41:44.170Z</updated>
    
    <content type="html"><![CDATA[<p>2023年的尾声早就已经过了，时间总是如同利刃一般无情地划过。我坐在西电网安大楼的实验室中，脑袋已经相当沉重了。突然想着必须记录2023年，为什么呢？内心不禁发问。我想我必须记录自己的存在。时间总是无痕，我必须在这个无痕且透明的世界里面，刻下属于我的痕迹。</p><h2 id="逃离"><a href="#逃离" class="headerlink" title="逃离"></a>逃离</h2><p>我为什么第一个想到的词就是逃离呢？因为我自己在2023年4月份的时候，已经疲倦了。每当疲倦的时候，我都不禁地反问自己，打代码的意义是什么呢？在面实习的过程中基本没有得到什么正反馈，不知道自己该为何而战。心里开始烦躁了，焦急了，幸福的口袋似乎什么东西都没有装下。不经让我想起《Rewrite》游戏里面的经典话语：</p><blockquote><p>有一天，察觉到自己一无所有。</p><p>发现本以为堆满着幸福的口袋，其实空无一物。</p><p>因为我没为在口袋里塞些什么而努力，所以是理所当然的。</p></blockquote><p>我一直以打代码自豪，这是我重要的存在意义。然而，我深刻地记得我当时对自我的怀疑，我真的能够打代码一辈子吗？我真的喜欢代码吗？我真的不是通过代码来逃避自我吗？我很迷惘。于是乎我决定逃离，本来不打算五一回家的我，以逃离为目的踏上了回家的旅程。</p><h3 id="与挚友的交谈"><a href="#与挚友的交谈" class="headerlink" title="与挚友的交谈"></a>与挚友的交谈</h3><p>从西安到成都的高铁很快，我简单地背了个包就回到了成都。和我的挚友见面了，他仍然是往常一样，我也如往常一样。吃喝玩乐过后，我和他在他那租的一套一里面聊着天。我们从初中聊到高中再聊到大学，我第一次知道了他在初中的细腻情感，第一次知道了他在高中时候将自己的情感诉诸于暴力的悔恨。我许久没有如此真挚的聊天了。我不仅又反思了自己的傲慢与偏见，我们真的能够轻易地了解一个人吗？我们总是凭借一些自己或他人的所见，就轻易对一个人下定义。</p><p>不禁意间，天就变亮了，我的挚友给我说不要逃避自己的感情，既然无法开辟一段新的未来，就认真地审视自己的过去。我一下就想到了我这次回家的主题：<strong>逃离</strong>。我不由黯然伤神。这几年，我不知道自己是否前进了。我的一些朋友一直认为我是在原地踏步，说实话，我很讨厌听到这样的话。但是反过来想，如果是一个不那么了解我的人，也确实很容易得出这样的结论。</p><h3 id="回家的反思"><a href="#回家的反思" class="headerlink" title="回家的反思"></a>回家的反思</h3><p>家永远是最温暖的港湾，不论哪个时间回家都会有这样的感慨。和父母聊了聊天，见了见外公，吃到了熟悉的香味。我的烦恼与疑惑已然被抛在了脑后。然而，我不由自主地又翻阅了我和曾经的她在高中时期写的书信，所有的信都用一种巧妙的方式送回到了我的手里。我每次都觉得这是命运使然，似乎可能在那时她就已经决定分手了。</p><p>我每次看信的内容，涌起的都是极其复杂的感情。好的方面有对往日回忆的追忆，对昔日温暖的遗憾。坏的方面，我不禁质疑这份感情的真假，我无法分清什么是假什么是真。每次一想到这个，我感觉到极其的厌倦，爱过的人和事物为什么这么让人扫兴。</p><p>五一节和另外的挚友相见了，我说起了我的迷惘，对代码的疑惑，自己能否将其作为终生的事业而奋斗呢？对困扰自己多年的感情的疑惑，自己是真的在前进，还是在逃避自己的内心呢？</p><h3 id="重返重庆"><a href="#重返重庆" class="headerlink" title="重返重庆"></a>重返重庆</h3><p>为什么突然想去重庆呢？没有理由，就是想去了。见到了自己的大学室友，他没什么变化，一如既往地真诚。我才明白我追求的是什么，我希望能与人建立真诚的关系，希望我真诚待别人，也希望别人真诚待我。然而，我追求的这个东西确实这个世间最为宝贵的东西，我矛盾的地方就在于明知有些东西难以强求却想强求。</p><p>回到了自己本科的母校，见到了那个时候对我启发甚大的学业导师。他仍然是对科研保留着属于他的纯粹。我很羡慕他，我觉得我的科研生涯完全没有去解决过一个真正的科学问题，从来没有去探索过计算机科学应该去解决的问题。他推荐我去滑铁卢大学读博士，我说我硕士期间的科研素养积累的不够，他批评了我的想法，我现在都记得他的话语：</p><blockquote><p>你为什么不去看看国外是怎么搞科研的呢？如果你觉得不合适，退学就行了啊。而且去国外体会一下别人的文化，别人的生活，感受一下也是一个好事。</p></blockquote><p>我才意识到自己的局限性，才意识到自己在西电读研失去的一个最宝贵的东西：<strong>勇气</strong>。我自己扼杀了自己的可能性。我老师问我，你觉得一个博士最重要的是什么？我已经忘记我怎么回答的呢？他给我说了一个我很震撼的话，博士最重要的是去研究真正的科学问题。实际上，我在读研之前他应该也给我说过，但是在当时我很难理解这句话的真正含义。我不禁再次反思，我想成为一个纯粹的人，然而我的逃离证明了我对代码并不纯粹。</p><p>当我沉溺于另一个世界中的时候，我另一位朋友带着她的男朋友来了西安，突然将我从这段妄想拉回了现实，我急忙买了一张从重庆到西安的动车票回到了西安。似乎给我这段旅程画上了句号。</p><h2 id="正视"><a href="#正视" class="headerlink" title="正视"></a>正视</h2><p>五一的逃离让我认识了许多东西，我才发现所谓的实习和秋招我不应该焦虑，我保持我自己，做好我自己该做的事情。</p><ul><li>ehForwarderBot社区贡献：抽空认真地阅读了<code>efb-qq-plugin-go-cqhttp</code>的源码，给所有的方法添加了注解，并提供了发送文件和视频的功能。</li><li>database-systems-complete-book-solutions：基本完成了这本讲数据库原理的课后习题答案。</li></ul><p>当时我自己也学了很多东西，毕竟我还是很热爱coding的。</p><h2 id="婚礼"><a href="#婚礼" class="headerlink" title="婚礼"></a>婚礼</h2><p>来西安的朋友结婚了，在9月份给我发了请帖国庆她要结婚了，我由衷地为她感到高兴，她找到了属于自己的归宿。我曾经去过她家拜访，那个时候我只觉得自己格格不入，那种生活的气息甚至让我觉得有些许的窒息。我突然意识到自己是不是离小小的幸福越来越远了。</p><p>本以为能高兴去参加的婚礼，更多的却是给自己带来的痛苦。当我知道伴娘是我的前女友的时候，内心是一丝丝的期待，主要是好奇这五年的时间她变成了一个什么样的人。同时，我也很想见我的哥们儿。来到成都东站的时候，兄弟们已经在等着我了，然而殊不知噩梦才刚刚开始。</p><p>到了婚礼前一天晚上的时候，我一个哥们儿突然说，我的前女友叫我们过去玩。实际上，对于我来说我的内心是不愿意的，我或多或少觉得有点尴尬。我不知道我该说些什么，所以在与她见面的那一瞬间，我有逃避的想法，不过既然来了，肯定不能逃避了，我现在都能清晰地记得我内心所说的话：</p><blockquote><p>人不能逃避自我。</p></blockquote><p>然而，我的前女友只叫了我另外四个兄弟的名字，而忽略了我的名字，我一瞬间就语塞了。因为我的存在在此时此刻已经被她恶意地抽离了。于是乎，大家热烈地讨论明天早上堵门的事情，商量着小游戏该如何安排，而我并不存在，这让我第一次意识到了我似乎成为了《局外人》这本小说的主人公。只不过，他是主动的，而我是被动的。最讽刺的是，我一个兄弟对我所说的话：</p><blockquote><p>这么多年了，你还没走出来吗？</p></blockquote><p>我已经无法解释什么了，我第一次意识到了距离。然而，也是有开心的事情。第二天我听到新娘的致辞的时候，我才明白这份幸福属于她是应该的。真挚的情感表达，她放弃成为一个医生，选择了其他职业，感谢了她的父母对她职业选择的理解。感谢与她老公的相遇。说实话，我的眼睛湿润了，唯独真挚的情感能让人心震撼。</p><p>结婚的当天下午，他们都去打牌了，我自己选择了逃离。把我的MBP打开，写了一下午的MIT 6.824。这可谓是我最快乐的时光了，我终于找回了属于自己的存在。我从来不讨厌孤独一人，而是厌恶被人恶意的孤立。</p><p><img src="https://s2.loli.net/2024/01/31/E1LtaWz9ycKrGBj.png" alt="10月4日的提交记录"></p><h2 id="工作"><a href="#工作" class="headerlink" title="工作"></a>工作</h2><p>人生第一次参加了真正意义上的工作，然而第一份实习在NVIDIA，可能直接会把我惯坏吧。不如说已经把我惯坏了。上班的体验是很好的，然而晚上一个人待在上海小小的出租屋里面，感受到的确实实在的寂寞。突然急切地希望自己身边能有一个人陪伴，然而我却厌恶自己的想法，如果是因为寂寞就去靠近一个人，对她来说这是一个不公平的事情。所以由衷地觉得，晚上的时间要好好把握。要去充实自己的生活。不禁苦笑，说总是比做起来容易。</p><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>人的一年说多也多，说少也少。然而，正如我开头所讲，简单地记录自己的存在。</p>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;p&gt;2023年的尾声早就已经过了，时间总是如同利刃一般无情地划过。我坐在西电网安大楼的实验室中，脑袋已经相当沉重了。突然想着必须记录2023年，为什么呢？内心不禁发问。我想我必须记录自己的存在。时间总是无痕，我必须在这个无痕且透明的世界里面，刻下属于我的痕迹。&lt;/p&gt;
&lt;h2</summary>
        
      
    
    
    
    <category term="随笔" scheme="https://luolibrary.com/categories/%E9%9A%8F%E7%AC%94/"/>
    
    
  </entry>
  
  <entry>
    <title>Write A Shell In Action (1)</title>
    <link href="https://luolibrary.com/2023/12/24/Write-A-Shell-In-Action-1/"/>
    <id>https://luolibrary.com/2023/12/24/Write-A-Shell-In-Action-1/</id>
    <published>2023-12-24T15:23:03.000Z</published>
    <updated>2023-12-24T15:23:29.951Z</updated>
    
    <content type="html"><![CDATA[<p>Why should we write a shell? Well, I think the most important reason is that it is fun. Learning how to write a shell will improve the system programming experience for the UNIX environment. Well, as the Feynman said:</p><blockquote><p>What I cannot create, I do not understand</p></blockquote><h2 id="The-Minimal-Setup"><a href="#The-Minimal-Setup" class="headerlink" title="The Minimal Setup"></a>The Minimal Setup</h2><p>In this tutorial, we will use the c++ as the language to write the shell. So at we should use <code>cmake</code> to construct the build environment. If you are not familiar with <code>cmake</code>, don’t worry about it. It’s easy to understand. For convenience, I hope you can just use the following commands to initialize the repository.</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">mkdir</span> miniShell &amp;&amp; <span class="built_in">cd</span> miniShell</span><br><span class="line">git init</span><br><span class="line">git remote add upstream https://github.com/shejialuo/miniShell</span><br><span class="line">git pull upstream start-code</span><br></pre></td></tr></table></figure><p>At this time, you will find the <code>miniShell.cpp</code> under the root tree, the functionality of the <code>miniShell.cpp</code> is simple enough. Because this is tutorial, we just simplify the program. We assume <code>miniShell</code> would have only two modes like any shell:</p><ul><li>Interactive</li><li>Batch mode</li></ul><p>The idea is actually the same. However, at now. We do not implement any functionality.</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;iostream&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">printUsage</span><span class="params">()</span> </span>&#123; std::cout &lt;&lt; <span class="string">&quot;Usage: ./miniShell [file]\n&quot;</span>; &#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">(<span class="type">int</span> argc, <span class="type">char</span> *argv[])</span> </span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (argc != <span class="number">1</span> &amp;&amp; argc != <span class="number">2</span>) &#123;</span><br><span class="line">    <span class="built_in">printUsage</span>();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (argc == <span class="number">1</span>) &#123;</span><br><span class="line">    std::cout &lt;&lt; <span class="string">&quot;This is a mini shell\n&quot;</span>;</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    std::cout &lt;&lt; <span class="string">&quot;This is a mini shell called with a file &quot;</span> &lt;&lt; argv[<span class="number">1</span>] &lt;&lt; <span class="string">&#x27;\n&#x27;</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>If you are interested in the <code>CMakeLists.txt</code>. You could just provide its content to the ChatGPT, it will give you a wonderful explanation. At now, you could build this program:</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">mkdir</span> build &amp;&amp; <span class="built_in">cd</span> build &amp;&amp; cmake .. &amp;&amp; cmake --build .</span><br></pre></td></tr></table></figure><p>Under the <code>build</code> directory, you will find the <code>miniShell</code> executable.</p><h2 id="The-Parser"><a href="#The-Parser" class="headerlink" title="The Parser"></a>The Parser</h2><p>We have already set up a minimal development environment, now we need to parse the shell script. Actually, writing a parser could be another theme. So in this tutorial, we will just use native way to parse the shell script.</p><p>So we should first make sure the functionality of the shell. I don’t think it’s a good idea to support the condition or loop control. It’s not the point. We just want to learn the system programming. Below is the spec:</p><ol><li><p>The operations for variables.</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">variable=5</span><br><span class="line"><span class="built_in">echo</span> <span class="variable">$variable</span> <span class="comment"># 5</span></span><br><span class="line"><span class="built_in">echo</span> <span class="variable">$&#123;variable&#125;</span> <span class="comment"># 5</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&#x27;$&#123;variable&#125;&#x27;</span> <span class="comment"># $&#123;variable&#125;</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;<span class="variable">$&#123;variable&#125;</span>&quot;</span> <span class="comment"># 5</span></span><br></pre></td></tr></table></figure></li><li><p>Command substitution</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">variable=`<span class="built_in">echo</span> 5` <span class="comment"># variable=5</span></span><br><span class="line">variable=$(<span class="built_in">echo</span> 5) <span class="comment"># variable=5</span></span><br></pre></td></tr></table></figure></li><li><p>Redirection</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">echo</span> <span class="string">&#x27;5&#x27;</span> &gt; /tmp/txt</span><br><span class="line"><span class="built_in">echo</span> <span class="string">&#x27;55&#x27;</span> &gt;&gt; /tmp/txt</span><br><span class="line"><span class="built_in">cat</span> &lt; /tmp/txt</span><br></pre></td></tr></table></figure></li><li><p>Pipe</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">command1 | command2 | command3 | command4</span><br></pre></td></tr></table></figure></li><li><p>Job control</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sleep</span> 100 &amp;</span><br><span class="line"><span class="built_in">bg</span> %1</span><br></pre></td></tr></table></figure></li></ol><p>As you can see, actually we could simplify the question. We first split the script line by line. For each line, we split the line by <code>|</code>. And we will generate the following classes for further process:</p><ol><li><code>Variable</code>: handle the <code>a=3</code>.</li><li><code>Command</code>: handle the <code>&lt;command&gt; &lt;argument1&gt; &lt;argument2&gt;</code>.</li><li><code>PipeCommand</code>: handle the <code>Command1 | Command2 | Command3</code>.</li></ol><p>I will not dive into this part, because it would be annoying and far away from the theme of this tutorial. Here, I give a snippet:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Command</span> : <span class="keyword">public</span> CommandBase &#123;</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">  std::string command;</span><br><span class="line">  std::vector&lt;std::string&gt; arguments;</span><br><span class="line">  std::optional&lt;std::string&gt; redirectFile;</span><br><span class="line">  RedirectStatus redirectStatus;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">  <span class="built_in">Command</span>() = <span class="keyword">delete</span>;</span><br><span class="line">  <span class="built_in">Command</span>(std::string &amp;&amp;c, std::vector&lt;std::string&gt; &amp;&amp;ar, std::optional&lt;std::string&gt; &amp;&amp;rf, RedirectStatus status)</span><br><span class="line">      : CommandBase&#123;&#125;</span><br><span class="line">      , command&#123;std::<span class="built_in">move</span>(c)&#125;</span><br><span class="line">      , arguments&#123;std::<span class="built_in">move</span>(ar)&#125;</span><br><span class="line">      , redirectFile&#123;std::<span class="built_in">move</span>(rf)&#125;</span><br><span class="line">      , redirectStatus&#123;status&#125; &#123;&#125;</span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">execute</span><span class="params">()</span> <span class="keyword">final</span></span>;</span><br><span class="line">  <span class="function"><span class="keyword">virtual</span> std::string <span class="title">inspect</span><span class="params">()</span> <span class="keyword">final</span> </span>&#123;</span><br><span class="line">    std::string result = command;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">const</span> <span class="keyword">auto</span> &amp;argument : arguments) &#123;</span><br><span class="line">      result += <span class="string">&quot; &quot;</span> + argument;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> result;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">virtual</span> ~<span class="built_in">Command</span>() = <span class="keyword">default</span>;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>From the above snippet, all we need to do is define the <code>execute</code> function. For each line, we will call the <code>execute</code> function. You could use the following command to pull the latest code:</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git pull upstream add-parser</span><br></pre></td></tr></table></figure><p>Below is the code hierarchy, if you are interested about the detail, you could see the <code>parser</code> directory.</p><figure class="highlight txt"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">.</span><br><span class="line">├── CMakeLists.txt</span><br><span class="line">├── command</span><br><span class="line">│   ├── CMakeLists.txt</span><br><span class="line">│   ├── command.cpp</span><br><span class="line">│   └── command.hpp</span><br><span class="line">├── miniShell.cpp</span><br><span class="line">├── parser</span><br><span class="line">│   ├── CMakeLists.txt</span><br><span class="line">│   ├── parser.cpp</span><br><span class="line">│   ├── parser.hpp</span><br><span class="line">│   └── tests</span><br><span class="line">│       ├── CMakeLists.txt</span><br><span class="line">│       └── parserTest.cpp</span><br><span class="line">└── README.md</span><br></pre></td></tr></table></figure><p>Look at the <code>miniShell.cpp</code>:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;parser/parser.hpp&quot;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;fstream&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;stdlib.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;string&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">printUsage</span><span class="params">()</span> </span>&#123; std::cout &lt;&lt; <span class="string">&quot;Usage: ./miniShell [file]\n&quot;</span>; &#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">start</span><span class="params">(std::istream &amp;is, <span class="type">bool</span> isFile = <span class="literal">false</span>)</span> </span>&#123;</span><br><span class="line">  Parser parser&#123;&#125;;</span><br><span class="line">  <span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (!isFile) &#123;</span><br><span class="line">      std::cout &lt;&lt; <span class="string">&quot;$ &quot;</span>;</span><br><span class="line">      std::cout.<span class="built_in">flush</span>();</span><br><span class="line">    &#125;</span><br><span class="line">    std::string script&#123;&#125;;</span><br><span class="line">    std::<span class="built_in">getline</span>(is, script);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (script.<span class="built_in">empty</span>()) &#123;</span><br><span class="line">      <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (!script.<span class="built_in">empty</span>() &amp;&amp; script.<span class="built_in">back</span>() == <span class="string">&#x27;\n&#x27;</span>) &#123;</span><br><span class="line">      script.<span class="built_in">erase</span>(script.<span class="built_in">length</span>() - <span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">auto</span> command = parser.<span class="built_in">parseScript</span>(script);</span><br><span class="line">    <span class="keyword">if</span> (command != <span class="literal">nullptr</span>) &#123;</span><br><span class="line">      command-&gt;<span class="built_in">execute</span>();</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">(<span class="type">int</span> argc, <span class="type">char</span> *argv[])</span> </span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (argc != <span class="number">1</span> &amp;&amp; argc != <span class="number">2</span>) &#123;</span><br><span class="line">    <span class="built_in">printUsage</span>();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (argc == <span class="number">1</span>) &#123;</span><br><span class="line">    <span class="built_in">start</span>(std::cin);</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    std::ifstream ifs&#123;argv[<span class="number">1</span>]&#125;;</span><br><span class="line">    <span class="keyword">if</span> (!ifs.<span class="built_in">is_open</span>()) &#123;</span><br><span class="line">      std::cout &lt;&lt; <span class="string">&quot;Cannot open file &quot;</span> &lt;&lt; argv[<span class="number">1</span>] &lt;&lt; <span class="string">&#x27;\n&#x27;</span>;</span><br><span class="line">      <span class="built_in">exit</span>(<span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="built_in">start</span>(ifs, <span class="literal">true</span>);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Due to the nice abstraction from cpp, the file stream and standard input stream are all the <code>istream</code>. So we define a <code>start</code> command to abstract the process. The most important function you could see is the <code>execute</code> function.</p><h2 id="A-Simple-Start"><a href="#A-Simple-Start" class="headerlink" title="A Simple Start"></a>A Simple Start</h2><p>Now we try a simple command <code>/usr/bin/ls</code>, we will write the method <code>Command::execute</code>. We will use <code>fork</code> system call to create a child process and waits for its action which would use <code>exec</code> system call for <code>/usr/bin/ls</code>. If you do not understand these concepts, you could read the man page of the <code>fork</code> or just read <a href="https://en.wikipedia.org/wiki/Advanced_Programming_in_the_Unix_Environment">Advanced Programming in the UNIX Environment</a> chapter 8.</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">Command::execute</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  <span class="type">pid_t</span> pid = fork();</span><br><span class="line">  <span class="keyword">if</span> (pid == <span class="number">-1</span>) &#123;</span><br><span class="line">    std::cout &lt;&lt; <span class="string">&quot;[fork error]: cannot fork\n&quot;</span>;</span><br><span class="line">    <span class="built_in">exit</span>(EXIT_FAILURE);</span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (pid == <span class="number">0</span>) &#123;</span><br><span class="line">    std::vector&lt;<span class="type">char</span> *&gt; argv&#123;&#125;;</span><br><span class="line">    argv.<span class="built_in">resize</span>(arguments.<span class="built_in">size</span>() + <span class="number">2</span>);</span><br><span class="line"></span><br><span class="line">    argv[<span class="number">0</span>] = <span class="built_in">const_cast</span>&lt;<span class="type">char</span> *&gt;(command.<span class="built_in">c_str</span>());</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">size_t</span> i = <span class="number">0</span>; i &lt; arguments.<span class="built_in">size</span>(); i++) &#123;</span><br><span class="line">      argv[i + <span class="number">1</span>] = <span class="built_in">const_cast</span>&lt;<span class="type">char</span> *&gt;(arguments[i].<span class="built_in">c_str</span>());</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    argv.<span class="built_in">back</span>() = <span class="literal">nullptr</span>;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">execv</span>(command.<span class="built_in">c_str</span>(), argv.<span class="built_in">data</span>());</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="built_in">wait</span>(<span class="literal">NULL</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>However, the above code is clear. What we do is simply pass the command and arguments to the <code>execv</code> system call. Like the following figure illustrates, when we type <code>/usr/bin/ls</code>, the result is OK. However, when we type <code>ls</code>, there is no result. This is because we need to search the <code>$PATH</code> environment. We will improve our code for part 2.</p><p><img src="https://s2.loli.net/2023/12/24/DjPidQtCuGmUHvb.png" alt="ls test result"></p><p>As usual, you could use the following command to get the code:</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git pull upstream simple-ls</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;p&gt;Why should we write a shell? Well, I think the most important reason is that it is fun. Learning how to write a shell will improve the</summary>
        
      
    
    
    
    <category term="Write a Shell In Action" scheme="https://luolibrary.com/categories/Write-a-Shell-In-Action/"/>
    
    
    <category term="技术" scheme="https://luolibrary.com/tags/%E6%8A%80%E6%9C%AF/"/>
    
    <category term="教程" scheme="https://luolibrary.com/tags/%E6%95%99%E7%A8%8B/"/>
    
  </entry>
  
  <entry>
    <title>秋招总结</title>
    <link href="https://luolibrary.com/2023/11/20/%E7%A7%8B%E6%8B%9B%E6%80%BB%E7%BB%93/"/>
    <id>https://luolibrary.com/2023/11/20/%E7%A7%8B%E6%8B%9B%E6%80%BB%E7%BB%93/</id>
    <published>2023-11-19T16:23:11.000Z</published>
    <updated>2023-12-02T17:16:55.682Z</updated>
    
    <content type="html"><![CDATA[<p>我觉得还是有必要记录一下我今年的秋招，但实际上记录的目的是为了什么呢？或许，仍然是刻印下自己的足迹并或多或少给阅读之人带来些许启发。</p><h2 id="暑期实习"><a href="#暑期实习" class="headerlink" title="暑期实习"></a>暑期实习</h2><p>其实投暑期实习的时候，我还是十分自信的，觉得靠着自己的能力拿下一个暑期实习是没有什么问题的。国内的大厂我投了百度、美团、阿里和腾讯。拼多多还有字节我直接选择没投，我觉得我没有那个命，而且我很不认同拼多多对不懂计算机的人群的歧视（参考<a href="https://mp.weixin.qq.com/s/P_EYQxOEupqdU0BJMRqWsw">2022年度最不可赦漏洞</a>）。同时我也投了一些外企的暑期实习，比如说亚马逊，英伟达和Paypal这些公司。</p><p>由于我本科双非，阿里实习简历过不了（面了个所谓的电话面），外企的面试属于可遇不可求。我总共只面试了百度，美团和腾讯这三个公司的暑期实习。</p><h3 id="百度"><a href="#百度" class="headerlink" title="百度"></a>百度</h3><p>百度是我最开始投的公司，当时我选择的岗位是C++软件研发工程师，做笔试的时候运气好，三道算法题我做出了两道算法题，拿到了面试机会。我现在已经记不清楚面试的内容了，我只记得一面做的是<a href="https://leetcode.cn/problems/reorder-list/">LeetCode 143</a>，二面做的是<a href="https://leetcode.cn/problems/reverse-nodes-in-k-group">LeetCode 25</a>。由于当时就没刷题，在只有20分钟左右的时间下，我很难写出在ACM模式下的LeetCode 25题。同时由于我自己自言自语地说了一句，感觉思路不对，然后面试官回了一句，“确实思路肯定有问题，你想一想”。又浪费了不少的时间。</p><p>题做不起，自然而然就被挂了。实际上二面的面试官问我做的科研横向项目在分布式集群下怎么收集的数据（实际上这个横向项目技术含量比较低，苦于没有实习经历，我还是得写一个看起来正经的项目吧），我说是使用python来收集的，他反问我为什么不适用Kafka。最后结束的时候他给我说搞工程和搞学术是不一样的。</p><p>其实，我很不能理解这些面试官的心态，没有必要教育我一顿。我的业务场景根本不需要那么重的东西，我只需要收集点数据处理就行了。</p><h3 id="美团"><a href="#美团" class="headerlink" title="美团"></a>美团</h3><p>美团我的笔试做的一塌糊涂，100分拿了个40分？美团我进行到了第二面，由于是投的基础研发部，所以面试需要进行三轮。</p><p>一面的面试官，主要是询问我做的国外公开课的<a href="https://github.com/shejialuo/CS149-fall21">CS149</a>中的能够解析任务依赖的异步Fork-Join任务库是怎么实现的，我记得当时我是把整个数据结构怎么设计的，以及同步的过程详细给面试官说了。可能因为回答的好，后面面试官给了我一个很简单的算法题。</p><p>然而，最令我震撼的是那个面试官在反问环节对我说的一番话（当时我问了他，你工作的时候觉得做的最有意思的事情是什么？）：</p><blockquote><p>你做的这个东西，我们一般都是直接用别人的库，平时我们很少写这些。可能你因为还是学生，没有上班，可能还比较幼稚，工作本身就是没有意义的，可能最大的乐趣就是解决了一个BUG。</p></blockquote><p>实际上，我被这句话震撼了。我其实并不是不明白上面这句话的道理。我感到了深深的悲哀，我一下意识到了我如果从事互联网的工作，可能也是这样。我该追求什么呢？</p><p>二面就比较抽象了，面试官一上来就问我我为什么笔试做的这么糟糕，我说题比较难，我尽力了。然后让我写了一个简化的我没有做的笔试题，然后他说我函数参数传错了。我或多或少写了5年的cpp了，真的不至于传参数都能传错。然后我直接说我确实参数传错了，然后面试就这样结束了，时间不到30分钟。</p><h3 id="腾讯"><a href="#腾讯" class="headerlink" title="腾讯"></a>腾讯</h3><p>腾讯是面的最舒服的，因为他直接问我现在能来实习吗？我说不能，然后面试就结束了。多好不要浪费大家的时间。</p><h3 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h3><p>我在面完上面三家公司，感到了深深的疲倦。于是在当时我在朋友圈发了以下的内容：</p><blockquote><p>从2月份到现在，面试了百度，腾讯，阿里这三家公司。我的体会是，面试变成了一件相当无聊的事情，二十分钟不明所以聊一下你做的项目，然后给你一道LeetCode的中等题目或者hard的题目让你做一做，然后你就一直做，面试官几乎不说话，做得起就继续，做不起就拜拜。如果你做起了，还会因为你现在不能来实习成为廉价劳动力直接挂你。</p><p>我在想这些面试官对我的了解基本等于0，就因为我能做起一个题就让我继续下一轮……，我是真心觉得无语。问些八股还要去找公司里面的题库，问<code>new</code>和<code>malloc</code>的区别这种无聊的问题。</p><p>从现在的经验来看，国内的互联网做的东西真心很无聊。还是思考一下换赛道吧，还是希望能做点有意思的东西的。</p></blockquote><p>于是乎，我做了一个很不理智的决定，直接放弃了其他大厂的暑期实习。根本不想面，我觉得面试毫无意义，同时去这些大厂实习也是毫无意义（当然，我肯定是偏激的观点）。不如在学校做点有意思的事情。</p><p>差不多从四月份开始我就开始学习<a href="https://interpreterbook.com/">Writing An Interpreter in Go</a>和<a href="https://compilerbook.com/">Writing A Compiler In Go</a>，我用cpp进行了自己的一个实现（见<a href="https://github.com/shejialuo/cppmpiler">cppmpiler</a>）。</p><p>然后，给<a href="https://github.com/ehForwarderBot">ehForwarderBot</a>社区做了一些贡献，主要是给slave端的QQ添加发送文件的支持。</p><p>当然，我不得不刷LeetCode。这可谓是我秋招过程中做的最痛苦的事情。让我回想起高中为了成绩不得不刷题的感觉。明明我还想写一个模拟器，还想去探索一下内存模型和缓存一致性。</p><h2 id="秋招"><a href="#秋招" class="headerlink" title="秋招"></a>秋招</h2><p>秋招是从7月份就开始的，因为很多公司已经开始提前批了。还是希望自己能毕业进大厂的，至少平台要高一些。所以还是采取了海投策略。</p><h3 id="百度提前批"><a href="#百度提前批" class="headerlink" title="百度提前批"></a>百度提前批</h3><p>百度提前批约我面试的时间应该是2023年7月中旬左右，一面的面试官是一位女生，我已经记不清她开了摄像头还是没开摄像头，听着她虚弱的声音，我就觉得她已经很累了。她问了我一些并行计算的项目，然后就让我做两道算法题了。我直接把两道算法题秒了，毕竟我不是在做题，我只是在默写。</p><p>二面咋说了，面试官还是比较有水平的，问了我并行计算，怎么使用CUDA的，然后怎么做Reduce操作。然后问了我个CPU指令乱序，回答不上（那个时候体系结构方面的知识确实忘了许多）。然后写了个LRU，给我介绍了一下部门的情况，面完二面就直接挂了。</p><p>要说我不觉得不甘，那肯定是扯淡的。不过从实习我得到的一个宝贵的经验就是面试也就是这个样，主要还是看顺眼了就行了。许多东西都是强求不得的。</p><h3 id="美团-1"><a href="#美团-1" class="headerlink" title="美团"></a>美团</h3><p>美团由于填写简历的时候填错了，学历填成了非统招，所以做完笔试就没有后续了，实际上我觉得cpp面美团也没有什么优势，毕竟java基本不懂，数据库也只会底层的原理，不懂上层的东西。</p><h3 id="阿里"><a href="#阿里" class="headerlink" title="阿里"></a>阿里</h3><p>阿里总共有6个部门，因为阿里卡第一学历，所以我阿里的简历筛选一个都通过不了。我其实觉得这个公司的HR挺无能的，作为一个公司核心就应该是选择人才，如果通过卡第一学历甚至那什么28所名单来进行筛选，只能够体现这个公司的无能。</p><h3 id="腾讯-1"><a href="#腾讯-1" class="headerlink" title="腾讯"></a>腾讯</h3><p>腾讯我投的是后台开发，说实在的，我觉得cpp就不应该投后台开发，然而我没有更好的选择。腾讯第一次捞我，把我调到技术运营的岗位去了，我研究生做的是云方面的研究，这个岗位就是针对内部系统的运营维护的，面试了就是聊天加项目加八股。没什么新意。然后二面也比较常规，三面主管面没发挥好，可能主管觉得我go语言基础不是那么好加上iptables的实现没有解释的清楚（我忘了），三面就给我挂了。</p><p>后面第二次捞我，是腾讯健康的部门的后台开发，第一面面了两个小时，当时我就知道多半这次面试就已经结束了。实际上这个题的代码很不好写，内存有限制，面试官一定要让我把代码写出来。前面八股问了半个小时，我和他讨论思路用了50分钟，怎么设计数据结构是合理的，怎么避免内存限制，怎样效率最高。然后我就开始写了，这个代码很不好调试，从4点开始面到5点50左右，我的精神已经开始恍惚了。我主要想吃饭了，然后我主动给面试官说我写不了了，就这样结束吧，大家好吃饭。</p><h3 id="B站"><a href="#B站" class="headerlink" title="B站"></a>B站</h3><p>B站由于认识HR，虽说简历全挂了，还是给了我一个分布式存储开发岗位的机会。一面的话是八股加体系结构，这可以说是我进行的最有深度的一次面试了，给面试官讲解内存子系统。当然二面只进行了20分钟就结束了，显然B站不招人嘛。但是我觉得这是给我想法转变一次的面试，cpp真的要搞后台开发吗？我学的技能就不是互联网的东西。</p><h3 id="龙芯"><a href="#龙芯" class="headerlink" title="龙芯"></a>龙芯</h3><p>龙芯的面试我是已经在英伟达实习的时候面的了，这个岗位相当有意思，名字叫基础软件研发，主要做gcc和ocaml的编译器适配和优化，你可知对于一个学习过Haskell的人并接触过一些ocaml语言的人来说，这个岗位有多么的吸引。面试的过程还是主要考察体系结构的知识，以及询问我怎么在CPU上实现矩阵的乘法的优化。二面主要问我调试器是怎么实现的，怎么做并行计算的，怎么实现SUNDR（一个安全方面课程的最后的作业），然后问了我个在Linux系统上用鼠标左键点击PPT翻页，从设备到内核态到用户态的整个过程（这是一个很有意思的问题）。目前，状态在泡池子中。</p><h3 id="其他"><a href="#其他" class="headerlink" title="其他"></a>其他</h3><p>实际上，我还投了许多公司。</p><ul><li>简历挂的：字节，拼多多，京东，滴滴，小米，米哈游，大疆，网易。</li><li>泡池子的：华为（云计算开发工程师），比亚迪（操作系统研发工程师）。</li></ul><h3 id="英伟达"><a href="#英伟达" class="headerlink" title="英伟达"></a>英伟达</h3><p>我在秋招的时候仍然是投了英伟达的实习岗位，其实我只是抱着试一试的心态，无论是学历还是经历，我比起申请的人都差了不少。但是英伟达给了我面试的机会，这个岗位是Software Intern - CUDA Test Development。实际上英伟达的面试能够让我感受到很重要的一点，就是注重对基础的考察，而且在面试过程中与面试官的交流才是最重要的，怎么去解决问题。面试官会和你一起去沟通解决这个问题。</p><p>实际上我在面完这个岗，是给我发了拒信的。当时候另外一个实习岗位Software Intern - Autonomous Vehicles给了我面试机会。然而，好巧不巧，HR找到我，说上一个岗位准备给我发offer了（其实是有人鸽了），在确定性和不确定性之间，我选择了确定性的offer。因为已经9月份底了，加上英伟达的光环，我还是非常希望能够转正的，毕竟我没拿到一个大厂的offer（或者说，他们连面试的机会都不愿意给我）。于是我选择放弃了当前的面试，接受了这个offer。实际上目前回想，我不应该做这个选择，我应该坚持我想要的。</p><p>正式入职英伟达的时候已经10月中旬了，软件测试的工作说来有趣说来也无趣，公司的同事以及主管都对我很好，英伟达给的福利待遇也很好。我其实已经十分地幸运了，能够拿到这样的实习，能够有转正的机会。这是我敢都不敢想的事情。然而，我的主管很大方地支持我去投内部的秋招，我是非常的感动。而且，实习也会给你自己探索的机会，我就在实习期间写了个python的命令行工具来自动化流程，提升大家的效率。主管还给了我20分钟的时间作汇报，我当时才进组两周左右。</p><p>于是乎，我投递了System Software Engineer, SoC - New College Grad的岗位，我为什么想投这个岗位，是因为我内心想做的事情驱动着我。我非常感谢能给我这个面试机会，如果不是因为我能够拿到英伟达实习的机会，我是绝对拿不到秋招的面试机会。面试主要还是问体系结构方面的知识，我最感动的是第一位面试官对我的评价：</p><blockquote><p>从你的身上，我感受到你对技术的热爱与热情。</p></blockquote><p>我从来没有得到过这么高的评价，在我看来，这是对我很高的认可。然后就是二面从1点进行到5点，仍然是我所说的，交流沟通才是最重要的，在会议室和面试官一起在白板上写代码，一起讨论问题。英伟达的面试体验可谓是我面过的公司最好的，二面基本都是4个小时，足以体现对面试者相当高的尊重。</p><p>我最终也是幸运地拿到了这份offer，我做梦也不敢想的事情发生到了自己的身上。</p><h2 id="感想"><a href="#感想" class="headerlink" title="感想"></a>感想</h2><p>我的秋招并不是那么顺利，因为第一学历的限制我几乎没有什么好的选择，加上我学的东西几乎与业务无关（我在整个学生生涯写的web后端还有sql几乎等于没有），我几乎拿不到大厂的offer，如果没有英伟达的offer，我就只能等华为，龙芯还有比亚迪了。</p><p>然而，我一直在思考我自己想要的是什么，我前段时间看到的文章<a href="https://justinyan.me/post/5790">每个人每天都只有24小时，希望我的选择真的是我的选择</a>让我深思了不少，正如同文章的题目一样，对自我的叩问才是成长，做什么样的事，成为什么样的人，需要用一辈子的时间去寻找答案。我为什么面试互联网大厂几乎到主管面就不行，我思考过这个问题，是因为我没有态度，我内心其实一直在排斥。如果我去做后台开发自己学的这些底层知识还有什么用。所以我面试的时候很多时候遇到无语的问题，我一般不会反驳，而是说“嗯嗯，你说得对”。</p><p>在整个秋招期间，我看到了本硕西电的有些朋友学java能够拿很多大厂的offer，我的内心难免会有嫉妒之情，对自己第一学历被卡的无奈，以及底层知识远离业务的无奈。每当面试官面试的时候问我，你做这个东西有什么意义的时候，我总是无奈一笑，说我挺喜欢做这些没有意义的活。</p><p>后面我意识到了不能着急，于是我仍然按照我自己的步骤走，因为学习并行计算，认识到了必须要去深入了内存子系统才能提高程序性能，于是我认真了学习了这两本书（从9月份到现在，实际上正是因为这两本书让我在英伟达秋招的这个岗位占了优势）：</p><ul><li>UNIX systems for modern architectures: symmetric multiprocesssing and caching for kernel programmers.</li><li>A Primer on Memory Consistency and Cache Coherence.</li></ul><p>实际上，我觉得人生就应该做一些没有意义的事情。这些没有意义的事情总会在运气到来的时候给予我们或多或少的帮助。在我看来，面试还是运气大于实习，我不喜欢超人的想法，觉得努力就能够有所收获，这是一种傲慢的想法。失去了同理心的想法。我一点也不觉得我能拿到这个offer是我的实力占主要因素，能够得到面试机会的人，没有人实力很差，至少我觉得大家都差不多。</p><p>我最后想用《三傻大闹宝莱坞》中的结尾话作为总结：</p><blockquote><p>追求卓越，成功将不期而至。</p></blockquote>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;p&gt;我觉得还是有必要记录一下我今年的秋招，但实际上记录的目的是为了什么呢？或许，仍然是刻印下自己的足迹并或多或少给阅读之人带来些许启发。&lt;/p&gt;
&lt;h2 id=&quot;暑期实习&quot;&gt;&lt;a href=&quot;#暑期实习&quot; class=&quot;headerlink&quot;</summary>
        
      
    
    
    
    <category term="随笔" scheme="https://luolibrary.com/categories/%E9%9A%8F%E7%AC%94/"/>
    
    
  </entry>
  
  <entry>
    <title>TopKProblem实战</title>
    <link href="https://luolibrary.com/2023/09/19/TopKProblem%E5%AE%9E%E6%88%98/"/>
    <id>https://luolibrary.com/2023/09/19/TopKProblem%E5%AE%9E%E6%88%98/</id>
    <published>2023-09-19T15:06:35.000Z</published>
    <updated>2023-09-19T15:17:31.131Z</updated>
    
    <content type="html"><![CDATA[<p>最近面试一直被问关于Top K的问题。网上有许多已经有这方面的答案了，然而我认为这些答案仅仅是局限于表面。网上的答案几乎都没有任何的代码实现，我认为理解这个问题需要自己去进行实现。仍然引用费曼的经典话：</p><blockquote><p>What I cannot create, I do not understand.</p></blockquote><p>本教程的代码位于<a href="https://github.com/shejialuo/topKProblem">topKProblem</a>仓库中。</p><h2 id="最经典的Top-K问题"><a href="#最经典的Top-K问题" class="headerlink" title="最经典的Top K问题"></a>最经典的Top K问题</h2><p>最经典的Top K问题忽略任何机器的限制，也就是从一个数组中得到前K大的数。有两种经典的方法：</p><ul><li>快速选择</li><li>堆排序</li></ul><h3 id="快速选择"><a href="#快速选择" class="headerlink" title="快速选择"></a>快速选择</h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">static</span> <span class="type">int</span> <span class="title">partition</span><span class="params">(std::vector&lt;<span class="type">int</span>&gt; &amp;data, <span class="type">int</span> start, <span class="type">int</span> end)</span> </span>&#123;</span><br><span class="line">  <span class="type">int</span> pivot = data[end];</span><br><span class="line"></span><br><span class="line">  <span class="type">int</span> i = start - <span class="number">1</span>, j = start;</span><br><span class="line">  <span class="keyword">while</span> (j &lt; end) &#123;</span><br><span class="line">    <span class="keyword">if</span> (data[j] &gt; pivot) &#123;</span><br><span class="line">      i++;</span><br><span class="line">      std::<span class="built_in">swap</span>(data[i], data[j]);</span><br><span class="line">    &#125;</span><br><span class="line">    j++;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  std::<span class="built_in">swap</span>(data[i + <span class="number">1</span>], data[end]);</span><br><span class="line">  <span class="keyword">return</span> i + <span class="number">1</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">static</span> <span class="type">int</span> <span class="title">helper</span><span class="params">(std::vector&lt;<span class="type">int</span>&gt; &amp;data, <span class="type">int</span> k, <span class="type">int</span> start, <span class="type">int</span> end)</span> </span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (start == end) &#123;</span><br><span class="line">    <span class="keyword">return</span> start;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="type">int</span> mid = <span class="built_in">partition</span>(data, start, end);</span><br><span class="line">  <span class="type">int</span> index = mid - start + <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (index == k) &#123;</span><br><span class="line">    <span class="keyword">return</span> mid;</span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (index &gt; k) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">helper</span>(data, k, start, mid - <span class="number">1</span>);</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">helper</span>(data, k - index, mid + <span class="number">1</span>, end);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function">std::vector&lt;<span class="type">int</span>&gt; <span class="title">topKUsingPartition</span><span class="params">(std::vector&lt;<span class="type">int</span>&gt; &amp;data, <span class="type">int</span> k)</span> </span>&#123;</span><br><span class="line">  <span class="type">int</span> index = <span class="built_in">helper</span>(data, k, <span class="number">0</span>, data.<span class="built_in">size</span>() - <span class="number">1</span>);</span><br><span class="line">  <span class="keyword">return</span> std::vector&lt;<span class="type">int</span>&gt;&#123;data.<span class="built_in">cbegin</span>(), data.<span class="built_in">cbegin</span>() + index + <span class="number">1</span>&#125;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="堆排序"><a href="#堆排序" class="headerlink" title="堆排序"></a>堆排序</h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">std::vector&lt;<span class="type">int</span>&gt; <span class="title">topKUsingHeap</span><span class="params">(<span class="type">const</span> std::vector&lt;<span class="type">int</span>&gt; &amp;data, <span class="type">int</span> k)</span> </span>&#123;</span><br><span class="line">  <span class="function">std::vector&lt;<span class="type">int</span>&gt; <span class="title">ans</span><span class="params">(k, <span class="number">0</span>)</span></span>;</span><br><span class="line"></span><br><span class="line">  std::priority_queue&lt;<span class="type">int</span>, std::vector&lt;<span class="type">int</span>&gt;, std::greater&lt;<span class="type">int</span>&gt;&gt; heap&#123;&#125;;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; k; i++) &#123;</span><br><span class="line">    heap.<span class="built_in">push</span>(data[i]);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">for</span> (<span class="type">int</span> i = k; i &lt; data.<span class="built_in">size</span>(); i++) &#123;</span><br><span class="line">    <span class="keyword">if</span> (data[i] &gt; heap.<span class="built_in">top</span>()) &#123;</span><br><span class="line">      heap.<span class="built_in">pop</span>();</span><br><span class="line">      heap.<span class="built_in">push</span>(data[i]);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">for</span> (<span class="type">int</span> i = k - <span class="number">1</span>; i &gt;= <span class="number">0</span>; i--) &#123;</span><br><span class="line">    ans[i] = heap.<span class="built_in">top</span>();</span><br><span class="line">    heap.<span class="built_in">pop</span>();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> ans;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="受限环境下的Top-K问题"><a href="#受限环境下的Top-K问题" class="headerlink" title="受限环境下的Top K问题"></a>受限环境下的Top K问题</h2><p>假设我们的机器只有30M的内存，我们希望处理500M的<code>int_32</code>类型的数据，这些数据存储在文件中。我在此处假设<code>k</code>的大小是我们能够接受的，也就是<code>k</code>的大小对于内存而言可以忽略。</p><p>此部分的代码可以直接在<a href="https://github.com/shejialuo/topKProblem/tree/master/src/topKRestricted">仓库</a>中查看。</p><h3 id="使用堆排序"><a href="#使用堆排序" class="headerlink" title="使用堆排序"></a>使用堆排序</h3><p>最简单的方式可能仍然是使用堆排序，因为我们仅仅只需要在内存中维护$O(k)$的大小，对于大文件我们每次只需要读取20M即可，因此内存永远不会超过。然而我们必须思考一个问题，数字是以什么形式保存在文件中。我在这让假设数字都是通过二进制方式的形式存储的，减少分割等操作。</p><p>我采取了如下的思路实现，在Linux系统下通过<code>setrlimit</code>限制进程的内存大小，使用c++17的<code>filesystem</code>标准库在临时文件中写入数据，进行操作。</p><h3 id="使用快速选择"><a href="#使用快速选择" class="headerlink" title="使用快速选择"></a>使用快速选择</h3><p>对于大文件我们每次只需要读取20M即可，然后选择前<code>k</code>大的数。我们就能得到一系列的数组，然后我们直接将数组合并，得到一个新的数组，其大小不可能超过内存，然后再次选择前<code>k</code>大的数，从而得出答案。当然我们也可以使用堆来处理，毕竟这属于一个子问题。</p><h3 id="效率对比"><a href="#效率对比" class="headerlink" title="效率对比"></a>效率对比</h3><p>执行的结果如下所示：</p><figure class="highlight txt"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">=== Result</span><br><span class="line">Heap average time: 1.4765 s</span><br><span class="line">Select average time: 1.90026 s</span><br></pre></td></tr></table></figure><p>可以看出，使用堆还是要快一些的，这是因为减少了内存的拷贝。</p><h2 id="受限条件下基于多线程的快速选择"><a href="#受限条件下基于多线程的快速选择" class="headerlink" title="受限条件下基于多线程的快速选择"></a>受限条件下基于多线程的快速选择</h2><p>基于快速选择的方法可以使用多线程进行加速，这是使用堆比不上的优势，现在的处理器基本上都是多核，此处使用多线程进行加速，如何构建多线程是一个问题。首先每个线程即是生产者也是消费者，这样代码写出来就极其的复杂，所以我的一个思路还是确定需要完成的任务数，通过任务数来实现线程之间的同步。</p><h2 id="问题变种"><a href="#问题变种" class="headerlink" title="问题变种"></a>问题变种</h2><p>所谓的问题变种，无非就是通过哈希得到频率，然后根据频率来得到Top K。没有任何本质的区别。</p><h2 id="思考"><a href="#思考" class="headerlink" title="思考"></a>思考</h2><p>实践出真知。</p>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;p&gt;最近面试一直被问关于Top K的问题。网上有许多已经有这方面的答案了，然而我认为这些答案仅仅是局限于表面。网上的答案几乎都没有任何的代码实现，我认为理解这个问题需要自己去进行实现。仍然引用费曼的经典话：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;What I cannot</summary>
        
      
    
    
    
    
    <category term="技术" scheme="https://luolibrary.com/tags/%E6%8A%80%E6%9C%AF/"/>
    
    <category term="学习" scheme="https://luolibrary.com/tags/%E5%AD%A6%E4%B9%A0/"/>
    
  </entry>
  
  <entry>
    <title>MIT6.858-SUNDR的实现</title>
    <link href="https://luolibrary.com/2023/09/11/MIT6-858-SUNDR%E7%9A%84%E5%AE%9E%E7%8E%B0/"/>
    <id>https://luolibrary.com/2023/09/11/MIT6-858-SUNDR%E7%9A%84%E5%AE%9E%E7%8E%B0/</id>
    <published>2023-09-11T07:09:18.000Z</published>
    <updated>2023-09-11T07:09:35.922Z</updated>
    
    <content type="html"><![CDATA[<p>MIT6.858最后一个实验是复现论文<a href="https://www.usenix.org/legacy/event/osdi04/tech/full_papers/li_j/li_j.pdf">Secure Untrusted Data Repository</a>的串行化实现。在开始做这个实验之前，你应该仔细地阅读这篇论文。</p><h2 id="源码阅读"><a href="#源码阅读" class="headerlink" title="源码阅读"></a>源码阅读</h2><p>MIT6.858提供了基本的文件系统代码，了解这个文件系统是如何构建的，对于代码实现的相当重要。</p><h3 id="基本的类型定义"><a href="#基本的类型定义" class="headerlink" title="基本的类型定义"></a>基本的类型定义</h3><p>在SUNDR中，有用户和群组的划分。同时每一个文件都是由<code>&lt;principal, i-number&gt;</code>对应的。显然，每个用户和群组都可以有多个文件，所以只需要再定义一个类包含用户（群组）和所有的文件个数。故源代码中定义了三个类型：</p><ul><li><code>User</code>：继承于<code>Principal</code>。</li><li><code>Group</code>：继承于<code>Principal</code>。</li><li><code>I</code>：包含<code>Principal</code>和<code>n</code>。</li></ul><h3 id="基本的存储实现"><a href="#基本的存储实现" class="headerlink" title="基本的存储实现"></a>基本的存储实现</h3><p>在文件系统中，最基本的存储单元是<code>Block</code>类，其可以存储数据、inode等信息。由于SUNDR基于快照的机制保障安全，故其与git的实现机制相似，基于content-hash机制对每一个Block进行哈希。故<code>Block</code>类提供了两个函数：</p><ul><li><code>store(blob)</code>：<code>blob</code>是存储的数据，其为二进制数据。其返回值为二进制内容的sha1sum。</li><li><code>load(chash)</code>：根据哈希值找到相应的block，进而读取block中的二进制数据。</li></ul><p>同时，文件系统定义了<code>Inode</code>类，用于作为元数据。其包含了某一个文件或者目录的基本信息，例如类型，创建的时间，修改的时间及其对应的所有的block的哈希值。</p><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Inode</span>:</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self</span>):</span><br><span class="line">        self.size = <span class="number">0</span></span><br><span class="line">        self.kind = <span class="number">0</span> <span class="comment"># 0 is dir, 1 is file</span></span><br><span class="line">        self.ex = <span class="literal">False</span></span><br><span class="line">        self.ctime = <span class="number">0</span></span><br><span class="line">        self.mtime = <span class="number">0</span></span><br><span class="line">        self.blocks = []</span><br></pre></td></tr></table></figure><p>显然，<code>Inode</code>类也是存储在block中的，所以我们需要基于<code>ihash</code>通过调用<code>block.load(chash)</code>函数得到其二进制数据，然后进行反序列化得到其字段值。当我们想要获得文件的值时，我们直接使用如下的函数即可：</p><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">read</span>(<span class="params">self</span>):</span><br><span class="line">    <span class="keyword">return</span> <span class="string">b&quot;&quot;</span>.join([secfs.store.block.load(b) <span class="keyword">for</span> b <span class="keyword">in</span> self.blocks])</span><br></pre></td></tr></table></figure><p>然而，我们必须考虑目录这种情况。在SUNDR中，目录的每一项元素都是一个元组<code>(filename, I)</code>。其中其定义了<code>add</code>函数来实现在一个目录里面添加一个新项。同时定义了<code>find_under</code>函数用来查找目录。</p><h3 id="Itable"><a href="#Itable" class="headerlink" title="Itable"></a>Itable</h3><p>每个用户和群组都拥有一个<code>i-table</code>。对于用户而言，<code>i-table</code>保存的是<code>i-number</code>到<code>ihash</code>的映射。对于群组而言，<code>i-table</code>保存的是<code>i-number</code>到<code>I</code>的映射。因此对于<code>Itable</code>类而言，源码利用了python的动态性，直接定义了一个dict。</p><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Itable</span>:</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self</span>):</span><br><span class="line">        self.mapping = &#123;&#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">load</span>(<span class="params">ihandle</span>):</span><br><span class="line">        b = secfs.store.block.load(ihandle)</span><br><span class="line">        <span class="keyword">if</span> b == <span class="literal">None</span>:</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">None</span></span><br><span class="line"></span><br><span class="line">        t = Itable()</span><br><span class="line">        t.mapping = pickle.loads(b)</span><br><span class="line">        <span class="keyword">return</span> t</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">bytes</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="keyword">return</span> pickle.dumps(self.mapping)</span><br></pre></td></tr></table></figure><p>然后，其定义了一个关键的函数<code>resolve</code>，基于<code>i-table</code>解析得到<code>ihash</code>或者<code>I</code>。同时，该函数支持递归地解析<code>I</code>最终得到<code>ihash</code>。其思路实现比较简单。</p><p>最关键的在于<code>modmap(mod_as, i, ihash)</code>函数，对于某个用户<code>mod_as</code>而言，给定一个<code>I</code>类，需要修改或者创建其在<code>i-table</code>中的映射。然而关键在于<code>I</code>有两类情况，一种是用户，另一种是群组。</p><ul><li>对于用户而言，其<code>mod_as</code>和<code>i.p</code>必须相等。我们需要修改或者创建其在<code>i-table</code>中的映射，这个过程相当简单。</li><li>对于群组而言，情况就会比较复杂了。我们首先需要基于提供的<code>i</code>找到其对应的<code>I</code>，也就是调用<code>resolve</code>函数并进行一层的解析。如果<code>I.p = mod_as</code>，证明群组的修改和用户的修改是一致的，我们一定会最先对用户的修改进行处理，所以此处不需要进行任何操作。如果不是那么证明我们需要创建一个新的<code>I</code>，于是调用<code>modmap(mod_as, I(mod_as), ihash)</code>在<code>mod_as</code>用户的<code>i-table</code>中添加映射。</li></ul><h3 id="文件系统API接口"><a href="#文件系统API接口" class="headerlink" title="文件系统API接口"></a>文件系统API接口</h3><p>源码已经给出了某些文件系统接口的实现，例如<code>link</code>函数，<code>link</code>函数的作用即将一个<code>I</code>添加到当前目录中，其原理很简单，首先需要基于当前目录构建一个<code>Directory</code>。然后调用<code>add</code>函数添加目录，并使用<code>modmap</code>修改映射关系。</p><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Omit syntax check and write check</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">link</span>(<span class="params"><span class="keyword">def</span> link(<span class="params">link_as: User, i: I, parent_i: I, name: <span class="built_in">str</span></span>):</span></span><br><span class="line"><span class="params">    parent_ihash = secfs.store.tree.add(<span class="params">parent_i, name, i</span>)</span></span><br><span class="line"><span class="params">    secfs.tables.modmap(<span class="params">link_as, parent_i, parent_ihash</span>)</span>)</span><br></pre></td></tr></table></figure><p>在SUNDR论文中，文件系统的初始化是root用户生成一对私钥&#x2F;公钥，同时创建<code>.users</code>和<code>.groups</code>文件，存储相应的公钥及印映射信息。文件系统使用<code>init</code>函数提供了接口。其首先需要创建一个类型为目录的inode，然后添加<code>.</code>和<code>..</code>目录。同时需要创建<code>.users</code>和<code>.groups</code>文件，调用<code>link</code>函数添加到根目录中。</p><p>在了解了基本的数据结构以及映射关系后，我们就可以开始编写自己的代码。</p><h2 id="实现"><a href="#实现" class="headerlink" title="实现"></a>实现</h2><h3 id="实现创建文件和目录的功能"><a href="#实现创建文件和目录的功能" class="headerlink" title="实现创建文件和目录的功能"></a>实现创建文件和目录的功能</h3><p>我们需要所做的第一个工作就是实现创建文件和目录。无论是文件还是目录我们都需要首先创建一个<code>Inode</code>。然后我们需要将其存储在文件系统中，并得到其hash值<code>ihash</code>。然后我们需要调用<code>modmap</code>修改itable的映射关系。对于目录而言，我们需要做更多额外的工作。我们需要添加在当前目录创建<code>.</code>和<code>..</code>，并调用<code>modmap</code>修改itable的映射关系。然后我们调用<code>link</code>维护目录结构关系。</p><p>然而，我们需要考虑如果是为一个群组创建文件和目录呢？实际上面的流程都是一样的，因为对于群组来说，<code>itable</code>是存储的映射，所以我们仅仅只需要更新这个映射关系即可。</p><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">_create</span>(<span class="params">parent_i: I, name: <span class="built_in">str</span>, create_as: User, create_for: Principal, isdir: <span class="built_in">bool</span></span>):</span><br><span class="line">    ...</span><br><span class="line">    ihash = secfs.store.block.store(node.<span class="built_in">bytes</span>())</span><br><span class="line">    store_i = secfs.tables.modmap(create_as, I(create_for), ihash)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> isdir:</span><br><span class="line">        new_ihash = secfs.store.tree.add(store_i, <span class="string">b&#x27;.&#x27;</span>, store_i)</span><br><span class="line">        secfs.tables.modmap(create_as, store_i, new_ihash)</span><br><span class="line">        new_ihash = secfs.store.tree.add(store_i, <span class="string">b&#x27;..&#x27;</span>, parent_i)</span><br><span class="line">        secfs.tables.modmap(create_as, store_i, new_ihash)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> create_for.is_group():</span><br><span class="line">        secfs.tables.modmap(create_as, I(create_for), store_i)</span><br><span class="line"></span><br><span class="line">    link(create_as, store_i, parent_i, name)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> store_i</span><br></pre></td></tr></table></figure><h3 id="Version-Structure-List实现"><a href="#Version-Structure-List实现" class="headerlink" title="Version Structure List实现"></a>Version Structure List实现</h3><p>目前所有的映射关系都是存储在<code>current_table</code>中的，其是存储在内存中的，当有其他客户端时，其访问服务端，得不到<code>current_table</code>的信息，所以需要对<code>current_table</code>进行修改，其需要存储在文件系统中。在这个过程中，我们不考虑任何安全方面的实现。</p><p>我们首先需要认识到<code>current_table</code>保存的是<code>Principal</code>到<code>Itable</code>的映射。由于函数<code>resolve</code>以及<code>modmap</code>严重依赖于<code>current_table</code>，所以应该尽可能合理地设计数据结构，减少代码的更改。</p><p>在SUNDR原论文中，其提供的数据结构<code>VersionStructure</code>包含每一个用户的<code>i-handle</code>，所属的群组的<code>g-handle</code>以及<code>version_vector</code>。这是与当前的逻辑矛盾的，当前的映射单独保存了用户和群组的，为了简便起见，我没有采取这样的数据结构。我仍然按照当前的逻辑进行实现。因此，我定义了如下的数据结构。</p><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">VersionStructure</span>:</span><br><span class="line">    <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">    `VersionStructure` is the most important data structure in this lab. In</span></span><br><span class="line"><span class="string">    the original SUNDR paper, the version structure is specified only for</span></span><br><span class="line"><span class="string">    the `User`. Each user could have one and more group handles. However,</span></span><br><span class="line"><span class="string">    it&#x27;s a bad idea for the current code. Because the `current_itables`</span></span><br><span class="line"><span class="string">    mapping the `I` to the inode. The `I.p` could be user or the group.</span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line">    version_vector : <span class="built_in">dict</span>[Principal, <span class="built_in">int</span>]</span><br><span class="line">    <span class="comment"># It could be the user or the group, it is only the hash, should</span></span><br><span class="line">    <span class="comment"># later read from</span></span><br><span class="line">    i_handle: <span class="built_in">str</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self</span>):</span><br><span class="line">        self.version_vector = &#123;&#125;</span><br><span class="line">        self.i_handle = <span class="string">&quot;&quot;</span></span><br></pre></td></tr></table></figure><p>根据SUNDR论文，每次每个客户端对文件系统进行操作之间，都需要从服务器下载最新的VSL，当进行了修改需要对VSL进行验证，如果验证通过，则进行数字签名，上传VSL。从性能的角度来说，传递单独的<code>VersionStructure</code>是节省带宽的，然而我认为此处不是这个lab的核心，因此我才用了最粗暴的方式，即上传整个VSL。故定义了如下的数据结构：</p><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">VersionStructureList</span>:</span><br><span class="line">    <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">    `VersionStructureList` is the core data structure here, It</span></span><br><span class="line"><span class="string">    contains the the user or the group `VersionStructure`.</span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line">    version_structures: <span class="built_in">dict</span>[Principal, VersionStructure]</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self</span>) -&gt; <span class="literal">None</span>:</span><br><span class="line">        self.version_structures = &#123;&#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">download</span>(<span class="params">self</span>) -&gt; <span class="literal">None</span>:</span><br><span class="line">        <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">        Download the `version_structure_list` from the server, it should</span></span><br><span class="line"><span class="string">        be called every time it operates on the file system. It&#x27;s may</span></span><br><span class="line"><span class="string">        be a bad idea, but it is simple.</span></span><br><span class="line"><span class="string">        &quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line">        <span class="keyword">global</span> server</span><br><span class="line">        blob = server.read_version_structure_list()</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> blob == <span class="literal">None</span>:</span><br><span class="line">            <span class="keyword">return</span></span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> <span class="string">&quot;data&quot;</span> <span class="keyword">in</span> blob:</span><br><span class="line">            <span class="keyword">import</span> base64</span><br><span class="line">            blob = base64.b64decode(blob[<span class="string">&quot;data&quot;</span>])</span><br><span class="line"></span><br><span class="line">        self.version_structures = pickle.loads(blob)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">upload</span>(<span class="params">self</span>) -&gt; <span class="literal">None</span>:</span><br><span class="line"></span><br><span class="line">        <span class="keyword">global</span> server</span><br><span class="line">        blob = pickle.dumps(self.version_structures)</span><br><span class="line">        server.store_version_structure_list(blob)</span><br></pre></td></tr></table></figure><p>我们要在server端添加<code>store_version_structure_list</code>RPC调用，以及<code>read_version_structure_list</code>RPC调用。</p><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">SecFSRPC</span>():</span><br><span class="line">    ...</span><br><span class="line"><span class="meta">    @Pyro4.expose</span></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">read_version_structure_list</span>(<span class="params">self</span>):</span><br><span class="line">        chash = self.version_structure_list_hash</span><br><span class="line">        <span class="keyword">if</span> chash != <span class="literal">None</span> <span class="keyword">and</span> chash <span class="keyword">in</span> self.blocks:</span><br><span class="line">            <span class="keyword">return</span> self.blocks[chash]</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">None</span></span><br><span class="line"></span><br><span class="line"><span class="meta">    @Pyro4.expose</span></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">store_version_structure_list</span>(<span class="params">self, blob</span>):</span><br><span class="line">        <span class="keyword">if</span> <span class="string">&quot;data&quot;</span> <span class="keyword">in</span> blob:</span><br><span class="line">            <span class="keyword">import</span> base64</span><br><span class="line">            blob = base64.b64decode(blob[<span class="string">&quot;data&quot;</span>])</span><br><span class="line"></span><br><span class="line">        <span class="keyword">import</span> hashlib</span><br><span class="line">        chash = hashlib.sha224(blob).hexdigest()</span><br><span class="line">        self.blocks[chash] = blob</span><br><span class="line">        self.version_structure_list_hash = chash</span><br></pre></td></tr></table></figure><p>现在到达了最关键的一步，也就是我们需要修改<code>resolve</code>和<code>modmap</code>中的代码，我们需要替换掉<code>current_table</code>。我们需要一点即可，即<code>VersionStructure</code>存储的是<code>i-handle</code>我们必须进行磁盘的读写，而不是类似源码中直接存储<code>Itable</code>（实际上这一步是可以优化的，如果其<code>i-handle</code>没有发生变化，我们可以实现缓存，而不是从磁盘中实现读写，然而我忽略了因为这并不是这个lab的核心）。</p><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">resolve</span>(<span class="params">i: I, resolve_groups = <span class="literal">True</span></span>):</span><br><span class="line">    ...</span><br><span class="line">    <span class="keyword">global</span> version_structure_list</span><br><span class="line">    <span class="keyword">if</span> principal <span class="keyword">not</span> <span class="keyword">in</span> version_structure_list.version_structures:</span><br><span class="line">        <span class="comment"># User does not yet have an itable</span></span><br><span class="line">        <span class="keyword">return</span> <span class="literal">None</span></span><br><span class="line"></span><br><span class="line">    i_handle = version_structure_list.version_structures[principal].i_handle</span><br><span class="line">    t = Itable.load(i_handle)</span><br><span class="line">    ...</span><br></pre></td></tr></table></figure><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">modmap</span>(<span class="params">mod_as: User, i: I, ihash</span>) -&gt; I:</span><br><span class="line"></span><br><span class="line">    ...</span><br><span class="line">    <span class="comment"># find (or create) the principal&#x27;s itable</span></span><br><span class="line">    t = <span class="literal">None</span></span><br><span class="line">    <span class="keyword">global</span> version_structure_list</span><br><span class="line">    <span class="keyword">if</span> i.p <span class="keyword">not</span> <span class="keyword">in</span> version_structure_list.version_structures:</span><br><span class="line">        <span class="keyword">if</span> i.allocated():</span><br><span class="line">            <span class="comment"># this was unexpected;</span></span><br><span class="line">            <span class="comment"># user did not have an itable, but an inumber was given</span></span><br><span class="line">            <span class="keyword">raise</span> ReferenceError(<span class="string">&quot;itable not available&quot;</span>)</span><br><span class="line">        t = Itable()</span><br><span class="line">        version_structure_list.version_structures[i.p] = VersionStructure()</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">&quot;no current list for principal&quot;</span>, i.p, <span class="string">&quot;; creating empty table&quot;</span>, t.mapping)</span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">        i_handle = version_structure_list.version_structures[i.p].i_handle</span><br><span class="line">        t = Itable.load(i_handle)</span><br><span class="line"></span><br><span class="line">    ...</span><br><span class="line"></span><br><span class="line">    i_handle = secfs.store.block.store(t.<span class="built_in">bytes</span>())</span><br><span class="line">    version_structure_list.version_structures[i.p].i_handle = i_handle</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> i</span><br></pre></td></tr></table></figure><p>到了此处，我们就可以通过百分之87的测试。可见，我们已经完成了大部分的工作。剩下的就是实现安全方面的功能，这个lab最难的并不在于代码，而是你如何进行数据结构的设计。</p><h3 id="安全方面实现"><a href="#安全方面实现" class="headerlink" title="安全方面实现"></a>安全方面实现</h3><p>要实现安全方面就很简单了，需要考虑如下两个（由于时间关系，此处我并没有进行验证）：</p><ol><li>验证服务器端的身份</li><li>实现SUNDR协议</li></ol>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;p&gt;MIT6.858最后一个实验是复现论文&lt;a href=&quot;https://www.usenix.org/legacy/event/osdi04/tech/full_papers/li_j/li_j.pdf&quot;&gt;Secure Untrusted Data</summary>
        
      
    
    
    
    <category term="安全" scheme="https://luolibrary.com/categories/%E5%AE%89%E5%85%A8/"/>
    
    
    <category term="学习" scheme="https://luolibrary.com/tags/%E5%AD%A6%E4%B9%A0/"/>
    
  </entry>
  
  <entry>
    <title>CS149-Extra-Assignment-Implement Dense Matrix-Matrix Multiplication</title>
    <link href="https://luolibrary.com/2023/08/27/CS149-Extra-Assignment-Implement-Dense-Matrix-Matrix-Multiplication/"/>
    <id>https://luolibrary.com/2023/08/27/CS149-Extra-Assignment-Implement-Dense-Matrix-Matrix-Multiplication/</id>
    <published>2023-08-27T07:48:12.000Z</published>
    <updated>2023-08-29T05:45:33.518Z</updated>
    
    <content type="html"><![CDATA[<p>It’s hard to implement Matrix-Matrix Multiplication. So I recommend the following repository for you. I also refer to this repository when doing this assignment.</p><ul><li><a href="https://github.com/flame/blislab">blislab</a></li></ul><h2 id="The-native-implementation"><a href="#The-native-implementation" class="headerlink" title="The native implementation"></a>The native implementation</h2><p>We do not use 2-D array, we flat the array to one dimension. We assume that $A$ is $M \times K$, $B$ is $K \times N$, and $C$ is $M \times N$. We need to implement the $\alpha A \times B + \beta \times C$.</p><p><img src="https://s2.loli.net/2023/08/23/dHTJDkhwBfIiA74.png" alt="Flatten matrix multiplication"></p><p>We could calculate the following:</p><ul><li>$C[0] &#x3D; A[0]B[0] + A[1]B[N] + \cdots + A[K - 1]B[(K - 1)N]$</li><li>$C[1] &#x3D; A[K]B[1] + A[K + 1]B[N + 1] + \cdots + A[2K - 1]B[(K - 1)N + 1]$</li></ul><p>Thus, we could writing the following code:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Gemm</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * @brief Disable constructor</span></span><br><span class="line"><span class="comment">   *</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  <span class="built_in">Gemm</span>() = <span class="keyword">delete</span>;</span><br><span class="line"></span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * @brief Calculate the GEMM sequentially</span></span><br><span class="line"><span class="comment">   *</span></span><br><span class="line"><span class="comment">   * @details The matrix A, B, C are all one-dimensional array, so the need</span></span><br><span class="line"><span class="comment">   * to find the way to calculate the matrix multiplication.</span></span><br><span class="line"><span class="comment">   *</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  <span class="function"><span class="type">static</span> <span class="type">void</span> <span class="title">gemm</span><span class="params">(<span class="type">int</span> m, <span class="type">int</span> n, <span class="type">int</span> k, <span class="type">double</span> *A, <span class="type">double</span> *B, <span class="type">double</span> *C,</span></span></span><br><span class="line"><span class="params"><span class="function">                   <span class="type">double</span> alpha, <span class="type">double</span> beta)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; m; i++) &#123;</span><br><span class="line">      <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">0</span>; j &lt; n; j++) &#123;</span><br><span class="line">        <span class="type">double</span> inner_pod = <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> kk = <span class="number">0</span>; kk &lt; k; kk++) &#123;</span><br><span class="line">          inner_pod += A[i * k + kk] * B[kk * n + j];</span><br><span class="line">        &#125;</span><br><span class="line">        C[i * n + j] = alpha * inner_pod + beta * C[i * n + j];</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">gemm</span><span class="params">(<span class="type">int</span> m, <span class="type">int</span> n, <span class="type">int</span> k, <span class="type">double</span> *A, <span class="type">double</span> *B, <span class="type">double</span> *C, <span class="type">double</span> alpha,</span></span></span><br><span class="line"><span class="params"><span class="function">          <span class="type">double</span> beta)</span> </span>&#123;</span><br><span class="line">  Gemm::<span class="built_in">gemm</span>(m, n, k, A, B, C, alpha, beta);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>We assume that $A$ is $1024 \times 1024$ and $B$ is $1024 \times 1024$, we could see that the Intel mkl library would only execute 18ms, the reference implementation would execute 62ms. However, the native implementation executes 5746ms.</p><p><img src="https://s2.loli.net/2023/08/24/AJwG1Nect8qREkh.png" alt="The native implementation result"></p><h2 id="Blocked-matrix-multiplication"><a href="#Blocked-matrix-multiplication" class="headerlink" title="Blocked matrix multiplication"></a>Blocked matrix multiplication</h2><p>The efficiency is low because we do not use the cache. So the idea is use the blocked matrix multiplication thus improving the cache hit rate. The idea is intuitive. If you’are not familiar with the<br>blocked multiplication, I recommend you to read some materials.</p><p>From the below figure, we first need to split the matrix into the blocked matrices. We assume each block would be the $size \times size$. For $A$, we first need to split for the row $M$. And for $B$, we need to split for the col $N$. And for both $A$ and $B$, we could simultaneously handle $K$. However, the trouble is that we need to know each block top-left coordinate, because we need to find the absolute position. So we should define the coordinates for the absolute position.</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * @brief A wrapper to wrap the coordinates for indicating the</span></span><br><span class="line"><span class="comment"> * start left-top point for the current blocked matrix.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">struct</span> <span class="title class_">Point2D</span> &#123;</span><br><span class="line">  <span class="type">int</span> i&#123;&#125;; <span class="comment">/**&lt; The absolute i */</span></span><br><span class="line">  <span class="type">int</span> j&#123;&#125;; <span class="comment">/**&lt; The absolute j */</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><img src="https://s2.loli.net/2023/08/27/kMEThCBtFmDdW82.png" alt="Blocked matrix-matrix multiplication"></p><p>So we need to first split for $M$ and for $N$, then for $K$, we could get the following incomplete c++ code:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; m; i += size;) &#123;</span><br><span class="line">  <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">0</span>; j &lt; n; j += size;) &#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> kk = <span class="number">0</span>; kk &lt; k; k += size) &#123;</span><br><span class="line">      ...</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>In the first loop, we should set the $A$’s start point of <code>i</code> and we also need to tell whether <code>i + size &lt; m</code>. Similarly, in the second loop, we should set the $B$’s start point of <code>j</code> and we also need to tell whether <code>j + size &lt; n</code>. In the last loop, we should set the $A$’s start point of <code>j</code> and $B$’s start point of <code>i</code> and also need to tell whether <code>k + size &lt; kk</code>.</p><p>It’s easy to calculate the start points of $C$. It is dependent on the <code>i</code> and <code>j</code>. Thus we could have the following code:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">Point2D a&#123;&#125;, b&#123;&#125;, c&#123;&#125;;</span><br><span class="line"><span class="type">const</span> <span class="type">int</span> size = <span class="number">6</span>;</span><br><span class="line"><span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; m; i += size) &#123;</span><br><span class="line">  <span class="type">int</span> mBlock = i + size &lt; m ? size : m - i;</span><br><span class="line">  a.i = i;</span><br><span class="line">  c.i = i;</span><br><span class="line">  <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">0</span>; j &lt; n; j += size) &#123;</span><br><span class="line">    b.j = j;</span><br><span class="line">    c.j = j;</span><br><span class="line">    <span class="type">int</span> nBlock = j + size &lt; n ? size : n - j;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> kk = <span class="number">0</span>; kk &lt; k; kk += size) &#123;</span><br><span class="line">      a.j = kk;</span><br><span class="line">      b.i = kk;</span><br><span class="line">      <span class="type">int</span> kBlock = kk + size &lt; k ? size : k - kk;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>At now, it’s not hard to write the calculate:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">GemmBlock</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * @brief Disable constructor</span></span><br><span class="line"><span class="comment">   *</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  <span class="built_in">GemmBlock</span>() = <span class="keyword">delete</span>;</span><br><span class="line"></span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * @brief Apply the C = beta C, it should be calculated at first.</span></span><br><span class="line"><span class="comment">   *</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  <span class="function"><span class="type">static</span> <span class="type">void</span> <span class="title">scalarMultiplication</span><span class="params">(<span class="type">int</span> mBlock, <span class="type">int</span> nBlock, <span class="type">int</span> n, Point2D &amp;c,</span></span></span><br><span class="line"><span class="params"><span class="function">                                   <span class="type">double</span> *C, <span class="type">double</span> beta)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; mBlock; i++) &#123;</span><br><span class="line">      <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">0</span>; j &lt; nBlock; j++) &#123;</span><br><span class="line">        C[(i + c.i) * n + (j + c.j)] = beta * C[(i + c.i) * n + (j + c.j)];</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * @brief Matrix multiplication with block</span></span><br><span class="line"><span class="comment">   *</span></span><br><span class="line"><span class="comment">   * @details In order to implement the block matrix multiplication, we need</span></span><br><span class="line"><span class="comment">   * to calculate the blocked matrix A and blocked matrix B. For example</span></span><br><span class="line"><span class="comment">   * A11 A12  B11 B12  C11 C12</span></span><br><span class="line"><span class="comment">   * A21 A22  B21 B22  C21 C22</span></span><br><span class="line"><span class="comment">   * C11 = A11 X b11 + A12 X B21</span></span><br><span class="line"><span class="comment">   *</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  <span class="function"><span class="type">static</span> <span class="type">void</span> <span class="title">matrixMultiplicationBlock</span><span class="params">(<span class="type">int</span> mBlock, <span class="type">int</span> nBlock, <span class="type">int</span> kBlock,</span></span></span><br><span class="line"><span class="params"><span class="function">                                        <span class="type">int</span> n, <span class="type">int</span> k, Point2D &amp;a, Point2D &amp;b,</span></span></span><br><span class="line"><span class="params"><span class="function">                                        Point2D &amp;c, <span class="type">double</span> *A, <span class="type">double</span> *B,</span></span></span><br><span class="line"><span class="params"><span class="function">                                        <span class="type">double</span> *C, <span class="type">double</span> alpha)</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; mBlock; i++) &#123;</span><br><span class="line">      <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">0</span>; j &lt; nBlock; j++) &#123;</span><br><span class="line">        <span class="type">double</span> inner_pod = <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> kk = <span class="number">0</span>; kk &lt; kBlock; kk++) &#123;</span><br><span class="line">          inner_pod +=</span><br><span class="line">              A[(i + a.i) * k + (kk + a.j)] * B[(kk + b.i) * n + (j + b.j)];</span><br><span class="line">        &#125;</span><br><span class="line">        C[(i + c.i) * n + (j + c.j)] += alpha * inner_pod;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * @brief Split the matrix</span></span><br><span class="line"><span class="comment">   *</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  <span class="function"><span class="type">static</span> <span class="type">void</span> <span class="title">gemmUsingBlock</span><span class="params">(<span class="type">int</span> m, <span class="type">int</span> n, <span class="type">int</span> k, <span class="type">double</span> *A, <span class="type">double</span> *B,</span></span></span><br><span class="line"><span class="params"><span class="function">                             <span class="type">double</span> *C, <span class="type">double</span> alpha, <span class="type">double</span> beta)</span> </span>&#123;</span><br><span class="line">    Point2D a&#123;&#125;, b&#123;&#125;, c&#123;&#125;;</span><br><span class="line">    <span class="type">const</span> <span class="type">int</span> size = <span class="number">6</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; m; i += size) &#123;</span><br><span class="line">      <span class="type">int</span> mBlock = i + size &lt; m ? size : m - i;</span><br><span class="line">      a.i = i;</span><br><span class="line">      c.i = i;</span><br><span class="line">      <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">0</span>; j &lt; n; j += size) &#123;</span><br><span class="line">        b.j = j;</span><br><span class="line">        c.j = j;</span><br><span class="line">        <span class="type">int</span> nBlock = j + size &lt; n ? size : n - j;</span><br><span class="line">        <span class="built_in">scalarMultiplication</span>(mBlock, nBlock, n, c, C, beta);</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> kk = <span class="number">0</span>; kk &lt; k; kk += size) &#123;</span><br><span class="line">          a.j = kk;</span><br><span class="line">          b.i = kk;</span><br><span class="line">          <span class="type">int</span> kBlock = kk + size &lt; k ? size : k - kk;</span><br><span class="line">          <span class="built_in">matrixMultiplicationBlock</span>(mBlock, nBlock, kBlock, n, k, a, b, c, A, B,</span><br><span class="line">                                    C, alpha);</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="type">static</span> <span class="type">void</span> <span class="title">gemm</span><span class="params">(<span class="type">int</span> m, <span class="type">int</span> n, <span class="type">int</span> k, <span class="type">double</span> *A, <span class="type">double</span> *B, <span class="type">double</span> *C,</span></span></span><br><span class="line"><span class="params"><span class="function">                   <span class="type">double</span> alpha, <span class="type">double</span> beta)</span> </span>&#123;</span><br><span class="line">    <span class="built_in">gemmUsingBlock</span>(m, n, k, A, B, C, alpha, beta);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>Then executing <code>./gemm 1024</code>. As the result shows, we execute 843ms. It is much better than the native implementation.</p><p><img src="https://s2.loli.net/2023/08/27/YyUZxQDwveBTsGc.png" alt="Blocked matrix-matrix multiplication result"></p><h2 id="Blocked-matrix-multiplication-with-memory-layout-change"><a href="#Blocked-matrix-multiplication-with-memory-layout-change" class="headerlink" title="Blocked matrix multiplication with memory layout change"></a>Blocked matrix multiplication with memory layout change</h2><p>We can still improvement the efficiency, we still access the matrix $B$ with col. However, when the matrix size is big, it would cause cache miss. When executing <code>./gemm 2048</code>. The program would cause 7476ms. This is because the memory layout of $B$ is bad for cache hit. So we could change the layout of $B$.</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">GemmBlockWithMemoryLayoutChange</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * @brief Disable constructor</span></span><br><span class="line"><span class="comment">   *</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  <span class="built_in">GemmBlockWithMemoryLayoutChange</span>() = <span class="keyword">delete</span>;</span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="type">static</span> <span class="type">void</span> <span class="title">scalarMultiplication</span><span class="params">(<span class="type">int</span> mBlock, <span class="type">int</span> nBlock, <span class="type">int</span> n, Point2D &amp;c,</span></span></span><br><span class="line"><span class="params"><span class="function">                                   <span class="type">double</span> *C, <span class="type">double</span> beta)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; mBlock; i++) &#123;</span><br><span class="line">      <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">0</span>; j &lt; nBlock; j++) &#123;</span><br><span class="line">        C[(i + c.i) * n + (j + c.j)] = beta * C[(i + c.i) * n + (j + c.j)];</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="type">static</span> <span class="type">void</span> <span class="title">matrixMultiplicationBlock</span><span class="params">(<span class="type">int</span> mBlock, <span class="type">int</span> nBlock, <span class="type">int</span> kBlock,</span></span></span><br><span class="line"><span class="params"><span class="function">                                        <span class="type">int</span> n, <span class="type">int</span> k, Point2D &amp;a, Point2D &amp;b,</span></span></span><br><span class="line"><span class="params"><span class="function">                                        Point2D &amp;c, <span class="type">double</span> *A, <span class="type">double</span> *B,</span></span></span><br><span class="line"><span class="params"><span class="function">                                        <span class="type">double</span> *C, <span class="type">double</span> alpha)</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; mBlock; i++) &#123;</span><br><span class="line">      <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">0</span>; j &lt; nBlock; j++) &#123;</span><br><span class="line">        <span class="type">double</span> inner_pod = <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> kk = <span class="number">0</span>; kk &lt; kBlock; kk++) &#123;</span><br><span class="line">          inner_pod +=</span><br><span class="line">              A[(i + a.i) * k + (kk + a.j)] * B[(j + b.i) * n + (kk + b.j)];</span><br><span class="line">        &#125;</span><br><span class="line">        C[(i + c.i) * n + (j + c.j)] += alpha * inner_pod;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="type">static</span> <span class="type">void</span> <span class="title">gemmUsingBlock</span><span class="params">(<span class="type">int</span> m, <span class="type">int</span> n, <span class="type">int</span> k, <span class="type">double</span> *A, <span class="type">double</span> *B,</span></span></span><br><span class="line"><span class="params"><span class="function">                             <span class="type">double</span> *C, <span class="type">double</span> alpha, <span class="type">double</span> beta)</span> </span>&#123;</span><br><span class="line">    Point2D a&#123;&#125;, b&#123;&#125;, c&#123;&#125;;</span><br><span class="line">    <span class="type">const</span> <span class="type">int</span> size = <span class="number">6</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; m; i += size) &#123;</span><br><span class="line">      <span class="type">int</span> mBlock = i + size &lt; m ? size : m - i;</span><br><span class="line">      a.i = i;</span><br><span class="line">      c.i = i;</span><br><span class="line">      <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">0</span>; j &lt; n; j += size) &#123;</span><br><span class="line">        b.i = j;</span><br><span class="line">        c.j = j;</span><br><span class="line">        <span class="type">int</span> nBlock = j + size &lt; n ? size : n - j;</span><br><span class="line">        <span class="built_in">scalarMultiplication</span>(mBlock, nBlock, n, c, C, beta);</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> kk = <span class="number">0</span>; kk &lt; k; kk += size) &#123;</span><br><span class="line">          a.j = kk;</span><br><span class="line">          b.j = kk;</span><br><span class="line">          <span class="type">int</span> kBlock = kk + size &lt; k ? size : k - kk;</span><br><span class="line">          <span class="built_in">matrixMultiplicationBlock</span>(mBlock, nBlock, kBlock, n, k, a, b, c, A, B,</span><br><span class="line">                                    C, alpha);</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="type">static</span> <span class="type">void</span> <span class="title">gemm</span><span class="params">(<span class="type">int</span> m, <span class="type">int</span> n, <span class="type">int</span> k, <span class="type">double</span> *A, <span class="type">double</span> *B, <span class="type">double</span> *C,</span></span></span><br><span class="line"><span class="params"><span class="function">                   <span class="type">double</span> alpha, <span class="type">double</span> beta)</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="type">double</span> *newB = <span class="keyword">new</span> <span class="type">double</span>[n * k];</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">0</span>; j &lt; n; j++) &#123;</span><br><span class="line">      <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; k; i++) &#123;</span><br><span class="line">        newB[j * k + i] = B[i * n + j];</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">gemmUsingBlock</span>(m, n, k, A, newB, C, alpha, beta);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">delete</span>[] newB;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>Then executing <code>./gemm 1024</code>. As the result shows, we execute 478ms. We gain more performance.</p><p><img src="https://s2.loli.net/2023/08/27/VhNcertR6vYDEim.png" alt="Blocked matrix-matrix multiplication with memory layout change result"></p><h2 id="Blocking-for-multiple-levels-of-cache"><a href="#Blocking-for-multiple-levels-of-cache" class="headerlink" title="Blocking for multiple levels of cache"></a>Blocking for multiple levels of cache</h2><p>It’s hard to explain this idea, you should carefully read the <a href="https://github.com/flame/blislab">blislab</a> tutorial and read the following paper carefully:</p><ul><li><a href="https://dl.acm.org/doi/10.1145/1356052.1356053">Anatomy of High-Performance Matrix Multiplication</a></li></ul><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">GemmBlockWithThreeCacheLevel</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * @brief Disable constructing this class</span></span><br><span class="line"><span class="comment">   *</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  <span class="built_in">GemmBlockWithThreeCacheLevel</span>() = <span class="keyword">delete</span>;</span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="type">static</span> <span class="type">void</span> <span class="title">matrixMultiplicationBlock</span><span class="params">(<span class="type">int</span> mBlock, <span class="type">int</span> nBlock, <span class="type">int</span> kBlock,</span></span></span><br><span class="line"><span class="params"><span class="function">                                        <span class="type">int</span> n, <span class="type">int</span> k, Point2D &amp;a, Point2D &amp;b,</span></span></span><br><span class="line"><span class="params"><span class="function">                                        Point2D &amp;c, <span class="type">double</span> *A, <span class="type">double</span> *B,</span></span></span><br><span class="line"><span class="params"><span class="function">                                        <span class="type">double</span> *C, <span class="type">double</span> alpha)</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; mBlock; i++) &#123;</span><br><span class="line">      <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">0</span>; j &lt; nBlock; j++) &#123;</span><br><span class="line">        <span class="type">double</span> inner_pod = <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> kk = <span class="number">0</span>; kk &lt; kBlock; kk++) &#123;</span><br><span class="line">          inner_pod +=</span><br><span class="line">              A[(i + a.i) * k + (kk + a.j)] * B[(j + b.i) * k + (kk + b.j)];</span><br><span class="line">        &#125;</span><br><span class="line">        C[(i + c.i) * n + (j + c.j)] += alpha * inner_pod;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * @brief Use five loop tp calculate the matrix-matrix multiplication, for</span></span><br><span class="line"><span class="comment">   * simplicity here, I only implement C = alpha AB.</span></span><br><span class="line"><span class="comment">   *</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  <span class="function"><span class="type">static</span> <span class="type">void</span> <span class="title">gemmUsingBlock</span><span class="params">(<span class="type">int</span> m, <span class="type">int</span> n, <span class="type">int</span> k, <span class="type">double</span> *A, <span class="type">double</span> *B,</span></span></span><br><span class="line"><span class="params"><span class="function">                             <span class="type">double</span> *C, <span class="type">double</span> alpha, <span class="type">double</span> beta)</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="type">const</span> <span class="type">int</span> sizeN = <span class="number">4096</span>;</span><br><span class="line">    <span class="type">const</span> <span class="type">int</span> sizeK = <span class="number">256</span>;</span><br><span class="line">    <span class="type">const</span> <span class="type">int</span> sizeM = <span class="number">96</span>;</span><br><span class="line"></span><br><span class="line">    Point2D a&#123;&#125;, b&#123;&#125;, c&#123;&#125;, ra&#123;&#125;, rb&#123;&#125;, rc&#123;&#125;;</span><br><span class="line"></span><br><span class="line">    <span class="type">const</span> <span class="type">int</span> rSizeN = <span class="number">4</span>;</span><br><span class="line">    <span class="type">const</span> <span class="type">int</span> rSizeM = <span class="number">8</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// We need to create pack A and pack B. It&#x27;s a bad idea to allocate</span></span><br><span class="line">    <span class="comment">// memory in the loop.</span></span><br><span class="line">    <span class="type">double</span> *packedA = <span class="keyword">new</span> <span class="type">double</span>[sizeM * sizeK];</span><br><span class="line">    <span class="type">double</span> *packedB = <span class="keyword">new</span> <span class="type">double</span>[sizeN * sizeK];</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">0</span>; j &lt; n; j += sizeN) &#123;</span><br><span class="line">      <span class="comment">// In this loop, we only handle for the matrix B, it would</span></span><br><span class="line">      <span class="comment">// be split up by `sizeN` for the col.</span></span><br><span class="line">      b.j = j;</span><br><span class="line">      c.j = j;</span><br><span class="line">      <span class="type">int</span> nBlock = j + sizeN &lt; n ? sizeN : n - j;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">for</span> (<span class="type">int</span> kk = <span class="number">0</span>; kk &lt; k; kk += sizeK) &#123;</span><br><span class="line">        <span class="comment">// In this loop, we handle for the matrix A and matrix B.</span></span><br><span class="line">        <span class="comment">// For the matrix A it would be split up by `sizeK` for the col</span></span><br><span class="line">        <span class="comment">// For the matrix B it would be split up by `sizeK` for the row</span></span><br><span class="line">        a.j = kk;</span><br><span class="line">        b.i = kk;</span><br><span class="line">        <span class="type">int</span> kBlock = kk + sizeK &lt; k ? sizeK : k - kk;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// At here, we should pack the B into a consecutive memory, in</span></span><br><span class="line">        <span class="comment">// order to improve the cache hit rate and avoid TLB miss. We make</span></span><br><span class="line">        <span class="comment">// `packedB` into the L3 cache, So the L3 size should be greater</span></span><br><span class="line">        <span class="comment">// than the `sizeN * sizeK`. So in the following `for` loop all</span></span><br><span class="line">        <span class="comment">// the access to the `packedB` would gain great efficiency. And we</span></span><br><span class="line">        <span class="comment">// will convert the row-major to the col-major.</span></span><br><span class="line"></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> j_ = <span class="number">0</span>; j_ &lt; nBlock; j_++) &#123;</span><br><span class="line">          <span class="keyword">for</span> (<span class="type">int</span> i_ = <span class="number">0</span>; i_ &lt; kBlock; i_++) &#123;</span><br><span class="line">            packedB[j_ * sizeK + i_] = B[(b.i + i_) * n + (b.j + j_)];</span><br><span class="line">          &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; m; i += sizeM) &#123;</span><br><span class="line">          <span class="comment">// In this loop, we handle for the matrix A, it would be split</span></span><br><span class="line">          <span class="comment">// up by the `sizeI` for the row.</span></span><br><span class="line">          a.i = i;</span><br><span class="line">          c.i = i;</span><br><span class="line">          <span class="type">int</span> mBlock = i + sizeM &lt; m ? sizeM : m - i;</span><br><span class="line"></span><br><span class="line">          <span class="comment">// At here, we should pack the A into a consecutive memory, in</span></span><br><span class="line">          <span class="comment">// order to improve the cache hit rate and avoid TLB miss. We</span></span><br><span class="line">          <span class="comment">// make `packedA` into the L2 cache, so the L2 cache must be</span></span><br><span class="line">          <span class="comment">// greater than `sizeM * sizeK`. So the following `for` loop</span></span><br><span class="line">          <span class="comment">// for accessing to the `packedA` would get great efficiency.</span></span><br><span class="line">          <span class="keyword">for</span> (<span class="type">int</span> i_ = <span class="number">0</span>; i_ &lt; mBlock; i_++) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> j_ = <span class="number">0</span>; j_ &lt; kBlock; j_++) &#123;</span><br><span class="line">              packedA[i_ * sizeK + j_] = A[(a.i + i_) * k + (a.j + j_)];</span><br><span class="line">            &#125;</span><br><span class="line">          &#125;</span><br><span class="line"></span><br><span class="line">          <span class="comment">// Here, we already split the matrix A and matrix B into the block.</span></span><br><span class="line">          <span class="comment">// However, we need to split again just like the above, this would</span></span><br><span class="line">          <span class="comment">// be super easy.</span></span><br><span class="line">          <span class="keyword">for</span> (<span class="type">int</span> jr = <span class="number">0</span>; jr &lt; nBlock; jr += rSizeN) &#123;</span><br><span class="line">            <span class="type">int</span> rNBlock = jr + rSizeN &lt; nBlock ? rSizeN : nBlock - jr;</span><br><span class="line">            rb.i = jr;</span><br><span class="line">            rc.j = jr + c.j;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> ri = <span class="number">0</span>; ri &lt; mBlock; ri += rSizeM) &#123;</span><br><span class="line">              <span class="type">int</span> rMBlock = ri + rSizeM &lt; mBlock ? rSizeM : mBlock - ri;</span><br><span class="line">              ra.i = ri;</span><br><span class="line">              rc.i = ri + c.i;</span><br><span class="line"></span><br><span class="line">              <span class="keyword">for</span> (<span class="type">int</span> internalK = <span class="number">0</span>; internalK &lt; kBlock; internalK++) &#123;</span><br><span class="line">                ra.j = internalK;</span><br><span class="line">                rb.j = internalK;</span><br><span class="line"></span><br><span class="line">                <span class="built_in">matrixMultiplicationBlock</span>(rMBlock, rNBlock, <span class="number">1</span>, n, sizeK, ra, rb,</span><br><span class="line">                                          rc, packedA, packedB, C, alpha);</span><br><span class="line">              &#125;</span><br><span class="line">            &#125;</span><br><span class="line">          &#125;</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">delete</span>[] packedA;</span><br><span class="line">    <span class="keyword">delete</span>[] packedB;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="type">static</span> <span class="type">void</span> <span class="title">gemm</span><span class="params">(<span class="type">int</span> m, <span class="type">int</span> n, <span class="type">int</span> k, <span class="type">double</span> *A, <span class="type">double</span> *B, <span class="type">double</span> *C,</span></span></span><br><span class="line"><span class="params"><span class="function">                   <span class="type">double</span> alpha, <span class="type">double</span> beta)</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">gemmUsingBlock</span>(m, n, k, A, B, C, alpha, beta);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;p&gt;It’s hard to implement Matrix-Matrix Multiplication. So I recommend the following repository for you. I also refer to this repository</summary>
        
      
    
    
    
    <category term="CS149" scheme="https://luolibrary.com/categories/CS149/"/>
    
    
    <category term="技术" scheme="https://luolibrary.com/tags/%E6%8A%80%E6%9C%AF/"/>
    
    <category term="学习" scheme="https://luolibrary.com/tags/%E5%AD%A6%E4%B9%A0/"/>
    
  </entry>
  
  <entry>
    <title>多核处理器带来的缓存一致性问题</title>
    <link href="https://luolibrary.com/2023/08/22/%E5%A4%9A%E6%A0%B8%E5%A4%84%E7%90%86%E5%99%A8%E5%B8%A6%E6%9D%A5%E7%9A%84%E7%BC%93%E5%AD%98%E4%B8%80%E8%87%B4%E6%80%A7%E9%97%AE%E9%A2%98/"/>
    <id>https://luolibrary.com/2023/08/22/%E5%A4%9A%E6%A0%B8%E5%A4%84%E7%90%86%E5%99%A8%E5%B8%A6%E6%9D%A5%E7%9A%84%E7%BC%93%E5%AD%98%E4%B8%80%E8%87%B4%E6%80%A7%E9%97%AE%E9%A2%98/</id>
    <published>2023-08-22T08:06:28.000Z</published>
    <updated>2023-11-12T07:54:33.746Z</updated>
    
    <content type="html"><![CDATA[<p>在前面的章节中，我们介绍了缓存的底层实现。我们一直假设计算机只有一个CPU核心，然而在现代的计算机体系结构中，CPU拥有多个核心。为了简单起见，如下图所示，每个核心都有一块独立的私有缓存，以及一个由所有核心共享的末级缓存(LLC)。</p><p><img src="https://s2.loli.net/2023/08/10/gT5WfKButCqpOSV.png" alt="多核处理器模型"></p><h2 id="缓存一致性问题"><a href="#缓存一致性问题" class="headerlink" title="缓存一致性问题"></a>缓存一致性问题</h2><p>假设我们运行一个进程，其有两个线程，一个线程运行在C1，另一个线程运行在C2。从程序员的观点出发，这段代码是相当简单的。然而，我们需要从缓存的角度思考这个问题。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;thread&gt;</span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">C1</span><span class="params">(<span class="type">int</span> *A)</span> </span>&#123;</span><br><span class="line">  *A = <span class="number">43</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">C2</span><span class="params">(<span class="type">int</span> *A)</span> </span>&#123;</span><br><span class="line">  <span class="keyword">while</span> (*A == <span class="number">42</span>) &#123;</span><br><span class="line">    ;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">(<span class="type">int</span> argc, <span class="type">char</span> *argv[])</span> </span>&#123;</span><br><span class="line">  <span class="type">int</span> A = <span class="number">42</span>;</span><br><span class="line">  thread thread1&#123;C1, &amp;A&#125;;</span><br><span class="line">  thread thread2&#123;C2, &amp;A&#125;;</span><br><span class="line"></span><br><span class="line">  thread2.<span class="built_in">join</span>();</span><br><span class="line">  thread1.<span class="built_in">join</span>();</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如下表所示：</p><ul><li>Time 1时刻，对于C1来说，其需要对地址A写入值43，由于缓存未命中，其会将A的值42写入其私有缓存中。对于C2来说，其需要检查地址A的内容，由于缓存未命中，其会将A的值42写入其私有缓存中。(可能读者会想如果采取的<em>write-through</em>策略，C2有可能会读取到43的值，我们在这个例子中忽略这种情况，实际上如果涉及到两个核同时读写，<em>write-through</em>策略也会导致缓存的不一致性)</li><li>Time 2时刻，C2继续进行循环操作，由于其能命中其私有缓存，故其一直会进行死循环，然而实际上A的值应该是43。</li></ul><table><thead><tr><th>Time</th><th>Core C1</th><th>Core C2</th></tr></thead><tbody><tr><td>1</td><td>S1: A&#x3D;43;</td><td>L1:while(A &#x3D;&#x3D; 42);</td></tr><tr><td>2</td><td></td><td>L2:while(A &#x3D;&#x3D; 42);</td></tr><tr><td>3</td><td></td><td>L3:while(A &#x3D;&#x3D; 42);</td></tr><tr><td>4</td><td></td><td>…</td></tr><tr><td>n</td><td></td><td>Ln:while(A &#x3D;&#x3D; 42);</td></tr></tbody></table><p>从上述的过程可以得出一个重要的结论，多核问题会产生新的缓存不一致问题。由于私有缓存的存在，当某两个私有缓存指向的是同一个物理地址时，需要保持同步，维护一致性。</p><h2 id="一致性问题解决通用方法"><a href="#一致性问题解决通用方法" class="headerlink" title="一致性问题解决通用方法"></a>一致性问题解决通用方法</h2><p>从上述的例子，我们其实可以很快地可以类比在多线程编程中的读写同步问题。当只读的时候，不存在不一致性问题，问题就在于写入缓存时。在实际的处理器中采用了<em>cache coherence protocol</em>，分为两类：</p><ul><li><em>Consistency-agnostic coherence</em>：首先需要将写入的这个事件通过缓存中的内部网络传递到其它核，再写入缓存。</li><li><em>Consistency-directed coherence</em>：先写入缓存，再广播事件，常用于GPGPU。</li></ul><h2 id="Consistency-agnostic-缓存一致性不变量"><a href="#Consistency-agnostic-缓存一致性不变量" class="headerlink" title="(Consistency-agnostic)缓存一致性不变量"></a>(Consistency-agnostic)缓存一致性不变量</h2><p>我们能够确定一个十分明确的不变量，<em>single-writer-multiple-reader</em>(SWMR)，如下图所示，对于任一时刻，对于某一个内存地址来说，其要么有多个核心在执行读操作，要么只有一个核心在进行读写操作。</p><p><img src="https://s2.loli.net/2023/08/10/Zs2ypk5TmYiIgHc.png" alt="SWMR"></p><p>同时我们还需要确保数据的一致性，内存位置的值与其最后一个读写时期结束时的值相同。因此我们可以明确如下的两个缓存一致性不变量：</p><ol><li><strong>Single-Writer, Multiple-Read (SWMR) Invariant</strong>.</li><li><strong>Data-Value Invariant</strong>.</li></ol><h2 id="通用实现方法"><a href="#通用实现方法" class="headerlink" title="通用实现方法"></a>通用实现方法</h2><p>我们希望所有CPU的私有缓存以及所有核心共享的末级缓存都能够维持缓存一致性不变量。目前采用的通用方式就是对于每一个私有缓存以及所有核心共享的末级缓存通过添加一个<em>coherence controller</em>状态机来维持这两个不变量。</p><p>对于私有缓存的<em>coherence controller</em>，特称为<em>cache controller</em>，其需要处理来自CPU的Load&#x2F;Store请求，当是Load请求的时候返回相应的数据给CPU。同时其需要发送请求和响应，接收其他controller的请求和响应。对于末级缓存而言，特称为<em>memory controller</em>，其只需要发送请求和响应，接收其他controller的请求和响应。</p><p>在后面的所有讨论中，我们都默认<em>cache</em>策略为<em>write-back</em>。</p><h3 id="一个简单协议的实现"><a href="#一个简单协议的实现" class="headerlink" title="一个简单协议的实现"></a>一个简单协议的实现</h3><p>首先，对于状态机而言，我们必须定义状态以及导致状态变化的事件：</p><ul><li>对于<em>cache controller</em>的每一个<em>cache block</em>而言，其有三种状态，$\text{I}$(invalid)，$\text{V}$(valid), $\text{IV}^{\text{D}}$。最后一个状态的存在是由于需要从内存里面读取到缓存，我们必须考虑stall的影响。</li><li>对于<em>memory controller</em>的每一个<em>cache block</em>而言，其只有两种状态，$\text{I}$(invalid)，$\text{V}$(valid)。<ul><li>当状态为$\text{I}$时，所有的cache controller相对应的cache block状态都为$\text{I}$。</li><li>当状态为$\text{V}$时，有且只有一个cache controller对于的cache block状态为$\text{V}$。</li></ul></li><li>三类主要事件：<ul><li><em>Get</em>：请求一个cache block。</li><li><em>DateResp</em>：传输cache block里的数据。</li><li><em>Put</em>：将cache block里的数据写回内存。</li></ul></li></ul><p>我们可以给出如下图所示的状态转换：</p><p><img src="https://s2.loli.net/2023/11/12/EHgMnZYblKV4aC9.png" alt="cache controller specification"></p><p><img src="https://s2.loli.net/2023/11/12/7SOtqrJfFh6IxZC.png" alt="memory controller specification"></p><p>可以发现这个协议的实现效率十分地低，实际上我们完全在多核处理器下实现的串行操作，在使用当某几个核心竞争相同的<em>cache block</em>时，会发生大量的stall，导致性能的降低。</p><h2 id="设计缓存一致性协议的通用思路"><a href="#设计缓存一致性协议的通用思路" class="headerlink" title="设计缓存一致性协议的通用思路"></a>设计缓存一致性协议的通用思路</h2><h3 id="状态"><a href="#状态" class="headerlink" title="状态"></a>状态</h3><p>在多核系统中，缓存的状态定义变得尤为关键，为了提高效率实现共享不能简单地将其状态划分为invalid和valid：</p><ul><li>Validity：一个<em>valid</em>的cache block必须是其包含了最新的数据。</li><li>Dirtiness：Dirtiness一定是暗含了Validity。当cache block的状态发生变化时，其必须把结果写入到内存中。</li><li>Exclusivity：同样Exclusivity一定时暗含了Validity，且其是唯一一个拥有memory block的副本。</li><li>Ownership：是当前memory block的所有者，负责控制其响应和请求。</li></ul><h4 id="Stable-states"><a href="#Stable-states" class="headerlink" title="Stable states"></a>Stable states</h4><ul><li>$\text{M}$(modified)：valid，exclusive，owned and potentially dirty。</li><li>$\text{O}$(owned)：valid，owned，and potentially dirty but not exclusive。对于这种状态而言，<em>cache block</em>仅仅是只读的，其他核心的<em>cache block</em>也可能共享这个数据，但是其并不拥有控制权，所有的请求和响应都需要是$O$的<em>coherence controller</em>来处理。</li><li>$\text{E}$(exclusive)：valid，exclusive，and clean。</li><li>$\text{S}$(shared)：valid but not exclusive，not dirty and not owned。</li><li>$\text{I}$(invalid)：可以理解为Nothing。</li></ul><h4 id="Transient-states"><a href="#Transient-states" class="headerlink" title="Transient states"></a>Transient states</h4><p>我们定义如下的规则，对于$\text{XY}^{\text{Z}}$而言，其意味着只有经历完事件$\text{Z}$，才能完成状态$\text{X}$到$\text{Y}$的转换。</p><h3 id="Transactions"><a href="#Transactions" class="headerlink" title="Transactions"></a>Transactions</h3><p>我们定义如下的transactions:</p><ol><li>GetShared(<code>GetS</code>)：将block变为$\text{S}$状态。</li><li>GetModified(<code>GetM</code>)：将block变为$\text{M}$状态。</li><li>Upgrade(<code>Upg</code>)：将block从$\text{S}$或者$\text{O}$状态变为$\text{M}$状态。</li><li>PutShared(<code>PutS</code>)：从$\text{S}$状态释放block。</li><li>PutExclusive(<code>PutE</code>)：从$\text{E}$状态释放block。</li><li>PutOwned(<code>PutO</code>)：从$\text{O}$状态释放block。</li><li>PutModified(<code>PutM</code>)：从$\text{M}$状态释放block。</li></ol><p>同时我们可以知道CPU会对<em>cache controller</em>发送如下的事件：</p><ol><li>Load：如果缓存命中了，直接读取，否则执行<code>GetS</code>。</li><li>Store：如果在$\text{M}$或者$\text{E}$状态下命中了缓存，直接写入缓存，否则执行<code>GetM</code>或者<code>Upg</code>。</li><li>Atomic read-modify-write：如果在$\text{M}$或者$\text{E}$状态下命中了缓存，直接执行RMW语义，否则执行<code>GetM</code>或者<code>Upg</code>。</li><li>Instruction fetch：如果缓存命中了，直接读取，否则执行<code>GetS</code>。</li><li>Replace：执行<code>PutS</code>，<code>PutE</code>，<code>PutO</code>或者<code>PutM</code>。</li></ol><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><ul><li>A Primer on Memory Consistency and Cache Coherence chapter 2 and chapter 6</li></ul>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;p&gt;在前面的章节中，我们介绍了缓存的底层实现。我们一直假设计算机只有一个CPU核心，然而在现代的计算机体系结构中，CPU拥有多个核心。为了简单起见，如下图所示，每个核心都有一块独立的私有缓存，以及一个由所有核心共享的末级缓存(LLC)。&lt;/p&gt;
&lt;p&gt;&lt;img</summary>
        
      
    
    
    
    <category term="缓存与内存专栏" scheme="https://luolibrary.com/categories/%E7%BC%93%E5%AD%98%E4%B8%8E%E5%86%85%E5%AD%98%E4%B8%93%E6%A0%8F/"/>
    
    
    <category term="技术" scheme="https://luolibrary.com/tags/%E6%8A%80%E6%9C%AF/"/>
    
    <category term="学习" scheme="https://luolibrary.com/tags/%E5%AD%A6%E4%B9%A0/"/>
    
  </entry>
  
  <entry>
    <title>物理缓存</title>
    <link href="https://luolibrary.com/2023/08/18/%E7%89%A9%E7%90%86%E7%BC%93%E5%AD%98/"/>
    <id>https://luolibrary.com/2023/08/18/%E7%89%A9%E7%90%86%E7%BC%93%E5%AD%98/</id>
    <published>2023-08-18T09:15:04.000Z</published>
    <updated>2023-08-18T10:38:24.289Z</updated>
    
    <content type="html"><![CDATA[<h2 id="物理缓存的基本操作"><a href="#物理缓存的基本操作" class="headerlink" title="物理缓存的基本操作"></a>物理缓存的基本操作</h2><p>物理缓存完全采用物理地址作为索引。由于物理地址具有的唯一性，其能够避免歧义和别名问题。如下图所示，首先虚拟地址通过MMU转化为物理地址，然后根据物理地址判断缓存是否命中。</p><p><img src="https://s2.loli.net/2023/08/18/YnqESIGakT18t9m.png" alt="物理缓存结构"></p><h2 id="操作系统与物理缓存的交互"><a href="#操作系统与物理缓存的交互" class="headerlink" title="操作系统与物理缓存的交互"></a>操作系统与物理缓存的交互</h2><p>由于物理缓存采取物理地址进行索引，无论是共享内存还是独享内存，物理地址的唯一性确保了歧义和别名问题都可以避免。然而，我们还需要关心一下I&#x2F;O问题。</p><h3 id="I-O与总线监听技术"><a href="#I-O与总线监听技术" class="headerlink" title="I&#x2F;O与总线监听技术"></a>I&#x2F;O与总线监听技术</h3><p>物理缓存基于总线监听技术来保证I&#x2F;O操作的缓存一致性。如下图所示，总线连接了CPU&#x2F;MMU&#x2F;cache，内存以及I&#x2F;O设备。使得每个设备都能收到其他设备的广播信息。如下图所示，当CPU需要读内存时，其会把物理地址的信息通过总线发送给内存，然后内存会把值发送给CPU。</p><p><img src="https://s2.loli.net/2023/08/18/i9hl3CYW1ndFfVH.png" alt="System bus"></p><p>总线中的数据传递是基于事务的思想实现的，通过一个或者多个总线事务进行传输。每一个总线事务只能够发送一定大小的数据，例如当需要从从磁盘里面读取数据时，会划分为多个总线事务，每个事务都会包含内存地址及数据。所有的设备都可以观察总线上的事务，当物理缓存没有使用总线时，其会监听总线上所有的总线事务(<em>snooping</em>)。</p><p>物理缓存会检查每个总线事务的地址，去判断给物理地址是否命中缓存，如果没有命中缓存，我们什么也不需要处理。然而当命中了缓存后，我们就需要思考如何进行处理呢。我们首先考虑<em>write-through</em>策略，当I&#x2F;O设备需要从这个地址读取内容时，我们不需要采取任何的内容，因为<em>write-through</em>策略保证了缓存和内存的一致性。然而，当I&#x2F;O设备需要写入内容到该地址时，必须要把对应的物理缓存的cache line的valid位置为0，从而让下次读取时产生cache miss，让CPU从内存中读取数据，保证缓存与内存的一致性。当然，有些实现直接修改cache line的block值。</p><p>对于<em>write-back</em>策略，当I&#x2F;O设备需要从这个地址读取内容时，如果dirty位为0，I&#x2F;O设备可以直接从内存中读，如果dirty为1，I&#x2F;O设备直接从cache读取数据，对于I&#x2F;O设备中这个过程是透明的，I&#x2F;O设备并不知道它是从内存读还是缓存读的。当I&#x2F;O设备需要写入内容到该地址时，如果其dirty位为0，缓存可以直接将其valid位置为0。如果正好写入的大小等于cache line，那么我们就可以直接替代cache line的值，然而很有可能写入的大小小于cache line，就需要硬件提供局部写入的机制，因为不能丢失原先已经写入的缓存的信息。</p>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;h2 id=&quot;物理缓存的基本操作&quot;&gt;&lt;a href=&quot;#物理缓存的基本操作&quot; class=&quot;headerlink&quot;</summary>
        
      
    
    
    
    <category term="缓存与内存专栏" scheme="https://luolibrary.com/categories/%E7%BC%93%E5%AD%98%E4%B8%8E%E5%86%85%E5%AD%98%E4%B8%93%E6%A0%8F/"/>
    
    
    <category term="技术" scheme="https://luolibrary.com/tags/%E6%8A%80%E6%9C%AF/"/>
    
    <category term="学习" scheme="https://luolibrary.com/tags/%E5%AD%A6%E4%B9%A0/"/>
    
  </entry>
  
  <entry>
    <title>带有键值的虚拟缓存</title>
    <link href="https://luolibrary.com/2023/08/17/%E5%B8%A6%E6%9C%89%E9%94%AE%E5%80%BC%E7%9A%84%E8%99%9A%E6%8B%9F%E7%BC%93%E5%AD%98/"/>
    <id>https://luolibrary.com/2023/08/17/%E5%B8%A6%E6%9C%89%E9%94%AE%E5%80%BC%E7%9A%84%E8%99%9A%E6%8B%9F%E7%BC%93%E5%AD%98/</id>
    <published>2023-08-17T12:52:15.000Z</published>
    <updated>2024-04-29T13:08:10.662Z</updated>
    
    <content type="html"><![CDATA[<p>虚拟缓存存在一个很大的问题，由于每一个进程都拥有独立的地址空间。当上下文切换后，必须对缓存进行刷新。一个很简单的想法就是把pid和虚拟地址结合起来作为索引进行缓存查找，从而减少刷新缓存的次数。</p><h2 id="带有键值的虚拟缓存的基本操作"><a href="#带有键值的虚拟缓存的基本操作" class="headerlink" title="带有键值的虚拟缓存的基本操作"></a>带有键值的虚拟缓存的基本操作</h2><p>首先是硬件结构的变化，由于需要支持键值，每个cache line的tag都会增加一个进程键值用于区分不同的进程。同时我们需要一个新的寄存器来保存这个进程键值，如下图所示：</p><p><img src="https://s2.loli.net/2023/08/17/DMP4FEZoe96C5XQ.png" alt="带有键值的虚拟缓存"></p><p>带有键值的虚拟缓存除了其加了一个新的键值以外其本质与虚拟缓存的操作没有差别。其仍然通过虚拟地址进行索引，然而由于有额外的键值信息可以判断当前缓存是否属于当前进程。一个美好的愿望是硬件为系统中的每一个进程都生成一个独一无二的标识符，这样我们就不会产生歧义和别名。愿望始终是愿望，硬件能够提供的标识符往往数目很少，有些硬件仅仅支持8个标识符。所以会有某些进程共享同一个标识符，我们仍然会面临虚拟缓存的歧义和别名问题。</p><h2 id="操作系统与带有键值的虚拟缓存的交互"><a href="#操作系统与带有键值的虚拟缓存的交互" class="headerlink" title="操作系统与带有键值的虚拟缓存的交互"></a>操作系统与带有键值的虚拟缓存的交互</h2><h3 id="上下文切换"><a href="#上下文切换" class="headerlink" title="上下文切换"></a>上下文切换</h3><p>当键值充足时，我们可以为每一个在系统中运行的进程分配一个独一无二的键，这样上下文切换的时候，对于<em>write-through</em>而言，我们都不需要刷新缓存，仅仅只需要改变寄存器中的进程键值即可。</p><p>当键值不够使用时，操作系统需要重新分配键值。一个常见的思路是换入和换出，我们仍然需要将某个键值换出，然后把所有有关于该键值的缓存进行刷新。对于<em>write-through</em>而言，我们做的操作会比较简单，我们直接可以直接把cache line的valid位置为0。对于<em>write-back</em>而言，我们还需要写入内存。实际上，你会发现，这样也带来了另外的复杂度，操作系统应该换出哪个键呢？</p><p>对于采取<em>write-back</em>的写策略，还有一个更加严重的问题。当我们对进程A进行了上下文切换后，其cache line的值可能已经被改变了，然而很有可能另一个进程B会刷新这个cache line，但是对于进程B而言，操作系统并没有进程A的页表信息，操作系统无法知道该写入哪个具体的物理内存。因此，大多数采取带有键值的虚拟缓存会采用<em>write-through</em>策略。</p><p>有些硬件提供了额外的机制，既然目前已经有键值信息，就可以把键值信息和页表信息组成一个映射，这样对于上述情况，就能够找到物理地址，从而写入数据。然而，却极大地增加了硬件负担。</p><h3 id="共享内存"><a href="#共享内存" class="headerlink" title="共享内存"></a>共享内存</h3><p>对于虚拟缓存而言，两个进程之间共享内存会因为上下文切换时自动对缓存进行刷新的机制避免别名问题，然而带有键值的虚拟内存会更加的复杂。</p><p>对于直接映射缓存而言，我们假设两个进程的相同的虚拟地址指向相同的物理地址。同时假设操作系统给进程$A$分配的键值为1，给进程$B$分配的键值为2。我们可以明确地知道由于虚拟地址相同，其找到的cache set必然相同，由于每个cache set只有一个cache line。如下图所示，当进程A切换到进程B时，进程B访问该虚拟地址，由于键值不同，会导致cache miss，然后读取<strong>相同的物理内存</strong>到cache中。然而，物理内存没有发生任何的改变。</p><p><img src="https://s2.loli.net/2023/08/17/iyScjte1s5PT3rD.png" alt="共享内存-直接映射虚拟缓存"></p><p>然而，大多数情况这两个进程的虚拟地址是不相同的，这样就和虚拟缓存的情况一样了，会产生别名的情况。当我们需要使用共享内存时，cache在一定程度上就失效了。</p><h2 id="应用"><a href="#应用" class="headerlink" title="应用"></a>应用</h2><p>MMU的一个重要作用就是将虚拟地址转化为物理地址。MMU内部提供了自动将虚拟地址转化为物理地址的机制，操作系统通过构建虚拟地址到物理地址的映射，对于每个进程操作系统都会构建页表的基地址将其放到某个特定的寄存器中。</p><h3 id="MMU与操作系统的交互"><a href="#MMU与操作系统的交互" class="headerlink" title="MMU与操作系统的交互"></a>MMU与操作系统的交互</h3><p>Sv39 RISC-V只使用虚拟地址的低39位作为索引来翻译物理地址。如下图所示，其使用了三层页表结构来将虚拟地址翻译为物理地址。其转化的过程相当简单，首先通过页表的基地址找到其L2的偏移量，然后读取其值找到二级页表的基地址找到其L1的偏移量，然后这样循环地进行找到物理地址加上虚拟地址中的偏移量即可。</p><p><img src="https://s2.loli.net/2022/06/06/pFzaiEU7LS2ZdAX.png" alt="RISC-V address translation details"></p><p>对于操作系统，只需要将页表的基地址放入<code>satp</code>寄存器中即可。硬件会自动完成虚拟地址到物理地址的转换。</p><h3 id="TLB"><a href="#TLB" class="headerlink" title="TLB"></a>TLB</h3><p>从上述的过程可以得知，既然硬件需要完成虚拟地址到物理地址，我们已经讲述过的虚拟缓存是不是完美符合这个需求的。现有的MMU都有一小块虚拟缓存，即TLB。TLB保存的缓存信息即为虚拟地址对应的物理地址以及页属性。由于不同进程的页表不同，其虚拟地址对应的物理地址也必然不同：</p><ul><li>对于虚拟缓存来说，每次进行上下文切换都需要将对应的cache line valid位置0。</li><li>对于带有键值的虚拟缓存来说，由于其键值存在，不需要每次上下文切换刷新TLB，当键值不够分配时，才需要将对应的cache line valid位置0。</li></ul><p>TLB是最适合使用虚拟缓存的，这是因为TLB是一个只读缓存且没有共享内存机制。</p><h2 id="基于物理标签的虚拟缓存"><a href="#基于物理标签的虚拟缓存" class="headerlink" title="基于物理标签的虚拟缓存"></a>基于物理标签的虚拟缓存</h2><p>虚拟缓存最重要的一个问题在于虚拟地址与物理地址的映射问题，虚拟地址是操作系统与硬件结合起来给进程提供的抽象，其映射关系会随着上下文切换发生改变，从而造成了别名和歧义问题。于是就提出了另一种类型的虚拟缓存，我们仍然使用虚拟缓存对cache se定位，同时找寻block offset。但是缓存中的tag使用物理地址。然而，这样我们不得不增加新的负担，也就是我们必须用通过MMU将虚拟地址转化为物理地址。</p><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><ul><li>UNIX Systems for Modern Architectures Chapter 4</li><li>UNIX Systems for Modern Architectures Chapter 5</li><li><a href="https://zhuanlan.zhihu.com/p/107096130">Cache 组织方式</a></li><li><a href="https://zhuanlan.zhihu.com/p/108425561">TLB原理</a></li></ul>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;p&gt;虚拟缓存存在一个很大的问题，由于每一个进程都拥有独立的地址空间。当上下文切换后，必须对缓存进行刷新。一个很简单的想法就是把pid和虚拟地址结合起来作为索引进行缓存查找，从而减少刷新缓存的次数。&lt;/p&gt;
&lt;h2 id=&quot;带有键值的虚拟缓存的基本操作&quot;&gt;&lt;a</summary>
        
      
    
    
    
    <category term="缓存与内存专栏" scheme="https://luolibrary.com/categories/%E7%BC%93%E5%AD%98%E4%B8%8E%E5%86%85%E5%AD%98%E4%B8%93%E6%A0%8F/"/>
    
    
    <category term="技术" scheme="https://luolibrary.com/tags/%E6%8A%80%E6%9C%AF/"/>
    
    <category term="学习" scheme="https://luolibrary.com/tags/%E5%AD%A6%E4%B9%A0/"/>
    
  </entry>
  
  <entry>
    <title>虚拟缓存</title>
    <link href="https://luolibrary.com/2023/08/17/%E8%99%9A%E6%8B%9F%E7%BC%93%E5%AD%98/"/>
    <id>https://luolibrary.com/2023/08/17/%E8%99%9A%E6%8B%9F%E7%BC%93%E5%AD%98/</id>
    <published>2023-08-17T08:27:08.000Z</published>
    <updated>2024-03-26T15:05:45.018Z</updated>
    
    <content type="html"><![CDATA[<p>我们已经讲述了缓存最基本的结构，然而一个核心的关键问题在于在现代的体系结构中，运行中的进程都是使用的虚拟地址，这是由硬件和操作系统提供了一个美好的假象。在上一节中，我们已经明确了一个核心关键点，内存地址与缓存之间是有一定的映射关系，那么我们是应该用虚拟地址还是物理地址呢？一种美好的愿望是我们直接使用虚拟地址，因为我们这样就不需要把虚拟地址通过MMU转化为物理地址，从而提高效率。然而，愿望始终是愿望。虚拟缓存给操作系统带来了极大的负担来保证一致性。</p><h2 id="虚拟缓存的基本操作"><a href="#虚拟缓存的基本操作" class="headerlink" title="虚拟缓存的基本操作"></a>虚拟缓存的基本操作</h2><p>无论如何，我们也只能有两种基本的操作，要么读要么写。当我们去读一个虚拟地址其内存值的时候，我们仍然按照相同的操作去查询缓存是否命中，如果缓存命中了，直接返回缓存值即可。如果缓存没有命中，我们需要先把虚拟地址通过MMU转化为物理地址，然后从内存中读取数据，并加载到缓存中。</p><p>写的问题就更加复杂了，由于写的策略有两种write-through和write-back（我们假设其都支持write-allocate）。我们需要考虑在这两种策略下，虚拟缓存是如何进行写的操作的。对于write-through，无论是命中了cache还是没有命中cache，其都需要把虚拟地址通过MMU转化为物理地址，然后写入内存。对于没有命中cache的情况，其会直接读取内存的值写入cache中。对于write-back而言，对于没有命中cache的情况，其和write-through是相似的，对于命中了cache的情况，我们只需要在cache中写入即可。</p><p><img src="https://s2.loli.net/2023/08/16/PGzDoUNlqK4ZdxX.png" alt="虚拟缓存"></p><p>你可能会发现，除了我们需要读取内存的时候要把虚拟地址转化为物理地址的情况，虚拟缓存似乎也没有什么本质改变。然而，我们必须得思考一个问题，就是读写权限的问题。</p><ul><li>缓存读的虚拟地址对应的物理地址实际上已经变得无法读取了，然而缓存并不知道这个信息，如果我们仍然访问这个虚拟地址，缓存会直接给CPU返回其保存的值，显然我们需要判断这个物理地址是否可读。</li><li>同理，缓存写的虚拟地址对应的物理地址也可能变得无法写入了。对于cache miss的情况，我们能够轻易地进行判断，因为我们会把虚拟地址通过MMU转化为物理地址，可以判断这个物理地址是否可写。对于cache hit的情况，如果其dirty flag已经是1，我们显然可以得知这个虚拟地址对应的物理地址是可写的。麻烦的情况在于dirty flag为0，我们必须知道其到底是否可写，所以我们需要把虚拟地址转化为物理地址去判断这个地址是否可写。</li></ul><h2 id="歧义-Ambiguity-与别名-Alias"><a href="#歧义-Ambiguity-与别名-Alias" class="headerlink" title="歧义(Ambiguity)与别名(Alias)"></a>歧义(Ambiguity)与别名(Alias)</h2><h3 id="歧义"><a href="#歧义" class="headerlink" title="歧义"></a>歧义</h3><p>我们首先回忆一下操作系统是如何与硬件结合实现虚拟地址转化到物理地址。对于每一个进程，操作系统需要维持一个页表，这个页表包含了从虚拟地址到物理地址的映射，操作系统分配内存时，会自动从空闲的内存中分配页然后修改页表，然后按照硬件规则完成虚拟地址到物理地址的映射。也就是对于两个不同的进程，其相同的虚拟地址完全可以对应不同的物理地址。当操作系统对进程进行调度后，由于我们是按照虚拟地址进行缓存的映射，虚拟缓存无法意识到这个物理地址已经改变了。因此，操作系统在此时必须做一些事情来刷新缓存。</p><p><img src="https://s2.loli.net/2023/08/16/qvKxtZG1NngHer8.png" alt="虚拟缓存-歧义"></p><h3 id="别名"><a href="#别名" class="headerlink" title="别名"></a>别名</h3><p>进程间的通信有一个重要的方法就是共享内存，即不同的虚拟地址能够映射到同一个物理地址。然而，由于两个进程的虚拟地址不同，其对应的cache set也会不同，假设采取的策略为write-back，当某一个进程修改了共享内存，其只会改变与其对应的cache line的值，却不会改变另一个进程的cache line的值，进而导致不一致问题。</p><p><img src="https://s2.loli.net/2023/08/16/sdqVXHNUEbKjBCQ.png" alt="虚拟缓存-别名"></p><h2 id="操作系统与虚拟缓存的交互"><a href="#操作系统与虚拟缓存的交互" class="headerlink" title="操作系统与虚拟缓存的交互"></a>操作系统与虚拟缓存的交互</h2><p>由于歧义和别名问题，在许多场景下，操作系统必须要采取一些策略来保证缓存的一致性。</p><h3 id="上下文切换"><a href="#上下文切换" class="headerlink" title="上下文切换"></a>上下文切换</h3><p>当发生上下文切换时，一个虚拟地址可能会映射到另一个物理地址，此时虚拟缓存就应该失效了。所以内核会强制刷新所有当前进程的缓存。对于只进行了读操作的cache line，我们只需要让这个cache line的valid位置为0。对于进行了写的操作，如果其策略为write-through，其操作也很简单，将valid位置为0，然而如果写策略为write-back，我们需要把每一个改变了的cache line重新写入内存，会花费大量的时间。</p><p>除此之外，如果上下文切换相当地频繁，缓存的命中率会急剧地下降。因此，虚拟缓存最合适的场景是计算型任务以及批处理任务。减少了虚拟地址转化为物理地址的损耗，同时避免上下文切换带来的性能损耗。</p><h3 id="共享内存"><a href="#共享内存" class="headerlink" title="共享内存"></a>共享内存</h3><p>对于两个进程使用共享内存的情况，是不存在别名问题的，因为上下文切换时会自动解决这个问题。然而，麻烦的在于可能存在同一个进程两个不同的虚拟地址映射到同一块共享内存，这样就导致了别名问题。一个很简单的解决方法就是不使用cache。</p><h3 id="I-O"><a href="#I-O" class="headerlink" title="I&#x2F;O"></a>I&#x2F;O</h3><p>对于buffered IO而言，由于操作系统管理的内核使用统一的虚拟地址，其不会存在歧义和别名问题。然而，最大的问题在于DMA设备，由于DMA设备直接与内存交互，会导致缓存与内存的不一致问题，即unbuffered I&#x2F;O。</p><p>当用户采用系统调用<code>write</code>时，且缓存写策略为write-back时，不会直接把修改的数据写入到内存中，如果此时DMA设备读取相关的数据，就会导致不一致性问题。所以当DMA设备需要读取数据时，操作系统应强制刷新缓存。如果采用阻塞I&#x2F;O，当执行<code>write</code>或者<code>read</code>系统调用时，操作系统本身就会进行上下文的切换，对缓存进行刷新。</p><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><ul><li>UNIX Systems for Modern Architectures Chapter 3</li><li><a href="https://zhuanlan.zhihu.com/p/107096130">Cache 组织方式</a></li></ul>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;p&gt;我们已经讲述了缓存最基本的结构，然而一个核心的关键问题在于在现代的体系结构中，运行中的进程都是使用的虚拟地址，这是由硬件和操作系统提供了一个美好的假象。在上一节中，我们已经明确了一个核心关键点，内存地址与缓存之间是有一定的映射关系，那么我们是应该用虚拟地址还是物理地址呢？一</summary>
        
      
    
    
    
    <category term="缓存与内存专栏" scheme="https://luolibrary.com/categories/%E7%BC%93%E5%AD%98%E4%B8%8E%E5%86%85%E5%AD%98%E4%B8%93%E6%A0%8F/"/>
    
    
    <category term="技术" scheme="https://luolibrary.com/tags/%E6%8A%80%E6%9C%AF/"/>
    
    <category term="学习" scheme="https://luolibrary.com/tags/%E5%AD%A6%E4%B9%A0/"/>
    
  </entry>
  
  <entry>
    <title>缓存基础知识</title>
    <link href="https://luolibrary.com/2023/08/16/%E7%BC%93%E5%AD%98%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86/"/>
    <id>https://luolibrary.com/2023/08/16/%E7%BC%93%E5%AD%98%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86/</id>
    <published>2023-08-16T06:32:49.000Z</published>
    <updated>2024-03-26T13:06:31.788Z</updated>
    
    <content type="html"><![CDATA[<h2 id="通用的缓存组织结构"><a href="#通用的缓存组织结构" class="headerlink" title="通用的缓存组织结构"></a>通用的缓存组织结构</h2><p>缓存的目的在于减少CPU直接从内存中读取数据的次数。缓存是静态存储器，其不需要对电容进行刷新的性质决定了其访问速度必然快于动态存储器（内存）。如何构建内存与缓存之间的映射关系是缓存的一个最重要的基础知识。对于开发者来说，缓存的存在是透明的。然而，当编写高性能计算或者多线程的程序时，我们不能忽视缓存这个在计算机体系结构中起到重要作用的部件。</p><p>我们假定某个计算机支持$m$位的内存地址。显然，常见的$m$为32位或者64位。如下图所示，缓存是由多个<em>cache set</em>组成的，每个<em>cache set</em>由多个<em>cache line</em>组成，同时每一个<em>cache line</em>包含一个<em>cache block</em>。我们定义如下的符号含义：</p><ul><li>$S$：cache set的数目。</li><li>$E$: 每一个cache set所包含的cache line的数目。</li><li>$B$: 每一个cache line所包含的cache block的总字节。</li></ul><p><img src="https://s2.loli.net/2023/08/15/Wr2qhtOZKT4b6Jl.png" alt="通用的缓存组织结构-基本"></p><p>我们需要根据当前的内存地址，首先定位到cache set，由于cache set的数目为$S$，故我们需要$s$位数来定位到某个cache set（显然，$2^{s} &#x3D; S$）。同时，我们需要$b$位数来定位到cache block的偏移量（显然，$2^{b} &#x3D; B$）。此时，我们还有一个问题没有解决，我们仍然需要判断其位于哪个cache line中。我们在前两个过程中已经使用了$s + b$位，故我们可以使用剩下的$t &#x3D; m - (s + b)$位来去定位cache line。然而，目前的缓存组织结构中并不存在这个信息，所以我们需要在缓存中添加。同时，我们在cache line中添加一个标志位，判断当前cache line是否有效，如下图所示&#96;</p><p><img src="https://s2.loli.net/2023/08/15/97SbKtfo5JPCmsA.png" alt="通用的缓存组织结构"></p><h2 id="直接映射缓存-Direct-Mapped-caches"><a href="#直接映射缓存-Direct-Mapped-caches" class="headerlink" title="直接映射缓存(Direct-Mapped caches)"></a>直接映射缓存(Direct-Mapped caches)</h2><p>当$E &#x3D; 1$时，就被称为直接映射缓存，其原理就相当简单了。由于每一个cache set只包含一个cache line，所以每一个地址有且只能有一个cache line与之对应。然而，这样在某些情况下会带来很大的性能问题。举个例子，如下面的代码所示，我们循环地依次在地址$A$和地址$B$中写入数据。恰好不好，地址$A$的地址$B$的$s$是一致的。显然，他们会访问到同一个cache set，由于只有一个cache line，每次其对比tag值都不相同，故缓存硬件会从内存中读取相应的数据放到cache line中，导致每次cache line中的数据都会被刷新，也就是一直<em>cache miss</em>。这种现象被称为cache颠簸（<em>cache thrashing</em>）。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">(<span class="type">int</span> argc, <span class="type">char</span> *argv[])</span> </span>&#123;</span><br><span class="line">  <span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">    *A = <span class="built_in">computeA</span>();</span><br><span class="line">    *B = <span class="built_in">computeB</span>();</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="组相连缓存-Set-Associative-Caches"><a href="#组相连缓存-Set-Associative-Caches" class="headerlink" title="组相连缓存(Set Associative Caches)"></a>组相连缓存(Set Associative Caches)</h2><p>为了解决上述问题，每一个cache set可以含有多个cache line，假如$E &#x3D; 2$，对于上述例子而言，硬件会寻找两个cache line的tag值是否与物理地址的tag相等，这样就在一定程度上可以缓解cache颠簸。然而，这样也带了另一个问题，如果没有一个cache line与之对应，产生了cache miss。那么缓存必须换出一个cache line（如果有空的cache line，当然就直接使用了）。显然，这就回到了常见的换入换出策略了，例如LRU或者LFU。</p><h2 id="全相连缓存"><a href="#全相连缓存" class="headerlink" title="全相连缓存"></a>全相连缓存</h2><p>当$S &#x3D; 1$时，即只存在一个cache set时，我们直接舍弃了$s$。然而，我们增大了寻找cache line的成本，意味着需要遍历来找到我们所需的cache line，如果是cache miss的情况，我们甚至需要遍历所有的cache line。当然，硬件可能提供一些并行来加快速度，然而由于极高的硬件成本，采用率极少。</p><h2 id="缓存写策略"><a href="#缓存写策略" class="headerlink" title="缓存写策略"></a>缓存写策略</h2><ul><li><em>write-through</em>: 同时写入缓存和内存。其优势在于能够始终保持内存和缓存的一致性，劣势在于每次写CPU都会调用一次写内存操作，浪费时钟。</li><li><em>write-back</em>: 先写入缓存，后面再写入内存。每个cache line中会有一个bit位记录数据是否被修改过，称之为<em>dirty bit</em>。优势在于避免了写内存操作，然而劣势在于需要处理不一致性。</li></ul><h2 id="write-allocate"><a href="#write-allocate" class="headerlink" title="write-allocate"></a>write-allocate</h2><p>当使用<em>write-back</em>时，<em>write-allocate</em>通常一起被使用，CPU将数据写入内存的情况，如果缓存未命中其总是被写入缓存中。</p><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><ul><li><a href="https://zhuanlan.zhihu.com/p/102293437">Cache的基本原理</a></li><li><a href="https://csapp.cs.cmu.edu/">CSAPP Chapter 6</a></li><li>UNIX Systems for Modern Architectures Chapter 2</li></ul>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;h2 id=&quot;通用的缓存组织结构&quot;&gt;&lt;a href=&quot;#通用的缓存组织结构&quot; class=&quot;headerlink&quot;</summary>
        
      
    
    
    
    <category term="缓存与内存专栏" scheme="https://luolibrary.com/categories/%E7%BC%93%E5%AD%98%E4%B8%8E%E5%86%85%E5%AD%98%E4%B8%93%E6%A0%8F/"/>
    
    
    <category term="技术" scheme="https://luolibrary.com/tags/%E6%8A%80%E6%9C%AF/"/>
    
    <category term="学习" scheme="https://luolibrary.com/tags/%E5%AD%A6%E4%B9%A0/"/>
    
  </entry>
  
  <entry>
    <title>CS149-Assignment4 Big Graph Processing in OpenMP</title>
    <link href="https://luolibrary.com/2023/06/08/CS149-Assignment4-Big-Graph-Processing-in-OpenMP/"/>
    <id>https://luolibrary.com/2023/06/08/CS149-Assignment4-Big-Graph-Processing-in-OpenMP/</id>
    <published>2023-06-08T07:36:01.000Z</published>
    <updated>2023-08-11T02:56:29.946Z</updated>
    
    <content type="html"><![CDATA[<h2 id="Part-1"><a href="#Part-1" class="headerlink" title="Part 1"></a>Part 1</h2><p>Before we use openMP to parallelize the code. We just first write the serial form. It is easy.</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">pageRank</span><span class="params">(Graph g, <span class="type">double</span>* solution, <span class="type">double</span> damping, <span class="type">double</span> convergence)</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">  <span class="type">int</span> numNodes = <span class="built_in">num_nodes</span>(g);</span><br><span class="line">  <span class="type">double</span> equal_prob = <span class="number">1.0</span> / numNodes;</span><br><span class="line">  <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; numNodes; ++i) &#123;</span><br><span class="line">    solution[i] = equal_prob;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="type">double</span>* tempArray = <span class="keyword">new</span> <span class="type">double</span>[g-&gt;num_nodes];</span><br><span class="line"></span><br><span class="line">  <span class="type">bool</span> converged = <span class="literal">false</span>;</span><br><span class="line">  <span class="keyword">while</span> (!converged) &#123;</span><br><span class="line">    <span class="keyword">for</span> (Vertex v = <span class="number">0</span>; v &lt; g-&gt;num_nodes; ++v) &#123;</span><br><span class="line">      tempArray[v] = <span class="number">0.0</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">for</span> (Vertex v = <span class="number">0</span>; v &lt; g-&gt;num_nodes; ++v) &#123;</span><br><span class="line">      <span class="keyword">for</span> (<span class="type">const</span> Vertex* iv = <span class="built_in">incoming_begin</span>(g, v); iv != <span class="built_in">incoming_end</span>(g, v); ++iv) &#123;</span><br><span class="line">        tempArray[v] += solution[*iv] / <span class="built_in">static_cast</span>&lt;<span class="type">double</span>&gt;(<span class="built_in">outgoing_size</span>(g, *iv));</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">for</span> (Vertex v = <span class="number">0</span>; v &lt; g-&gt;num_nodes; ++v) &#123;</span><br><span class="line">      tempArray[v] = tempArray[v] * damping + (<span class="number">1.0</span> - damping) / <span class="built_in">static_cast</span>&lt;<span class="type">double</span>&gt;(numNodes);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="type">double</span> t = <span class="number">0.0</span>;</span><br><span class="line">    <span class="keyword">for</span> (Vertex v = <span class="number">0</span>; v &lt; g-&gt;num_nodes; ++v) &#123;</span><br><span class="line">      <span class="keyword">if</span>(<span class="built_in">outgoing_size</span>(g, v) == <span class="number">0</span>) &#123;</span><br><span class="line">          t += damping * solution[v] / <span class="built_in">static_cast</span>&lt;<span class="type">double</span>&gt;(numNodes);</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> (Vertex v = <span class="number">0</span>; v &lt; g-&gt;num_nodes; ++v) &#123;</span><br><span class="line">      tempArray[v] += t;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="type">double</span> globalDiff = <span class="number">0.0</span>;</span><br><span class="line">    <span class="keyword">for</span> (Vertex v = <span class="number">0</span>; v &lt; g-&gt;num_nodes; ++v) &#123;</span><br><span class="line">      globalDiff += <span class="built_in">abs</span>(tempArray[v] - solution[v]);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">for</span> (Vertex v = <span class="number">0</span>; v &lt; g-&gt;num_nodes; ++v) &#123;</span><br><span class="line">      solution[v] = tempArray[v];</span><br><span class="line">    &#125;</span><br><span class="line">    converged = globalDiff &lt; convergence;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">delete</span>[] tempArray;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Now we can use openMP to parallelize the code above.</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">pageRank</span><span class="params">(Graph g, <span class="type">double</span>* solution, <span class="type">double</span> damping, <span class="type">double</span> convergence)</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">  <span class="type">int</span> numNodes = <span class="built_in">num_nodes</span>(g);</span><br><span class="line">  <span class="type">double</span> equal_prob = <span class="number">1.0</span> / numNodes;</span><br><span class="line">  <span class="meta">#<span class="keyword">pragma</span> omp parallel for</span></span><br><span class="line">  <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; numNodes; ++i) &#123;</span><br><span class="line">    solution[i] = equal_prob;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="type">double</span>* tempArray = <span class="keyword">new</span> <span class="type">double</span>[g-&gt;num_nodes];</span><br><span class="line"></span><br><span class="line">  <span class="type">bool</span> converged = <span class="literal">false</span>;</span><br><span class="line">  <span class="keyword">while</span> (!converged) &#123;</span><br><span class="line">    <span class="meta">#<span class="keyword">pragma</span> omp parallel for default(none) shared(tempArray, g)</span></span><br><span class="line">    <span class="keyword">for</span> (Vertex v = <span class="number">0</span>; v &lt; g-&gt;num_nodes; ++v) &#123;</span><br><span class="line">      tempArray[v] = <span class="number">0.0</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">#<span class="keyword">pragma</span> omp parallel for default(none) shared(tempArray, g, solution)</span></span><br><span class="line">    <span class="keyword">for</span> (Vertex v = <span class="number">0</span>; v &lt; g-&gt;num_nodes; ++v) &#123;</span><br><span class="line">      <span class="keyword">for</span> (<span class="type">const</span> Vertex* iv = <span class="built_in">incoming_begin</span>(g, v); iv != <span class="built_in">incoming_end</span>(g, v); ++iv) &#123;</span><br><span class="line">        tempArray[v] += solution[*iv] / <span class="built_in">static_cast</span>&lt;<span class="type">double</span>&gt;(<span class="built_in">outgoing_size</span>(g, *iv));</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">#<span class="keyword">pragma</span> omp parallel for default(none) shared(tempArray, g, damping, numNodes)</span></span><br><span class="line">    <span class="keyword">for</span> (Vertex v = <span class="number">0</span>; v &lt; g-&gt;num_nodes; ++v) &#123;</span><br><span class="line">      tempArray[v] = tempArray[v] * damping + (<span class="number">1.0</span> - damping) / <span class="built_in">static_cast</span>&lt;<span class="type">double</span>&gt;(numNodes);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="type">double</span> t = <span class="number">0.0</span>;</span><br><span class="line">    <span class="meta">#<span class="keyword">pragma</span> omp parallel for default(none) reduction(+: t) shared(solution, g, numNodes, damping)</span></span><br><span class="line">    <span class="keyword">for</span> (Vertex v = <span class="number">0</span>; v &lt; g-&gt;num_nodes; ++v) &#123;</span><br><span class="line">      <span class="keyword">if</span>(<span class="built_in">outgoing_size</span>(g, v) == <span class="number">0</span>) &#123;</span><br><span class="line">        t += damping * solution[v] / <span class="built_in">static_cast</span>&lt;<span class="type">double</span>&gt;(numNodes);</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">#<span class="keyword">pragma</span> omp parallel for default(none) shared(tempArray, g, t)</span></span><br><span class="line">    <span class="keyword">for</span> (Vertex v = <span class="number">0</span>; v &lt; g-&gt;num_nodes; ++v) &#123;</span><br><span class="line">      tempArray[v] += t;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="type">double</span> globalDiff = <span class="number">0.0</span>;</span><br><span class="line">    <span class="meta">#<span class="keyword">pragma</span> omp parallel for default(none) reduction(+: globalDiff) shared(tempArray, solution, g)</span></span><br><span class="line">    <span class="keyword">for</span> (Vertex v = <span class="number">0</span>; v &lt; g-&gt;num_nodes; ++v) &#123;</span><br><span class="line">      globalDiff += <span class="built_in">abs</span>(tempArray[v] - solution[v]);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">#<span class="keyword">pragma</span> omp parallel for default(none) shared(tempArray, solution, g)</span></span><br><span class="line">    <span class="keyword">for</span> (Vertex v = <span class="number">0</span>; v &lt; g-&gt;num_nodes; ++v) &#123;</span><br><span class="line">      solution[v] = tempArray[v];</span><br><span class="line">    &#125;</span><br><span class="line">    converged = globalDiff &lt; convergence;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">delete</span>[] tempArray;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Part-2"><a href="#Part-2" class="headerlink" title="Part 2"></a>Part 2</h2><h3 id="Top-Down-BFS"><a href="#Top-Down-BFS" class="headerlink" title="Top Down BFS"></a>Top Down BFS</h3><p>The easiest idea for implementing top-down bfs is to use two variables, one is for the current frontier, and another is for the next frontier. And when we add the new node to the next frontier, we should use <code>#pragma omp critical</code> to protect the next frontier. However, this method is too slow.</p><p>However, we could let each thread use its local next frontier. Thus we could avoid critical section and make the code faster.</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">top_down_step</span><span class="params">(</span></span></span><br><span class="line"><span class="params"><span class="function">    Graph g,</span></span></span><br><span class="line"><span class="params"><span class="function">    vertex_set* frontier,</span></span></span><br><span class="line"><span class="params"><span class="function">    vertex_set* localList,</span></span></span><br><span class="line"><span class="params"><span class="function">    <span class="type">int</span>* count,</span></span></span><br><span class="line"><span class="params"><span class="function">    <span class="type">int</span>* distances)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="meta">#<span class="keyword">pragma</span> omp parallel for</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i=<span class="number">0</span>; i&lt;frontier-&gt;count; i++) &#123;</span><br><span class="line"></span><br><span class="line">        <span class="type">int</span> node = frontier-&gt;vertices[i];</span><br><span class="line"></span><br><span class="line">        <span class="type">int</span> start_edge = g-&gt;outgoing_starts[node];</span><br><span class="line">        <span class="type">int</span> end_edge = (node == g-&gt;num_nodes - <span class="number">1</span>)</span><br><span class="line">                           ? g-&gt;num_edges</span><br><span class="line">                           : g-&gt;outgoing_starts[node + <span class="number">1</span>];</span><br><span class="line"></span><br><span class="line">        <span class="comment">// attempt to add all neighbors to the new frontier</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> neighbor=start_edge; neighbor&lt;end_edge; neighbor++) &#123;</span><br><span class="line">            <span class="type">int</span> outgoing = g-&gt;outgoing_edges[neighbor];</span><br><span class="line">            <span class="keyword">if</span> (distances[outgoing] == NOT_VISITED_MARKER &amp;&amp;</span><br><span class="line">            __sync_bool_compare_and_swap(&amp;distances[outgoing], NOT_VISITED_MARKER, distances[node] + <span class="number">1</span>)) &#123;</span><br><span class="line">                  <span class="type">int</span> index = localList[<span class="built_in">omp_get_thread_num</span>()].count++;</span><br><span class="line">                  localList[<span class="built_in">omp_get_thread_num</span>()].vertices[index] = outgoing;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="type">int</span> totalCount = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; <span class="built_in">omp_get_max_threads</span>(); ++i) &#123;</span><br><span class="line">      count[i] = totalCount;</span><br><span class="line">      totalCount += localList[i].count;</span><br><span class="line">    &#125;</span><br><span class="line">    frontier-&gt;count = totalCount;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Parallel copy the data from `localList` to `frontier`</span></span><br><span class="line">    <span class="meta">#<span class="keyword">pragma</span> omp parallel for</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; <span class="built_in">omp_get_max_threads</span>(); ++i) &#123;</span><br><span class="line">        <span class="built_in">memcpy</span>(frontier-&gt;vertices + count[i], localList[i].vertices,</span><br><span class="line">                localList[i].count * <span class="built_in">sizeof</span>(<span class="type">int</span>));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Bottom-Up-BFS"><a href="#Bottom-Up-BFS" class="headerlink" title="Bottom Up BFS"></a>Bottom Up BFS</h3><p>The Top Down BFS is an easy job to do actually. Because it is a prior algorithm. However, in the code, we would do many logical operations especially the frontier size is large and its ancestors’ size is small. Look at the following code snippet, which would cause so many logical operations.</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (distances[outgoing] == NOT_VISITED_MARKER &amp;&amp;</span><br><span class="line">  __sync_bool_compare_and_swap(&amp;distances[outgoing], NOT_VISITED_MARKER, distances[node] + <span class="number">1</span>)) &#123;</span><br><span class="line">    <span class="type">int</span> index = localList[<span class="built_in">omp_get_thread_num</span>()].count++;</span><br><span class="line">    localList[<span class="built_in">omp_get_thread_num</span>()].vertices[index] = outgoing;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Well, I cannot understand the principle of Bottom Up BFS. So I decide to read an essay which is called <a href="https://downloads.hindawi.com/journals/sp/2013/702694.pdf">Direction-optimizing breadth-first-search</a>.</p><p>I think the paper is enough. So I omit detail here. You should carefully read the paper. And I record some information here.</p><blockquote><p>The majority of the computational work in BFS is checking edges of the frontier to see if the endpoint has been visited. The total number of edge checks in the conventional top-down algorithm is equal to the number of edges in the connected component containing the source vertex, as on each step every edge in the frontier is checked.</p></blockquote><p>Well, at now the bottom-up way is clear. For every step, we just traverse all the vertices. And we have the current <code>frontier</code> information. From Top Down BFS, we need to check for every vertex’s neighbor to add it to the <code>next_frontier</code>. In the Bottom Up BFS, we just find whether the <code>child</code> is the ancestor of the vertices in the <code>frontier</code>. Thus every child would only can be visited once.</p><p>Now we can code.</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">uint <span class="title">bottom_up_step</span><span class="params">(Graph g, <span class="type">bool</span>* frontier, <span class="type">bool</span>* new_frontier, <span class="type">int</span>* distances)</span> </span>&#123;</span><br><span class="line">    uint count = <span class="number">0</span>;</span><br><span class="line">    <span class="meta">#<span class="keyword">pragma</span> omp parallel for reduction(+: count)</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; g-&gt;num_nodes; ++i) &#123;</span><br><span class="line">        <span class="keyword">if</span> (distances[i] == NOT_VISITED_MARKER) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">const</span> Vertex* incoming = <span class="built_in">incoming_begin</span>(g, i); incoming != <span class="built_in">incoming_end</span>(g, i); ++incoming) &#123;</span><br><span class="line">                <span class="keyword">if</span> (frontier[*incoming]) &#123;</span><br><span class="line">                    distances[i] = distances[*incoming] + <span class="number">1</span>;</span><br><span class="line">                    new_frontier[i] = <span class="literal">true</span>;</span><br><span class="line">                    count += <span class="number">1</span>;</span><br><span class="line">                    <span class="keyword">break</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> count;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">bfs_bottom_up</span><span class="params">(Graph graph, solution* sol)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="type">bool</span> *frontier = <span class="keyword">new</span> <span class="type">bool</span>[graph-&gt;num_nodes];</span><br><span class="line">    <span class="type">bool</span> *new_frontier = <span class="keyword">new</span> <span class="type">bool</span>[graph-&gt;num_nodes];</span><br><span class="line"></span><br><span class="line">    <span class="type">bool</span> *current = frontier;</span><br><span class="line">    <span class="type">bool</span> *next = new_frontier;</span><br><span class="line"></span><br><span class="line">    <span class="meta">#<span class="keyword">pragma</span> omp parallel for</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; graph-&gt;num_nodes; ++i) &#123;</span><br><span class="line">        current[i] = <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Set for the root node</span></span><br><span class="line">    current[ROOT_NODE_ID] = <span class="literal">true</span>;</span><br><span class="line"></span><br><span class="line">    <span class="meta">#<span class="keyword">pragma</span> omp parallel for</span></span><br><span class="line">    <span class="comment">// initialize all nodes to NOT_VISITED</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i=<span class="number">0</span>; i&lt;graph-&gt;num_nodes; i++)</span><br><span class="line">        sol-&gt;distances[i] = NOT_VISITED_MARKER;</span><br><span class="line">    sol-&gt;distances[ROOT_NODE_ID] = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    uint count = <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">while</span> (count != <span class="number">0</span>) &#123;</span><br><span class="line"></span><br><span class="line">        <span class="meta">#<span class="keyword">pragma</span> omp parallel for</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; graph-&gt;num_nodes; ++i) &#123;</span><br><span class="line">            next[i] = <span class="literal">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        count = <span class="built_in">bottom_up_step</span>(graph, current, next, sol-&gt;distances);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Swap the pointer</span></span><br><span class="line">        <span class="type">bool</span> * temp = next;</span><br><span class="line">        next = current;</span><br><span class="line">        current = temp;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">delete</span>[] frontier;</span><br><span class="line">    <span class="keyword">delete</span>[] new_frontier;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>However, the result is illustrated below.</p><p><img src="https://s2.loli.net/2023/06/08/GM7OjLKNf98ipeq.png" alt="bottom_up_first"></p><p>As you can see, I cannot get the full grades. I am wondering the reason. You could look at this <a href="http://www.nic.uoregon.edu/~khuck/ts/acumem-report/manual_html/ch06s07.html">article</a>.</p><p>And the article provides some tips:</p><ul><li>Avoid writing to global data that is accessed from multiple threads.</li><li>Align shared global data to cache line boundaries.</li><li>Don’t store temporary, thread specific data in an array indexed by th thread id or rank.</li><li>When parallelizing an algorithm, partition data sets along cache lines, not across cache lines.</li></ul><p>The last tip is what we could do. So we could use <code>schedule(dynamic, chunk_size)</code> for openMP to improve efficiency.</p><h3 id="Hybrid-BFS"><a href="#Hybrid-BFS" class="headerlink" title="Hybrid BFS"></a>Hybrid BFS</h3><p>The pairing of the top-down approach with the bottom-up approach is complementary, since when the frontier is its largest, the bottom-up approach will be at its best whereas the top-down approach will be at its worst, and vice versa.</p><p>So the idea is simple. Uses the Top Down BFS for steps when the frontier is large. We begin each search with the Top Down BFS and continue until the frontier becomes too large, at which point we switch to the Bottom Up BFS.</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">bfs_hybrid</span><span class="params">(Graph graph, solution* sol)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="comment">// A flag</span></span><br><span class="line">    <span class="type">bool</span> isUpDownBFS = <span class="literal">true</span>;</span><br><span class="line">    <span class="type">bool</span> switchFlag = <span class="literal">true</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Set up for Bottom Up BFS</span></span><br><span class="line">    <span class="type">int</span> chunk_size = MACHINE_CACHE_LINE_SIZE * <span class="number">16</span>;</span><br><span class="line">    <span class="keyword">while</span> (graph-&gt;num_nodes &lt; <span class="built_in">omp_get_max_threads</span>() * chunk_size) &#123;</span><br><span class="line">        chunk_size /= <span class="number">2</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="type">bool</span> *frontier_bottom_up_bfs = <span class="keyword">new</span> <span class="type">bool</span>[graph-&gt;num_nodes];</span><br><span class="line">    <span class="type">bool</span> *new_frontier_bottom_up_bfs = <span class="keyword">new</span> <span class="type">bool</span>[graph-&gt;num_nodes];</span><br><span class="line">    <span class="type">bool</span> *current = frontier_bottom_up_bfs;</span><br><span class="line">    <span class="type">bool</span> *next = new_frontier_bottom_up_bfs;</span><br><span class="line">    <span class="meta">#<span class="keyword">pragma</span> omp parallel for</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; graph-&gt;num_nodes; ++i) &#123;</span><br><span class="line">        current[i] = <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    current[ROOT_NODE_ID] = <span class="literal">true</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Set up for Top Down BFS</span></span><br><span class="line">    vertex_set list;</span><br><span class="line">    <span class="built_in">vertex_set_init</span>(&amp;list, graph-&gt;num_nodes);</span><br><span class="line">    <span class="type">int</span> maxThreadNum = <span class="built_in">omp_get_max_threads</span>();</span><br><span class="line">    vertex_set localList[maxThreadNum];</span><br><span class="line">    <span class="meta">#<span class="keyword">pragma</span> omp parallel for</span></span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> i = <span class="number">0</span>; i &lt; maxThreadNum; ++i) &#123;</span><br><span class="line">        <span class="built_in">vertex_set_init</span>(&amp;localList[i], graph-&gt;num_nodes);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="type">int</span> count[maxThreadNum];</span><br><span class="line">    vertex_set* frontier_top_down_bfs = &amp;list;</span><br><span class="line">    frontier_top_down_bfs-&gt;vertices[frontier_top_down_bfs-&gt;count++] = ROOT_NODE_ID;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Global configuration</span></span><br><span class="line">    <span class="meta">#<span class="keyword">pragma</span> omp parallel for</span></span><br><span class="line">    <span class="comment">// initialize all nodes to NOT_VISITED</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i=<span class="number">0</span>; i&lt;graph-&gt;num_nodes; i++)</span><br><span class="line">        sol-&gt;distances[i] = NOT_VISITED_MARKER;</span><br><span class="line">    sol-&gt;distances[ROOT_NODE_ID] = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    uint frontier_length = <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">while</span> (frontier_length != <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (isUpDownBFS) &#123;</span><br><span class="line">            <span class="comment">// We come from Bottom Up BFS</span></span><br><span class="line">            <span class="keyword">if</span> (!switchFlag) &#123;</span><br><span class="line">                <span class="meta">#<span class="keyword">pragma</span> omp parallel for</span></span><br><span class="line">                <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; maxThreadNum; ++i) &#123;</span><br><span class="line">                    <span class="built_in">vertex_set_clear</span>(&amp;localList[i]);</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="comment">// We need to copy the information from `current`</span></span><br><span class="line">                <span class="comment">// to the `frontier_top_down, it is trivial.</span></span><br><span class="line">                <span class="meta">#<span class="keyword">pragma</span> omp parallel for</span></span><br><span class="line">                <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; graph-&gt;num_nodes; ++i) &#123;</span><br><span class="line">                    <span class="keyword">if</span> (current[i]) &#123;</span><br><span class="line">                        <span class="type">int</span> index = localList[<span class="built_in">omp_get_thread_num</span>()].count++;</span><br><span class="line">                        localList[<span class="built_in">omp_get_thread_num</span>()].vertices[index] = i;</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="type">int</span> totalCount = <span class="number">0</span>;</span><br><span class="line">                <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; <span class="built_in">omp_get_max_threads</span>(); ++i) &#123;</span><br><span class="line">                    count[i] = totalCount;</span><br><span class="line">                    totalCount += localList[i].count;</span><br><span class="line">                &#125;</span><br><span class="line">                frontier_top_down_bfs-&gt;count = totalCount;</span><br><span class="line">                <span class="meta">#<span class="keyword">pragma</span> omp parallel for</span></span><br><span class="line">                <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; <span class="built_in">omp_get_max_threads</span>(); ++i) &#123;</span><br><span class="line">                    <span class="built_in">memcpy</span>(frontier_top_down_bfs-&gt;vertices + count[i], localList[i].vertices,</span><br><span class="line">                    localList[i].count * <span class="built_in">sizeof</span>(<span class="type">int</span>));</span><br><span class="line">                &#125;</span><br><span class="line">                switchFlag = <span class="literal">true</span>;</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="meta">#<span class="keyword">pragma</span> omp parallel for</span></span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; maxThreadNum; ++i) &#123;</span><br><span class="line">                <span class="built_in">vertex_set_clear</span>(&amp;localList[i]);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="built_in">top_down_step</span>(graph, frontier_top_down_bfs, localList, count, sol-&gt;distances);</span><br><span class="line">            frontier_length = frontier_top_down_bfs-&gt;count;</span><br><span class="line">            <span class="keyword">if</span> (frontier_length != <span class="number">0</span> &amp;&amp; graph-&gt;num_nodes / frontier_length&lt; UP_THRESHOLD) &#123;</span><br><span class="line">              isUpDownBFS = <span class="literal">false</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="comment">// We come from Top Down BFS</span></span><br><span class="line">            <span class="keyword">if</span> (switchFlag) &#123;</span><br><span class="line">                <span class="comment">// We should first clear the `current` because it has</span></span><br><span class="line">                <span class="comment">// some dirty data.</span></span><br><span class="line">                <span class="meta">#<span class="keyword">pragma</span> omp parallel for</span></span><br><span class="line">                <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; graph-&gt;num_nodes; ++i) &#123;</span><br><span class="line">                    current[i] = <span class="literal">false</span>;</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="comment">// Transfer the information from `frontier_top_down_bfs` to</span></span><br><span class="line">                <span class="comment">// `current`. It is easy for converting from Top Down BFS to</span></span><br><span class="line">                <span class="comment">// Bottom Up BFS</span></span><br><span class="line">                <span class="meta">#<span class="keyword">pragma</span> omp parallel for</span></span><br><span class="line">                <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; frontier_top_down_bfs-&gt;count; ++i) &#123;</span><br><span class="line">                    current[frontier_top_down_bfs-&gt;vertices[i]] = <span class="literal">true</span>;</span><br><span class="line">                &#125;</span><br><span class="line">                switchFlag = <span class="literal">false</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="meta">#<span class="keyword">pragma</span> omp parallel for</span></span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; graph-&gt;num_nodes; ++i) &#123;</span><br><span class="line">                next[i] = <span class="literal">false</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            frontier_length = <span class="built_in">bottom_up_step</span>(graph, current, next, sol-&gt;distances, chunk_size);</span><br><span class="line">            <span class="type">bool</span> * temp = next;</span><br><span class="line">            next = current;</span><br><span class="line">            current = temp;</span><br><span class="line">            <span class="keyword">if</span> (frontier_length != <span class="number">0</span> &amp;&amp; graph-&gt;num_nodes / frontier_length &gt; BOTTOM_THRESHOLD ) &#123;</span><br><span class="line">              isUpDownBFS = <span class="literal">true</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">delete</span>[] frontier_bottom_up_bfs;</span><br><span class="line">    <span class="keyword">delete</span>[] new_frontier_bottom_up_bfs;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;h2 id=&quot;Part-1&quot;&gt;&lt;a href=&quot;#Part-1&quot; class=&quot;headerlink&quot; title=&quot;Part 1&quot;&gt;&lt;/a&gt;Part 1&lt;/h2&gt;&lt;p&gt;Before we use openMP to parallelize the code. We just</summary>
        
      
    
    
    
    <category term="CS149" scheme="https://luolibrary.com/categories/CS149/"/>
    
    
    <category term="技术" scheme="https://luolibrary.com/tags/%E6%8A%80%E6%9C%AF/"/>
    
    <category term="学习" scheme="https://luolibrary.com/tags/%E5%AD%A6%E4%B9%A0/"/>
    
  </entry>
  
  <entry>
    <title>CS149-Assignment3 A Simple CUDA Renderer</title>
    <link href="https://luolibrary.com/2023/06/07/CS149-Assignment3-A-Simple-CUDA-Renderer/"/>
    <id>https://luolibrary.com/2023/06/07/CS149-Assignment3-A-Simple-CUDA-Renderer/</id>
    <published>2023-06-07T14:18:34.000Z</published>
    <updated>2023-08-22T14:08:26.598Z</updated>
    
    <content type="html"><![CDATA[<h2 id="Part-1"><a href="#Part-1" class="headerlink" title="Part 1"></a>Part 1</h2><p>The result is illustrated in the following figure.</p><h3 id="Question-1"><a href="#Question-1" class="headerlink" title="Question 1"></a>Question 1</h3><p>As you can see, due to the parallelism of the GPU, the bandwidth is huge.</p><h3 id="Question-2"><a href="#Question-2" class="headerlink" title="Question 2"></a>Question 2</h3><p>From the results, we can get that the bottleneck is the memory and CPU of the host itself. For the execution time of the kernel, the speed is very fast. However, the speed is low for copying the host memory into the device memory and vice versa.</p><h2 id="Part-2"><a href="#Part-2" class="headerlink" title="Part 2"></a>Part 2</h2><p>First, we need to implement the parallelism form of <code>scan</code>. The algorithm is super wonderful. You should first understand the algorithm.</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">exclusive_scan_iterative</span><span class="params">(<span class="type">int</span>* start, <span class="type">int</span>* end, <span class="type">int</span>* output)</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="type">int</span> N = end - start;</span><br><span class="line">    memmove(output, start, N*<span class="keyword">sizeof</span>(<span class="type">int</span>));</span><br><span class="line"></span><br><span class="line">    <span class="comment">// upsweep phase</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> two_d = <span class="number">1</span>; two_d &lt;= N/<span class="number">2</span>; two_d*=<span class="number">2</span>) &#123;</span><br><span class="line">        <span class="type">int</span> two_dplus1 = <span class="number">2</span>*two_d;</span><br><span class="line">        parallel_for (<span class="type">int</span> i = <span class="number">0</span>; i &lt; N; i += two_dplus1) &#123;</span><br><span class="line">            output[i+two_dplus1<span class="number">-1</span>] += output[i+two_d<span class="number">-1</span>];</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    output[N<span class="number">-1</span>] = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// downsweep phase</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> two_d = N/<span class="number">2</span>; two_d &gt;= <span class="number">1</span>; two_d /= <span class="number">2</span>) &#123;</span><br><span class="line">        <span class="type">int</span> two_dplus1 = <span class="number">2</span>*two_d;</span><br><span class="line">        parallel_for (<span class="type">int</span> i = <span class="number">0</span>; i &lt; N; i += two_dplus1) &#123;</span><br><span class="line">            <span class="type">int</span> t = output[i+two_d<span class="number">-1</span>];</span><br><span class="line">            output[i+two_d<span class="number">-1</span>] = output[i+two_dplus1<span class="number">-1</span>];</span><br><span class="line">            output[i+two_dplus1<span class="number">-1</span>] += t;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>It may seem that we need <code>N</code> threads for every inner loop, however, this is a stupid idea, we should calculate how many threads we need for each inner loop.</p><h2 id="Part-3"><a href="#Part-3" class="headerlink" title="Part 3"></a>Part 3</h2><p>Before diving into how to write a render using CUDA. We first think about how to implement a render with cpp. And after that, you need to read the code written with CUDA.</p><h3 id="First-Implementation"><a href="#First-Implementation" class="headerlink" title="First Implementation"></a>First Implementation</h3><p>For the first implementation, I focus on how to write the correct code. The hint part <code>exclusive_scan</code> has made me think we could first store the circle render parameters for each pixel. Thus we can scan the circle render parameters for each pixel.</p><p>Thus, we can solve the two important questions:</p><ul><li><em>Atomicity</em>: we have made the memory independent for each pixel. So we can write whatever we want without any synchronization and mutation.</li><li><em>Order</em>: We have make an explicit array of render parameters. So the order doesn’t matter.</li></ul><p>So, We first need to construct an array to hold the render parameters for each pixel.</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cudaMalloc</span>(&amp;cudaDevicePixelData, image-&gt;width * image-&gt;height * numCircles * <span class="number">4</span> * <span class="built_in">sizeof</span>(<span class="type">float</span>));</span><br></pre></td></tr></table></figure><p>Now, we first write the render information for every pixel. The core code is below:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> pixelPtrStart = <span class="number">4</span> * cuConstRendererParams.numCircles * (pixelY * imageWidth + pixelX) + <span class="number">4</span> * circleIndex;</span><br><span class="line">float4* pixelPtr = (float4*)(&amp;cuConstRendererParams.pixelData[pixelPtrStart]);</span><br><span class="line">float4 color;</span><br><span class="line">color.x = rgb.x;</span><br><span class="line">color.y = rgb.y;</span><br><span class="line">color.z = rgb.z;</span><br><span class="line">color.w = alpha;</span><br><span class="line"></span><br><span class="line">*pixelPtr = color;</span><br></pre></td></tr></table></figure><p>Then we can launch a new kernel to calculate for each pixel:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; numCircles; ++i) &#123;</span><br><span class="line">    float4* pixelPtr = (float4*)(&amp;cuConstRendererParams.pixelData[startIndex + i * <span class="number">4</span>]);</span><br><span class="line">    color.x = (*pixelPtr).w * (*pixelPtr).x + (<span class="number">1</span> - (*pixelPtr).w) * color.x;</span><br><span class="line">    color.y = (*pixelPtr).w * (*pixelPtr).y + (<span class="number">1</span> - (*pixelPtr).w) * color.y;</span><br><span class="line">    color.z = (*pixelPtr).w * (*pixelPtr).z + (<span class="number">1</span> - (*pixelPtr).w) * color.z;</span><br><span class="line">    color.w += (*pixelPtr).w;</span><br><span class="line">  &#125;</span><br><span class="line">  *imagePtr = color;</span><br></pre></td></tr></table></figure><p>However, <code>cudaDevicePixelData</code> is too big, which will consume so much memory, which would exceed the maximum memory 16GB. So we need to reuse the render information. We should not store the information for every pixel. Because the render information of each circle is deterministic. But the things we need to make sure is that whether the circle has contributed to the pixel. So instead of storing the render information for each pixel, we could just use bit mask to indicate whether the circle has contributed to the pixel.</p><p>Now, the result is illustrated by below.</p><p><img src="https://s2.loli.net/2023/06/07/JyrZ64K8W2uI3Tp.png" alt="first render score"></p><p>As you can see, we have passed some tests and the performance is not good at all. The reason why there are some failed tests is that the bytes exceed $2^{63}$, which would overflow.</p><h3 id="Best-practice"><a href="#Best-practice" class="headerlink" title="Best practice"></a>Best practice</h3><p>We should use a clever way to solve this problem. The problem is a two-dimensional problem. So we’d better design the grid and block for two dimensions, as the following figures illustrates:</p><p><img src="https://s2.loli.net/2023/08/22/MWiVgyBYRU2CmlD.png" alt="grid and block design for rendering"></p><p>From the above figures, we could write the following code to initialize the grid and block size:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">CudaRenderer::render</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  <span class="type">int</span> block_x = <span class="number">16</span>, block_y = <span class="number">32</span>;</span><br><span class="line">  <span class="function">dim3 <span class="title">blockDim</span><span class="params">(block_x, block_y)</span></span>;</span><br><span class="line">  <span class="type">int</span> grid_x = (image-&gt;width + blockDim.x - <span class="number">1</span>) / blockDim.x;</span><br><span class="line">  <span class="type">int</span> grid_y = (image-&gt;height + blockDim.y - <span class="number">1</span>) / blockDim.y;</span><br><span class="line">  <span class="function">dim3 <span class="title">gridDim</span><span class="params">(grid_x, grid_y)</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>And we should render a block each time, we have already split the pixels into the block. For each block, we should handle the each pixel, we could calculate the current pixel’s x coordinate and y coordinate and the current thread id:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> thread_id = threadIdx.y * blockDim.x + threadId.x;</span><br><span class="line"><span class="type">int</span> pixel_index_x = blockId.x * blockDim.x + threadId.x;</span><br><span class="line"><span class="type">int</span> pixel_index_y = blockId.y * blockDim.y + threadId.y;</span><br></pre></td></tr></table></figure><p>So what a thread should do? We have already mapped a pixel $(x, y)$ with a specified thread. So the idea should be simple enough.</p><ul><li><strong>We could traverse sequentially all the circles to tell whether the circle has contributed to this pixel, and we calculate to achieve rendering</strong>.</li></ul><p>However, this way is too slow. For every pixel, we could calculate a lot of useless calculation. For example, if there are total 100,000,000 circles, there would be many useless circles for this pixel. So we should find a way to find the circles in the current block. So we could use a shared memory (a bit map) to know whether the <em>BLOCK</em> size circle has contributed to the block. So for each pixel (each thread) we could calculate ONLY one circle to know whether it has contributed to this block. And for each pixel, we could use the contributed circles to sequentially achieve rendering.</p><p>Look at what parallelism we have done:</p><ul><li>Each pixel renders itself using two-dimension task split.</li><li>Each pixel handles 1 circle to tell whether it contributes to the current block for $num_{circles} &#x2F; size_{block}$ loops.</li></ul><p>It’s a really hard question, but it is very useful.</p>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;h2 id=&quot;Part-1&quot;&gt;&lt;a href=&quot;#Part-1&quot; class=&quot;headerlink&quot; title=&quot;Part 1&quot;&gt;&lt;/a&gt;Part 1&lt;/h2&gt;&lt;p&gt;The result is illustrated in the following</summary>
        
      
    
    
    
    <category term="CS149" scheme="https://luolibrary.com/categories/CS149/"/>
    
    
    <category term="技术" scheme="https://luolibrary.com/tags/%E6%8A%80%E6%9C%AF/"/>
    
    <category term="学习" scheme="https://luolibrary.com/tags/%E5%AD%A6%E4%B9%A0/"/>
    
  </entry>
  
  <entry>
    <title>《秽翼的尤斯蒂娅》——荒诞还是存在？</title>
    <link href="https://luolibrary.com/2023/06/07/%E3%80%8A%E7%A7%BD%E7%BF%BC%E7%9A%84%E5%B0%A4%E6%96%AF%E8%92%82%E5%A8%85%E3%80%8B%E2%80%94%E2%80%94%E8%8D%92%E8%AF%9E%E8%BF%98%E6%98%AF%E5%AD%98%E5%9C%A8%EF%BC%9F/"/>
    <id>https://luolibrary.com/2023/06/07/%E3%80%8A%E7%A7%BD%E7%BF%BC%E7%9A%84%E5%B0%A4%E6%96%AF%E8%92%82%E5%A8%85%E3%80%8B%E2%80%94%E2%80%94%E8%8D%92%E8%AF%9E%E8%BF%98%E6%98%AF%E5%AD%98%E5%9C%A8%EF%BC%9F/</id>
    <published>2023-06-07T13:53:50.000Z</published>
    <updated>2023-06-07T14:04:12.815Z</updated>
    
    <content type="html"><![CDATA[<p>实际上，这部作品在我高中的时候我已经想玩了，然而伴随着时间的推移却是在我研二的时光用两天的时间就完了。回想起来仍然感谢自己是在现在这个时间段欣赏了这个作品。</p><p>故事的整体背景实际上是非常好理解的，伴随着整个国家牢狱、下层和上层的划分，人物的阶级属性也就十分鲜明了。整个故事的剧情是线性的，与许多感想讨论的最终章的电车理论相比，我个人将这部作品的基调定为对存在、虚无和荒诞的思考。实际上，我个人认为这部作品对于脚本家想表达的核心就是对存在的思考。然而，GalGame终究是GalGame，本作品在一些章节中考虑的东西过于幼稚，这也是没有办法的事情。然而，我认为这部作品成功的原因就是把其思考的问题贯穿到了整部作品中。</p><h2 id="菲奥奈"><a href="#菲奥奈" class="headerlink" title="菲奥奈"></a>菲奥奈</h2><p>菲奥奈篇可谓是本部作品中剧情和人物塑造最好的一个篇章。本篇章的开始即交代了当前的背景，不蚀金锁成为了目前牢狱的实质管辖人，由于黑羽的出现扰乱了牢狱本身的秩序，导致了防疫局和不蚀金锁进行合作（实际上，这一幕远没有这么简单，这个时候就已经为下一章的内容进行了铺垫）。菲奥奈作为防疫局队长，刚正不阿，为人正直，无不体现其正义性。菲奥奈深知工作不得牢狱人民的人心，但身怀荣耀，坚信自己的意志。然而，她拥有的是抽象的正义。她并不理解牢狱的人往往没有选择。但是，我认为她是明白这个世界的道理的，只是她也在挣扎。</p><p>在后续的剧情中，逐渐地揭示了菲奥奈的理念来源：</p><ul><li>集体荣誉感。</li><li>恩赐之剑：家族荣耀，父亲清廉的象征。</li><li>父亲也得了羽化病，然而父亲支持他们的工作，父亲是她公正信念的又一来源。</li><li>进入防疫局的原因就是因为哥哥，认为防疫局的工作就是命运。</li></ul><p>然而，在追捕黑羽的过程中，其依赖的东西接而破碎。首先是团队内部的叛徒破坏了团队内部保护羽化病人的信念，反而借黑羽的名杀戮羽化病人。其次是意识到了黑羽可能是自己的哥哥，对自己和男主之间进行的交易感到痛恨。再而是明白了防疫局的真相，防疫局从来不保护羽化病人，而是对这些羽化病人进行惨无人道的人体实验。在我个人看来，菲奥奈的信念已然崩塌。就如同“大崩落”一样，将菲奥奈奋斗至今的意义全部抹杀。其开始怀疑其自身存在的价值。</p><p>在这个篇章中，玩家有一个最重要的选择：</p><ol><li>不能让菲奥奈的双手染上亲人的鲜血</li><li>应该让菲奥奈来做决定</li></ol><p>如果这不是游戏，我们会做什么选择了，如果是以理性的角度来讨论这个问题，我们都应该选择第二个选项。因为这涉及到了菲奥奈自身的存在。值得玩味的是，如果选择了第一个选项，菲奥奈就会依赖男主，得到小小的幸福。我从来不认为小小的幸福就不是一个好的选择。而是，菲奥奈应该去寻找自己的意义。她已然虚无，自己的信念全部被打碎了。最后，菲奥奈哥哥给了他的忠告，告诉其应该关注具体的正义：</p><blockquote><p>剑的正义会随着挥剑之人而改变</p><p>你要役剑</p><p>绝对不要为剑所役</p><p>你不要被家族之名束缚地活下去</p><p>用自己的双眼看透何为正义</p></blockquote><p>在最后的剧情中，男主选择通过另一种方式告诉了菲奥奈要去寻找自身的意义。实际上我认为这部分能够写的更有深度的。但是，在我看来这个篇章描述了一个很重要的问题，菲奥奈的抽象正义从何而来，当其破碎了，她又该怎么走向存在呢？还是走向荒诞呢？</p><h2 id="艾莉丝"><a href="#艾莉丝" class="headerlink" title="艾莉丝"></a>艾莉丝</h2><p>个人认为这个篇章极其地鬼畜，主要在于在菲奥奈篇中艾莉丝的形象简直发生了翻天覆地的变化。我并不打算分析艾莉丝这个角色，其角色的塑造是显而易见的。然而，我认为在这个篇章中塑造的核心角色反而是男主。这个篇章中揭示了男主在“大崩落”发生的具体情况。实际上，这个篇章中男主的复杂性是第一次体现出来的。</p><p>男主为了摆脱男妓，成为了一名职业杀手。然而，男主内心实际上是痛苦和迷茫的。剧情中有许多他的心理暗示。</p><blockquote><p>我不想卖身，而先代给我指出了男妓之外的道路。这就已经足够了。</p><p>想要活下去，就必须要忍受、放弃很多的事情。</p><p>生活在这里，就只能考虑现在的自己能做些什么。</p><p>不管理想和不满反差有多么强烈，也不会产生任何改变。</p></blockquote><p>可以看出，男主是并认为牢狱有什么资格谈理想以及未来的，然而男主真的没有这个资格么？男主在牢狱这个环境里面，也是上层阶级。显然，男主扼杀了自己对生存的意义的追问。然而，男主却对艾莉丝说出了，“我希望你能成为一个自由的人”。实际上，在后面剧情中也证实了这是男主对自己的期望。正是因为自己没有去追问生存的意义，所以希望艾莉丝成为这样的人。</p><blockquote><p>每个人都必定会有其生存的意义。</p><p>人生中最重要的事情，就是努力地经营自己的人生，然后去找到生存的意义。</p></blockquote><p>当然，当男主回想起了上述的话，其本质也是在逃避的。男主认为这些话在牢狱里面都是不现实的想法，在牢狱里面优先考虑的就是食物。抱有这种想法，在牢狱是活不下去的。这一章中，男主的复杂性就展现出来了。实际上，男主一直在追问生存的意义。面对在牢狱里的虚无，男主通过荒诞的形式去反抗。</p><h2 id="圣女伊莲"><a href="#圣女伊莲" class="headerlink" title="圣女伊莲"></a>圣女伊莲</h2><p>我认为这条线可谓是本游戏中最为精彩的一条线。这条线是对荒诞最大深层次的思考。这个世间所有的人们都认为这个悬浮在空中的世界都是依靠圣女的天使的祈祷的。所以上一次“大崩落”的发生归咎于第一代圣女没有认真的履行自己的指责。然而，事实的真相却是圣女的祈祷是没有任何作用的，这个悬浮在空中的世界并不是依靠圣女的力量。人们只需要在发生“大崩落”的时候，把圣女送上刑场，平息百姓的愤怒。</p><p>当代圣女使用自己的方式来对抗这种荒诞，正因为她自己意识到了这种荒诞，她必须坚信自己的信仰来维持她自身，以维持自身的存在。为一个虚无的事物坚持是毫无意义的。正也是为什么其面对许多外界压力时，仍然坚持其信仰的根本原因。她的存在必须让她这么做。所以面对拉菲的质疑时，圣女伤心的真正原因就在此。其自身的存在容不得被消灭。为什么圣女的双眼看不清呢？难道不就是为了对抗这种荒诞吗？</p><p>这也是我对这条线最欣赏的原因。这条线的圣女展现出了完全与现阶段剧情中人们不一样的观念，面对这个世界的虚无，通过自己的方式尽力地走向存在。其次，圣女和男主下国际象棋博弈的过程，更为精彩，与其说是下棋，不如说是思想的斗争。圣女必须坚持自己的信仰，这是她存在这个世上最重要的意义，否则她就无法面对祈祷这件事情的虚无。而男主却认为手段无所谓，目的达成即可。</p><p>当男主得知了事情的真相后，如同菲奥奈的信念被破碎了一样。男主也产生了迷茫，其在牢狱的一切生存理由都被打破了。任谁都会觉得荒诞，本来认为的真相竟然全部都是虚伪的谎言：</p><blockquote><p>如果让都市浮起的不是圣女的话</p><p>为什么，大崩落会发生呢？</p><p>为什么，我的家人会不得不死呢？</p><p>为什么，我会被迫在那名为牢狱的地狱中生活呢？</p></blockquote><p>本剧情中，最精彩的可能就是圣女给男主传达的话语了：</p><blockquote><p>你一直停滞不前，该考虑一下自己的生存方式了。</p></blockquote><p>当然，我也很喜欢这段话。如果只有自己得到利益，才会肯定自己的生存方式，是得不到自由的。</p><blockquote><p>因为被拯救了而去相信，因为没有被拯救而不去相信</p><p>这种想法，是绝对得不到拯救的</p><p>咱们都很弱小，因为一点事就会飘摇不定</p><p>所以，才要相信</p><p>并不是在被拯救之后才去相信……而是要去相信而得到救赎</p></blockquote><p>在这一条线中，男主这个人物已然具体了。实际上男主的复杂性已经完全体现。由于生活在牢狱中，必须给自己的生存方式加以理由。然而，意义真的存在吗？其实到目前的剧情中，每个人都是伤痕累累的。信念也受到了崩塌，我无意探讨其细节之处。其真的走向存在了吗？实际上这个问题我回答不了，我们又有谁能说自己能够走向存在呢？</p><h2 id="莉西娅"><a href="#莉西娅" class="headerlink" title="莉西娅"></a>莉西娅</h2><p>莉西娅线纯粹就是为了爽。同样，我认为莉西娅线塑造的角色并不是莉西娅。当然，莉西娅也面临着意义的问题，走向为王路上的迷茫等。然而，我认为这个线最有趣的角色就是戈尔。戈尔就是男主。戈尔认为她和男主属于同一类人，都是在牢狱通过杀戮来生存的人。显然戈尔为了对抗这种虚无感，直接走向了荒诞。杀人与不杀人已然无所谓了。因为这就是她的生存方式。她为什么想杀死男主？难道不是因为她想把自己也杀死吗，她可能也嫉妒着男主吧。所以我认为她是本章中最为复杂的人。</p><p>在我看来，这就是写脚本的人的映射。男主实际上一直都是迷茫的，但是男主并没有直接选择荒诞来对抗这种虚无感。我认为这才是这一章最核心的东西。实际上男主的独白很值得玩味：</p><blockquote><p>不知为何，感觉死去的她就好像自己的某个亲人。</p><p>事实上，戈尔和我的人生轨迹几乎完全相同。</p><p>作为他人的所有物，而一直毫无道理地夺取他人的性命。</p><p>在这种生活之中，迷失了自己与他人的生存意义。</p><p>戈尔所面对的结局，应该就是像我们这样的人所经常会抵达的重点之一吧。</p><p>但是，我与戈尔不同。</p><p>我并没有输给自己所抱持的空虚，而将自己无谓地舍弃。</p><p>戈尔的死，让我再次加深了这个想法。</p></blockquote><h2 id="尤斯蒂娅"><a href="#尤斯蒂娅" class="headerlink" title="尤斯蒂娅"></a>尤斯蒂娅</h2><p>这一章节被许多人诟病。实际上我对这一章的看法完全没有从电车问题出发。当然，男主的哥哥持有的理念是牺牲牢狱的人而保护下层的人和上层的人，通过最小的代价来保证大多数的权益。而菲奥奈为了保护下层而战，吉克为牢狱而战。圣女为了坚持自己的信仰，为了自己的信仰而战。艾莉丝成为了一名称职的医生。</p><p>而男主呢？最后男主表示了自己确定了自己的生存信念：拯救尤斯蒂娅。我并不认可这种想法，实际上这是相当肤浅的。然而，没办法，受到商业的影响（毕竟需要恋爱故事）肯定有所妥协的。尤斯蒂娅为了拯救男主，选择净化了这个世界的污秽。</p><p>到这里，我不仅思考生存的意义对于男主而言到底是什么，实际上我认为终章并没有把这个问题阐述清楚。当然，我并不觉得只有男主在迷茫。难道其他角色没有迷茫过吗？哪怕他们的信念是坚定的，也只是他们必须要在这个虚无的世界中坚信信念才能走向存在。我并不认为存在才是解决问题的方式。可能这才是我对这一部分不满意的地方。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>在我的心里，我觉得这部作品最精彩的就是第一章，其紧凑的剧情以及优秀的人物塑造和背景让我一下就喜欢上了这部作品。然后后面的部分剧情是相对比较俗套的，但是圣女线这部分我认为是很多GalGame写不出的剧本，其探讨了很有价值的东西，反而是这部作品中很有深度的一章。然而，这部作品优秀的地方在于其对生存的意义的探讨。我们面对这个世界的虚无，应该做出怎么样的选择，我并不觉得里面的角色都能走向存在。然而，这部作品也温柔地探讨了这个问题。</p><p>这部作品有一个十分关键的词语：秽翼。浅薄的理解就是尤斯蒂娅能够净化这个世界的污秽，哪怕自己的翅膀染上污秽。然而在剧情中，天使的翅膀永远是纯净的。可能这就是这个世间。因为本身就是污秽，无论在牢狱、下层还是上层，我们都无可避免地去发问什么是生存的意义。</p>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;p&gt;实际上，这部作品在我高中的时候我已经想玩了，然而伴随着时间的推移却是在我研二的时光用两天的时间就完了。回想起来仍然感谢自己是在现在这个时间段欣赏了这个作品。&lt;/p&gt;
&lt;p&gt;故事的整体背景实际上是非常好理解的，伴随着整个国家牢狱、下层和上层的划分，人物的阶级属性也就十分鲜明了</summary>
        
      
    
    
    
    <category term="游戏笔记" scheme="https://luolibrary.com/categories/%E6%B8%B8%E6%88%8F%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="GalGame" scheme="https://luolibrary.com/tags/GalGame/"/>
    
  </entry>
  
  <entry>
    <title>手把手教你写一个简单的SMTP服务器（五）</title>
    <link href="https://luolibrary.com/2023/03/14/%E6%89%8B%E6%8A%8A%E6%89%8B%E6%95%99%E4%BD%A0%E5%86%99%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84SMTP%E6%9C%8D%E5%8A%A1%E5%99%A8%EF%BC%88%E4%BA%94%EF%BC%89/"/>
    <id>https://luolibrary.com/2023/03/14/%E6%89%8B%E6%8A%8A%E6%89%8B%E6%95%99%E4%BD%A0%E5%86%99%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84SMTP%E6%9C%8D%E5%8A%A1%E5%99%A8%EF%BC%88%E4%BA%94%EF%BC%89/</id>
    <published>2023-03-14T11:41:26.000Z</published>
    <updated>2023-03-14T11:45:22.073Z</updated>
    
    <content type="html"><![CDATA[<ul><li><a href="https://luolibrary.com/2023/03/12/%E6%89%8B%E6%8A%8A%E6%89%8B%E6%95%99%E4%BD%A0%E5%86%99%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84SMTP%E6%9C%8D%E5%8A%A1%E5%99%A8%EF%BC%88%E4%B8%80%EF%BC%89/">手把手教你写一个简单的SMTP服务器（一）</a></li><li><a href="https://luolibrary.com/2023/03/14/%E6%89%8B%E6%8A%8A%E6%89%8B%E6%95%99%E4%BD%A0%E5%86%99%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84SMTP%E6%9C%8D%E5%8A%A1%E5%99%A8%EF%BC%88%E4%BA%8C%EF%BC%89/">手把手教你写一个简单的SMTP服务器（二）</a></li><li><a href="https://luolibrary.com/2023/03/14/%E6%89%8B%E6%8A%8A%E6%89%8B%E6%95%99%E4%BD%A0%E5%86%99%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84SMTP%E6%9C%8D%E5%8A%A1%E5%99%A8%EF%BC%88%E4%B8%89%EF%BC%89/">手把手教你写一个简单的SMTP服务器（三）</a></li><li><a href="https://luolibrary.com/2023/03/14/%E6%89%8B%E6%8A%8A%E6%89%8B%E6%95%99%E4%BD%A0%E5%86%99%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84SMTP%E6%9C%8D%E5%8A%A1%E5%99%A8%EF%BC%88%E5%9B%9B%EF%BC%89/">手把手教你写一个简单的SMTP服务器（四）</a></li><li><a href="https://luolibrary.com/2023/03/14/%E6%89%8B%E6%8A%8A%E6%89%8B%E6%95%99%E4%BD%A0%E5%86%99%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84SMTP%E6%9C%8D%E5%8A%A1%E5%99%A8%EF%BC%88%E4%BA%94%EF%BC%89/">手把手教你写一个简单的SMTP服务器（五）</a></li></ul><hr><h2 id="Context"><a href="#Context" class="headerlink" title="Context"></a>Context</h2><p>在教程四中，我们已经实现了状态机，接下来我们定义一个<code>Context</code>类，其仅仅简单地封装一下，维持一个<code>current</code>变量指向当前的状态即可。我们需要在<code>miniSMTPServer/context</code>目录下创建<code>context.hpp</code>和<code>context.cpp</code>文件并修改<code>CMakeLists.txt</code>。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// miniSMTPServer/context/context.hpp</span></span><br><span class="line"><span class="meta">#<span class="keyword">pragma</span> once</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;state.hpp&quot;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;memory&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Context</span> &#123;</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">  std::unique_ptr&lt;State&gt; *current;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">  <span class="built_in">Context</span>();</span><br><span class="line">  <span class="function">std::string <span class="title">transitive</span><span class="params">(std::vector&lt;std::string&gt; &amp;parameters)</span></span>;</span><br><span class="line">  ~<span class="built_in">Context</span>() = <span class="keyword">default</span>;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// miniSMTPServer/context/context.cpp</span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;context.hpp&quot;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;state.hpp&quot;</span></span></span><br><span class="line"></span><br><span class="line">Context::<span class="built_in">Context</span>() &#123; current = &amp;States::idleState; &#125;</span><br><span class="line"><span class="function">std::string <span class="title">Context::transitive</span><span class="params">(std::vector&lt;std::string&gt; &amp;parameters)</span> </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> (*current)-&gt;<span class="built_in">transitive</span>(parameters, current);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight cmake"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># miniSMTPServer/context/CMakeLists.txt</span></span><br><span class="line"><span class="keyword">add_library</span>(context STATIC state.cpp context.cpp)</span><br><span class="line">...</span><br></pre></td></tr></table></figure><p>可以看出<code>Context</code>类十分的简单，这是因为大部分工作都已经实现了。</p><h2 id="SMTP-Server"><a href="#SMTP-Server" class="headerlink" title="SMTP Server"></a>SMTP Server</h2><p>现在，我们终于可以开始写我们的Server了，我们只需要完成一个简单得不能再简单的工作：按照空格分割字符串。然而你可能会想到最直接的方法就是定义一个<code>istringstream</code>。然而这是不正确的，因为可能会存在如下的情况：</p><figure class="highlight txt"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">MAIL shejialuo@gmail.com OK</span><br><span class="line">MAIL  shejialuo@gmail.com NOT OK</span><br></pre></td></tr></table></figure><p>ABNF中严格地定义了只允许拥有一个空格。而所有命令的参数最多不超过1个。我们采取的策略就可以非常简单了，直接以一个空格分割字符串即可。但是值得注意的是，我们必须删除接收到的字符串的最后两个，根据协议其最后两个字符为<code>CRLF</code>，即<code>\r\n</code>。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// miniSMTPServer/miniSMTPServer.cpp</span></span><br><span class="line"><span class="function">std::vector&lt;std::string&gt; <span class="title">getParameters</span><span class="params">(std::string &amp;request)</span> </span>&#123;</span><br><span class="line">  request.<span class="built_in">pop_back</span>();</span><br><span class="line">  request.<span class="built_in">pop_back</span>();</span><br><span class="line">  <span class="type">int</span> split = <span class="number">0</span>;</span><br><span class="line">  <span class="keyword">for</span> (; split &lt; request.<span class="built_in">size</span>(); split++) &#123;</span><br><span class="line">    <span class="keyword">if</span> (request[split] == <span class="string">&#x27; &#x27;</span>) &#123;</span><br><span class="line">      <span class="keyword">break</span>;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  std::string command = request.<span class="built_in">substr</span>(<span class="number">0</span>, split);</span><br><span class="line">  <span class="keyword">if</span> (split != request.<span class="built_in">size</span>()) &#123;</span><br><span class="line">    std::string parameter = request.<span class="built_in">substr</span>(split + <span class="number">1</span>, request.<span class="built_in">size</span>() - split - <span class="number">1</span>);</span><br><span class="line">    <span class="keyword">return</span> &#123;command, parameter&#125;;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> &#123;command&#125;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>最后我们只需要修改主函数就可以了，其逻辑十分的简单：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// miniSMTPServer/miniSMTPServer.cpp</span></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  std::cout &lt;&lt; <span class="string">&quot;Hello, This is a simple SMTP server\n&quot;</span>;</span><br><span class="line"></span><br><span class="line">  TCPSocket socket&#123;&#125;;</span><br><span class="line">  socket.<span class="built_in">set_reuseaddr</span>();</span><br><span class="line">  socket.<span class="built_in">bind</span>();</span><br><span class="line">  socket.<span class="built_in">listen</span>();</span><br><span class="line"></span><br><span class="line">  std::string request&#123;&#125;;</span><br><span class="line">  Context context&#123;&#125;;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">    <span class="keyword">auto</span> s = socket.<span class="built_in">accept</span>();</span><br><span class="line"></span><br><span class="line">    <span class="type">bool</span> isDone = <span class="literal">false</span>;</span><br><span class="line">    <span class="keyword">while</span> (!isDone) &#123;</span><br><span class="line">      s.<span class="built_in">read</span>(request);</span><br><span class="line">      <span class="keyword">if</span> (request.<span class="built_in">empty</span>()) &#123;</span><br><span class="line">        std::cout &lt;&lt; <span class="string">&quot;S: Connection lost\n&quot;</span>;</span><br><span class="line">        s.<span class="built_in">close</span>();</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">      &#125;</span><br><span class="line">      std::cout &lt;&lt; <span class="string">&quot;C: &quot;</span> &lt;&lt; request;</span><br><span class="line">      std::string parameter&#123;&#125;;</span><br><span class="line">      std::vector&lt;std::string&gt; parameters = <span class="built_in">getParameters</span>(request);</span><br><span class="line"></span><br><span class="line">      std::string result = context.<span class="built_in">transitive</span>(parameters);</span><br><span class="line"></span><br><span class="line">      s.<span class="built_in">write</span>(result + <span class="string">&quot;\r\n&quot;</span>);</span><br><span class="line">      std::cout &lt;&lt; <span class="string">&quot;S: &quot;</span> &lt;&lt; result &lt;&lt; std::endl;</span><br><span class="line">      <span class="keyword">if</span> (result.<span class="built_in">substr</span>(<span class="number">0</span>, <span class="number">3</span>) == <span class="string">&quot;221&quot;</span>) &#123;</span><br><span class="line">        s.<span class="built_in">close</span>();</span><br><span class="line">        isDone = <span class="literal">true</span>;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>最后我们需要修改<code>miniSMTPServer/CMakeLists.txt</code>文件如下所示：</p><figure class="highlight cmake"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># miniSMTPServer/CMakeLists.txt</span></span><br><span class="line"><span class="keyword">add_executable</span>(miniSMTP miniSMTPServer.cpp)</span><br><span class="line"></span><br><span class="line"><span class="keyword">set_target_properties</span>(miniSMTP PROPERTIES RUNTIME_OUTPUT_DIRECTORY <span class="variable">$&#123;PROJECT_SOURCE_DIR&#125;</span>/)</span><br><span class="line"></span><br><span class="line"><span class="keyword">target_include_directories</span>(miniSMTP PRIVATE ./util ./context)</span><br><span class="line"></span><br><span class="line"><span class="keyword">target_link_libraries</span>(miniSMTP util context)</span><br><span class="line"></span><br><span class="line"><span class="keyword">add_subdirectory</span>(./util)</span><br><span class="line"><span class="keyword">add_subdirectory</span>(./context)</span><br></pre></td></tr></table></figure><p>然后进行编译最终运行<code>./miniSMTP</code>。你可以尝试许多的输入输出，如果你发现了Bug，欢迎给仓库提交<a href="https://github.com/shejialuo/miniSMTPServer">miniSMTPServer</a></p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git checkout finish-all</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>你可能会发现我们没有对邮件进行任何的处理，之所以我不打算做这样的处理是因为这并不是我们应该做的重点。如果你想保存用户发送的邮件，是一个非常简单的事情。你可能会想是不是需要在<code>transitive</code>函数中做这样的处理。实际上最合理的方式是新增加一个<code>doAction</code>的虚函数。对于每一个状态其存在一个处理函数：</p><ul><li><code>IdleState</code>：清空邮件信息的缓存。</li><li><code>EhloState</code>：清空邮件信息的缓存。</li><li><code>MailState</code>：记录邮件的发送人。</li><li><code>RcptState</code>：记录邮件的接收人。</li><li><code>DataStartState</code>：记录发送的信息。</li><li><code>DataStartDone</code>：如果收到了<code>QUIT</code>命令，将邮件保存到硬盘。</li></ul><p>希望能够阅读到这儿的你，或多或少能有所收获。这是我第一次尝试写一个教程，终于明白了写一个教程的艰辛。希望你能有一天用自己的知识帮助到他人。</p>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;ul&gt;
&lt;li&gt;&lt;a</summary>
        
      
    
    
    
    <category term="手把手教你写一个简单的SMTP服务器" scheme="https://luolibrary.com/categories/%E6%89%8B%E6%8A%8A%E6%89%8B%E6%95%99%E4%BD%A0%E5%86%99%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84SMTP%E6%9C%8D%E5%8A%A1%E5%99%A8/"/>
    
    
    <category term="技术" scheme="https://luolibrary.com/tags/%E6%8A%80%E6%9C%AF/"/>
    
    <category term="学习" scheme="https://luolibrary.com/tags/%E5%AD%A6%E4%B9%A0/"/>
    
    <category term="教程" scheme="https://luolibrary.com/tags/%E6%95%99%E7%A8%8B/"/>
    
  </entry>
  
  <entry>
    <title>手把手教你写一个简单的SMTP服务器（四）</title>
    <link href="https://luolibrary.com/2023/03/14/%E6%89%8B%E6%8A%8A%E6%89%8B%E6%95%99%E4%BD%A0%E5%86%99%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84SMTP%E6%9C%8D%E5%8A%A1%E5%99%A8%EF%BC%88%E5%9B%9B%EF%BC%89/"/>
    <id>https://luolibrary.com/2023/03/14/%E6%89%8B%E6%8A%8A%E6%89%8B%E6%95%99%E4%BD%A0%E5%86%99%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84SMTP%E6%9C%8D%E5%8A%A1%E5%99%A8%EF%BC%88%E5%9B%9B%EF%BC%89/</id>
    <published>2023-03-14T11:41:21.000Z</published>
    <updated>2023-03-14T11:45:35.173Z</updated>
    
    <content type="html"><![CDATA[<ul><li><a href="https://luolibrary.com/2023/03/12/%E6%89%8B%E6%8A%8A%E6%89%8B%E6%95%99%E4%BD%A0%E5%86%99%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84SMTP%E6%9C%8D%E5%8A%A1%E5%99%A8%EF%BC%88%E4%B8%80%EF%BC%89/">手把手教你写一个简单的SMTP服务器（一）</a></li><li><a href="https://luolibrary.com/2023/03/14/%E6%89%8B%E6%8A%8A%E6%89%8B%E6%95%99%E4%BD%A0%E5%86%99%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84SMTP%E6%9C%8D%E5%8A%A1%E5%99%A8%EF%BC%88%E4%BA%8C%EF%BC%89/">手把手教你写一个简单的SMTP服务器（二）</a></li><li><a href="https://luolibrary.com/2023/03/14/%E6%89%8B%E6%8A%8A%E6%89%8B%E6%95%99%E4%BD%A0%E5%86%99%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84SMTP%E6%9C%8D%E5%8A%A1%E5%99%A8%EF%BC%88%E4%B8%89%EF%BC%89/">手把手教你写一个简单的SMTP服务器（三）</a></li><li><a href="https://luolibrary.com/2023/03/14/%E6%89%8B%E6%8A%8A%E6%89%8B%E6%95%99%E4%BD%A0%E5%86%99%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84SMTP%E6%9C%8D%E5%8A%A1%E5%99%A8%EF%BC%88%E5%9B%9B%EF%BC%89/">手把手教你写一个简单的SMTP服务器（四）</a></li><li><a href="https://luolibrary.com/2023/03/14/%E6%89%8B%E6%8A%8A%E6%89%8B%E6%95%99%E4%BD%A0%E5%86%99%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84SMTP%E6%9C%8D%E5%8A%A1%E5%99%A8%EF%BC%88%E4%BA%94%EF%BC%89/">手把手教你写一个简单的SMTP服务器（五）</a></li></ul><hr><p>在教程三中，我们已经完成了<code>IdleState</code>的操作，那么接下来的核心就是完成其他所有类的操作。为了方便读者的阅读，我们继续给出在教程二中绘制出了的状态图。</p><pre class="mermaid">stateDiagram-v2    [*] --> idle: server start    idle --> ehlo: EHLO    idle --> idle: RSET    ehlo --> mail: MAIL    mail --> rcpt: RCPT    rcpt --> rcpt: RCPT    rcpt --> dataStart: DATA    dataStart --> dataStart : except .    dataStart --> dataDone: .    ehlo --> ehlo: RSET, EHLO    mail --> ehlo: RSET, EHLO    rcpt --> ehlo: RSET, EHLO    dataStart --> ehlo: RSET, EHLO    dataDone --> ehlo: RSET, EHLO    dataDone --> mail: MAIL    idle --> [*]: QUIT    ehlo --> [*]: QUIT    mail --> [*]: QUIT    rcpt --> [*]: QUIT    dataStart --> [*]: QUIT    dataDone --> [*]: QUIT</pre><h2 id="EhloState"><a href="#EhloState" class="headerlink" title="EhloState"></a>EhloState</h2><p>对于<code>EhloState</code>，首先我们应该考虑其能够接收的额外命令。由状态图可知，我们需要<code>MAIL</code>命令。因此我们需要在<code>allowed</code>中添加额外的<code>MAIL</code>。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// miniSMTPServer/context/state.cpp</span></span><br><span class="line">EhloState::<span class="built_in">EhloState</span>() &#123; allowed.<span class="built_in">insert</span>(<span class="string">&quot;MAIL&quot;</span>); &#125;</span><br></pre></td></tr></table></figure><p>同时，我们也需要在<code>isCorrectParameters</code>中添加对<code>MAIL</code>命令参数的判断。我们给出<code>MAIL</code>命令请求和响应的ABNF范式。在这个范式中，我省略了对邮件格式正确性的描述，因为我认为这不是这个教程的重点，在本教程中，我们统一使用如下的正则表达式<code>regex = &quot;(\\w+)(\\.|_)?(\\w*)@(\\w+)(\\.(\\w+))+&quot;</code>来表示邮件的格式。</p><figure class="highlight abnf"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">mail-request</span> <span class="operator">=</span> <span class="string">&quot;MAIL &lt;source&gt; CRLF&quot;</span></span><br><span class="line"><span class="attribute">mail-ok-reponse</span> <span class="operator">=</span> <span class="string">&quot;250&quot;</span> <span class="keyword">SP</span> <span class="string">&quot;Requested mail action okay, completed&quot;</span> <span class="keyword">CRLF</span></span><br></pre></td></tr></table></figure><p>因此，我们就可以修改<code>isCorrectParameters</code>函数，添加对<code>MAIL</code>命令的判断：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// miniSMTPServer/context/state.cpp</span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;regex&gt;</span></span></span><br><span class="line">...</span><br><span class="line"><span class="function">std::optional&lt;std::string&gt; <span class="title">State::isCorrectParameters</span><span class="params">(std::vector&lt;std::string&gt; &amp;parameters)</span> </span>&#123;</span><br><span class="line">  <span class="type">const</span> std::regex pattern&#123;<span class="string">&quot;(\\w+)(\\.|_)?(\\w*)@(\\w+)(\\.(\\w+))+&quot;</span>&#125;;</span><br><span class="line"></span><br><span class="line">  ...</span><br><span class="line">  <span class="keyword">else</span> <span class="keyword">if</span> (parameters[<span class="number">0</span>] == <span class="string">&quot;MAIL&quot;</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (parameters.<span class="built_in">size</span>() != <span class="number">2</span> || !std::<span class="built_in">regex_match</span>(parameters[<span class="number">1</span>], pattern)) &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="string">&quot;501 &quot;</span> + codeToMessages[<span class="string">&quot;501&quot;</span>];</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> std::<span class="literal">nullopt</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>然后，我们添加相应的单元测试：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// miniSMTPServer/context/tests/stateTest.cpp</span></span><br><span class="line"><span class="built_in">TEST</span>(State, isCorrectParametersMAIL) &#123;</span><br><span class="line">  std::vector&lt;std::vector&lt;std::string&gt;&gt; tests&#123;</span><br><span class="line">      &#123;<span class="string">&quot;MAIL&quot;</span>&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;MAIL&quot;</span>, <span class="string">&quot;MAIL&quot;</span>&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;MAIL&quot;</span>, <span class="string">&quot;NOOP&quot;</span>, <span class="string">&quot;MAIL&quot;</span>&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;MAIL&quot;</span>, <span class="string">&quot;shejialuo@gamil..com&quot;</span>&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;EHLO&quot;</span>, <span class="string">&quot;shejialuo&quot;</span>&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;EHLO&quot;</span>, <span class="string">&quot;shejialuo@.com.com&quot;</span>&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;EHLO&quot;</span>, <span class="string">&quot;shejialuo@123.1.cn&quot;</span>&#125;,</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">auto</span> state = std::<span class="built_in">make_unique</span>&lt;IdleState&gt;();</span><br><span class="line"></span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">auto</span> &amp;&amp;test : tests) &#123;</span><br><span class="line">    <span class="keyword">auto</span> result = state-&gt;<span class="built_in">isCorrectParameters</span>(test);</span><br><span class="line">    <span class="built_in">ASSERT_TRUE</span>(result.<span class="built_in">has_value</span>());</span><br><span class="line">    <span class="built_in">ASSERT_EQ</span>(result.<span class="built_in">value</span>(), <span class="string">&quot;501 &quot;</span> + codeToMessages[<span class="string">&quot;501&quot;</span>]);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  std::vector&lt;std::string&gt; successful&#123;<span class="string">&quot;MAIL&quot;</span>, <span class="string">&quot;shejialuo@gmail.com&quot;</span>&#125;;</span><br><span class="line"></span><br><span class="line">  <span class="built_in">ASSERT_FALSE</span>(state-&gt;<span class="built_in">isCorrectParameters</span>(successful).<span class="built_in">has_value</span>());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>然后，我们仍然按照测试驱动的方式完成<code>EhloState</code>状态下的操作，测试的代码有许多可以重用<code>TEST(State, IdleStateTransitive)</code>。<code>RSET</code>和<code>EHLO</code>命令都不会改变其状态，与其说不改变而是从<code>EhloState</code>到<code>EhloState</code>。所以我们只需要关心<code>MAIL</code>命令，<code>MAIL</code>命令会转化到<code>MailState</code>中，根据上述分析，我们可以得出如下的测试代码：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// miniSMTPServer/context/tests/stateTest.cpp</span></span><br><span class="line"><span class="built_in">TEST</span>(State, EhloStateTransitive) &#123;</span><br><span class="line">  std::vector&lt;std::vector&lt;std::string&gt;&gt; tests&#123;</span><br><span class="line">      &#123;<span class="string">&quot;RSET&quot;</span>&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;NOOP&quot;</span>&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;QUIT&quot;</span>&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;EHLO&quot;</span>, <span class="string">&quot;127.0.0.1&quot;</span>&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;MAIL&quot;</span>, <span class="string">&quot;shejialuo@gmail.com&quot;</span>&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;RSTE&quot;</span>&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;DATA&quot;</span>&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;.&quot;</span>&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;RCPT&quot;</span>&#125;,</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  std::vector&lt;std::pair&lt;std::string, std::unique_ptr&lt;State&gt; *&gt;&gt; expects&#123;</span><br><span class="line">      &#123;<span class="string">&quot;250 &quot;</span> + codeToMessages[<span class="string">&quot;250&quot;</span>], &amp;States::ehloState&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;250 &quot;</span> + codeToMessages[<span class="string">&quot;250&quot;</span>], &amp;States::ehloState&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;221 &quot;</span> + codeToMessages[<span class="string">&quot;221&quot;</span>], &amp;States::idleState&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;250 &quot;</span> + codeToMessages[<span class="string">&quot;250&quot;</span>], &amp;States::ehloState&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;250 &quot;</span> + codeToMessages[<span class="string">&quot;250&quot;</span>], &amp;States::mailState&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;500 &quot;</span> + codeToMessages[<span class="string">&quot;500&quot;</span>], &amp;States::ehloState&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;503 &quot;</span> + codeToMessages[<span class="string">&quot;503&quot;</span>], &amp;States::ehloState&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;503 &quot;</span> + codeToMessages[<span class="string">&quot;503&quot;</span>], &amp;States::ehloState&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;503 &quot;</span> + codeToMessages[<span class="string">&quot;503&quot;</span>], &amp;States::ehloState&#125;,</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; tests.<span class="built_in">size</span>(); ++i) &#123;</span><br><span class="line">    <span class="keyword">auto</span> state = std::<span class="built_in">make_unique</span>&lt;EhloState&gt;();</span><br><span class="line">    std::unique_ptr&lt;State&gt; *current = &amp;States::ehloState;</span><br><span class="line">    std::string result = state-&gt;<span class="built_in">transitive</span>(tests[i], current);</span><br><span class="line">    <span class="built_in">EXPECT_EQ</span>(result, expects[i].first);</span><br><span class="line">    <span class="built_in">EXPECT_EQ</span>(current, expects[i].second);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>然后我们开始实现<code>EhloState::transitive</code>方法。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// miniSMTPServer/context/state.cpp</span></span><br><span class="line"><span class="function">std::string <span class="title">EhloState::transitive</span><span class="params">(std::vector&lt;std::string&gt; &amp;parameters, std::unique_ptr&lt;State&gt; *&amp;current)</span> </span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (<span class="keyword">auto</span> result = <span class="built_in">transitiveHelper</span>(parameters, current); result.<span class="built_in">has_value</span>()) &#123;</span><br><span class="line">    <span class="keyword">return</span> result.<span class="built_in">value</span>();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (parameters[<span class="number">0</span>] == <span class="string">&quot;MAIL&quot;</span>) &#123;</span><br><span class="line">    current = &amp;States::mailState;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="string">&quot;250 &quot;</span> + codeToMessages[<span class="string">&quot;250&quot;</span>];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>你可能已经发现了，我们的代码实际上非常的整洁，比起复杂的<code>if-else</code>，我们通过层层地抽象让代码变得十分的简单。你可以编译代码并执行测试。如果你的代码有问题你可以执行如下的命令切换到现在的状态：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cd</span> build &amp;&amp; make -j12 &amp;&amp; ctest --output-on-failure</span><br><span class="line"></span><br><span class="line"><span class="comment"># optional</span></span><br><span class="line">git checkout ehlo-state</span><br></pre></td></tr></table></figure><h2 id="MailState"><a href="#MailState" class="headerlink" title="MailState"></a>MailState</h2><p>对于<code>MailState</code>，首先我们应该考虑其能够接收的额外命令。由状态图可知，我们需要<code>RCPT</code>命令。因此我们需要在<code>allowed</code>中添加额外的<code>RCPT</code>。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// miniSMTPServer/context/state.cpp</span></span><br><span class="line">MailState::<span class="built_in">MailState</span>() &#123; allowed.<span class="built_in">insert</span>(<span class="string">&quot;RCPT&quot;</span>); &#125;</span><br></pre></td></tr></table></figure><p>同时，我们也需要在<code>isCorrectParameters</code>中添加对<code>RCPT</code>命令参数的判断。我们给出<code>RCPT</code>命令请求和响应的ABNF范式，其与<code>MAIL</code>命令类似，此处不赘述。</p><figure class="highlight abnf"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">rcpt-request</span> <span class="operator">=</span> <span class="string">&quot;RCPT &lt;source&gt; CRLF&quot;</span></span><br><span class="line"><span class="attribute">rcpt-ok-reponse</span> <span class="operator">=</span> <span class="string">&quot;250&quot;</span> <span class="keyword">SP</span> <span class="string">&quot;Requested mail action okay, completed&quot;</span> <span class="keyword">CRLF</span></span><br></pre></td></tr></table></figure><p>因此，我们就可以修改<code>isCorrectParameters</code>函数，添加对<code>RCPT</code>命令的判断：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// miniSMTPServer/context/state.cpp</span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;regex&gt;</span></span></span><br><span class="line"><span class="function">std::optional&lt;std::string&gt; <span class="title">State::isCorrectParameters</span><span class="params">(std::vector&lt;std::string&gt; &amp;parameters)</span> </span>&#123;</span><br><span class="line">  ...</span><br><span class="line">  <span class="keyword">else</span> <span class="keyword">if</span> (parameters[<span class="number">0</span>] == <span class="string">&quot;MAIL&quot;</span> || parameters[<span class="number">0</span>] == <span class="string">&quot;RCPT&quot;</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (parameters.<span class="built_in">size</span>() != <span class="number">2</span> || !std::<span class="built_in">regex_match</span>(parameters[<span class="number">1</span>], pattern)) &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="string">&quot;501 &quot;</span> + codeToMessages[<span class="string">&quot;501&quot;</span>];</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> std::<span class="literal">nullopt</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>由于在<code>MAIL</code>命令中我们已经给出了很详细的单元测试，此处就忽略了单元测试。然后，我们仍然按照测试驱动的方式完成<code>MailState</code>状态下的操作，<code>RSET</code>和<code>EHLO</code>命令都改变其状态让其回到<code>EhloState</code>状态。对于<code>RCPT</code>命令，<code>RCPT</code>命令会令其转化到<code>RCPTState</code>中，根据上述分析，我们可以得出如下的测试代码：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// miniSMTPServer/context/tests/stateTest.cpp</span></span><br><span class="line"><span class="built_in">TEST</span>(State, MailStateTransitive) &#123;</span><br><span class="line">  std::vector&lt;std::vector&lt;std::string&gt;&gt; tests&#123;</span><br><span class="line">      &#123;<span class="string">&quot;RSET&quot;</span>&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;NOOP&quot;</span>&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;QUIT&quot;</span>&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;EHLO&quot;</span>, <span class="string">&quot;127.0.0.1&quot;</span>&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;RCPT&quot;</span>, <span class="string">&quot;shejialuo@gmail.com&quot;</span>&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;MAIL&quot;</span>, <span class="string">&quot;shejialuo@gmail.com&quot;</span>&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;DATA&quot;</span>&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;.&quot;</span>&#125;,</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  std::vector&lt;std::pair&lt;std::string, std::unique_ptr&lt;State&gt; *&gt;&gt; expects&#123;</span><br><span class="line">      &#123;<span class="string">&quot;250 &quot;</span> + codeToMessages[<span class="string">&quot;250&quot;</span>], &amp;States::ehloState&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;250 &quot;</span> + codeToMessages[<span class="string">&quot;250&quot;</span>], &amp;States::mailState&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;221 &quot;</span> + codeToMessages[<span class="string">&quot;221&quot;</span>], &amp;States::idleState&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;250 &quot;</span> + codeToMessages[<span class="string">&quot;250&quot;</span>], &amp;States::ehloState&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;250 &quot;</span> + codeToMessages[<span class="string">&quot;250&quot;</span>], &amp;States::rcptState&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;503 &quot;</span> + codeToMessages[<span class="string">&quot;503&quot;</span>], &amp;States::mailState&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;503 &quot;</span> + codeToMessages[<span class="string">&quot;503&quot;</span>], &amp;States::mailState&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;503 &quot;</span> + codeToMessages[<span class="string">&quot;503&quot;</span>], &amp;States::mailState&#125;,</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; tests.<span class="built_in">size</span>(); ++i) &#123;</span><br><span class="line">    <span class="keyword">auto</span> state = std::<span class="built_in">make_unique</span>&lt;MailState&gt;();</span><br><span class="line">    std::unique_ptr&lt;State&gt; *current = &amp;States::mailState;</span><br><span class="line">    std::string result = state-&gt;<span class="built_in">transitive</span>(tests[i], current);</span><br><span class="line">    <span class="built_in">EXPECT_EQ</span>(result, expects[i].first);</span><br><span class="line">    <span class="built_in">EXPECT_EQ</span>(current, expects[i].second);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>然后我们开始实现<code>MailState::transitive</code>方法。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// miniSMTPServer/context/state.cpp</span></span><br><span class="line"><span class="function">std::string <span class="title">MailState::transitive</span><span class="params">(std::vector&lt;std::string&gt; &amp;parameters, std::unique_ptr&lt;State&gt; *&amp;current)</span> </span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (<span class="keyword">auto</span> result = <span class="built_in">transitiveHelper</span>(parameters, current); result.<span class="built_in">has_value</span>()) &#123;</span><br><span class="line">    <span class="keyword">return</span> result.<span class="built_in">value</span>();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (parameters[<span class="number">0</span>] == <span class="string">&quot;RCPT&quot;</span>) &#123;</span><br><span class="line">    current = &amp;States::rcptState;</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    current = &amp;States::ehloState;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="string">&quot;250 &quot;</span> + codeToMessages[<span class="string">&quot;250&quot;</span>];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>你可以编译代码并执行测试。如果你的代码有问题你可以执行如下的命令切换到现在的状态：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cd</span> build &amp;&amp; make -j12 &amp;&amp; ctest --output-on-failure</span><br><span class="line"></span><br><span class="line"><span class="comment"># optional</span></span><br><span class="line">git checkout mail-state</span><br></pre></td></tr></table></figure><h2 id="RcptState"><a href="#RcptState" class="headerlink" title="RcptState"></a>RcptState</h2><p>对于<code>MailState</code>，首先我们应该考虑其能够接收的额外命令。由状态图可知，我们需要<code>RCPT</code>命令和<code>DATA</code>命令。因此我们需要在<code>allowed</code>中添加额外的<code>RCPT</code>和<code>DATA</code>。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// miniSMTPServer/context/state.cpp</span></span><br><span class="line">RcptState::<span class="built_in">RcptState</span>() &#123;</span><br><span class="line">  allowed.<span class="built_in">insert</span>(<span class="string">&quot;RCPT&quot;</span>);</span><br><span class="line">  allowed.<span class="built_in">insert</span>(<span class="string">&quot;DATA&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>我们已经处理了<code>RCPT</code>命令的判断，剩下就是<code>DATA</code>命令的判断，根据<code>DATA</code>命令的ABNF范式，我们可以做很简单的处理操作。</p><figure class="highlight abnf"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">data-request</span> <span class="operator">=</span> <span class="string">&quot;DATA CRLF&quot;</span></span><br><span class="line"><span class="attribute">data-ok-reponse</span> <span class="operator">=</span> <span class="string">&quot;250&quot;</span> <span class="keyword">SP</span> <span class="string">&quot;Requested mail action okay, completed&quot;</span> <span class="keyword">CRLF</span></span><br></pre></td></tr></table></figure><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// miniSMTPServer/context/state.cpp</span></span><br><span class="line"><span class="function">std::optional&lt;std::string&gt; <span class="title">State::isCorrectParameters</span><span class="params">(std::vector&lt;std::string&gt; &amp;parameters)</span> </span>&#123;</span><br><span class="line">  ...</span><br><span class="line">  <span class="keyword">if</span> (command == <span class="string">&quot;NOOP&quot;</span> || command == <span class="string">&quot;QUIT&quot;</span> || command == <span class="string">&quot;RSET&quot;</span> || command == <span class="string">&quot;DATA&quot;</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (parameters.<span class="built_in">size</span>() != <span class="number">1</span>) &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="string">&quot;501 &quot;</span> + codeToMessages[<span class="string">&quot;501&quot;</span>];</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>然后，我们仍然按照测试驱动的方式完成<code>RCPTState</code>状态下的操作，<code>RSET</code>和<code>EHLO</code>命令都改变其状态让其回到<code>EhloState</code>状态。对于<code>RCPT</code>命令，<code>RCPT</code>命令并不会改变其状态。当其接收到<code>DATA</code>命令后，其会转化为<code>DataStartState</code>。根据上述分析，我们可以得出如下的测试代码：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">TEST</span>(State, RCPTStateTransitive) &#123;</span><br><span class="line">  std::vector&lt;std::vector&lt;std::string&gt;&gt; tests&#123;</span><br><span class="line">      &#123;<span class="string">&quot;RSET&quot;</span>&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;NOOP&quot;</span>&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;QUIT&quot;</span>&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;EHLO&quot;</span>, <span class="string">&quot;127.0.0.1&quot;</span>&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;RCPT&quot;</span>, <span class="string">&quot;shejialuo@gmail.com&quot;</span>&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;MAIL&quot;</span>, <span class="string">&quot;shejialuo@gmail.com&quot;</span>&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;DATA&quot;</span>&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;.&quot;</span>&#125;,</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  std::vector&lt;std::pair&lt;std::string, std::unique_ptr&lt;State&gt; *&gt;&gt; expects&#123;</span><br><span class="line">      &#123;<span class="string">&quot;250 &quot;</span> + codeToMessages[<span class="string">&quot;250&quot;</span>], &amp;States::ehloState&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;250 &quot;</span> + codeToMessages[<span class="string">&quot;250&quot;</span>], &amp;States::rcptState&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;221 &quot;</span> + codeToMessages[<span class="string">&quot;221&quot;</span>], &amp;States::idleState&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;250 &quot;</span> + codeToMessages[<span class="string">&quot;250&quot;</span>], &amp;States::ehloState&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;250 &quot;</span> + codeToMessages[<span class="string">&quot;250&quot;</span>], &amp;States::rcptState&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;503 &quot;</span> + codeToMessages[<span class="string">&quot;503&quot;</span>], &amp;States::rcptState&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;250 &quot;</span> + codeToMessages[<span class="string">&quot;250&quot;</span>], &amp;States::dataStartState&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;503 &quot;</span> + codeToMessages[<span class="string">&quot;503&quot;</span>], &amp;States::rcptState&#125;,</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; tests.<span class="built_in">size</span>(); ++i) &#123;</span><br><span class="line">    <span class="keyword">auto</span> state = std::<span class="built_in">make_unique</span>&lt;RcptState&gt;();</span><br><span class="line">    std::unique_ptr&lt;State&gt; *current = &amp;States::rcptState;</span><br><span class="line">    std::string result = state-&gt;<span class="built_in">transitive</span>(tests[i], current);</span><br><span class="line">    <span class="built_in">EXPECT_EQ</span>(result, expects[i].first);</span><br><span class="line">    <span class="built_in">EXPECT_EQ</span>(current, expects[i].second);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>然后我们开始实现<code>RcptState::transitive</code>方法。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">std::string <span class="title">RcptState::transitive</span><span class="params">(std::vector&lt;std::string&gt; &amp;parameters, std::unique_ptr&lt;State&gt; *&amp;current)</span> </span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (<span class="keyword">auto</span> result = <span class="built_in">transitiveHelper</span>(parameters, current); result.<span class="built_in">has_value</span>()) &#123;</span><br><span class="line">    <span class="keyword">return</span> result.<span class="built_in">value</span>();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (parameters[<span class="number">0</span>] == <span class="string">&quot;DATA&quot;</span>) &#123;</span><br><span class="line">    current = &amp;States::dataStartState;</span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (parameters[<span class="number">0</span>] == <span class="string">&quot;RCPT&quot;</span>) &#123;</span><br><span class="line">    current = &amp;States::rcptState;</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    current = &amp;States::ehloState;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="string">&quot;250 &quot;</span> + codeToMessages[<span class="string">&quot;250&quot;</span>];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>你可以编译代码并执行测试。如果你的代码有问题你可以执行如下的命令切换到现在的状态：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cd</span> build &amp;&amp; make -j12 &amp;&amp; ctest --output-on-failure</span><br><span class="line"></span><br><span class="line"><span class="comment"># optional</span></span><br><span class="line">git checkout rcpt-state</span><br></pre></td></tr></table></figure><h2 id="DataStartState-DataDoneState"><a href="#DataStartState-DataDoneState" class="headerlink" title="DataStartState &amp; DataDoneState"></a>DataStartState &amp; DataDoneState</h2><p>对于<code>DataStartState</code>，首先我们应该考虑其能够接收的额外命令。由状态图可知，服务器端只能接收<code>.</code>命令，此时我们必须转换我们的思路了。我们原先的思路是通过<code>transitiveHelper</code>来处理，但由于在接收用户发送的数据阶段，哪怕用户发送<code>MAIL</code>，我们也不能将其作为命令。所以当且仅当服务器接收<code>.</code>命令时，我们才能进入到<code>DataDoneState</code>阶段。我们不能够重用任何以前的方法，哪怕用户输入了<code>. .</code>我们都必须认为这是邮件的内容。因此我们直接修改<code>transitive</code>方法：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// miniSMTPServer/context/state.cpp</span></span><br><span class="line"><span class="function">std::string <span class="title">DataStartState::transitive</span><span class="params">(std::vector&lt;std::string&gt; &amp;parameters, std::unique_ptr&lt;State&gt; *&amp;current)</span> </span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (parameters[<span class="number">0</span>] == <span class="string">&quot;.&quot;</span> &amp;&amp; parameters.<span class="built_in">size</span>() == <span class="number">1</span>) &#123;</span><br><span class="line">    current = &amp;States::dataDoneState;</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&quot;250 &quot;</span> + codeToMessages[<span class="string">&quot;250&quot;</span>];</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> <span class="string">&quot;354 &quot;</span> + codeToMessages[<span class="string">&quot;354&quot;</span>];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>对于该方法的测试，我们需要考虑到更多的边界条件，首先我们必须要思考的是如何构建我们的测试，首先我们必须测试会不会解析除了<code>.</code>以外的命令，其次就是考虑类似于<code>. .</code>是否会解析。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// miniSMTPServer/context/tests/stateTest.cpp</span></span><br><span class="line"><span class="built_in">TEST</span>(State, DataStartStateTransitive) &#123;</span><br><span class="line">  std::vector&lt;std::vector&lt;std::string&gt;&gt; tests&#123;</span><br><span class="line">      &#123;<span class="string">&quot;RSET&quot;</span>&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;RSET&quot;</span>, <span class="string">&quot;1&quot;</span>&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;NOOP&quot;</span>&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;NOOP&quot;</span>, <span class="string">&quot;NOOP&quot;</span>&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;QUIT&quot;</span>&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;EHLO&quot;</span>, <span class="string">&quot;127.0.0.1&quot;</span>&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;RCPT&quot;</span>, <span class="string">&quot;shejialuo@gmail.com&quot;</span>&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;MAIL&quot;</span>, <span class="string">&quot;shejialuo@gmail.com&quot;</span>&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;DATA&quot;</span>&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;..&quot;</span>&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;.&quot;</span>, <span class="string">&quot;.&quot;</span>&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;.&quot;</span>&#125;,</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  std::vector&lt;std::pair&lt;std::string, std::unique_ptr&lt;State&gt; *&gt;&gt; expects&#123;</span><br><span class="line">      &#123;<span class="string">&quot;354 &quot;</span> + codeToMessages[<span class="string">&quot;354&quot;</span>], &amp;States::dataStartState&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;354 &quot;</span> + codeToMessages[<span class="string">&quot;354&quot;</span>], &amp;States::dataStartState&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;354 &quot;</span> + codeToMessages[<span class="string">&quot;354&quot;</span>], &amp;States::dataStartState&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;354 &quot;</span> + codeToMessages[<span class="string">&quot;354&quot;</span>], &amp;States::dataStartState&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;354 &quot;</span> + codeToMessages[<span class="string">&quot;354&quot;</span>], &amp;States::dataStartState&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;354 &quot;</span> + codeToMessages[<span class="string">&quot;354&quot;</span>], &amp;States::dataStartState&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;354 &quot;</span> + codeToMessages[<span class="string">&quot;354&quot;</span>], &amp;States::dataStartState&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;354 &quot;</span> + codeToMessages[<span class="string">&quot;354&quot;</span>], &amp;States::dataStartState&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;354 &quot;</span> + codeToMessages[<span class="string">&quot;354&quot;</span>], &amp;States::dataStartState&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;354 &quot;</span> + codeToMessages[<span class="string">&quot;354&quot;</span>], &amp;States::dataStartState&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;354 &quot;</span> + codeToMessages[<span class="string">&quot;354&quot;</span>], &amp;States::dataStartState&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;250 &quot;</span> + codeToMessages[<span class="string">&quot;250&quot;</span>], &amp;States::dataDoneState&#125;,</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; tests.<span class="built_in">size</span>(); ++i) &#123;</span><br><span class="line">    <span class="keyword">auto</span> state = std::<span class="built_in">make_unique</span>&lt;DataStartState&gt;();</span><br><span class="line">    std::unique_ptr&lt;State&gt; *current = &amp;States::dataStartState;</span><br><span class="line">    std::string result = state-&gt;<span class="built_in">transitive</span>(tests[i], current);</span><br><span class="line">    <span class="built_in">EXPECT_EQ</span>(result, expects[i].first);</span><br><span class="line">    <span class="built_in">EXPECT_EQ</span>(current, expects[i].second);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>对于<code>DataDoneState</code> 由状态图可以观察出来，实际上其与<code>EhloState</code>是一致的，之所以用两个状态进行区分，是为了方便读者的理解。故这部分就不赘述了。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// miniSMTPServer/context/state.cpp</span></span><br><span class="line">DataDoneState::<span class="built_in">DataDoneState</span>() &#123; allowed.<span class="built_in">insert</span>(<span class="string">&quot;MAIL&quot;</span>); &#125;</span><br><span class="line"><span class="function">std::string <span class="title">DataDoneState::transitive</span><span class="params">(std::vector&lt;std::string&gt; &amp;parameters, std::unique_ptr&lt;State&gt; *&amp;current)</span> </span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (<span class="keyword">auto</span> result = <span class="built_in">transitiveHelper</span>(parameters, current); result.<span class="built_in">has_value</span>()) &#123;</span><br><span class="line">    <span class="keyword">return</span> result.<span class="built_in">value</span>();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (parameters[<span class="number">0</span>] == <span class="string">&quot;MAIL&quot;</span>) &#123;</span><br><span class="line">    current = &amp;States::mailState;</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    current = &amp;States::ehloState;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="string">&quot;250 &quot;</span> + codeToMessages[<span class="string">&quot;250&quot;</span>];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>你可以编译代码并执行测试。如果你的代码有问题你可以执行如下的命令切换到现在的状态：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cd</span> build &amp;&amp; make -j12 &amp;&amp; ctest --output-on-failure</span><br><span class="line"></span><br><span class="line"><span class="comment"># optional</span></span><br><span class="line">git checkout finished-states</span><br></pre></td></tr></table></figure><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>你应该能够发现，这一节反而是简单的一节。我们所做的绝大多数工作都是重复。其实最重要的过程一直在我们是如何抽象出<code>State</code>这个虚基类的。希望读者阅读到这儿能够有所启发，理解到良好的架构对于代码的整洁和拓展性拥有很大的作用。</p>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;ul&gt;
&lt;li&gt;&lt;a</summary>
        
      
    
    
    
    <category term="手把手教你写一个简单的SMTP服务器" scheme="https://luolibrary.com/categories/%E6%89%8B%E6%8A%8A%E6%89%8B%E6%95%99%E4%BD%A0%E5%86%99%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84SMTP%E6%9C%8D%E5%8A%A1%E5%99%A8/"/>
    
    
    <category term="技术" scheme="https://luolibrary.com/tags/%E6%8A%80%E6%9C%AF/"/>
    
    <category term="学习" scheme="https://luolibrary.com/tags/%E5%AD%A6%E4%B9%A0/"/>
    
    <category term="教程" scheme="https://luolibrary.com/tags/%E6%95%99%E7%A8%8B/"/>
    
  </entry>
  
  <entry>
    <title>手把手教你写一个简单的SMTP服务器（三）</title>
    <link href="https://luolibrary.com/2023/03/14/%E6%89%8B%E6%8A%8A%E6%89%8B%E6%95%99%E4%BD%A0%E5%86%99%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84SMTP%E6%9C%8D%E5%8A%A1%E5%99%A8%EF%BC%88%E4%B8%89%EF%BC%89/"/>
    <id>https://luolibrary.com/2023/03/14/%E6%89%8B%E6%8A%8A%E6%89%8B%E6%95%99%E4%BD%A0%E5%86%99%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84SMTP%E6%9C%8D%E5%8A%A1%E5%99%A8%EF%BC%88%E4%B8%89%EF%BC%89/</id>
    <published>2023-03-14T11:41:18.000Z</published>
    <updated>2023-03-14T11:45:18.189Z</updated>
    
    <content type="html"><![CDATA[<ul><li><a href="https://luolibrary.com/2023/03/12/%E6%89%8B%E6%8A%8A%E6%89%8B%E6%95%99%E4%BD%A0%E5%86%99%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84SMTP%E6%9C%8D%E5%8A%A1%E5%99%A8%EF%BC%88%E4%B8%80%EF%BC%89/">手把手教你写一个简单的SMTP服务器（一）</a></li><li><a href="https://luolibrary.com/2023/03/14/%E6%89%8B%E6%8A%8A%E6%89%8B%E6%95%99%E4%BD%A0%E5%86%99%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84SMTP%E6%9C%8D%E5%8A%A1%E5%99%A8%EF%BC%88%E4%BA%8C%EF%BC%89/">手把手教你写一个简单的SMTP服务器（二）</a></li><li><a href="https://luolibrary.com/2023/03/14/%E6%89%8B%E6%8A%8A%E6%89%8B%E6%95%99%E4%BD%A0%E5%86%99%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84SMTP%E6%9C%8D%E5%8A%A1%E5%99%A8%EF%BC%88%E4%B8%89%EF%BC%89/">手把手教你写一个简单的SMTP服务器（三）</a></li><li><a href="https://luolibrary.com/2023/03/14/%E6%89%8B%E6%8A%8A%E6%89%8B%E6%95%99%E4%BD%A0%E5%86%99%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84SMTP%E6%9C%8D%E5%8A%A1%E5%99%A8%EF%BC%88%E5%9B%9B%EF%BC%89/">手把手教你写一个简单的SMTP服务器（四）</a></li><li><a href="https://luolibrary.com/2023/03/14/%E6%89%8B%E6%8A%8A%E6%89%8B%E6%95%99%E4%BD%A0%E5%86%99%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84SMTP%E6%9C%8D%E5%8A%A1%E5%99%A8%EF%BC%88%E4%BA%94%EF%BC%89/">手把手教你写一个简单的SMTP服务器（五）</a></li></ul><hr><p>在教程二中我们已经完成了最重要的工作，构建了最基本的虚基类<code>State</code>。回顾在教程二中我们对于状态的定义，我们拟定义如下6个类来表征状态机中的状态：</p><ol><li><code>IdleState</code></li><li><code>EhloState</code></li><li><code>MailState</code></li><li><code>RcptState</code></li><li><code>DataStartState</code></li><li><code>DataDoneState</code></li></ol><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// miniSMTPServer/context/state.hpp</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">IdleState</span> : <span class="keyword">public</span> State &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">  <span class="built_in">IdleState</span>();</span><br><span class="line">  <span class="function">std::string <span class="title">transitive</span><span class="params">(std::vector&lt;std::string&gt; &amp;parameters, std::unique_ptr&lt;State&gt; *&amp;current)</span> <span class="keyword">override</span></span>;</span><br><span class="line">  ~<span class="built_in">IdleState</span>() <span class="keyword">override</span> = <span class="keyword">default</span>;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">EhloState</span> : <span class="keyword">public</span> State &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">  <span class="built_in">EhloState</span>();</span><br><span class="line">  <span class="function">std::string <span class="title">transitive</span><span class="params">(std::vector&lt;std::string&gt; &amp;parameters, std::unique_ptr&lt;State&gt; *&amp;current)</span> <span class="keyword">override</span></span>;</span><br><span class="line">  ~<span class="built_in">EhloState</span>() <span class="keyword">override</span> = <span class="keyword">default</span>;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">MailState</span> : <span class="keyword">public</span> State &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">  <span class="built_in">MailState</span>();</span><br><span class="line">  <span class="function">std::string <span class="title">transitive</span><span class="params">(std::vector&lt;std::string&gt; &amp;parameters, std::unique_ptr&lt;State&gt; *&amp;current)</span> <span class="keyword">override</span></span>;</span><br><span class="line">  ~<span class="built_in">MailState</span>() <span class="keyword">override</span> = <span class="keyword">default</span>;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">RcptState</span> : <span class="keyword">public</span> State &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">  <span class="built_in">RcptState</span>();</span><br><span class="line">  <span class="function">std::string <span class="title">transitive</span><span class="params">(std::vector&lt;std::string&gt; &amp;parameters, std::unique_ptr&lt;State&gt; *&amp;current)</span> <span class="keyword">override</span></span>;</span><br><span class="line">  ~<span class="built_in">RcptState</span>() <span class="keyword">override</span> = <span class="keyword">default</span>;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">DataStartState</span> : <span class="keyword">public</span> State &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">  <span class="built_in">DataStartState</span>();</span><br><span class="line">  <span class="function">std::string <span class="title">transitive</span><span class="params">(std::vector&lt;std::string&gt; &amp;parameters, std::unique_ptr&lt;State&gt; *&amp;current)</span> <span class="keyword">override</span></span>;</span><br><span class="line">  ~<span class="built_in">DataStartState</span>() <span class="keyword">override</span> = <span class="keyword">default</span>;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">DataDoneState</span> : <span class="keyword">public</span> State &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">  <span class="built_in">DataDoneState</span>();</span><br><span class="line">  <span class="function">std::string <span class="title">transitive</span><span class="params">(std::vector&lt;std::string&gt; &amp;parameters, std::unique_ptr&lt;State&gt; *&amp;current)</span> <span class="keyword">override</span></span>;</span><br><span class="line">  ~<span class="built_in">DataDoneState</span>() <span class="keyword">override</span> = <span class="keyword">default</span>;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>然后我们需要实现其方法，在现在这个阶段，我们当让其做空操作，或者返回默认值。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// miniSMTPServer/context/state.cpp</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// <span class="doctag">TODO:</span> add implementation for IdleState</span></span><br><span class="line">IdleState::<span class="built_in">IdleState</span>() &#123;&#125;</span><br><span class="line"><span class="function">std::string <span class="title">IdleState::transitive</span><span class="params">(std::vector&lt;std::string&gt; &amp;parameters, std::unique_ptr&lt;State&gt; *&amp;current)</span> </span>&#123; <span class="keyword">return</span> &#123;&#125;; &#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// <span class="doctag">TODO:</span> add implementation for EhloState</span></span><br><span class="line">EhloState::<span class="built_in">EhloState</span>() &#123;&#125;</span><br><span class="line"><span class="function">std::string <span class="title">EhloState::transitive</span><span class="params">(std::vector&lt;std::string&gt; &amp;parameters, std::unique_ptr&lt;State&gt; *&amp;current)</span> </span>&#123; <span class="keyword">return</span> &#123;&#125;; &#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// <span class="doctag">TODO:</span> add implementation for MailState</span></span><br><span class="line">MailState::<span class="built_in">MailState</span>() &#123;&#125;</span><br><span class="line"><span class="function">std::string <span class="title">MailState::transitive</span><span class="params">(std::vector&lt;std::string&gt; &amp;parameters, std::unique_ptr&lt;State&gt; *&amp;current)</span> </span>&#123; <span class="keyword">return</span> &#123;&#125;; &#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// <span class="doctag">TODO:</span> add implementation for RcptState</span></span><br><span class="line">RcptState::<span class="built_in">RcptState</span>() &#123;&#125;</span><br><span class="line"><span class="function">std::string <span class="title">RcptState::transitive</span><span class="params">(std::vector&lt;std::string&gt; &amp;parameters, std::unique_ptr&lt;State&gt; *&amp;current)</span> </span>&#123; <span class="keyword">return</span> &#123;&#125;; &#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// <span class="doctag">TODO:</span> add implementation for DataStartState</span></span><br><span class="line">DataStartState::<span class="built_in">DataStartState</span>() &#123;&#125;</span><br><span class="line"><span class="function">std::string <span class="title">DataStartState::transitive</span><span class="params">(std::vector&lt;std::string&gt; &amp;parameters, std::unique_ptr&lt;State&gt; *&amp;current)</span> </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> &#123;&#125;;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// <span class="doctag">TODO:</span> add implementation for DataDoneState</span></span><br><span class="line">DataDoneState::<span class="built_in">DataDoneState</span>() &#123;&#125;</span><br><span class="line"><span class="function">std::string <span class="title">DataDoneState::transitive</span><span class="params">(std::vector&lt;std::string&gt; &amp;parameters, std::unique_ptr&lt;State&gt; *&amp;current)</span> </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> &#123;&#125;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>根据教程二中设计，我们可以知道当服务器端收到客户端的命令时，其会调用<code>transitive</code>方法进行状态的转换，一个思路是我们可以使用<code>make_unique</code>方法构造一个完整的新类来表述状态，但是这样不是很高效。为了提高效率，我们应该使用全局的生命周期的变量，因此我们可以构造一个新类<code>States</code>包含指向6个已经存在的类的指针，我们使用<code>unique_ptr</code>管理这些指针，从而避免内存的泄漏。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// miniSMTPServer/context/state.hpp</span></span><br><span class="line"><span class="keyword">struct</span> <span class="title class_">States</span> &#123;</span><br><span class="line">  <span class="type">static</span> std::unique_ptr&lt;State&gt; idleState;</span><br><span class="line">  <span class="type">static</span> std::unique_ptr&lt;State&gt; ehloState;</span><br><span class="line">  <span class="type">static</span> std::unique_ptr&lt;State&gt; mailState;</span><br><span class="line">  <span class="type">static</span> std::unique_ptr&lt;State&gt; rcptState;</span><br><span class="line">  <span class="type">static</span> std::unique_ptr&lt;State&gt; dataStartState;</span><br><span class="line">  <span class="type">static</span> std::unique_ptr&lt;State&gt; dataDoneState;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// miniSMTPServer/context/state.cpp</span></span><br><span class="line">std::unique_ptr&lt;State&gt; States::idleState = std::<span class="built_in">make_unique</span>&lt;IdleState&gt;();</span><br><span class="line">std::unique_ptr&lt;State&gt; States::ehloState = std::<span class="built_in">make_unique</span>&lt;EhloState&gt;();</span><br><span class="line">std::unique_ptr&lt;State&gt; States::mailState = std::<span class="built_in">make_unique</span>&lt;MailState&gt;();</span><br><span class="line">std::unique_ptr&lt;State&gt; States::rcptState = std::<span class="built_in">make_unique</span>&lt;RcptState&gt;();</span><br><span class="line">std::unique_ptr&lt;State&gt; States::dataStartState = std::<span class="built_in">make_unique</span>&lt;DataStartState&gt;();</span><br><span class="line">std::unique_ptr&lt;State&gt; States::dataDoneState = std::<span class="built_in">make_unique</span>&lt;DataDoneState&gt;();</span><br></pre></td></tr></table></figure><p>完成了这一步，我们就可以修改<code>State</code>类的<code>transitiveFromQuit</code>方法：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// miniSMTPServer/context/state.cpp</span></span><br><span class="line"><span class="function">std::string <span class="title">State::transitiveFromQuit</span><span class="params">(std::unique_ptr&lt;State&gt; *&amp;current)</span> </span>&#123;</span><br><span class="line">  current = &amp;States::idleState;</span><br><span class="line">  <span class="keyword">return</span> <span class="string">&quot;221&quot;</span> + codeToMessages[<span class="string">&quot;221&quot;</span>];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>至此，我们完成了我们的状态机的雏形。同时你也可以通过执行命令<code>git checkout all-states</code>获得上面的代码。</p><h2 id="IdleState"><a href="#IdleState" class="headerlink" title="IdleState"></a>IdleState</h2><p>当状态机一启动时，其应该位于<code>IdleState</code>状态，其能够接收4个命令：<code>RSET</code>，<code>NOOP</code>，<code>QUIT</code>以及<code>EHLO</code>命令。其中<code>NOOP</code>和<code>QUIT</code>已经统一处理了，我们并不需要关心。在后面的状态中，我们会忽略这两个命令。对于<code>RSET</code>命令来说，其在这个状态没有任何的作用（严格来说，不是没有作用，而是从<code>IdleState</code>转化为<code>IdleState</code>）。对于<code>EHLO</code>命令来说，其应该从<code>IdleState</code>状态转化为<code>EhloState</code>。在完成这个工作之前，我们首先使用ABNF范式定义<code>RSET</code>等命令的请求和响应，为了简单起见，本教程直接设置了一个静态的”127.0.0.1”。</p><figure class="highlight abnf"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">rset-request</span> <span class="operator">=</span> <span class="string">&quot;RSET&quot;</span> <span class="keyword">CRLF</span></span><br><span class="line"><span class="attribute">rset-ok-response</span> <span class="operator">=</span> <span class="string">&quot;250&quot;</span> <span class="keyword">SP</span> <span class="string">&quot;Requested mail action okay, completed&quot;</span> <span class="keyword">CRLF</span></span><br><span class="line"></span><br><span class="line"><span class="attribute">noop-request</span> <span class="operator">=</span> <span class="string">&quot;NOOP&quot;</span> <span class="keyword">CRLF</span></span><br><span class="line"><span class="attribute">noop-ok-response</span> <span class="operator">=</span> <span class="string">&quot;250&quot;</span> <span class="keyword">SP</span> <span class="string">&quot;Requested mail action okay, completed&quot;</span> <span class="keyword">CRLF</span></span><br><span class="line"></span><br><span class="line"><span class="attribute">quit-request</span> <span class="operator">=</span> <span class="string">&quot;QUIT&quot;</span> <span class="keyword">CRLF</span></span><br><span class="line"><span class="attribute">quit-ok-response</span> <span class="operator">=</span> <span class="string">&quot;221&quot;</span> <span class="keyword">SP</span> <span class="string">&quot;Service closing transmission channel&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="attribute">ehlo-request</span> <span class="operator">=</span> <span class="string">&quot;EHLO&quot;</span> <span class="keyword">SP</span> <span class="string">&quot;127.0.0.1&quot;</span> <span class="keyword">CRLF</span></span><br><span class="line"><span class="attribute">ehlo-ok-response</span> <span class="operator">=</span> <span class="string">&quot;250&quot;</span> <span class="keyword">SP</span> <span class="string">&quot;Requested mail action okay, completed&quot;</span> <span class="keyword">CRLF</span></span><br></pre></td></tr></table></figure><p>因此，我们首先更新<code>State</code>基类中的<code>isCorrectParameters</code>方法。其操作很简单，对于<code>NOOP</code>，<code>QUIT</code>和<code>RSET</code>而言，这些命令都不需要任何的参数。而对于<code>EHLO</code>命令，我们直接严格按照ABNF定义即可。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">std::optional&lt;std::string&gt; <span class="title">State::isCorrectParameters</span><span class="params">(std::vector&lt;std::string&gt; &amp;parameters)</span> </span>&#123;</span><br><span class="line">  std::string &amp;command = parameters[<span class="number">0</span>];</span><br><span class="line">  <span class="keyword">if</span> (command == <span class="string">&quot;NOOP&quot;</span> || command == <span class="string">&quot;QUIT&quot;</span> || command == <span class="string">&quot;RSET&quot;</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (parameters.<span class="built_in">size</span>() != <span class="number">1</span>) &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="string">&quot;501 &quot;</span> + codeToMessages[<span class="string">&quot;501&quot;</span>];</span><br><span class="line">    &#125;</span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (parameters[<span class="number">0</span>] == <span class="string">&quot;EHLO&quot;</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (parameters.<span class="built_in">size</span>() != <span class="number">2</span> || parameters[<span class="number">1</span>] != <span class="string">&quot;127.0.0.1&quot;</span>) &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="string">&quot;501 &quot;</span> + codeToMessages[<span class="string">&quot;501&quot;</span>];</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> std::<span class="literal">nullopt</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>你可能觉得这个功能很简单，但是就我个人而言，我认为此处我们应该写一点单元测试保证这个函数的正确性。显然，用cpp写单元测试也是相当的繁琐。首先，你需要在<code>miniSMTPServer/context</code>目录下创建一个新的目录<code>tests</code>。然后在<code>tests</code>目录下添加如下的文件：</p><ul><li><code>CMakeLists.txt</code></li><li><code>stateTest.cpp</code></li></ul><p>对于位于<code>miniSMTPServer/context/tests</code>目录下的<code>CMakeLists.txt</code>，添加如下的代码：</p><figure class="highlight cmake"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># miniSMTPServer/context/tests/CMakeLists.txt</span></span><br><span class="line"><span class="keyword">enable_testing</span>()</span><br><span class="line"></span><br><span class="line"><span class="keyword">add_executable</span>(</span><br><span class="line">  stateTest</span><br><span class="line">  stateTest.cpp</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="keyword">target_include_directories</span>(stateTest PRIVATE ../)</span><br><span class="line"></span><br><span class="line"><span class="keyword">target_link_libraries</span>(</span><br><span class="line">  stateTest</span><br><span class="line">  context</span><br><span class="line">  GTest::gtest_main</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="keyword">include</span>(GoogleTest)</span><br><span class="line">gtest_discover_tests(stateTest)</span><br></pre></td></tr></table></figure><p>然后修改位于<code>miniSMTPServer/context</code>目录下的<code>CMakeLists.txt</code>文件，添加如下的代码：</p><figure class="highlight cmake"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># miniSMTPServer/context/CMakeLists.txt</span></span><br><span class="line">...</span><br><span class="line"><span class="keyword">add_subdirectory</span>(./tests)</span><br></pre></td></tr></table></figure><p>然后我们就可以开始写单元测试了，我们目前的单元测试主要关心<code>State::isCorrectParameters</code>方法。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// miniSMTPServer/context/tests/stateTest.cpp</span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;state.hpp&quot;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;gtest/gtest.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;memory&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;unordered_map&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;vector&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">static</span> std::unordered_map&lt;std::string, std::string&gt; codeToMessages&#123;</span><br><span class="line">    &#123;<span class="string">&quot;220&quot;</span>, <span class="string">&quot;Service ready&quot;</span>&#125;,</span><br><span class="line">    &#123;<span class="string">&quot;221&quot;</span>, <span class="string">&quot;Service closing transmission channel&quot;</span>&#125;,</span><br><span class="line">    &#123;<span class="string">&quot;250&quot;</span>, <span class="string">&quot;Requested mail action okay, completed&quot;</span>&#125;,</span><br><span class="line">    &#123;<span class="string">&quot;354&quot;</span>, <span class="string">&quot;Start mail input end &lt;CRLF&gt;.&lt;CRLF&gt;&quot;</span>&#125;,</span><br><span class="line">    &#123;<span class="string">&quot;500&quot;</span>, <span class="string">&quot;Syntax error, command unrecognized&quot;</span>&#125;,</span><br><span class="line">    &#123;<span class="string">&quot;501&quot;</span>, <span class="string">&quot;Syntax error in parameters or arguments&quot;</span>&#125;,</span><br><span class="line">    &#123;<span class="string">&quot;503&quot;</span>, <span class="string">&quot;Bad sequence of commands&quot;</span>&#125;,</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="built_in">TEST</span>(State, isCorrectParametersNOOP) &#123;</span><br><span class="line">  std::vector&lt;std::vector&lt;std::string&gt;&gt; tests&#123;</span><br><span class="line">      &#123;<span class="string">&quot;NOOP&quot;</span>, <span class="string">&quot;param1&quot;</span>&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;NOOP&quot;</span>, <span class="string">&quot;param1&quot;</span>, <span class="string">&quot;param2&quot;</span>&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;NOOP&quot;</span>, <span class="string">&quot;12&quot;</span>&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;NOOP&quot;</span>, <span class="string">&quot;3&quot;</span>&#125;,</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">auto</span> state = std::<span class="built_in">make_unique</span>&lt;IdleState&gt;();</span><br><span class="line"></span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">auto</span> &amp;&amp;test : tests) &#123;</span><br><span class="line">    <span class="keyword">auto</span> result = state-&gt;<span class="built_in">isCorrectParameters</span>(test);</span><br><span class="line">    <span class="built_in">ASSERT_TRUE</span>(result.<span class="built_in">has_value</span>());</span><br><span class="line">    <span class="built_in">ASSERT_EQ</span>(result.<span class="built_in">value</span>(), <span class="string">&quot;501 &quot;</span> + codeToMessages[<span class="string">&quot;501&quot;</span>]);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  std::vector&lt;std::string&gt; successful&#123;<span class="string">&quot;NOOP&quot;</span>&#125;;</span><br><span class="line"></span><br><span class="line">  <span class="built_in">ASSERT_FALSE</span>(state-&gt;<span class="built_in">isCorrectParameters</span>(successful).<span class="built_in">has_value</span>());</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="built_in">TEST</span>(State, isCorrectParametersQUIT) &#123;</span><br><span class="line">  std::vector&lt;std::vector&lt;std::string&gt;&gt; tests&#123;</span><br><span class="line">      &#123;<span class="string">&quot;QUIT&quot;</span>, <span class="string">&quot;NOOP&quot;</span>&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;QUIT&quot;</span>, <span class="string">&quot;NOOP&quot;</span>, <span class="string">&quot;EHLO&quot;</span>&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;QUIT&quot;</span>, <span class="string">&quot;12&quot;</span>, <span class="string">&quot;13&quot;</span>, <span class="string">&quot;14&quot;</span>, <span class="string">&quot;15&quot;</span>&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;QUIT&quot;</span>, <span class="string">&quot;3&quot;</span>, <span class="string">&quot;4&quot;</span>, <span class="string">&quot;11111&quot;</span>, <span class="string">&quot;22&quot;</span>&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;QUIT&quot;</span>, <span class="string">&quot;MAIL&quot;</span>, <span class="string">&quot;RCPT&quot;</span>, <span class="string">&quot;11111&quot;</span>, <span class="string">&quot;22&quot;</span>&#125;,</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">auto</span> state = std::<span class="built_in">make_unique</span>&lt;IdleState&gt;();</span><br><span class="line"></span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">auto</span> &amp;&amp;test : tests) &#123;</span><br><span class="line">    <span class="keyword">auto</span> result = state-&gt;<span class="built_in">isCorrectParameters</span>(test);</span><br><span class="line">    <span class="built_in">ASSERT_TRUE</span>(result.<span class="built_in">has_value</span>());</span><br><span class="line">    <span class="built_in">ASSERT_EQ</span>(result.<span class="built_in">value</span>(), <span class="string">&quot;501 &quot;</span> + codeToMessages[<span class="string">&quot;501&quot;</span>]);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  std::vector&lt;std::string&gt; successful&#123;<span class="string">&quot;QUIT&quot;</span>&#125;;</span><br><span class="line"></span><br><span class="line">  <span class="built_in">ASSERT_FALSE</span>(state-&gt;<span class="built_in">isCorrectParameters</span>(successful).<span class="built_in">has_value</span>());</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="built_in">TEST</span>(State, isCorrectParametersRSET) &#123;</span><br><span class="line">  std::vector&lt;std::vector&lt;std::string&gt;&gt; tests&#123;</span><br><span class="line">      &#123;<span class="string">&quot;RSET&quot;</span>, <span class="string">&quot;NOOP&quot;</span>&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;RSET&quot;</span>, <span class="string">&quot;NOOP&quot;</span>, <span class="string">&quot;EHLO&quot;</span>&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;RSET&quot;</span>, <span class="string">&quot;12&quot;</span>, <span class="string">&quot;13&quot;</span>, <span class="string">&quot;14&quot;</span>, <span class="string">&quot;15&quot;</span>&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;RSET&quot;</span>, <span class="string">&quot;3&quot;</span>, <span class="string">&quot;4&quot;</span>, <span class="string">&quot;11111&quot;</span>, <span class="string">&quot;22&quot;</span>&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;RSET&quot;</span>, <span class="string">&quot;MAIL&quot;</span>, <span class="string">&quot;RCPT&quot;</span>, <span class="string">&quot;11111&quot;</span>, <span class="string">&quot;22&quot;</span>&#125;,</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">auto</span> state = std::<span class="built_in">make_unique</span>&lt;IdleState&gt;();</span><br><span class="line"></span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">auto</span> &amp;&amp;test : tests) &#123;</span><br><span class="line">    <span class="keyword">auto</span> result = state-&gt;<span class="built_in">isCorrectParameters</span>(test);</span><br><span class="line">    <span class="built_in">ASSERT_TRUE</span>(result.<span class="built_in">has_value</span>());</span><br><span class="line">    <span class="built_in">ASSERT_EQ</span>(result.<span class="built_in">value</span>(), <span class="string">&quot;501 &quot;</span> + codeToMessages[<span class="string">&quot;501&quot;</span>]);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  std::vector&lt;std::string&gt; successful&#123;<span class="string">&quot;RSET&quot;</span>&#125;;</span><br><span class="line"></span><br><span class="line">  <span class="built_in">ASSERT_FALSE</span>(state-&gt;<span class="built_in">isCorrectParameters</span>(successful).<span class="built_in">has_value</span>());</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="built_in">TEST</span>(State, isCorrectParametersEHLO) &#123;</span><br><span class="line">  std::vector&lt;std::vector&lt;std::string&gt;&gt; tests&#123;</span><br><span class="line">      &#123;<span class="string">&quot;EHLO&quot;</span>&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;EHLO&quot;</span>, <span class="string">&quot;127.0.0.2&quot;</span>&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;EHLO&quot;</span>, <span class="string">&quot;127.0.1.1&quot;</span>&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;EHLO&quot;</span>, <span class="string">&quot;NOOP&quot;</span>, <span class="string">&quot;EHLO&quot;</span>&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;EHLO&quot;</span>, <span class="string">&quot;12&quot;</span>, <span class="string">&quot;13&quot;</span>, <span class="string">&quot;14&quot;</span>, <span class="string">&quot;15&quot;</span>&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;EHLO&quot;</span>, <span class="string">&quot;3&quot;</span>, <span class="string">&quot;4&quot;</span>, <span class="string">&quot;11111&quot;</span>, <span class="string">&quot;22&quot;</span>&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;EHLO&quot;</span>, <span class="string">&quot;MAIL&quot;</span>, <span class="string">&quot;RCPT&quot;</span>, <span class="string">&quot;11111&quot;</span>, <span class="string">&quot;22&quot;</span>&#125;,</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">auto</span> state = std::<span class="built_in">make_unique</span>&lt;IdleState&gt;();</span><br><span class="line"></span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">auto</span> &amp;&amp;test : tests) &#123;</span><br><span class="line">    <span class="keyword">auto</span> result = state-&gt;<span class="built_in">isCorrectParameters</span>(test);</span><br><span class="line">    <span class="built_in">ASSERT_TRUE</span>(result.<span class="built_in">has_value</span>());</span><br><span class="line">    <span class="built_in">ASSERT_EQ</span>(result.<span class="built_in">value</span>(), <span class="string">&quot;501 &quot;</span> + codeToMessages[<span class="string">&quot;501&quot;</span>]);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  std::vector&lt;std::string&gt; successful&#123;<span class="string">&quot;EHLO&quot;</span>, <span class="string">&quot;127.0.0.1&quot;</span>&#125;;</span><br><span class="line"></span><br><span class="line">  <span class="built_in">ASSERT_FALSE</span>(state-&gt;<span class="built_in">isCorrectParameters</span>(successful).<span class="built_in">has_value</span>());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上述代码提供的单元测试十分简单。不需要进行讲解，从后面开始，我们会以测试驱动来撰写代码，也就是我们先写测试文件再写相应的功能，当我们的代码能够通过测试后也就证明我们的代码是正确的。</p><p>现在我们要开始实现<code>IdleState</code>类中的方法。首先我们应该思考<code>IdleState</code>中允许存在什么命令，由上面的讲述可知，<code>IdleState</code>允许的命令与<code>State</code>中的<code>allowed</code>一致，所以对其构造函数我们可以不做任何的处理，那么关键的地方就在于<code>transitive</code>方法的实现。我们首先实现如下的单元测试的代码：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// miniSMTPServer/context/tests/stateTest.cpp</span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;utility&gt;</span></span></span><br><span class="line">...</span><br><span class="line"><span class="built_in">TEST</span>(State, IdleStateTransitive) &#123;</span><br><span class="line">  std::vector&lt;std::vector&lt;std::string&gt;&gt; tests&#123;</span><br><span class="line">      &#123;<span class="string">&quot;RSET&quot;</span>&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;NOOP&quot;</span>&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;QUIT&quot;</span>&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;EHLO&quot;</span>, <span class="string">&quot;127.0.0.1&quot;</span>&#125;,</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  std::vector&lt;std::pair&lt;std::string, std::unique_ptr&lt;State&gt; *&gt;&gt; expects&#123;</span><br><span class="line">      &#123;<span class="string">&quot;250 &quot;</span> + codeToMessages[<span class="string">&quot;250&quot;</span>], &amp;States::idleState&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;250 &quot;</span> + codeToMessages[<span class="string">&quot;250&quot;</span>], &amp;States::idleState&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;221 &quot;</span> + codeToMessages[<span class="string">&quot;221&quot;</span>], &amp;States::idleState&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;250 &quot;</span> + codeToMessages[<span class="string">&quot;250&quot;</span>], &amp;States::ehloState&#125;,</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; tests.<span class="built_in">size</span>(); ++i) &#123;</span><br><span class="line">    <span class="keyword">auto</span> state = std::<span class="built_in">make_unique</span>&lt;IdleState&gt;();</span><br><span class="line">    std::unique_ptr&lt;State&gt; *current = &amp;States::idleState;</span><br><span class="line">    std::string result = state-&gt;<span class="built_in">transitive</span>(tests[<span class="number">0</span>], current);</span><br><span class="line">    <span class="built_in">EXPECT_EQ</span>(result, expects[i].first);</span><br><span class="line">    <span class="built_in">EXPECT_EQ</span>(current, expects[i].second);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>测试的代码很简单，你可以编译然后使用<code>ctest --output-on-failure</code>可以发现很多错误，那么就让我们来更新<code>IdleState::transitive</code>方法，其更新的很简单，首先使用基类的方法<code>transitiveHelper</code>。然后只需要处理<code>EHLO</code>命令即可，因为<code>QUIT</code>和<code>NOOP</code>命令已经在<code>transitiveHelper</code>方法实现了，而<code>RSET</code>方法对于<code>idleState</code>没有任何作用。同时我也添加了许多其他测试。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// miniSMTPServer/context/tests/stateTest.cpp</span></span><br><span class="line"><span class="built_in">TEST</span>(State, IdleStateTransitive) &#123;</span><br><span class="line">  std::vector&lt;std::vector&lt;std::string&gt;&gt; tests&#123;</span><br><span class="line">      &#123;<span class="string">&quot;RSET&quot;</span>&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;NOOP&quot;</span>&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;QUIT&quot;</span>&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;EHLO&quot;</span>, <span class="string">&quot;127.0.0.1&quot;</span>&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;RSTE&quot;</span>&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;NOOQ&quot;</span>&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;RSET&quot;</span>, <span class="string">&quot;NOOP&quot;</span>&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;NOOP&quot;</span>, <span class="string">&quot;NOOP&quot;</span>&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;QUIT&quot;</span>, <span class="string">&quot;QUIT&quot;</span>&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;EHLO&quot;</span>, <span class="string">&quot;127.0.1.1&quot;</span>&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;DATA&quot;</span>&#125;,</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  std::vector&lt;std::pair&lt;std::string, std::unique_ptr&lt;State&gt; *&gt;&gt; expects&#123;</span><br><span class="line">      &#123;<span class="string">&quot;250 &quot;</span> + codeToMessages[<span class="string">&quot;250&quot;</span>], &amp;States::idleState&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;250 &quot;</span> + codeToMessages[<span class="string">&quot;250&quot;</span>], &amp;States::idleState&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;221 &quot;</span> + codeToMessages[<span class="string">&quot;221&quot;</span>], &amp;States::idleState&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;250 &quot;</span> + codeToMessages[<span class="string">&quot;250&quot;</span>], &amp;States::ehloState&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;500 &quot;</span> + codeToMessages[<span class="string">&quot;500&quot;</span>], &amp;States::idleState&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;500 &quot;</span> + codeToMessages[<span class="string">&quot;500&quot;</span>], &amp;States::idleState&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;501 &quot;</span> + codeToMessages[<span class="string">&quot;501&quot;</span>], &amp;States::idleState&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;501 &quot;</span> + codeToMessages[<span class="string">&quot;501&quot;</span>], &amp;States::idleState&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;501 &quot;</span> + codeToMessages[<span class="string">&quot;501&quot;</span>], &amp;States::idleState&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;501 &quot;</span> + codeToMessages[<span class="string">&quot;501&quot;</span>], &amp;States::idleState&#125;,</span><br><span class="line">      &#123;<span class="string">&quot;503 &quot;</span> + codeToMessages[<span class="string">&quot;503&quot;</span>], &amp;States::idleState&#125;,</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; tests.<span class="built_in">size</span>(); ++i) &#123;</span><br><span class="line">    <span class="keyword">auto</span> state = std::<span class="built_in">make_unique</span>&lt;IdleState&gt;();</span><br><span class="line">    std::unique_ptr&lt;State&gt; *current = &amp;States::idleState;</span><br><span class="line">    std::string result = state-&gt;<span class="built_in">transitive</span>(tests[i], current);</span><br><span class="line">    <span class="built_in">EXPECT_EQ</span>(result, expects[i].first);</span><br><span class="line">    <span class="built_in">EXPECT_EQ</span>(current, expects[i].second);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>再完成了测试代码的编写后，我们开始修改<code>IdleState::transitive</code>方法：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// miniSMTPServer/context/state.cpp</span></span><br><span class="line"><span class="function">std::optional&lt;std::string&gt; <span class="title">State::isCorrectParameters</span><span class="params">(std::vector&lt;std::string&gt; &amp;parameters)</span> </span>&#123;</span><br><span class="line">  std::string &amp;command = parameters[<span class="number">0</span>];</span><br><span class="line">  <span class="keyword">if</span> (command == <span class="string">&quot;NOOP&quot;</span> || command == <span class="string">&quot;QUIT&quot;</span> || command == <span class="string">&quot;RSET&quot;</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (parameters.<span class="built_in">size</span>() != <span class="number">1</span>) &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="string">&quot;501 &quot;</span> + codeToMessages[<span class="string">&quot;501&quot;</span>];</span><br><span class="line">    &#125;</span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (parameters[<span class="number">0</span>] == <span class="string">&quot;EHLO&quot;</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (parameters.<span class="built_in">size</span>() != <span class="number">2</span> || parameters[<span class="number">1</span>] != <span class="string">&quot;127.0.0.1&quot;</span>) &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="string">&quot;501 &quot;</span> + codeToMessages[<span class="string">&quot;501&quot;</span>];</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> std::<span class="literal">nullopt</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>然后我们编译代码运行测试:</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cd</span> build &amp;&amp; make -j12 &amp;&amp; ctest --output-on-failure</span><br></pre></td></tr></table></figure><p>其结果如下：</p><figure class="highlight txt"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">80% tests passed, 1 tests failed out of 5</span><br><span class="line"></span><br><span class="line">Total Test time (real) =   0.01 sec</span><br><span class="line"></span><br><span class="line">The following tests FAILED:</span><br><span class="line">          5 - State.IdleStateTransitive (Failed)</span><br><span class="line">Errors while running CTest</span><br></pre></td></tr></table></figure><p>产生这样的结果并不是我们代码的问题，而是由于<code>commands</code>变量的定义在教程二中没有完全定义，因此修改其定义如下：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// miniSMTPServer/context/state.cpp</span></span><br><span class="line"><span class="type">static</span> std::unordered_set&lt;std::string&gt; commands&#123;<span class="string">&quot;EHLO&quot;</span>, <span class="string">&quot;MAIL&quot;</span>, <span class="string">&quot;RCPT&quot;</span>, <span class="string">&quot;RSET&quot;</span>, <span class="string">&quot;NOOP&quot;</span>, <span class="string">&quot;QUIT&quot;</span>, <span class="string">&quot;DATA&quot;</span>, <span class="string">&quot;.&quot;</span>&#125;;</span><br></pre></td></tr></table></figure><p>然后我们重新编译代码运行测试:</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cd</span> build &amp;&amp; make -j12 &amp;&amp; ctest --output-on-failure</span><br></pre></td></tr></table></figure><p>你会发现我们能够通过所有的测试。</p><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>本节我们主要完善了我们的状态机并实现了第一个状态<code>IdleState</code>的操作，并通过测试驱动的方式写出了我们的代码。在后续的教程中我们将继续完善其他的状态操作。如果你产生了任何的问题，你可以运行如下的命令：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git checkout idle-state</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;ul&gt;
&lt;li&gt;&lt;a</summary>
        
      
    
    
    
    <category term="手把手教你写一个简单的SMTP服务器" scheme="https://luolibrary.com/categories/%E6%89%8B%E6%8A%8A%E6%89%8B%E6%95%99%E4%BD%A0%E5%86%99%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84SMTP%E6%9C%8D%E5%8A%A1%E5%99%A8/"/>
    
    
    <category term="技术" scheme="https://luolibrary.com/tags/%E6%8A%80%E6%9C%AF/"/>
    
    <category term="学习" scheme="https://luolibrary.com/tags/%E5%AD%A6%E4%B9%A0/"/>
    
    <category term="教程" scheme="https://luolibrary.com/tags/%E6%95%99%E7%A8%8B/"/>
    
  </entry>
  
  <entry>
    <title>手把手教你写一个简单的SMTP服务器（二）</title>
    <link href="https://luolibrary.com/2023/03/14/%E6%89%8B%E6%8A%8A%E6%89%8B%E6%95%99%E4%BD%A0%E5%86%99%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84SMTP%E6%9C%8D%E5%8A%A1%E5%99%A8%EF%BC%88%E4%BA%8C%EF%BC%89/"/>
    <id>https://luolibrary.com/2023/03/14/%E6%89%8B%E6%8A%8A%E6%89%8B%E6%95%99%E4%BD%A0%E5%86%99%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84SMTP%E6%9C%8D%E5%8A%A1%E5%99%A8%EF%BC%88%E4%BA%8C%EF%BC%89/</id>
    <published>2023-03-14T11:41:15.000Z</published>
    <updated>2023-03-14T11:43:44.733Z</updated>
    
    <content type="html"><![CDATA[<ul><li><a href="https://luolibrary.com/2023/03/12/%E6%89%8B%E6%8A%8A%E6%89%8B%E6%95%99%E4%BD%A0%E5%86%99%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84SMTP%E6%9C%8D%E5%8A%A1%E5%99%A8%EF%BC%88%E4%B8%80%EF%BC%89/">手把手教你写一个简单的SMTP服务器（一）</a></li><li><a href="https://luolibrary.com/2023/03/14/%E6%89%8B%E6%8A%8A%E6%89%8B%E6%95%99%E4%BD%A0%E5%86%99%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84SMTP%E6%9C%8D%E5%8A%A1%E5%99%A8%EF%BC%88%E4%BA%8C%EF%BC%89/">手把手教你写一个简单的SMTP服务器（二）</a></li><li><a href="https://luolibrary.com/2023/03/14/%E6%89%8B%E6%8A%8A%E6%89%8B%E6%95%99%E4%BD%A0%E5%86%99%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84SMTP%E6%9C%8D%E5%8A%A1%E5%99%A8%EF%BC%88%E4%B8%89%EF%BC%89/">手把手教你写一个简单的SMTP服务器（三）</a></li><li><a href="https://luolibrary.com/2023/03/14/%E6%89%8B%E6%8A%8A%E6%89%8B%E6%95%99%E4%BD%A0%E5%86%99%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84SMTP%E6%9C%8D%E5%8A%A1%E5%99%A8%EF%BC%88%E5%9B%9B%EF%BC%89/">手把手教你写一个简单的SMTP服务器（四）</a></li><li><a href="https://luolibrary.com/2023/03/14/%E6%89%8B%E6%8A%8A%E6%89%8B%E6%95%99%E4%BD%A0%E5%86%99%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84SMTP%E6%9C%8D%E5%8A%A1%E5%99%A8%EF%BC%88%E4%BA%94%EF%BC%89/">手把手教你写一个简单的SMTP服务器（五）</a></li></ul><hr><p>在教程一中，我们已经完成了一个十分简单的TCP服务器。如果你跳过了教程一的某些部分，你可以执行以下的命令获取到在此阶段的代码：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git <span class="built_in">clone</span> https://github.com/shejialuo/miniSMTPServer</span><br><span class="line"><span class="built_in">cd</span> miniSMTPServer &amp;&amp; git checkout tcp-server</span><br></pre></td></tr></table></figure><p>我们终于来到了有意思的部分了，我们要开始实现一个简单的SMTP协议。然而，RFC标准定义的SMTP协议过于复杂了，故本教程拟提取出一些核心的东西将其简化。</p><h2 id="顺序定义"><a href="#顺序定义" class="headerlink" title="顺序定义"></a>顺序定义</h2><h3 id="会话启动及客户端初始化"><a href="#会话启动及客户端初始化" class="headerlink" title="会话启动及客户端初始化"></a>会话启动及客户端初始化</h3><p>当客户端连接到服务器时，服务器会发送<code>220</code>代码加上其版本等信息。而客户端会发送<code>EHLO</code>命令，加上客户端的IP地址等自定义信息（在本教程中默认只发送IP地址，其值只能为127.0.0.1）。其过程如下所示：</p><figure class="highlight txt"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">S: 200 Service ready</span><br><span class="line">C: EHLO 127.0.0.1</span><br></pre></td></tr></table></figure><h3 id="邮件事务"><a href="#邮件事务" class="headerlink" title="邮件事务"></a>邮件事务</h3><p>邮件的事务总共有三步：事务的开始是通过<code>MAIL</code>命令开始的，<code>MAIL</code>命令需要指定发送人的邮箱。然后是一系列的<code>RCPT</code>命令指定接收人的邮箱。最后是<code>DATA</code>命令指定邮件的内容有哪些，最后以单独的<code>.</code>命令表示内容的结束。</p><h3 id="会话结束"><a href="#会话结束" class="headerlink" title="会话结束"></a>会话结束</h3><p>当客户端向服务器发送<code>QUIT</code>命令时，服务器发送回复就意味着本次SMTP会话的终结。</p><h3 id="命令顺序限制"><a href="#命令顺序限制" class="headerlink" title="命令顺序限制"></a>命令顺序限制</h3><ul><li>必须使用<code>EHLO</code>命令开启一个新的会话。</li><li><code>EHLO</code>命令可以在任何一个状态使用，其的使用将会清楚SMTP服务器所有的状态，其存储的发送人、收件人和邮件内容等缓存必须全部清空，该效果等同于客户端发送<code>RSET</code>命令一样。</li><li><code>RSET</code>命令可以在任意阶段使用，哪怕没有收到客户端发送的<code>EHLO</code>命令。</li><li><code>MAIL</code>命令开启一个新的邮件事务，其后面只能按照顺序依次接多个<code>RCPT</code>命令，然后是多个<code>DATA</code>命令。客户端可以通过<code>RSET</code>和<code>EHLO</code>命令重置本次邮件事务。当开启了一个新的邮件事务，<code>MAIL</code>命令就再也无法使用。在这个过程中，如果命令的参数不正确，服务器端必须发送<code>501</code>代码，且服务器维持的状态不能有任何的改变。如果命令顺序不对，服务器端就发送<code>503</code>代码。</li><li>会话里面最后的一个命令必须是<code>QUIT</code>。</li></ul><h2 id="请求和响应"><a href="#请求和响应" class="headerlink" title="请求和响应"></a>请求和响应</h2><p>为了简单起见，我们首先定义如下的全局规则：客户端的每次请求必须恰好对应一次服务器的响应。除第一次的客户端请求外，客户端都必须在收到服务器对其上一次请求的响应后才能进行下一次的请求。</p><h3 id="请求"><a href="#请求" class="headerlink" title="请求"></a>请求</h3><p>我们只支持如下的6个请求，下面的请求有些需要使用参数，有些不需要使用参数，我们会在实现具体的命令功能时再进行详细的介绍，现在我们先从整体的角度出发：</p><ul><li><code>EHLO</code>：客户端请求开始会话。</li><li><code>MAIL</code>：客户端开始邮件事务。</li><li><code>RCPT</code>：指定收件人。</li><li><code>RSET</code>：重置会话。</li><li><code>NOOP</code>：空操作。</li><li><code>QUIT</code>：结束会话。</li></ul><h3 id="返回值"><a href="#返回值" class="headerlink" title="返回值"></a>返回值</h3><p>对于返回值，我们给出如下的定义：</p><ul><li><code>220</code>: Service ready</li><li><code>221</code>: Service closing transmission channel</li><li><code>250</code>: Requested mail action okay, completed</li><li><code>354</code>: Start mail input; end with &lt;CRLF&gt;.&lt;CRLF&gt;</li><li><code>500</code>: Syntax error, command unrecognized</li><li><code>501</code>: Syntax error in parameters or arguments</li><li><code>503</code>: Bad sequence of commands</li></ul><h2 id="状态机"><a href="#状态机" class="headerlink" title="状态机"></a>状态机</h2><p>经过上述对协议规则的详细解释，我们可以作出下图所示的状态转换图。当客户端输入的命令与该状态转换图对应不上时，服务器端需要输出<code>503</code>错误代码。</p><pre class="mermaid">stateDiagram-v2    [*] --> idle: server start    idle --> ehlo: EHLO    idle --> idle: RSET    ehlo --> mail: MAIL    mail --> rcpt: RCPT    rcpt --> rcpt: RCPT    rcpt --> dataStart: DATA    dataStart --> dataStart : except .    dataStart --> dataDone: .    ehlo --> ehlo: RSET, EHLO    mail --> ehlo: RSET, EHLO    rcpt --> ehlo: RSET, EHLO    dataStart --> ehlo: RSET, EHLO    dataDone --> ehlo: RSET, EHLO    dataDone --> mail: MAIL    idle --> [*]: QUIT    ehlo --> [*]: QUIT    mail --> [*]: QUIT    rcpt --> [*]: QUIT    dataStart --> [*]: QUIT    dataDone --> [*]: QUIT</pre><p>由上图可知，我们根据规则设定了六个状态：<code>idle</code>, <code>ehlo</code>, <code>mail</code>, <code>rcpt</code>, <code>dataStart</code>和<code>dataDone</code>。在上述的状态图中，我们忽略了唯一一个命令<code>NOOP</code>，由于<code>NOOP</code>没有操作，就没有在图里面展示出来了。</p><h2 id="代码实现状态机"><a href="#代码实现状态机" class="headerlink" title="代码实现状态机"></a>代码实现状态机</h2><p>我们该如何通过代码实现上述的状态机了，有一个最简单的方式，就是定义一个<code>enum class State</code>包含状态机的6种状态，然后服务器端根据接收到的命令和目前现在维护的状态，判断是否应该前往下一步，其示例代码如下所示：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">enum class</span> <span class="title class_">State</span>&#123;</span><br><span class="line">  ...</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (state == State::idle &amp;&amp; command == <span class="string">&quot;ehlo&quot;</span>) &#123;</span><br><span class="line">  ...</span><br><span class="line">&#125; <span class="keyword">else</span> <span class="keyword">if</span> (state == State::rcpt &amp;&amp; command == <span class="string">&quot;quit&quot;</span>) &#123;</span><br><span class="line">  ...</span><br><span class="line">&#125; <span class="keyword">else</span> <span class="keyword">if</span> (...) &#123;</span><br><span class="line">  ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这样的代码是实现状态机最简单的方式，但是却是最不好的方式，原因如下：</p><ol><li>缺乏扩展性。如果我们的状态机新增了一个状态，我们需要不断地增加分支。</li><li>不断增加的分支会让代码的可读性下降。</li></ol><p>本教程拟采用c++运行时绑定机制（多态）来实现状态机，其基本的思路在于创建一个<code>Context</code>类，该<code>Context</code>类包含一个指向<code>State</code>类的指针，每当服务器收到了客户端发送的请求后，<code>Context</code>类将会执行<code>State</code>类的某个方法，根据该方法判断能否移动状态，进而处理客户端发送的请求。</p><p>因此我们需要定义虚基类<code>State</code>，此处我们必须尽最大可能抽象出基类应该所具备的能力。尽可能让代码变得干净一些。</p><ul><li><p>首先，我们在<code>miniSMTPServer/context/state.hpp</code>定义两个静态变量，其中一个为<code>commands</code>，表示所有能够接收的命令。其次是<code>codeToMessages</code>表明映射关系。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// miniSMTPServer/context/state.cpp</span></span><br><span class="line"><span class="type">static</span> std::unordered_set&lt;std::string&gt; commands&#123;<span class="string">&quot;EHLO&quot;</span>, <span class="string">&quot;MAIL&quot;</span>, <span class="string">&quot;RCPT&quot;</span>, <span class="string">&quot;RSET&quot;</span>, <span class="string">&quot;NOOP&quot;</span>, <span class="string">&quot;QUIT&quot;</span>, <span class="string">&quot;DATA&quot;</span>, <span class="string">&quot;.&quot;</span>&#125;;</span><br><span class="line"></span><br><span class="line"><span class="type">static</span> std::unordered_map&lt;std::string, std::string&gt; codeToMessages&#123;</span><br><span class="line">    &#123;<span class="string">&quot;220&quot;</span>, <span class="string">&quot;Service ready&quot;</span>&#125;,</span><br><span class="line">    &#123;<span class="string">&quot;221&quot;</span>, <span class="string">&quot;Service closing transmission channel&quot;</span>&#125;,</span><br><span class="line">    &#123;<span class="string">&quot;250&quot;</span>, <span class="string">&quot;Requested mail action okay, completed&quot;</span>&#125;,</span><br><span class="line">    &#123;<span class="string">&quot;354&quot;</span>, <span class="string">&quot;Start mail input end &lt;CRLF&gt;.&lt;CRLF&gt;&quot;</span>&#125;,</span><br><span class="line">    &#123;<span class="string">&quot;500&quot;</span>, <span class="string">&quot;Syntax error, command unrecognized&quot;</span>&#125;,</span><br><span class="line">    &#123;<span class="string">&quot;501&quot;</span>, <span class="string">&quot;Syntax error in parameters or arguments&quot;</span>&#125;,</span><br><span class="line">    &#123;<span class="string">&quot;503&quot;</span>, <span class="string">&quot;Bad sequence of commands&quot;</span>&#125;,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure></li><li><p>其次，对于所有的类都应该根据其自身现有的状态去判断是否应该接收当前的客户端命令。故每一个状态需要维护一个<code>allowed</code>的变量，存储当前状态允许接收的命令。显然，<code>allowed</code>变量需要具备快速查找能力，故使用<code>unordered_set</code>容器。同时，在<code>State</code>类中我们应该定义一个函数<code>canTransitive</code>用于实现该功能。在虚基类中，我们应该添加<code>RSET</code>，<code>EHLO</code>，<code>NOOP</code>，<code>QUIT</code>命令。无论服务器端处于什么样的状态，其都应该支持这些命令。对于客户端的命令，我们将其抽象为<code>std::vector&lt;std::string&gt;</code>。同时我们将该操作封装到<code>checkCommand</code>函数中。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// miniSMTPServer/context/state.hpp</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">State</span> &#123;</span><br><span class="line"><span class="keyword">protected</span>:</span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">  * @brief allowed operations for each state, it should be used in</span></span><br><span class="line"><span class="comment">  * derive class, so make it protected.</span></span><br><span class="line"><span class="comment">  *</span></span><br><span class="line"><span class="comment">  */</span></span><br><span class="line">  std::unordered_set&lt;std::string&gt; allowed;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">  * @brief Construct a new State object. It will initialize the `allowed`</span></span><br><span class="line"><span class="comment">  * for adding four commands.</span></span><br><span class="line"><span class="comment">  *</span></span><br><span class="line"><span class="comment">  */</span></span><br><span class="line">  <span class="built_in">State</span>();</span><br><span class="line"></span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">  * @brief can transitive to another state</span></span><br><span class="line"><span class="comment">  * @details check whether parameters[0] is in the allowed</span></span><br><span class="line"><span class="comment">  *</span></span><br><span class="line"><span class="comment">  * @param parameters the command and its parameters</span></span><br><span class="line"><span class="comment">  * @return true the command is OK for transition</span></span><br><span class="line"><span class="comment">  * @return false the command is not OK for transition</span></span><br><span class="line"><span class="comment">  */</span></span><br><span class="line">  <span class="function"><span class="type">bool</span> <span class="title">canTransitive</span><span class="params">(std::vector&lt;std::string&gt; &amp;parameters)</span></span>;</span><br><span class="line"></span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">  * @brief check the command whether it is in the command</span></span><br><span class="line"><span class="comment">  * and call `canTransitive`.</span></span><br><span class="line"><span class="comment">  *</span></span><br><span class="line"><span class="comment">  * @param parameters</span></span><br><span class="line"><span class="comment">  * @return std::optional&lt;std::string&gt;</span></span><br><span class="line"><span class="comment">  */</span></span><br><span class="line">  <span class="function">std::optional&lt;std::string&gt; <span class="title">checkCommand</span><span class="params">(std::vector&lt;std::string&gt; &amp;parameters)</span></span>;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// miniSMTPServer/context/state.cpp</span></span><br><span class="line">State::<span class="built_in">State</span>() &#123;</span><br><span class="line">  allowed.<span class="built_in">insert</span>(<span class="string">&quot;RSET&quot;</span>);</span><br><span class="line">  allowed.<span class="built_in">insert</span>(<span class="string">&quot;EHLO&quot;</span>);</span><br><span class="line">  allowed.<span class="built_in">insert</span>(<span class="string">&quot;QUIT&quot;</span>);</span><br><span class="line">  allowed.<span class="built_in">insert</span>(<span class="string">&quot;NOOP&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">bool</span> <span class="title">State::canTransitive</span><span class="params">(std::vector&lt;std::string&gt; &amp;parameters)</span> </span>&#123; <span class="keyword">return</span> allowed.<span class="built_in">count</span>(parameters[<span class="number">0</span>]); &#125;</span><br><span class="line"></span><br><span class="line"><span class="function">std::optional&lt;std::string&gt; <span class="title">State::checkCommand</span><span class="params">(std::vector&lt;std::string&gt; &amp;parameters)</span> </span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (!commands.<span class="built_in">count</span>(parameters[<span class="number">0</span>])) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&quot;500 &quot;</span> + codeToMessages[<span class="string">&quot;500&quot;</span>];</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">if</span> (!<span class="built_in">canTransitive</span>(parameters)) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&quot;503 &quot;</span> + codeToMessages[<span class="string">&quot;503&quot;</span>];</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> std::<span class="literal">nullopt</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>当服务器端收到命令时，其处理的逻辑如下：首先需要判断客户端的命令是否存在于<code>commands</code>中，然后判断在该状态下是否能够接收该命令，即在上述定义的<code>checkCommand</code>函数的作用，最后需要判断参数是否正确。观察可发现，前三者在所有的状态中都是共通的，故我们需要直接在基类定义这些函数，我们已经定义好了<code>checkCommand</code>函数，因此需要再定义<code>isCorrectParameters</code>用于判断当前的参数是否正确。在目前，我们对此不做任何的处理，只让其返回空值。我们会慢慢地完善这个函数。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// miniSMTPServer/context/state.hpp</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">State</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">  ...</span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">  * @brief is the parameters are correct</span></span><br><span class="line"><span class="comment">  *</span></span><br><span class="line"><span class="comment">  * @param[in] parameters the command and its parameters</span></span><br><span class="line"><span class="comment">  * @return std::optional&lt;std::string&gt;</span></span><br><span class="line"><span class="comment">  */</span></span><br><span class="line">  <span class="function">std::optional&lt;std::string&gt; <span class="title">isCorrectParameters</span><span class="params">(std::vector&lt;std::string&gt; &amp;parameters)</span></span>;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// miniSMTPServer/context/state.cpp</span></span><br><span class="line"><span class="function">std::optional&lt;std::string&gt; <span class="title">State::isCorrectParameters</span><span class="params">(std::vector&lt;std::string&gt; &amp;parameters)</span> </span>&#123;</span><br><span class="line">  <span class="comment">// <span class="doctag">TODO:</span> finish later</span></span><br><span class="line">  <span class="keyword">return</span> std::<span class="literal">nullopt</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>最后我们定义纯虚函数 <code>transitive</code>，用于表征状态转换过程应该实现的操作：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// miniSMTPServer/context/state.hpp</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">State</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">  ...</span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">  * @brief transitive to another state and return the response string.</span></span><br><span class="line"><span class="comment">  *</span></span><br><span class="line"><span class="comment">  * @param[in] parameters the command and its parameters</span></span><br><span class="line"><span class="comment">  * @param[out] current the current state</span></span><br><span class="line"><span class="comment">  * @return std::string the results should be sent back to the client</span></span><br><span class="line"><span class="comment">  */</span></span><br><span class="line">  <span class="function"><span class="keyword">virtual</span> std::string <span class="title">transitive</span><span class="params">(std::vector&lt;std::string&gt; &amp;parameters, std::unique_ptr&lt;State&gt; *&amp;current)</span> </span>= <span class="number">0</span>;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure></li><li><p>似乎现在我们已经尽可能地抽象出来了基类，但是我们还可以继续进一步地抽象，注意到<code>QUIT</code>和<code>NOOP</code>命令，对于任何状态来说，这个两个命令都是有确定的效果的。故我们直接可以在基类就直接处理。但是注意<code>QUIT</code>命令需要进行状态的转换，而我们现在却仅定义了一个虚基类，所以这部分我们将留在后面处理。同时，我们定义<code>transitiveHelper</code>函数抽象出所有状态都需要处理的操作。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// miniSMTPServer/context/state.hpp</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">State</span>&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">  ...</span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">  * @brief QUIT command handle</span></span><br><span class="line"><span class="comment">  *</span></span><br><span class="line"><span class="comment">  * @param[in] current the current state</span></span><br><span class="line"><span class="comment">  * @return std::string the response code</span></span><br><span class="line"><span class="comment">  */</span></span><br><span class="line">  <span class="function">std::string <span class="title">transitiveFromQuit</span><span class="params">(std::unique_ptr&lt;State&gt; *&amp;current)</span></span>;</span><br><span class="line"></span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">  * @brief NOOP command handle</span></span><br><span class="line"><span class="comment">  *</span></span><br><span class="line"><span class="comment">  * @return std::string the response code</span></span><br><span class="line"><span class="comment">  */</span></span><br><span class="line">  <span class="function">std::string <span class="title">transitiveFromNoop</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">  * @brief The operations all the states need to do</span></span><br><span class="line"><span class="comment">  *</span></span><br><span class="line"><span class="comment">  */</span></span><br><span class="line">  <span class="function">std::optional&lt;std::string&gt; <span class="title">transitiveHelper</span><span class="params">(std::vector&lt;std::string&gt; &amp;parameters, std::unique_ptr&lt;State&gt; *&amp;current)</span></span>;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// miniSMTPServer/context/state.cpp</span></span><br><span class="line"><span class="function">std::string <span class="title">State::transitiveFromQuit</span><span class="params">(std::unique_ptr&lt;State&gt; *&amp;current)</span> </span>&#123;</span><br><span class="line">  <span class="comment">// <span class="doctag">TODO:</span> add transition later</span></span><br><span class="line">  <span class="keyword">return</span> <span class="string">&quot;221 &quot;</span> + codeToMessages[<span class="string">&quot;221&quot;</span>];</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function">std::string <span class="title">State::transitiveFromNoop</span><span class="params">()</span> </span>&#123; <span class="keyword">return</span> <span class="string">&quot;250 &quot;</span> + codeToMessages[<span class="string">&quot;250&quot;</span>]; &#125;</span><br><span class="line"></span><br><span class="line"><span class="function">std::optional&lt;std::string&gt; <span class="title">State::transitiveHelper</span><span class="params">(std::vector&lt;std::string&gt; &amp;parameters,</span></span></span><br><span class="line"><span class="params"><span class="function">                                                  std::unique_ptr&lt;State&gt; *&amp;current)</span> </span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (<span class="keyword">auto</span> result = <span class="built_in">checkCommand</span>(parameters); result.<span class="built_in">has_value</span>()) &#123;</span><br><span class="line">    <span class="keyword">return</span> result.<span class="built_in">value</span>();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (<span class="keyword">auto</span> result = <span class="built_in">isCorrectParameters</span>(parameters); result.<span class="built_in">has_value</span>()) &#123;</span><br><span class="line">    <span class="keyword">return</span> result.<span class="built_in">value</span>();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (parameters[<span class="number">0</span>] == <span class="string">&quot;QUIT&quot;</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">transitiveFromQuit</span>(current);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (parameters[<span class="number">0</span>] == <span class="string">&quot;NOOP&quot;</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">transitiveFromNoop</span>();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> std::<span class="literal">nullopt</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>别忘记了将析构函数设置为虚函数。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// miniSMTPServer/context/state.hpp</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">State</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">  <span class="keyword">virtual</span> ~<span class="built_in">State</span>() = <span class="keyword">default</span>;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure></li></ul><p>最后别忘记了在再<code>miniSMTPServer/context</code>目录下创建<code>CMakeLists.txt</code>，同时修改<code>miniSMTPServer</code>中的<code>CMakeLists.txt</code>：</p><figure class="highlight cmake"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># miniSMTPServer/context/CMakeLists.txt</span></span><br><span class="line"><span class="keyword">add_library</span>(context STATIC state.cpp)</span><br><span class="line"></span><br><span class="line"><span class="comment"># miniSMTPServer/CMakeLists.txt</span></span><br><span class="line">...</span><br><span class="line"><span class="keyword">add_subdirectory</span>(./context)</span><br></pre></td></tr></table></figure><p>然后你就可以再次执行命令进行编译，如果你遇到了任何编译的问题，你可以先使用<code>git add . &amp;&amp; git stash</code>命令暂存你现在的工作区然后执行<code>git checkout state-class</code>发现你的错误。</p><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>在我看来，我们已经完成了最难部分的工作。看似我们的代码很少，但是我们已经抽象出了状态图并构建了最基本的代码结构，剩下的工作我们只需要定义每一个状态即可，根据每个状态实现相应的函数即可。</p>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;ul&gt;
&lt;li&gt;&lt;a</summary>
        
      
    
    
    
    <category term="手把手教你写一个简单的SMTP服务器" scheme="https://luolibrary.com/categories/%E6%89%8B%E6%8A%8A%E6%89%8B%E6%95%99%E4%BD%A0%E5%86%99%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84SMTP%E6%9C%8D%E5%8A%A1%E5%99%A8/"/>
    
    
    <category term="技术" scheme="https://luolibrary.com/tags/%E6%8A%80%E6%9C%AF/"/>
    
    <category term="学习" scheme="https://luolibrary.com/tags/%E5%AD%A6%E4%B9%A0/"/>
    
    <category term="教程" scheme="https://luolibrary.com/tags/%E6%95%99%E7%A8%8B/"/>
    
  </entry>
  
  <entry>
    <title>手把手教你写一个简单的SMTP服务器（一）</title>
    <link href="https://luolibrary.com/2023/03/12/%E6%89%8B%E6%8A%8A%E6%89%8B%E6%95%99%E4%BD%A0%E5%86%99%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84SMTP%E6%9C%8D%E5%8A%A1%E5%99%A8%EF%BC%88%E4%B8%80%EF%BC%89/"/>
    <id>https://luolibrary.com/2023/03/12/%E6%89%8B%E6%8A%8A%E6%89%8B%E6%95%99%E4%BD%A0%E5%86%99%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84SMTP%E6%9C%8D%E5%8A%A1%E5%99%A8%EF%BC%88%E4%B8%80%EF%BC%89/</id>
    <published>2023-03-12T09:15:26.000Z</published>
    <updated>2023-03-14T11:43:54.086Z</updated>
    
    <content type="html"><![CDATA[<ul><li><a href="https://luolibrary.com/2023/03/12/%E6%89%8B%E6%8A%8A%E6%89%8B%E6%95%99%E4%BD%A0%E5%86%99%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84SMTP%E6%9C%8D%E5%8A%A1%E5%99%A8%EF%BC%88%E4%B8%80%EF%BC%89/">手把手教你写一个简单的SMTP服务器（一）</a></li><li><a href="https://luolibrary.com/2023/03/14/%E6%89%8B%E6%8A%8A%E6%89%8B%E6%95%99%E4%BD%A0%E5%86%99%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84SMTP%E6%9C%8D%E5%8A%A1%E5%99%A8%EF%BC%88%E4%BA%8C%EF%BC%89/">手把手教你写一个简单的SMTP服务器（二）</a></li><li><a href="https://luolibrary.com/2023/03/14/%E6%89%8B%E6%8A%8A%E6%89%8B%E6%95%99%E4%BD%A0%E5%86%99%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84SMTP%E6%9C%8D%E5%8A%A1%E5%99%A8%EF%BC%88%E4%B8%89%EF%BC%89/">手把手教你写一个简单的SMTP服务器（三）</a></li><li><a href="https://luolibrary.com/2023/03/14/%E6%89%8B%E6%8A%8A%E6%89%8B%E6%95%99%E4%BD%A0%E5%86%99%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84SMTP%E6%9C%8D%E5%8A%A1%E5%99%A8%EF%BC%88%E5%9B%9B%EF%BC%89/">手把手教你写一个简单的SMTP服务器（四）</a></li><li><a href="https://luolibrary.com/2023/03/14/%E6%89%8B%E6%8A%8A%E6%89%8B%E6%95%99%E4%BD%A0%E5%86%99%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84SMTP%E6%9C%8D%E5%8A%A1%E5%99%A8%EF%BC%88%E4%BA%94%EF%BC%89/">手把手教你写一个简单的SMTP服务器（五）</a></li></ul><hr><p>本系列的教程拟使用现代C++一步一步地实现一个简单的SMTP服务器，其主要目的在于国内目前计算机网络教学多是针对于协议本身的教学而忽略了实践。<a href="https://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol">Simple Mail Transfer Protocol (SMTP)</a>是应用层中较为简单的协议，十分适合用来进行实践。在这里，借用费曼的话：</p><blockquote><p>What I cannot create, I do not understand.</p></blockquote><p>然而，完整的SMTP协议也是相当繁琐的。故本教程会对完整的SMTP协议进行简化，并假设SMTP服务器运行在本地的环境，不需要扩展以支持通过Internet的TCP连接进而能用在现实世界中。服务器所有的通信都应包含在本地回环（local loopback）中。</p><p>在开始之前，你应该对SMTP协议有一些基本的了解，并对C++的现代特性有所了解，如果你对此没有把握，你可以参考互联网的资料进行学习。</p><h2 id="假设"><a href="#假设" class="headerlink" title="假设"></a>假设</h2><p>为了让教程聚焦于核心的部分，本教程做出了以下的前提条件：</p><ul><li>SMTP服务器仅只需要运行在本地，所有的通信都应包含在本地回环中。</li><li>对于每一次客户端的请求，SMTP服务器仅只产生一个响应。且只有客户端接收了服务器的相应，才能够再次发送请求。</li><li>SMTP服务器仅支持以下的命令：<ul><li><code>EHLO</code></li><li><code>MAIL</code></li><li><code>RCPT</code></li><li><code>DATA</code></li><li><code>RSET</code></li><li><code>NOOP</code></li><li><code>QUIT</code></li></ul></li></ul><h2 id="基础知识"><a href="#基础知识" class="headerlink" title="基础知识"></a>基础知识</h2><ul><li><a href="https://zh.wikipedia.org/wiki/%E6%89%A9%E5%85%85%E5%B7%B4%E7%A7%91%E6%96%AF%E8%8C%83%E5%BC%8F">扩充巴科斯范式（ABNF）</a>：本教程将采用ABNF来定义通信的协议。其思路很容易理解，读者可参考链接有一个初步的认识。</li><li>Socket网络编程基础：本教程拟在Linux环境下实现，读者需掌握Socket的基本使用。</li></ul><h2 id="初始代码"><a href="#初始代码" class="headerlink" title="初始代码"></a>初始代码</h2><p>不幸的是，<code>cpp</code>的编译构建系统远不如其他语言方便，为了减少读者的心智负担，本教程提供了最初的代码。读者可以通过以下的命令下载初始代码：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">git <span class="built_in">clone</span> https://github.com/shejialuo/miniSMTPServer</span><br><span class="line"></span><br><span class="line">git checkout start-code</span><br></pre></td></tr></table></figure><p>用你最喜欢的编辑器或者IDE打开，你应该看到如下的目录结构：</p><figure class="highlight txt"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">├── CMakeLists.txt</span><br><span class="line">├── miniSMTPServer</span><br><span class="line">│   ├── CMakeLists.txt</span><br><span class="line">│   └── miniSMTPServer.cpp</span><br><span class="line">└── README.md</span><br></pre></td></tr></table></figure><p>然后你可以执行如下的命令进行编译并生成可执行文件（在这个过程中，你需要使用代理下载第三方库）：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">mkdir</span> build &amp;&amp; <span class="built_in">cd</span> build &amp;&amp; cmake .. &amp;&amp; cmake --build .</span><br></pre></td></tr></table></figure><p>当你执行完后，你会在主目录下看到生成了一个名为<code>miniSMTP</code>的可执行文件。在终端运行<code>./miniSMTP</code>，产生如下的结果：</p><figure class="highlight txt"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Hello, This is a simple SMTP server</span><br></pre></td></tr></table></figure><h2 id="封装TCP-Server"><a href="#封装TCP-Server" class="headerlink" title="封装TCP Server"></a>封装TCP Server</h2><p>实际上，本教程可以直接通过<code>socket</code>编程进行实现，然而这样的实现过于地丑陋。故本教程拟对TCP Server进行一个简单地封装。其核心的内容的思路来源于<a href="https://github.com/CS144/sponge">CS144</a>，如果你对此部分不感兴趣或者感到困难的话，可以直接跳过本部分，毕竟此处不是教程的重点。</p><h3 id="封装系统调用"><a href="#封装系统调用" class="headerlink" title="封装系统调用"></a>封装系统调用</h3><p>首先我们在<code>miniSMTPServer</code>目录下创建一个名为<code>util</code>的目录，然后在<code>util</code>目录下创建<code>util.hpp</code>，<code>util.cpp</code>和<code>CMakeLists.txt</code>。 我们只需要实现一个功能，就是封装系统调用，并提供相应的错误检测机制。其实现逻辑十分简单，此处不赘述。</p><figure class="highlight cmake"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># miniSMTPServer/util/CMakeLists.txt</span></span><br><span class="line"><span class="keyword">add_library</span>(util STATIC util.cpp)</span><br></pre></td></tr></table></figure><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// miniSMTPServer/util/util.hpp</span></span><br><span class="line"><span class="meta">#<span class="keyword">pragma</span> once</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;cerrno&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;string&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;system_error&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * @brief std::system_error plus the name of what was being</span></span><br><span class="line"><span class="comment"> * attempted.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">tagged_error</span> :</span> public <span class="built_in">std</span>::system_error &#123;</span><br><span class="line">private:</span><br><span class="line">  <span class="built_in">std</span>::<span class="built_in">string</span> attempt_and_error;  <span class="comment">//!&lt; what was attempted, and what happened</span></span><br><span class="line"></span><br><span class="line">public:</span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * @brief Construct from a category, an attempt, and an error code.</span></span><br><span class="line"><span class="comment">   *</span></span><br><span class="line"><span class="comment">   * @param[in] category is the category of error</span></span><br><span class="line"><span class="comment">   * @param[in] attempt is what was supposed to happen</span></span><br><span class="line"><span class="comment">   * @param[in] error_code is the resulting error</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  tagged_error(<span class="type">const</span> <span class="built_in">std</span>::error_category &amp;category, <span class="type">const</span> <span class="built_in">std</span>::<span class="built_in">string</span> &amp;attempt, <span class="type">const</span> <span class="type">int</span> error_code)</span><br><span class="line">      : <span class="built_in">std</span>::system_error&#123;error_code, category&#125;, attempt_and_error&#123;attempt + <span class="string">&quot;: &quot;</span> + <span class="built_in">std</span>::system_error::what()&#125; &#123;&#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * @brief Returns a C string describing the error</span></span><br><span class="line"><span class="comment">   *</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  <span class="type">const</span> <span class="type">char</span> *<span class="title function_">what</span><span class="params">()</span> <span class="type">const</span> noexcept override &#123; <span class="keyword">return</span> attempt_and_error.c_str(); &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * @brief A tagged_error for syscalls</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">unix_error</span> :</span> public tagged_error &#123;</span><br><span class="line">public:</span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * @brief  Construct from a syscall name and the resulting errno</span></span><br><span class="line"><span class="comment">   *</span></span><br><span class="line"><span class="comment">   * @param[in] attempt is the name of the syscall attempted</span></span><br><span class="line"><span class="comment">   * @param[in] error is the [errno(3)](\ref man3::errno) that resulted</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  explicit <span class="title function_">unix_error</span><span class="params">(<span class="type">const</span> <span class="built_in">std</span>::<span class="built_in">string</span> &amp;attempt, <span class="type">const</span> <span class="type">int</span> error = errno)</span></span><br><span class="line">      : tagged_error&#123;<span class="built_in">std</span>::system_category(), attempt, error&#125; &#123;&#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * @brief Error-checking wrapper for most syscalls</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="type">int</span> <span class="title function_">SystemCall</span><span class="params">(<span class="type">const</span> <span class="type">char</span> *attempt, <span class="type">const</span> <span class="type">int</span> return_value, <span class="type">const</span> <span class="type">int</span> errno_mask = <span class="number">0</span>)</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * @brief Version of SystemCall that takes a C++ std::string</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="type">int</span> <span class="title function_">SystemCall</span><span class="params">(<span class="type">const</span> <span class="built_in">std</span>::<span class="built_in">string</span> &amp;attempt, <span class="type">const</span> <span class="type">int</span> return_value, <span class="type">const</span> <span class="type">int</span> errno_mask = <span class="number">0</span>)</span>;</span><br></pre></td></tr></table></figure><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// miniSMTPServer/util/util.cpp</span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;util.hpp&quot;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;string&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">SystemCall</span><span class="params">(<span class="type">const</span> <span class="type">char</span> *attempt, <span class="type">const</span> <span class="type">int</span> return_value, <span class="type">const</span> <span class="type">int</span> errno_mask)</span> &#123;</span><br><span class="line">  <span class="keyword">if</span> (return_value &gt;= <span class="number">0</span> || errno == errno_mask) &#123;</span><br><span class="line">    <span class="keyword">return</span> return_value;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  throw unix_error(attempt);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">SystemCall</span><span class="params">(<span class="type">const</span> <span class="built_in">std</span>::<span class="built_in">string</span> &amp;attempt, <span class="type">const</span> <span class="type">int</span> return_value, <span class="type">const</span> <span class="type">int</span> errno_mask)</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> SystemCall(attempt.c_str(), return_value, errno_mask);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>最后，需要在<code>miniSMTPServer</code>的<code>CMakeLists.txt</code>的末尾添加如下的语句：</p><figure class="highlight cmake"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># miniSMTPServer/CMakeLists.txt</span></span><br><span class="line">...</span><br><span class="line"><span class="keyword">add_subdirectory</span>(./util)</span><br></pre></td></tr></table></figure><p>你也可以直接使用如下的命令获取到本小节的代码：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git checkout system-call</span><br></pre></td></tr></table></figure><h3 id="封装TCPSocket"><a href="#封装TCPSocket" class="headerlink" title="封装TCPSocket"></a>封装TCPSocket</h3><p>如何封装一个<code>TCPSocket</code>呢？我们可以分为一下三步走：</p><ol><li>封装文件描述符类<code>FileDescriptor</code></li><li>封装地址类<code>Address</code>。</li><li>继承<code>FileDescriptor</code>得到<code>TCPSocket</code>。</li></ol><p>首先我们在<code>util</code>目录下创建<code>socket.hpp</code>和<code>socket.cpp</code>，并修改<code>CMakeLists.txt</code>将<code>socket.cpp</code>加入编译单元中。</p><figure class="highlight cmake"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># miniSMTPServer/util/CMakeLists.txt</span></span><br><span class="line"><span class="keyword">add_library</span>(util STATIC util.cpp socket.cpp)</span><br></pre></td></tr></table></figure><p>首先，我们需要构造一个<code>FileDescriptor</code>类，实际上对于该类的封装的思路可以很简单，也可以很复杂，如果我们不考虑其复制等生命周期问题，我们完全可以直接使用一个<code>int</code>类型的变量然后添加一些操作文件描述符的方法。在此处，我们采用更为复杂的设计模式：</p><ol><li><code>FileDescriptor</code>包含一个私有的<code>FDWrapper</code>，其包含了真正的文件描述符的信息。<code>FDWrapper</code>不能被复制也不能被移动。</li><li>由于<code>FDWrapper</code>不能被复制也不能被移动，故<code>FileDescriptor</code>包含变量<code>internal_fd</code>的智能指针，表示<code>FDWrapper</code>被引用的数量，通过<code>shared_ptr</code>表示<code>FDWrapper</code>的生命周期。</li></ol><p>于是，我们就可以给出如下的代码：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// miniSMTPServer/util/socket.hpp</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">FileDescriptor</span> &#123;</span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * @brief  A handle on a kernel file descriptor.</span></span><br><span class="line"><span class="comment">   *</span></span><br><span class="line"><span class="comment">   * @details FileDescriptor objects contain a std::shared_ptr to a FDWrapper.</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  <span class="keyword">class</span> <span class="title class_">FDWrapper</span> &#123;</span><br><span class="line">  <span class="keyword">public</span>:</span><br><span class="line">    <span class="type">int</span> fd;               <span class="comment">//!&lt; The file descriptor number returned by the kernel</span></span><br><span class="line">    <span class="type">bool</span> eof = <span class="literal">false</span>;     <span class="comment">//!&lt; Flag indicating whether FDWrapper::_fd is at EOF</span></span><br><span class="line">    <span class="type">bool</span> closed = <span class="literal">false</span>;  <span class="comment">//!&lt; Flag indicating whether FDWrapper::_fd has been closed</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * @brief Construct from a file descriptor number returned by the kernel</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">explicit</span> <span class="title">FDWrapper</span><span class="params">(<span class="type">const</span> <span class="type">int</span> fd)</span></span>;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * @brief Closes the file descriptor upon destruction</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    ~<span class="built_in">FDWrapper</span>();</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * @brief Calls [close(2)](\ref man2::close) on FDWrapper::fd</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">close</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">FDWrapper</span>(<span class="type">const</span> FDWrapper &amp;other) = <span class="keyword">delete</span>;</span><br><span class="line">    FDWrapper &amp;<span class="keyword">operator</span>=(<span class="type">const</span> FDWrapper &amp;other) = <span class="keyword">delete</span>;</span><br><span class="line">    <span class="built_in">FDWrapper</span>(FDWrapper &amp;&amp;other) = <span class="keyword">delete</span>;</span><br><span class="line">    FDWrapper &amp;<span class="keyword">operator</span>=(FDWrapper &amp;&amp;other) = <span class="keyword">delete</span>;</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  <span class="comment">//! A reference-counted handle to a shared FDWrapper</span></span><br><span class="line">  std::shared_ptr&lt;FDWrapper&gt; internal_fd;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// private constructor used to duplicate the FileDescriptor (increase the reference count)</span></span><br><span class="line">  <span class="function"><span class="keyword">explicit</span> <span class="title">FileDescriptor</span><span class="params">(std::shared_ptr&lt;FDWrapper&gt; other_shared_ptr)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">  <span class="comment">//! Construct from a file descriptor number returned by the kernel</span></span><br><span class="line">  <span class="function"><span class="keyword">explicit</span> <span class="title">FileDescriptor</span><span class="params">(<span class="type">const</span> <span class="type">int</span> fd)</span></span>;</span><br><span class="line"></span><br><span class="line">  <span class="comment">//! Free the std::shared_ptr; the FDWrapper destructor calls close() when the refcount goes to zero.</span></span><br><span class="line">  ~<span class="built_in">FileDescriptor</span>() = <span class="keyword">default</span>;</span><br><span class="line"></span><br><span class="line">  <span class="comment">//! Read up to `limit` bytes</span></span><br><span class="line">  <span class="function">std::string <span class="title">read</span><span class="params">(<span class="type">const</span> <span class="type">size_t</span> limit = std::numeric_limits&lt;<span class="type">size_t</span>&gt;::max())</span></span>;</span><br><span class="line"></span><br><span class="line">  <span class="comment">//! Read up to `limit` bytes into `str` (caller can allocate storage)</span></span><br><span class="line">  <span class="function"><span class="type">void</span> <span class="title">read</span><span class="params">(std::string &amp;str, <span class="type">const</span> <span class="type">size_t</span> limit = std::numeric_limits&lt;<span class="type">size_t</span>&gt;::max())</span></span>;</span><br><span class="line"></span><br><span class="line">  <span class="comment">//! Write a string, possibly blocking until all is written</span></span><br><span class="line">  <span class="function"><span class="type">size_t</span> <span class="title">write</span><span class="params">(<span class="type">const</span> <span class="type">char</span> *str)</span></span>;</span><br><span class="line"></span><br><span class="line">  <span class="comment">//! Write a string, possibly blocking until all is written</span></span><br><span class="line">  <span class="function"><span class="type">size_t</span> <span class="title">write</span><span class="params">(<span class="type">const</span> std::string &amp;str)</span></span>;</span><br><span class="line"></span><br><span class="line">  <span class="comment">//! Close the underlying file descriptor</span></span><br><span class="line">  <span class="function"><span class="type">void</span> <span class="title">close</span><span class="params">()</span> </span>&#123; internal_fd-&gt;<span class="built_in">close</span>(); &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">//! Copy a FileDescriptor explicitly, increasing the FDWrapper refcount</span></span><br><span class="line">  <span class="function">FileDescriptor <span class="title">duplicate</span><span class="params">()</span> <span class="type">const</span></span>;</span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="type">int</span> <span class="title">fd_num</span><span class="params">()</span> <span class="type">const</span> </span>&#123; <span class="keyword">return</span> internal_fd-&gt;fd; &#125;</span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="type">bool</span> <span class="title">eof</span><span class="params">()</span> <span class="type">const</span> </span>&#123; <span class="keyword">return</span> internal_fd-&gt;eof; &#125;</span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="type">bool</span> <span class="title">closed</span><span class="params">()</span> <span class="type">const</span> </span>&#123; <span class="keyword">return</span> internal_fd-&gt;closed; &#125;</span><br><span class="line"></span><br><span class="line">  <span class="built_in">FileDescriptor</span>(<span class="type">const</span> FileDescriptor &amp;other) = <span class="keyword">delete</span>;</span><br><span class="line">  FileDescriptor &amp;<span class="keyword">operator</span>=(<span class="type">const</span> FileDescriptor &amp;other) = <span class="keyword">delete</span>;</span><br><span class="line">  <span class="built_in">FileDescriptor</span>(FileDescriptor &amp;&amp;other) = <span class="keyword">default</span>;</span><br><span class="line">  FileDescriptor &amp;<span class="keyword">operator</span>=(FileDescriptor &amp;&amp;other) = <span class="keyword">default</span>;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// miniSMTPServer/util/socket.cpp</span></span><br><span class="line">FileDescriptor::FDWrapper::<span class="built_in">FDWrapper</span>(<span class="type">const</span> <span class="type">int</span> f) : fd&#123;f&#125; &#123;</span><br><span class="line">  <span class="keyword">if</span> (fd &lt; <span class="number">0</span>) &#123;</span><br><span class="line">    <span class="keyword">throw</span> std::<span class="built_in">runtime_error</span>(<span class="string">&quot;invalid fd number:&quot;</span> + std::<span class="built_in">to_string</span>(fd));</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">void</span> FileDescriptor::FDWrapper::<span class="built_in">close</span>() &#123;</span><br><span class="line">  <span class="built_in">SystemCall</span>(<span class="string">&quot;close&quot;</span>, ::<span class="built_in">close</span>(fd));</span><br><span class="line">  eof = closed = <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">FileDescriptor::FDWrapper::~<span class="built_in">FDWrapper</span>() &#123;</span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (closed) &#123;</span><br><span class="line">      <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="built_in">close</span>();</span><br><span class="line">  &#125; <span class="built_in">catch</span> (<span class="type">const</span> std::exception &amp;e) &#123;</span><br><span class="line">    std::cerr &lt;&lt; <span class="string">&quot;Exception destructing FileWrapper: &quot;</span> &lt;&lt; e.<span class="built_in">what</span>() &lt;&lt; std::endl;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">FileDescriptor::<span class="built_in">FileDescriptor</span>(<span class="type">const</span> <span class="type">int</span> fd) : internal_fd&#123;std::<span class="built_in">make_shared</span>&lt;FDWrapper&gt;(fd)&#125; &#123;&#125;</span><br><span class="line"></span><br><span class="line">FileDescriptor::<span class="built_in">FileDescriptor</span>(std::shared_ptr&lt;FDWrapper&gt; other) : internal_fd&#123;std::<span class="built_in">move</span>(other)&#125; &#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="function">FileDescriptor <span class="title">FileDescriptor::duplicate</span><span class="params">()</span> <span class="type">const</span> </span>&#123; <span class="keyword">return</span> <span class="built_in">FileDescriptor</span>(internal_fd); &#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">FileDescriptor::read</span><span class="params">(std::string &amp;str, <span class="type">const</span> <span class="type">size_t</span> limit)</span> </span>&#123;</span><br><span class="line">  <span class="keyword">constexpr</span> <span class="type">size_t</span> BUFFER_SIZE = <span class="number">1024</span> * <span class="number">1024</span>;</span><br><span class="line">  <span class="type">const</span> <span class="type">size_t</span> size_to_read = std::<span class="built_in">min</span>(BUFFER_SIZE, limit);</span><br><span class="line">  str.<span class="built_in">resize</span>(size_to_read);</span><br><span class="line"></span><br><span class="line">  <span class="type">ssize_t</span> bytes_read = <span class="built_in">SystemCall</span>(<span class="string">&quot;read&quot;</span>, ::<span class="built_in">read</span>(<span class="built_in">fd_num</span>(), str.<span class="built_in">data</span>(), size_to_read));</span><br><span class="line">  <span class="keyword">if</span> (limit &gt; <span class="number">0</span> &amp;&amp; bytes_read == <span class="number">0</span>) &#123;</span><br><span class="line">    internal_fd-&gt;eof = <span class="literal">true</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (bytes_read &gt; <span class="built_in">static_cast</span>&lt;<span class="type">ssize_t</span>&gt;(size_to_read)) &#123;</span><br><span class="line">    <span class="keyword">throw</span> std::<span class="built_in">runtime_error</span>(<span class="string">&quot;read() read more than requested&quot;</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  str.<span class="built_in">resize</span>(bytes_read);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function">std::string <span class="title">FileDescriptor::read</span><span class="params">(<span class="type">const</span> <span class="type">size_t</span> limit)</span> </span>&#123;</span><br><span class="line">  std::string ret;</span><br><span class="line">  <span class="built_in">read</span>(ret, limit);</span><br><span class="line">  <span class="keyword">return</span> ret;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">size_t</span> <span class="title">FileDescriptor::write</span><span class="params">(<span class="type">const</span> <span class="type">char</span> *str)</span> </span>&#123;</span><br><span class="line">  <span class="type">size_t</span> bytes_written = <span class="number">0</span>;</span><br><span class="line">  bytes_written = <span class="built_in">SystemCall</span>(<span class="string">&quot;write&quot;</span>, ::<span class="built_in">write</span>(<span class="built_in">fd_num</span>(), str, <span class="built_in">strlen</span>(str)));</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (bytes_written == <span class="number">0</span> &amp;&amp; <span class="built_in">strlen</span>(str) != <span class="number">0</span>) &#123;</span><br><span class="line">    <span class="keyword">throw</span> std::<span class="built_in">runtime_error</span>(<span class="string">&quot;write() returned 0 given non-empty input&quot;</span>);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (bytes_written &gt; <span class="built_in">strlen</span>(str)) &#123;</span><br><span class="line">    <span class="keyword">throw</span> std::<span class="built_in">runtime_error</span>(<span class="string">&quot;write() wrote more than requested&quot;</span>);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> bytes_written;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">size_t</span> <span class="title">FileDescriptor::write</span><span class="params">(<span class="type">const</span> std::string &amp;str)</span> </span>&#123; <span class="keyword">return</span> <span class="built_in">write</span>(str.<span class="built_in">c_str</span>());</span><br></pre></td></tr></table></figure><p>如果为了更好的扩展性，我们应该去新建一个<code>Address</code>类表示地址，但是此处我偷懒了，因为我们的重心并不在此处。<code>TCPSocket</code>的操作就比较简单了，只需要在<code>FileDescriptor</code>的基础上，增加以下的方法：</p><ul><li><code>bind</code></li><li><code>listen</code></li><li><code>accept</code></li><li><code>set_reuseaddr</code></li></ul><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// miniSMTPServer/util/socket.hpp</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">TCPSocket</span> : <span class="keyword">public</span> FileDescriptor &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">  <span class="comment">//! Construct via [socket(2)](\ref man2::socket)</span></span><br><span class="line">  <span class="built_in">TCPSocket</span>();</span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="keyword">explicit</span> <span class="title">TCPSocket</span><span class="params">(FileDescriptor &amp;&amp;fd)</span></span>;</span><br><span class="line"></span><br><span class="line">  <span class="comment">//! Wrapper around [setsockopt(2)](\ref man2::setsockopt)</span></span><br><span class="line">  <span class="keyword">template</span> &lt;<span class="keyword">typename</span> option_type&gt;</span><br><span class="line">  <span class="function"><span class="type">void</span> <span class="title">setsockopt</span><span class="params">(<span class="type">const</span> <span class="type">int</span> level, <span class="type">const</span> <span class="type">int</span> option, <span class="type">const</span> option_type &amp;option_value)</span></span>;</span><br><span class="line"></span><br><span class="line">  <span class="comment">//! Bind a socket to a specified address with [bind(2)](\ref man2::bind), usually for listen/accept</span></span><br><span class="line">  <span class="function"><span class="type">void</span> <span class="title">bind</span><span class="params">(<span class="type">int</span> port = <span class="number">9400</span>)</span></span>;</span><br><span class="line"></span><br><span class="line">  <span class="comment">//! Mark a socket as listening for incoming connections</span></span><br><span class="line">  <span class="function"><span class="type">void</span> <span class="title">listen</span><span class="params">(<span class="type">const</span> <span class="type">int</span> backlog = <span class="number">16</span>)</span></span>;</span><br><span class="line"></span><br><span class="line">  <span class="comment">//! Accept a new incoming connection</span></span><br><span class="line">  <span class="function">TCPSocket <span class="title">accept</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">  <span class="comment">//! Allow local address to be reused sooner via [SO_REUSEADDR](\ref man7::socket)</span></span><br><span class="line">  <span class="function"><span class="type">void</span> <span class="title">set_reuseaddr</span><span class="params">()</span></span>;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// miniSMTPServer/util/socket.cpp</span></span><br><span class="line">TCPSocket::<span class="built_in">TCPSocket</span>() : FileDescriptor&#123;<span class="built_in">SystemCall</span>(<span class="string">&quot;socket&quot;</span>, ::<span class="built_in">socket</span>(AF_INET, SOCK_STREAM, <span class="number">0</span>))&#125; &#123;&#125;</span><br><span class="line"></span><br><span class="line">TCPSocket::<span class="built_in">TCPSocket</span>(FileDescriptor &amp;&amp;fd) : <span class="built_in">FileDescriptor</span>(std::<span class="built_in">move</span>(fd)) &#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">template</span> &lt;<span class="keyword">typename</span> option_type&gt;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">TCPSocket::setsockopt</span><span class="params">(<span class="type">const</span> <span class="type">int</span> level, <span class="type">const</span> <span class="type">int</span> option, <span class="type">const</span> option_type &amp;option_value)</span> </span>&#123;</span><br><span class="line">  <span class="built_in">SystemCall</span>(<span class="string">&quot;setsockopt&quot;</span>, ::<span class="built_in">setsockopt</span>(<span class="built_in">fd_num</span>(), level, option, &amp;option_value, <span class="built_in">sizeof</span>(option_value)));</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">TCPSocket::set_reuseaddr</span><span class="params">()</span> </span>&#123; <span class="built_in">setsockopt</span>(SOL_SOCKET, SO_REUSEADDR, <span class="built_in">int</span>(<span class="literal">true</span>)); &#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">TCPSocket::bind</span><span class="params">(<span class="type">int</span> port)</span> </span>&#123;</span><br><span class="line">  <span class="keyword">struct</span> <span class="title class_">sockaddr_in</span> address;</span><br><span class="line"></span><br><span class="line">  address.sin_family = AF_INET;</span><br><span class="line">  address.sin_port = <span class="built_in">htons</span>(port);</span><br><span class="line">  <span class="built_in">inet_pton</span>(AF_INET, <span class="string">&quot;127.0.0.1&quot;</span>, &amp;address.sin_addr);</span><br><span class="line">  <span class="built_in">SystemCall</span>(<span class="string">&quot;bind&quot;</span>, ::<span class="built_in">bind</span>(<span class="built_in">fd_num</span>(), (<span class="keyword">struct</span> sockaddr *)&amp;address, <span class="built_in">sizeof</span>(address)));</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">TCPSocket::listen</span><span class="params">(<span class="type">const</span> <span class="type">int</span> backlog)</span> </span>&#123; <span class="built_in">SystemCall</span>(<span class="string">&quot;listen&quot;</span>, ::<span class="built_in">listen</span>(<span class="built_in">fd_num</span>(), backlog)); &#125;</span><br><span class="line"></span><br><span class="line"><span class="function">TCPSocket <span class="title">TCPSocket::accept</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="built_in">TCPSocket</span>(<span class="built_in">FileDescriptor</span>(<span class="built_in">SystemCall</span>(<span class="string">&quot;accept&quot;</span>, ::<span class="built_in">accept</span>(<span class="built_in">fd_num</span>(), <span class="literal">nullptr</span>, <span class="literal">nullptr</span>))));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>你也可以直接使用以下的命令得到上述的代码：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git checkout tcp-socket</span><br></pre></td></tr></table></figure><p>你可以发现，我们其实绕了一大圈最终也只是为了实现这些命令，但是这些类的建立为我们写代码提供了良好的抽象能力。</p><h2 id="测试"><a href="#测试" class="headerlink" title="测试"></a>测试</h2><p>在完成了<code>TCPSocket</code>类的实现后，我们就可以开始写一个简单的服务器了，在这个简单的服务器中，我们不使用任何的线程，不使用任何的I&#x2F;O复用，我们就简单地一个一个处理到来的请求。毕竟我们的重点并不在于服务器高性能的实现。</p><p>在<code>miniSMTPServer/miniSMTPServer.cpp</code>中编写如下的代码：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;socket.hpp&quot;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;string&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  std::cout &lt;&lt; <span class="string">&quot;Hello, This is a simple SMTP server\n&quot;</span>;</span><br><span class="line"></span><br><span class="line">  TCPSocket socket&#123;&#125;;</span><br><span class="line">  socket.<span class="built_in">bind</span>();</span><br><span class="line">  socket.<span class="built_in">listen</span>();</span><br><span class="line"></span><br><span class="line">  std::string result&#123;&#125;;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">    <span class="keyword">auto</span> s = socket.<span class="built_in">accept</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">      s.<span class="built_in">read</span>(result);</span><br><span class="line">      <span class="keyword">if</span> (result.<span class="built_in">size</span>() == <span class="number">0</span>) &#123;</span><br><span class="line">        std::cout &lt;&lt; <span class="string">&quot;S: Connection lost\n&quot;</span>;</span><br><span class="line">        s.<span class="built_in">close</span>();</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">      &#125;</span><br><span class="line">      s.<span class="built_in">write</span>(<span class="string">&quot;Hello, this is the server\n&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>同时，我们需要修改<code>miniSMTPServer/CMakeLists.txt</code>为其添加相应的头文件和库：</p><figure class="highlight cmake"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">...</span><br><span class="line"></span><br><span class="line"><span class="keyword">target_include_directories</span>(miniSMTP PRIVATE ./util)</span><br><span class="line"><span class="keyword">target_link_libraries</span>(miniSMTP util)</span><br><span class="line"></span><br><span class="line">...</span><br></pre></td></tr></table></figure><p>同样地，你也可以使用下面的命令得到如上所示的代码：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git checkout tcp-server</span><br></pre></td></tr></table></figure><p>编译代码运行<code>./miniSMTP</code>，然后执行命令<code>telnet 127.0.0.1 9400</code>，你应该能够得到如下图所示的执行的结果。</p><p><img src="https://s2.loli.net/2023/03/12/TnurykNBVoLYeXx.png" alt="Simple Server Test Result"></p>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;ul&gt;
&lt;li&gt;&lt;a</summary>
        
      
    
    
    
    <category term="手把手教你写一个简单的SMTP服务器" scheme="https://luolibrary.com/categories/%E6%89%8B%E6%8A%8A%E6%89%8B%E6%95%99%E4%BD%A0%E5%86%99%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84SMTP%E6%9C%8D%E5%8A%A1%E5%99%A8/"/>
    
    
    <category term="技术" scheme="https://luolibrary.com/tags/%E6%8A%80%E6%9C%AF/"/>
    
    <category term="学习" scheme="https://luolibrary.com/tags/%E5%AD%A6%E4%B9%A0/"/>
    
    <category term="教程" scheme="https://luolibrary.com/tags/%E6%95%99%E7%A8%8B/"/>
    
  </entry>
  
</feed>
