<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Yu的炼金工房</title>
  
  
  <link href="https://atelieryu.site/atom.xml" rel="self"/>
  
  <link href="https://atelieryu.site/"/>
  <updated>2025-08-05T12:35:00.000Z</updated>
  <id>https://atelieryu.site/</id>
  
  <author>
    <name>Yu</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>自建Nas内网穿透——ZeroTier/FRP</title>
    <link href="https://atelieryu.site/Nas-%E5%86%85%E7%BD%91%E7%A9%BF%E9%80%8F.html"/>
    <id>https://atelieryu.site/Nas-%E5%86%85%E7%BD%91%E7%A9%BF%E9%80%8F.html</id>
    <published>2025-08-04T11:21:00.000Z</published>
    <updated>2025-08-05T12:35:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>六月份入手了一台群晖Nas。由于群晖自带的内网穿透不太好用（连接不稳定，无法访问套件），所以准备自己搭内网穿透，尝试了几种方法，简单记录一下。</p><h1 id="ZeroTier"><a href="#ZeroTier" class="headerlink" title="ZeroTier"></a>ZeroTier</h1><p>ZeroTier是一款强大的P2P VPN工具，它可以让你搭建虚拟局域网（异地组网）。其优点之一是设备之间是点对点直连的，经过中转服务器握手后，设备间进行P2P传输，既保证了速度，又提升了安全性。在设备间跳数较小的情况下是个不错的选择。</p><h2 id="创建私人网络"><a href="#创建私人网络" class="headerlink" title="创建私人网络"></a>创建私人网络</h2><p>首先在ZeroTier<a href="https://my.zerotier.com/">控制台</a>注册一个账号，点击Create Network创建网络。</p><p>记住这里的NETWORK ID，它是你的私人网络在互联网中的标识</p><p><img src="https://atelieryu.xyz/elog/202506/ee353de79fbaf07c1a966137fd292a95.png" alt="ZeroTier控制台首页"></p><h2 id="PC操作"><a href="#PC操作" class="headerlink" title="PC操作"></a>PC操作</h2><p>在<a href="https://www.zerotier.com/">官网</a>下载一个ZeroTier客户端并启动（傻瓜式安装，这里不再赘述）</p><p>打开客户端后在Window右下角的后台程序中应该能看到ZeroTier已经启动了，点击Join New Network，输入上一步的NETWORK ID</p><p><img src="https://atelieryu.xyz/elog/202506/c045bb1da03b5fae60f2d99bebfefd6e.png" alt="ZeroTier客户端"></p><p>之后需要在控制台中进行验证。上述步骤完成后控制台的Members中应该会出现一个新设备，可以比对上一步的My Address和Member中的Address来判断是否是目标设备（Address就是ZeroTier给每台设备分配的唯一标识号）</p><p>左侧勾选，点击Authorize即可完成认证。这样目标设备就正式加入了虚拟局域网。同一虚拟局域网的设备使用分配的Managed IP是可以ping通的</p><p>网络ID+控制台验证，这让ZeroTier的安全性达到了一个比较可靠的程度。</p><p><img src="https://atelieryu.xyz/elog/202506/1ac2c4a37138940bd86078dd9f7e4428.png" alt="ZeroTier认证"></p><h2 id="Nas操作"><a href="#Nas操作" class="headerlink" title="Nas操作"></a>Nas操作</h2><p>博主使用的群晖Nas，社区套件提供了比较快捷的安装方式，其他Nas品牌可以尝试通过docker安装ZeroTier，流程应该是大差不差的。（套件使用的是矿神源，安装方式请自行查询）</p><p>由于ZeroTier需要以root权限运行，所以需要先下载一个权限管理器</p><p><img src="https://atelieryu.xyz/elog/202506/7e872a4490fd63d54ba57fcb6d7b38a0.png" alt="权限管理器套件"></p><p>之后安装ZeroTier套件，安装时会让你输入Network ID，同理填入你的私人网路ID即可。</p><p><img src="https://atelieryu.xyz/elog/202506/b0c639f56802ea3b02e1b1bf3ccc8996.png" alt="ZeroTier套件"></p><p>之后在控制台进行认证即可，操作同PC。</p><p>现在就可以直接使用ZeroTier分配的虚拟局域网IP访问你的Nas啦，由于原理就是虚拟局域网，所以所有操作都与在本地局域网内相同。</p><p><img src="https://atelieryu.xyz/elog/202506/c727c1a35055eadf0c3e1eeb0e638a91.png" alt="成功通过虚拟局域网IP访问Nas"></p><h2 id="Moon服务器"><a href="#Moon服务器" class="headerlink" title="Moon服务器"></a>Moon服务器</h2><p>由于不同设备间握手还需要经过中转服务器（即官方说的Planet服务器），所以在连接安全性与稳定性上还可提升，方法就是自建一个中转服务器，即官方说的Moon服务器。理论上是挺好的，但我自建下来没有提升，反而导致我设备间连不通了，于是放弃了该方法。</p><h1 id="FRP"><a href="#FRP" class="headerlink" title="FRP"></a>FRP</h1><p>上面提到ZeroTier的稳定性受中转服务器、设备间跳数等因素影响，其实连接不是很稳定（在我校园网的环境下时常连不通，或传输速率慢）。于是准备寻找替代方法，最后选择了较为成熟的FRP</p><p>FRP本质上是一种反向代理操作，所以需要一台公网可访问的服务器作为代理服务器。</p><p>FRP的搭建分为FRPS（FRP Server）和FRPC（FRP Client）两部分。说白了就是将FRP的服务端与客户端程序分配部署到服务器与设备上，然后分别填写配置文件。</p><ul><li>操作参考了以下博客：<a href="https://gitee.com/spoto/natserver">https://gitee.com/spoto/natserver</a></li><li>frp官方文档：<a href="https://gofrp.org/zh-cn/">https://gofrp.org/zh-cn/</a></li></ul><h2 id="服务端部署（FRPS）"><a href="#服务端部署（FRPS）" class="headerlink" title="服务端部署（FRPS）"></a>服务端部署（FRPS）</h2><p>服务端就是你用来做反向代理的服务器。</p><h3 id="创建配置文件"><a href="#创建配置文件" class="headerlink" title="创建配置文件"></a>创建配置文件</h3><figure class="highlight bash"><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"># 创建存放目录</span></span><br><span class="line"><span class="built_in">sudo</span> <span class="built_in">mkdir</span> /etc/frp</span><br><span class="line"><span class="comment"># 创建frps.toml文件</span></span><br><span class="line">nano /etc/frp/frps.toml</span><br></pre></td></tr></table></figure><p>frps.toml配置文件内容参考（虽然是toml，但为了兼容性仍然使用ini文件的格式）：</p><figure class="highlight toml"><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="section">[common]</span></span><br><span class="line"><span class="comment"># 监听端口</span></span><br><span class="line"><span class="attr">bind_port</span> = xxxxx</span><br><span class="line"><span class="comment"># 面板端口</span></span><br><span class="line"><span class="attr">dashboard_port</span> = xxxxx</span><br><span class="line"><span class="comment"># 登录面板账号设置</span></span><br><span class="line"><span class="attr">dashboard_user</span> = xxxxx</span><br><span class="line"><span class="attr">dashboard_pwd</span> = xxxxx</span><br><span class="line"></span><br><span class="line"><span class="comment"># 身份验证</span></span><br><span class="line"><span class="attr">token</span> = xxxxxx</span><br></pre></td></tr></table></figure><ul><li>bind_port：监听端口，frp通过该端口提供反向代理服务，推荐修改为一个小众端口</li><li>dashboard_port：面板端口，用于frp流量监控，保持默认即可</li><li>token：身份验证，frp客户端配置文件中填入相同token才能获取frps服务，能一定程度提升安全性</li></ul><h3 id="docker安装frps"><a href="#docker安装frps" class="headerlink" title="docker安装frps"></a>docker安装frps</h3><figure class="highlight toml"><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">#服务器镜像：snowdreamtech/frps</span></span><br><span class="line"><span class="comment">#重启：always</span></span><br><span class="line"><span class="comment">#网络模式：host</span></span><br><span class="line"><span class="comment">#文件映射：/etc/frp/frps.toml:/etc/frp/frps.toml</span></span><br><span class="line"></span><br><span class="line">docker run <span class="attr">--restart</span>=always --network host -d -v /etc/frp/frps.toml:/etc/frp/frps.toml --name frps snowdreamtech/frps</span><br></pre></td></tr></table></figure><p>这样frps就配置完成了</p><h2 id="Nas部署（frpc）"><a href="#Nas部署（frpc）" class="headerlink" title="Nas部署（frpc）"></a>Nas部署（frpc）</h2><h3 id="创建配置文件-1"><a href="#创建配置文件-1" class="headerlink" title="创建配置文件"></a>创建配置文件</h3><p>在docker文件夹下创建frp文件夹，并在目录下新建一个frpc.toml文件</p><p>frpc.toml配置文件内容参考：</p><figure class="highlight toml"><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="section">[common]</span></span><br><span class="line"><span class="attr">server_addr</span> = <span class="string">&#x27;你的服务器公网IP&#x27;</span></span><br><span class="line"><span class="attr">server_port</span> = <span class="string">&#x27;服务器frps.toml配置文件中设置的bind_port&#x27;</span></span><br><span class="line"><span class="attr">token</span> = <span class="string">&#x27;服务器frps.toml配置文件中设置的token&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="section">[dsm]</span></span><br><span class="line"><span class="attr">type</span> = tcp</span><br><span class="line"><span class="attr">local_ip</span> = <span class="number">127.0</span>.<span class="number">0.1</span></span><br><span class="line"><span class="attr">local_port</span> = <span class="string">&#x27;dsm登录端口&#x27;</span></span><br><span class="line"><span class="attr">remote_port</span> = <span class="string">&#x27;服务器上任意空闲端口&#x27;</span></span><br></pre></td></tr></table></figure><ul><li>[common]<ul><li>frpc通用配置，按注释中配置即可</li></ul></li><li>[dsm]<ul><li>[dsm]：服务名，名字可以随意取，但不能重复</li><li>local_port：Nas上需要内网穿透的服务端口，这里希望内网穿透登录界面，所以绑定了登录端口（登录端口可以在<code>控制面板→系统→登录门户→网页服务</code>中设置，推荐使用HTTPS）</li><li>remote_port：服务器上的绑定Nas服务的端口，设置好后访问<code>服务器IP:remote_port</code>就等于访问<code>Nas的localhost:local_port</code></li></ul></li></ul><h3 id="安装frpc"><a href="#安装frpc" class="headerlink" title="安装frpc"></a>安装frpc</h3><p>Nas上通过Container Manager安装</p><p>在镜像仓库中搜索<code>snowdreamtech/frps</code> ，在映像中点击运行，下面是配置界面：</p><p><img src="https://atelieryu.xyz/elog/202506/0a87ac07c5a8d4a490930bb009d83e53.png" alt="容器配置1"></p><p><img src="https://atelieryu.xyz/elog/202506/11db5d54c1004badb5e659b86ee49c36.png" alt="容器配置2"></p><p><img src="https://atelieryu.xyz/elog/202506/2afc25b6d793fe430dea8920fda0ae0f.png" alt="容器配置3"></p><p>配置完成后容器就运行起来了，配置文件错误或服务器设置错误的话应该运行两下就会弹窗告诉你容器运行失败，这时可以通过容器运行日志来进行修复：</p><p><img src="https://atelieryu.xyz/elog/202506/33a14348961b40512a225d3d30f8c8e0.png" alt="容器运行日志"></p><p>常见的坑有端口没对应好、服务器防火墙没放行端口等。</p><p>现在，你已经可以通过<code>服务器IP:remote_port</code> 在异地访问登录你的Nas啦！</p><h2 id="服务器反向代理绑定子域名"><a href="#服务器反向代理绑定子域名" class="headerlink" title="服务器反向代理绑定子域名"></a>服务器反向代理绑定子域名</h2><p>通过IP+端口的访问感觉还是不够优雅，能不能通过域名访问Nas呢？当然是可以的！这里通过服务器上Nginx的反向代理实现这一功能。</p><p>为了操作方便，所以操作使用宝塔面板完成，面板的安装请自行搜索</p><h3 id="创建站点"><a href="#创建站点" class="headerlink" title="创建站点"></a>创建站点</h3><p>首先创建一个站点，域名里填上你想要使用的子域名。由于该域名仅用作反代，PHP版本选择纯静态</p><p><img src="https://atelieryu.xyz/elog/202506/c48ac23cc041af3dfb3693114451cbe3.png" alt="建站"></p><h3 id="Nginx反向代理"><a href="#Nginx反向代理" class="headerlink" title="Nginx反向代理"></a>Nginx反向代理</h3><p>在宝塔面板中打开刚刚创建站点的设置界面，反向代理界面中点击添加反向代理，配置见下图：</p><p><img src="https://atelieryu.xyz/elog/202506/4e64052b81d21d71804155b4cde5ea78.png" alt="反向代理"></p><h3 id="域名解析"><a href="#域名解析" class="headerlink" title="域名解析"></a>域名解析</h3><p>博主域名使用Cloudflare托管，所以域名解析操作以Cloudflare为基准</p><p>打开根域名的DNS界面，创建一条A记录，将子域名解析到服务器IP上，可以打开Cloudflare代理。</p><p><img src="https://atelieryu.xyz/elog/202506/d5c7bbd6813d8083683e7e8387bca0a7.png" alt="域名解析"></p><h3 id="申请证书"><a href="#申请证书" class="headerlink" title="申请证书"></a>申请证书</h3><p>到这一步其实你已经可以通过子域名访问Nas了，但是由于缺少证书，大部分浏览器应该都会提示不安全，甚至直接拒绝访问，所以我们还需要申请一个证书，同样在宝塔面板中进行</p><p>在站点设置界面的<code>SSL→加密</code>中点击申请即可完成自签证书的申请。自签申请的证书有效期为90天，记得定时续签</p><p><img src="https://atelieryu.xyz/elog/202506/f82128134d2fc979099c27bbe457d7f1.png" alt="申请证书"></p><p>至此，就可以通过域名直接访问处于内网中的Nas了</p><p><img src="https://atelieryu.xyz/elog/202506/%E5%9F%9F%E5%90%8D%E8%AE%BF%E9%97%AENas.png" alt="成功通过域名访问Nas"></p><p>在服务器的Frp监控面板上也能看到流量正常在走</p><p><img src="https://atelieryu.xyz/elog/202506/0c95066d04f89abfcb6da005d26a9d92.png" alt="FRP监控页面"></p><h2 id="Webdav配置"><a href="#Webdav配置" class="headerlink" title="Webdav配置"></a>Webdav配置</h2><p>Nas的主要目的是存储，那么如何方便地访问Nas的硬盘呢？</p><p>ZeroTier由于原理是异地组网，所以连上后可以直接在网络设备中搜索到Nas，通过smb协议访问到Nas的硬盘。</p><p>而Frp原理是反代，该怎么访问到Nas的硬盘呢？这里提供一下我的方案，可能还有更好的，仅供参考</p><h3 id="Nas安装Webdav服务器"><a href="#Nas安装Webdav服务器" class="headerlink" title="Nas安装Webdav服务器"></a>Nas安装Webdav服务器</h3><p>在Nas套件中心搜索Webdav，下载Webdav服务器</p><p><img src="https://atelieryu.xyz/elog/202506/2b8ec031c195233d9497580712423ff9.png" alt="WebDAV套件"></p><p>打开后记下Webdav在本地的端口，同样推荐使用HTTPS</p><p><img src="https://atelieryu.xyz/elog/202506/76b7be369e2649028a6f1d8b85c99302.png" alt="WebDAV界面，记住这里的端口"></p><h3 id="添加Frpc配置"><a href="#添加Frpc配置" class="headerlink" title="添加Frpc配置"></a>添加Frpc配置</h3><p>在Nas的frpc.toml配置文件中添加一下内容：</p><figure class="highlight toml"><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="section">[webdav]</span></span><br><span class="line"><span class="attr">type</span> = tcp</span><br><span class="line"><span class="attr">local_ip</span> = <span class="number">127.0</span>.<span class="number">0.1</span></span><br><span class="line"><span class="attr">local_port</span> = <span class="string">&#x27;Nas上Webdav服务器的运行端口，即上面的5006&#x27;</span></span><br><span class="line"><span class="attr">remote_port</span> = <span class="string">&#x27;服务器任意空闲端口&#x27;</span></span><br><span class="line"><span class="attr">use_encryption</span> = <span class="literal">true</span></span><br><span class="line"><span class="attr">use_compression</span> = <span class="literal">true</span></span><br></pre></td></tr></table></figure><p>之后重启一下容器配置就生效了</p><h3 id="PC上下载Webdav客户端"><a href="#PC上下载Webdav客户端" class="headerlink" title="PC上下载Webdav客户端"></a>PC上下载Webdav客户端</h3><p>在需要访问Nas硬盘的PC上下载一个Webdav客户端，博主选择的是<a href="https://www.raidrive.com/">RaiDrive</a>，一个开源免费的Webdav客户端，缺点是偶尔会跳弹窗广告，问题不大。</p><p>打开RaiDrive，右上角点<code>添加</code>，<code>服务类型→Nas</code>中选择WebDAV，地址中域名填写你的服务器IP，端口填写上面绑定的服务器remote_port。账户密码填写你Nas用户的账号和密码。最后点击连接，就完成配置了</p><p><img src="https://atelieryu.xyz/elog/202506/cc470737c011277433ac078c2510ee1f.png" alt="RaiDrive界面"></p><p>之后你就可以像本地磁盘一样访问Nas的硬盘了</p><p><img src="https://atelieryu.xyz/elog/202506/cea191749e430e3116ebec9b4bb8729d.png" alt="此电脑下多出了Nas硬盘"></p><h2 id="最后一点乌云"><a href="#最后一点乌云" class="headerlink" title="最后一点乌云"></a>最后一点乌云</h2><p>通过子域名或IP+端口访问Nas都是通过HTTPS协议的，所以打开Nas上的HTTPS网页都是没问题的（如Photos等群晖官方应用都是支持HTTPS的）</p><p>但Nas上多数应用都是不支持HTTPS的，如常用的jellyfin、Nastool，访问这些应用时无法自动由HTTPS切换到HTTP，所以无法打开。</p><p>找到的一个解决方案是在Nas上再嵌套一层反向代理，将HTTPS协议下的端口访问转发给HTTP协议的访问。但实际实现却是404。等到之后找到解决方法再来更新吧！</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;六月份入手了一台群晖Nas。由于群晖自带的内网穿透不太好用（连接不稳定，无法访问套件），所以准备自己搭内网穿透，尝试了几种方法，简单记录一下。&lt;/p&gt;
&lt;h1 id=&quot;ZeroTier&quot;&gt;&lt;a href=&quot;#ZeroTier&quot; class=&quot;headerlink&quot; titl</summary>
      
    
    
    
    <category term="技术随记" scheme="https://atelieryu.site/categories/%E6%8A%80%E6%9C%AF%E9%9A%8F%E8%AE%B0/"/>
    
    
    <category term="Nas" scheme="https://atelieryu.site/tags/Nas/"/>
    
  </entry>
  
  <entry>
    <title>2025四月番追番记录</title>
    <link href="https://atelieryu.site/%E8%BF%BD%E7%95%AA%E8%AE%B0%E5%BD%95-2025-Spring.html"/>
    <id>https://atelieryu.site/%E8%BF%BD%E7%95%AA%E8%AE%B0%E5%BD%95-2025-Spring.html</id>
    <published>2025-06-29T03:03:00.000Z</published>
    <updated>2025-08-09T15:59:00.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="食用说明"><a href="#食用说明" class="headerlink" title="食用说明"></a>食用说明</h1><ol><li>本文更偏向追番体验的记录，可能与补番体验略有不同</li><li>本文评分体系近似bangumi，以下为分数区间参考含义<ul><li>&gt;9：全人类都应该来看</li><li>&gt;8：必看神作</li><li>&gt;7：值得一看</li><li>&gt;6：闲的没事可以看</li><li>&gt;5：平平无奇</li><li>&lt;5：献给小众赤石爱好者的一封情书</li></ul></li><li>本文完全由主观构成，没有客观</li></ol><h1 id="2025一月番简评"><a href="#2025一月番简评" class="headerlink" title="2025一月番简评"></a>2025一月番简评</h1><h2 id="8分段"><a href="#8分段" class="headerlink" title="8分段"></a>8分段</h2><h3 id="时光流逝-饭菜依旧美味"><a href="#时光流逝-饭菜依旧美味" class="headerlink" title="时光流逝 饭菜依旧美味"></a>时光流逝 饭菜依旧美味</h3><p><img src="https://atelieryu.xyz/elog/202507/33fdb84b9ea5cfdf72cf02e61dba996c.jpg" alt="时光流逝 饭菜依旧美味"></p><blockquote><p>每周的幸福感充电站</p></blockquote><p>开播前以为大概率是近年众多PA平庸作品的其中之一，没想到看完第一集就被狠狠地腐乳了。轻松温馨的氛围，可爱的人设，有趣的互动，我们豚豚已经多久没有吃到这么优质的饲料了😭</p><ul><li><strong>轻松温馨的氛围</strong>：个人感觉豚片（或者说空气系动画？）最看重的就是其氛围，PA饭无疑做到了这一点。看着片中角色轻松有趣的互动，慢慢地嘴角就不禁向上翘起，脸上就带上了笑容，心里变得暖暖的，精神被喂的饱饱的，多谢款待！</li><li><strong>可爱有趣的人设</strong>：番中的每一个角色都特色鲜明，都令人印象深刻，这点还挺难得的。如果问我最喜欢哪个角色，一时半会还真答不上来，贤惠可爱适合当我老婆的まこ、成熟可靠适合当我妈妈的くれあ、波奇喜多二相性适合当我妹妹的なな、小小一只时而脱线适合当我女儿的つつじ、屑屑的又充满活力适合当我宠物的しのん。我全都要.jpg！</li><li><strong>（差口气的）豚片新标杆</strong>：另一方面这部番又有些可惜之处，除开前几集，后面出现了大量的外包，作画质量下滑严重，好在监督和编剧的发挥一直在线没有影响到观感。如果这部番制作水平能达到本季度mono女孩那种程度，那么它无疑能成为我心中和豚片之王摇曳露营以及豚片之神向山进发一样的高度。</li></ul><blockquote><p>综合打分：8.2</p></blockquote><ul><li>日常感：8.5</li><li>人设：8.5</li><li>动画制作：7.3</li></ul><p>（顺便晒一下托人淘来的漫画）</p><p><img src="https://atelieryu.xyz/elog/202507/37789db51c13cc8894f3673747b28085.png" alt="托人从日本淘来的漫画，好耶！"></p><h3 id="末日后酒店"><a href="#末日后酒店" class="headerlink" title="末日后酒店"></a>末日后酒店</h3><p><img src="https://atelieryu.xyz/elog/202507/5c6e31c0c7c8d2441dea5f1b19e130e8.jpg" alt="末日后酒店"></p><blockquote><p>为酒店书写故事</p></blockquote><p>本季度最大黑马，致敬最伟大的：世纪末动画、热血酒店动画、银河系工作动画、荒野求生动画、冷酷的美食动画、酒精派对动画、战斗英雄动画、王道酒店动画、暴走决斗动画、婚礼动画、悬疑惊悚动画、科幻酒店动画——末日后酒店！</p><ul><li><strong>收放自如的剧本</strong>：编剧对剧本的掌控如同炫技，每集都是不同类型不同主题的故事，且每集都能写出独特的风味。感觉编剧很厉害的一点是能分得清剧本的主次——主题与表达优先于情节与细节。对主题表达无用的地方哪怕看似很重要也会一笔带过，符合主题表达的地方哪怕看似摸不着头脑也会用大量笔墨描写。7、8两集已经算是我心中的神回了，1、11两集的独特氛围感也令人十分难忘。</li><li><strong>不落俗套的故事</strong>：开播前看pv对这番的故事有很多猜想，最后都没有完全命中。看似弱主线，其实每集都有在细节地方做细微铺垫，到了最后一集更是一波三折把这些小小的伏笔都回收了起来。整个故事的尾结的相当不错，可以说是高开高走，平稳落地了。</li><li><strong>优秀的主人公描写</strong>：本番虽然偏群像，但大部分故事还是围绕女主八千代进行的，女主作为参与者与摄像头，有种说不出的可爱感，让人想起另一部我非常喜欢的番《人类衰退之后》中的人类小姐，表面人机实际腹黑可爱（甚至两部番在一些设定上也有些相似之处）。除女主外，其他配角甚至一些单集里的过客都有着不错的描写。</li></ul><blockquote><p>综合打分：8.3</p></blockquote><ul><li>剧本：8.7</li><li>人设：7.8</li><li>制作：7.8</li></ul><h2 id="7分段"><a href="#7分段" class="headerlink" title="7分段"></a>7分段</h2><h3 id="mono女孩"><a href="#mono女孩" class="headerlink" title="mono女孩"></a>mono女孩</h3><p><img src="https://atelieryu.xyz/elog/202507/e2aa7edfa25a83d8173a83e9e8d62975.jpg" alt="mono女孩"></p><blockquote><p>作者的随兴画</p></blockquote><p>摇曳露营作者原作的一部番，感觉是作者拿来奖励自己用的，平时去什么地方旅游，有了什么新的爱好，整了什么新鲜的玩意就画漫画记录下来。配合几个JK有的没的交互还是蛮有意思的，前提是JK在。中间有几集JK全程下线，由几个阿姨撑起主场，观感就下降很多，尤其是羊宫配音的那个黑毛，一出场就是对观感灾难级的破坏。总的来说是体验十分过山车的一部番，前面三四集好看到大开香槟，后面有几集又无趣到想倍速。</p><blockquote><p>综合打分：7.6</p></blockquote><ul><li>日常感：7.6（8.2与7.2取平均）</li><li>人设：7.4</li><li>制作：7.9</li></ul><h3 id="忍者与杀手二人组的日常生活"><a href="#忍者与杀手二人组的日常生活" class="headerlink" title="忍者与杀手二人组的日常生活"></a>忍者与杀手二人组的日常生活</h3><p><img src="https://atelieryu.xyz/elog/202507/b557007338a90e50460b85cb2363f13d.jpg" alt="忍者与杀手二人组的日常生活"></p><blockquote><p>包着米线番皮的百合番？</p></blockquote><p>这番也算一种“低”开高走了，前五集由于角色便当太快太随意起了不少争议（我倒是看的蛮享受的，毕竟便当速度过快，还没对登场角色产生感情她就似了）。而从第五话这个超优秀单集回后，主角就逐渐成为人类，整部番的氛围也往日常百合向发展，也算对应上了标题。看点算是shaft莫名优秀的制作以及两女主间潜移默化的感情发展吧，最后一集看着居然还有点小感动。</p><blockquote><p>综合打分：7.5</p></blockquote><ul><li>剧情：7.5</li><li>人设：7.2</li><li>制作：7.7</li></ul><h3 id="前桥魔女"><a href="#前桥魔女" class="headerlink" title="前桥魔女"></a>前桥魔女</h3><p><img src="https://atelieryu.xyz/elog/202507/3f6c012b37526a1fa79e1ed099448c8c.jpg" alt="前桥魔女"></p><blockquote><p>前桥魔女永远不灭！</p></blockquote><p>（怎么说也算本季度第二大黑马吧）纯粹的低开高走，结局也平稳落地。</p><p>难得感受到动画里人际关系的变化是可见的、有温度的，角色们的关系在事件中逐渐改善，最终结成强大的团魂，前桥魔女永远不灭！</p><p>单个角色的成长曲线塑造的也十分优秀，角色单集后看似好像只是解决了表面问题，轻拿轻放了，但到结尾就会发现角色都潜移默化地以某种形式解决了自身内在&#x2F;外在的问题，所以就有了最后的“当不当魔女已经无所谓了”（甚至某些解决方案还真挺有现实指导意义的）。女主圣粉毛的塑造也挺有意思，大伙都在猜她是粉切黑，没想到一圣到底。</p><blockquote><p>综合打分：7.5</p></blockquote><ul><li>剧情：7.9</li><li>人设：7.3</li><li>制作：7.2</li></ul><p>（这评分趋势真励志吧）</p><p><img src="https://atelieryu.xyz/elog/202507/6df50b3eaac4d8d683adc0dee6007b6d.png" alt="bangumi评分趋势"></p><h3 id="赛马娘-芦毛灰姑娘"><a href="#赛马娘-芦毛灰姑娘" class="headerlink" title="赛马娘 芦毛灰姑娘"></a>赛马娘 芦毛灰姑娘</h3><p><img src="https://atelieryu.xyz/elog/202507/b57ea1208444aa33cbac37fe88ad7f61.jpg" alt="赛马娘 芦毛灰姑娘"></p><blockquote><p>披着赛马娘皮的正统体育番</p></blockquote><p>普通的好看，挺好看的，但就是有点普通。有着不错的剧情，不错的人设，不错的制作，看完一集也会期待下一集的发展，但就是缺少一个特别戳我的点，可能是我对体育番本身就不太感冒？</p><p>此外还有一个追番体验的缺点，就是断章太严重了，碰到比赛必定大断章，追番看的难受的要死。</p><blockquote><p>综合打分：7.4</p></blockquote><ul><li>剧情：7.6</li><li>人设：7</li><li>制作：7.6</li></ul><h3 id="机动战士高达GQuuuuuuX"><a href="#机动战士高达GQuuuuuuX" class="headerlink" title="机动战士高达GQuuuuuuX"></a>机动战士高达GQuuuuuuX</h3><p><img src="https://atelieryu.xyz/elog/202507/090fbd45f78fb016b7f9e5ab10567861.jpg" alt="机动战士高达GQuuuuuuX"></p><blockquote><p>高达0079主题乐园</p></blockquote><p>好康！爽！虽然剧情完成度低、剧情主题不明、人设塑造不足甚至缺失，但它确实看的爽啊，每集都有展开，每集结尾都有爆点，庞大的信息量塞进高速的发展，配上时髦又好听的bgm轰入大脑，太爽啦！一些情节放到正传里简直就是野史，但咱就好赤这口。总的来说虽然不会给它很高的评价，但追番上的体验真是本季度top级别的。</p><p>不过很多爆点是建立在对前作0079设定的了解上的，所以对一些刚入坑的观众不太友好。</p><blockquote><p>综合打分：7.2</p></blockquote><ul><li>剧情：6.5</li><li>人设：7.2</li><li>制作：7.8</li></ul><h2 id="6分段"><a href="#6分段" class="headerlink" title="6分段"></a>6分段</h2><h3 id="杂旅"><a href="#杂旅" class="headerlink" title="杂旅"></a>杂旅</h3><p><img src="https://atelieryu.xyz/elog/202507/ed8629c478d7a45b27f0a366ee4b2c79.jpg" alt="杂旅"></p><blockquote><p>放空大脑的随性旅行</p></blockquote><p>好的方向上十分平淡没有起伏的一部番，平淡的氛围、平淡的人设、平淡的互动、平淡的制作，看着看着就想睡觉了，所以就把它作为了我的睡前番，每次看完这番都能舒服的入睡（</p><blockquote><p>综合打分：6.5</p></blockquote><ul><li>日常感：7</li><li>人设：6.6</li><li>制作：6.4</li></ul><h3 id="男女之间存在友情吗-不存在"><a href="#男女之间存在友情吗-不存在" class="headerlink" title="男女之间存在友情吗(不存在)"></a>男女之间存在友情吗(不存在)</h3><p><img src="https://atelieryu.xyz/elog/202507/c22d2bf26491d91c61f011bae9d0b995.jpg" alt="男女之间存在友情吗(不存在)"></p><blockquote><p>神人们的恋爱头脑战</p></blockquote><p>神人蓝毛女主，看起来不神人但其实也是神人的男主和女二。虽然jc已经刻意向搞笑方向做了，但还是架不住这灾难的主线。不涉及主线的部分还是挺有趣的，一讲到主线就拳头梆硬。</p><p>题外话，这番op真上头啊，有事没事就想找出来溜两遍，年度歌单应该有这一首了，hw难得硬一次</p><blockquote><p>综合打分：6</p></blockquote><ul><li>剧情：5.5</li><li>人设：6.6</li><li>制作：6.2</li></ul><p>（这女主诗人啊？）</p><p><img src="https://atelieryu.xyz/elog/202507/08a69f40447956b72f764ab3e1a644b4.png" alt="这诗人？"></p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p><img src="https://atelieryu.xyz/elog/202507/a573d89cc8b40e7cdf94c7ed51f869c2.png" alt="2025春季番追番tier.png"></p><p>总的来说看的相当快乐的一个季度，感觉去年到今年一月番剧都挺平淡的，这个季度可算把我缺的营养这一块给补上了。上限有末日后酒店，豚豚饲料有PA饭和mono女孩，原创无法预测的舞台有gqux和前桥魔女，中坚力量有忍杀生活和赛马娘等，乐子档也有男女友情这种神人番。天哪，我们四月番的牌实在太多啦（</p><p>还有播一半的夏日口袋、魔女与使魔等下季度再一起记录吧，阳光马达棒球场看了几集也相当不错，等之后补完。</p><p>另外这季度歌质量都好高啊，狠狠扩了一波歌单，列一下比较喜欢的几首：</p><ul><li><a href="https://music.163.com/#/dj?id=3075188028">質問、恋って何でしょうか?</a>（男女友情OP）</li><li><a href="https://music.163.com/song?id=2712874089">ミッドナイト・リフレクション</a>（gqux插曲）</li><li><a href="https://music.163.com/song?id=2704167265">きえない</a>（gqux插曲）</li><li><a href="https://music.163.com/song?id=2704167264">HALO</a>（gqux插曲）</li><li><a href="https://music.163.com/song?id=2693084286">味噌汁とバター</a>（PA饭ED）</li><li><a href="https://music.163.com/song?id=2693371920">そんなもんね</a>（PA饭OP）</li><li><a href="https://music.163.com/song?id=2693374715">ウィークエンドロール</a>（mono女孩ED）</li><li><a href="https://music.163.com/song?id=2704166360">Watch me!</a>（魔女与使魔OP1）</li></ul><h1 id="2025四月番前瞻"><a href="#2025四月番前瞻" class="headerlink" title="2025四月番前瞻"></a>2025四月番前瞻</h1><p>好像特别期待的不多，点着点着居然点了23部！？</p><h2 id="拔作岛"><a href="#拔作岛" class="headerlink" title="拔作岛"></a>拔作岛</h2><p><img src="https://atelieryu.xyz/elog/202507/0297540a662a16b9229dc0b7a84e0640.jpg" alt="拔作岛"></p><p>比起期待更多是好奇的一部番，这东西怎么动画化？（不过看到下面的吊带袜天使，或许是我低估了日本动画的放送底线？）</p><h2 id="新吊带袜天使"><a href="#新吊带袜天使" class="headerlink" title="新吊带袜天使"></a>新吊带袜天使</h2><p><img src="https://atelieryu.xyz/elog/202507/ff10887729d671d58fad8c7476bcba63.jpg" alt="新吊带袜天使"></p><p>同样是极其黄暴的一部作品，记得还是初高中时期看的，具体内容都记不太清了，就记得两女主（尤其黄毛）的碧池人设，以及断章一般的结尾。没想到这么多年后居然出续作了，期待一手。</p><h2 id="小城日常"><a href="#小城日常" class="headerlink" title="小城日常"></a>小城日常</h2><p><img src="https://atelieryu.xyz/elog/202507/53fafc7c729919e3e34ffae1aff6c67a.jpg" alt="小城日常"></p><p>京都动画+日常作者原作，看pv表现质量有点夸张</p><h2 id="沉默魔女的秘密"><a href="#沉默魔女的秘密" class="headerlink" title="沉默魔女的秘密"></a>沉默魔女的秘密</h2><p><img src="https://atelieryu.xyz/elog/202507/e6c77b4b876c503768fa394e1cb4689f.jpg" alt="沉默魔女的秘密"></p><p>看过原作第一卷，女主可爱捏，但感觉整体风格有点女性向（里面的男角色都油油的）？</p><h2 id="与游戏中心的少女异文化交流的故事"><a href="#与游戏中心的少女异文化交流的故事" class="headerlink" title="与游戏中心的少女异文化交流的故事"></a>与游戏中心的少女异文化交流的故事</h2><p><img src="https://atelieryu.xyz/elog/202507/cfbbbb44d116b9b1121cf6a2fcdad9d2.jpg" alt="与游戏中心的少女异文化交流的故事"></p><p>哇是美味的小女孩（</p><p>女主配音好像是日英混血，英语可爱捏，老师我这是正经在学英语（</p><h2 id="美食广场明天见"><a href="#美食广场明天见" class="headerlink" title="美食广场明天见"></a>美食广场明天见</h2><p><img src="https://atelieryu.xyz/elog/202507/dea6da3e7b677d2377112536686711f9.jpg" alt="美食广场明天见"></p><p>JK们的闲聊日常，应该是那种比较放空大脑的番，编剧还是花田大老师，还能相信吗（</p><h2 id="碧蓝之海-第2期"><a href="#碧蓝之海-第2期" class="headerlink" title="碧蓝之海 第2期"></a>碧蓝之海 第2期</h2><p><img src="https://atelieryu.xyz/elog/202507/14aaf63fe18da4fdf7126a122bf940c3.jpg" alt="碧蓝之海 第2期"></p><p>前不久刚补完第一季度，猛猛期待了。唉，为什么我的大学生活没有这么阳光这么青春（</p><h2 id="瑠璃的宝石"><a href="#瑠璃的宝石" class="headerlink" title="瑠璃的宝石"></a>瑠璃的宝石</h2><p><img src="https://atelieryu.xyz/elog/202507/2aaa9910af35e9708cb00182b18bd301.jpg" alt="瑠璃的宝石"></p><p>看pv表现相当亮眼啊，感觉角色都是kirakira的。但是看一群美少女挖石头真的有趣吗（你怎么知道我真是地学相关专业的）</p><h2 id="更衣人偶坠入爱河第2期"><a href="#更衣人偶坠入爱河第2期" class="headerlink" title="更衣人偶坠入爱河第2期"></a>更衣人偶坠入爱河第2期</h2><p><img src="https://atelieryu.xyz/elog/202507/e32cdbfabc82e1a202f0072d3d03159e.jpg" alt="更衣人偶坠入爱河第2期"></p><p>第一季好看，第二季当然也追上</p><h2 id="章鱼噼的原罪"><a href="#章鱼噼的原罪" class="headerlink" title="章鱼噼的原罪"></a>章鱼噼的原罪</h2><p><img src="https://atelieryu.xyz/elog/202507/62effc314e826233fed85343aeac93ad.jpg" alt="章鱼噼的原罪"></p><p>第一集放出的早已经看完了，制作相当不错，整体压抑黑暗的氛围做的很好，想看番自找不自在的人有福了</p><h2 id="可爱史莱姆噗尼露第2期"><a href="#可爱史莱姆噗尼露第2期" class="headerlink" title="可爱史莱姆噗尼露第2期"></a>可爱史莱姆噗尼露第2期</h2><p><img src="https://atelieryu.xyz/elog/202507/17c09edc89cae8901e59ed3412f063aa.jpg" alt="可爱史莱姆噗尼露第2期"></p><p>没想到突然就有第二季看了，表面子供番，但故事还挺有意思的，女主也可爱捏</p><h2 id="坏女孩"><a href="#坏女孩" class="headerlink" title="坏女孩"></a>坏女孩</h2><p><img src="https://atelieryu.xyz/elog/202507/c60d1978ad05f07dae58afcae18357aa.jpg" alt="坏女孩"></p><p>不说都看不出来是芳文社作品，好像是偏向真百的轻百？</p><h2 id="我怎么可能成为-你的恋人-不行不行-※不是不可能"><a href="#我怎么可能成为-你的恋人-不行不行-※不是不可能" class="headerlink" title="我怎么可能成为 你的恋人 不行不行 (※不是不可能)"></a>我怎么可能成为 你的恋人 不行不行 (※不是不可能)</h2><p><img src="https://atelieryu.xyz/elog/202507/e1c497ce04781d8d5ab9331b01b5fb0c.jpg" alt="我怎么可能成为 你的恋人 不行不行 (※不是不可能)"></p><p>真百后宫？没见过的组合，看pv质量还行，希望别步恋语轻唱后尘吧</p><h2 id="薰香花朵凛然绽放"><a href="#薰香花朵凛然绽放" class="headerlink" title="薰香花朵凛然绽放"></a>薰香花朵凛然绽放</h2><p><img src="https://atelieryu.xyz/elog/202507/39b39bcf774f25b0fd556f93adab09ae.jpg" alt="薰香花朵凛然绽放"></p><p>看简介剧情有点老套，但耐不住女主真好看啊（</p><h2 id="青春猪头少年不会梦到圣诞服女郎"><a href="#青春猪头少年不会梦到圣诞服女郎" class="headerlink" title="青春猪头少年不会梦到圣诞服女郎"></a>青春猪头少年不会梦到圣诞服女郎</h2><p><img src="https://atelieryu.xyz/elog/202507/386e482f840e2bd2fe671ca43868b4d3.jpg" alt="青春猪头少年不会梦到圣诞服女郎"></p><p>上一部都多久之前的番了，有空就追着吧</p><h2 id="转生七王子的魔法全解-第2期"><a href="#转生七王子的魔法全解-第2期" class="headerlink" title="转生七王子的魔法全解 第2期"></a>转生七王子的魔法全解 第2期</h2><p><img src="https://atelieryu.xyz/elog/202507/e43dd05525a02361b640781838c9cb99.jpg" alt="转生七王子的魔法全解 第2期"></p><p>第一季也算豪华厕纸了，第二季也追着</p><h2 id="神椿市建设中"><a href="#神椿市建设中" class="headerlink" title="神椿市建设中"></a>神椿市建设中</h2><p><img src="https://atelieryu.xyz/elog/202507/25444c25575bd5705e05cbfbfd3379f8.jpg" alt="神椿市建设中"></p><p>这个企划的歌听过几首，看剧情口碑好的话也追着吧</p><h2 id="欢迎来到流放者食堂"><a href="#欢迎来到流放者食堂" class="headerlink" title="欢迎来到流放者食堂"></a>欢迎来到流放者食堂</h2><p><img src="https://atelieryu.xyz/elog/202507/57a8469d636cf5ac37431d345796f1e4.jpg" alt="欢迎来到流放者食堂"></p><p>一眼套路退队流，但女角色都挺可爱的，不知道会不会走轻松向发展</p><h2 id="光死去的夏天"><a href="#光死去的夏天" class="headerlink" title="光死去的夏天"></a>光死去的夏天</h2><p><img src="https://atelieryu.xyz/elog/202507/e74f3c33319e3730db5647778ae5f3ae.jpg" alt="光死去的夏天"></p><p>制作看起来很不错，但两个主角gay里gay气的，犹豫看不看</p><h2 id="9-nine"><a href="#9-nine" class="headerlink" title="9-nine-"></a>9-nine-</h2><p><img src="https://atelieryu.xyz/elog/202507/2ad4e425ddabd95ec3c9f89d8cdab6c8.jpg" alt="9-nine-.jpg"></p><p>原作剧情不错，但看pv制作好像有一点点似了</p><h2 id="石纪元-第4期-Part-2"><a href="#石纪元-第4期-Part-2" class="headerlink" title="石纪元 第4期 Part.2"></a>石纪元 第4期 Part.2</h2><p><img src="https://atelieryu.xyz/elog/202507/c09cf7e1e10da586122e40b76e16e6a8.jpg" alt="石纪元 第4期 Part.2"></p><p>稳定出这么多季了，普通的挺好看的</p><h2 id="胆大党-第2期"><a href="#胆大党-第2期" class="headerlink" title="胆大党 第2期"></a>胆大党 第2期</h2><p><img src="https://atelieryu.xyz/elog/202507/ed2832878395f6bdb07f2dc1955386ca.jpg" alt="胆大党 第2期"></p><p>第一季看完了，但感觉风格不是很对胃口，可能会看吧</p><h2 id="怪兽8号-第2期"><a href="#怪兽8号-第2期" class="headerlink" title="怪兽8号 第2期"></a>怪兽8号 第2期</h2><p><img src="https://atelieryu.xyz/elog/202507/320c3106aa8e2a34a89b564f46a52b83.jpg" alt="怪兽8号 第2期"></p><p>第一季全程二倍速看完了，制作还行，但男主人设烂完了，剧情也平平无奇，大概率不看？</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;食用说明&quot;&gt;&lt;a href=&quot;#食用说明&quot; class=&quot;headerlink&quot; title=&quot;食用说明&quot;&gt;&lt;/a&gt;食用说明&lt;/h1&gt;&lt;ol&gt;
&lt;li&gt;本文更偏向追番体验的记录，可能与补番体验略有不同&lt;/li&gt;
&lt;li&gt;本文评分体系近似bangumi，以下为分数区</summary>
      
    
    
    
    <category term="ACGN" scheme="https://atelieryu.site/categories/ACGN/"/>
    
    
    <category term="追番记录" scheme="https://atelieryu.site/tags/%E8%BF%BD%E7%95%AA%E8%AE%B0%E5%BD%95/"/>
    
    <category term="动画" scheme="https://atelieryu.site/tags/%E5%8A%A8%E7%94%BB/"/>
    
  </entry>
  
  <entry>
    <title>机娘制作记录——FAG 短剑 Swimsuit Ver.</title>
    <link href="https://atelieryu.site/%E6%9C%BA%E5%A8%98%E5%88%B6%E4%BD%9C-%E6%B3%B3%E8%A3%85%E7%9F%AD%E5%89%91.html"/>
    <id>https://atelieryu.site/%E6%9C%BA%E5%A8%98%E5%88%B6%E4%BD%9C-%E6%B3%B3%E8%A3%85%E7%9F%AD%E5%89%91.html</id>
    <published>2025-06-22T03:24:00.000Z</published>
    <updated>2025-06-22T06:20:00.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>五一去上海玩，路过寿屋官方线下店时偶然看到了这款，之前种草已久但一直犹豫要不要入。查了查定价和pdd，发现线下店带特典价格居然低于线上价格。直接一个物超所值，下单！</p><p><img src="https://atelieryu.xyz/elog/202506/79244f02ee49c4c0dff34f7a9b24ca9a.jpg" alt="物超所值！下单！"></p><h1 id="简评"><a href="#简评" class="headerlink" title="简评"></a>简评</h1><p>优点：</p><ul><li>设计可爱：尤其是三个预涂装脸，每个都可爱捏</li><li>分件优秀：大部分分色都通过分件实现了，喷涂的遮盖体验还算可以</li><li>补色较少：身上少量补白色，武器少量补黄色</li><li>身体曲线优秀：寿屋伟大</li><li>可玩性不错：两个武器，超多的耳朵配件（猫耳和机械猫耳太可爱了）</li><li>制作简单：零件数量不多，无缝的位置也很常规，哪怕只是简单素组也很出效果</li></ul><p>缺点：</p><ul><li>两根双马尾特别松：丙烯和蓝丁胶都不好救的那种</li><li>大臂上的两个蓝色零件特别脆弱（尤其对涂装遮盖不友好，我这边直接断了一个，干脆用胶水粘上了）</li></ul><div class="note blue flat"><p>总结——8.5&#x2F;10：不懂泳装短剑可爱的人有难了😡!</p></div><p><img src="https://atelieryu.xyz/elog/202506/cb1f04814b37990bc5a9bb868541e989.jpg"></p><p><img src="https://atelieryu.xyz/elog/202506/57f31b85f7e85cef80110db23b27d8ff.jpg"></p><p><img src="https://atelieryu.xyz/elog/202506/b617ca24d6363affc77a4a66c8e3e511.jpg"></p><h1 id="制作记录"><a href="#制作记录" class="headerlink" title="制作记录"></a>制作记录</h1><h2 id="素组"><a href="#素组" class="headerlink" title="素组"></a>素组</h2><p>简简单单素个组</p><p>注意关节等部分用砂纸磨一下，否则喷涂完关节的刮蹭会更加严重。尤其注意脖子和领口是一个嵌套结构，需要磨一下领口零件的内侧，这次没注意到这个零件导致最后组装的时候脖子部分的漆被刮出一条粗线，太灾难了</p><p><img src="https://atelieryu.xyz/elog/202506/09169d829210c7986614444fc7de7c9c.jpg" alt="部分素组零件"></p><h2 id="无缝"><a href="#无缝" class="headerlink" title="无缝"></a>无缝</h2><p>无缝位置主要在四肢、胸和腰的两侧</p><p>大部分比较直的缝还是采用田宫绿盖进行无缝</p><p>少部分复杂部位的缝尝试了<a href="https://www.bilibili.com/video/BV1h54y1s7nE">融化流道法</a>，剪出使用少量流道颗粒，泡在田宫绿盖里融化即可，算是五爽法的平替？但可能是我操作不到位，效果不是特别好，海绵砂纸磨完后表面有些坑坑洼洼</p><p><img src="https://atelieryu.xyz/elog/202506/9d57afdaa4625f348d443a20e3c88d8f.jpg" alt="融化后的流道，当胶水用即可"></p><p>做完无缝后胸部以下部分就变成一个零件了（遮盖地狱）</p><p><img src="https://atelieryu.xyz/elog/202506/72c6ce1231980ab0ba3af08daabd0906.jpg"></p><h2 id="补色"><a href="#补色" class="headerlink" title="补色"></a>补色</h2><p>笔涂真的好难啊，开始用的是之前买的酋长漆，但不太会涂笔痕过于离谱了</p><p>后面换了珐琅漆，体验非常不错，流平性好+容错高（涂出界直接x20擦）。但缺点是附着力太差了，别说指甲刮，用力点摸都可能蹭掉漆。</p><h2 id="涂装"><a href="#涂装" class="headerlink" title="涂装"></a>涂装</h2><p>（第一次的机娘涂装，wakuwaku）</p><h3 id="调色"><a href="#调色" class="headerlink" title="调色"></a>调色</h3><p>这次除了白色零件外也是全喷涂了。使用的漆大部分为郡士H水性漆</p><p>颜色方面主要是肤色、泳装的蓝色和黑色。</p><div class='checkbox blue'><input type="radio" />            <p>肤色</p>            </div><p>肤色我想喷老久了，看大佬喷出来的肤色那叫一个色啊（</p><p>调色参考了<a href="https://www.bilibili.com/video/BV1NA411o7EW">马桶盖</a>大佬，视频中提供的比例是<span class='p blue'>白 : 黄 : 荧光粉红 = 65 : 15 : 20</span>，但我按这个比例调出来颜色太偏粉了。最后向这大概7-8ml的调色漆里掺了一整瓶<span class='p blue'>郡士HMS01</span>肤色漆，颜色才大致对</p><div class='checkbox blue'><input type="radio" />            <p>蓝色</p>            </div><p>蓝色就按说明书里调的，主要是钴蓝，再加点紫色微调</p><p><img src="https://atelieryu.xyz/elog/202506/a4423d1f3838bac0bc0367de6b773adf.jpg" alt="调好的色漆"></p><div class='checkbox blue'><input type="radio" />            <p>黑色</p>            </div><p>黑色是之前调出来的，具体怎么调的给我忘了，大概是深蓝+黑？但感觉这次用的黑色有点太深了</p><h3 id="喷涂"><a href="#喷涂" class="headerlink" title="喷涂"></a>喷涂</h3><p>这部分想记录的是肤色的涂装，同样参考马桶盖大佬的方法，大致步骤如下：</p><ol><li>粉红补土打底</li><li>在零件的边缘处与凹陷处喷涂荧光粉红，作为阴影</li><li>小心地喷涂之前调的肤色主色，慢慢地遮盖零件与阴影，阴影遮盖到什么程度就取决于个人xp了（喷出来效果确实色色的，给我做爽了</li></ol><p><img src="https://atelieryu.xyz/elog/202506/11e9d2dbeb70856f747ee7b41ac4c7b2.png" alt="肤色喷涂过程"></p><p>其他位置比如头发的渐变色以及泳装的阴影变化也可以采用类似的方法</p><h3 id="预涂装脸的处理"><a href="#预涂装脸的处理" class="headerlink" title="预涂装脸的处理"></a>预涂装脸的处理</h3><p>参考了马桶盖大佬的这个<a href="https://www.bilibili.com/video/BV1q94y1Y7yP">视频</a>，大致步骤：</p><ol><li>利用两点一线原理，用刻线针定出两个点，用于之后的水贴定位</li><li>用海绵砂纸把原先的脸部移印磨掉（注意磨干净，没磨干净一是可能厚度不一致，二是涂装时不好遮盖）</li><li>喷涂一层色漆加一层光油，光油的目的是使零件表面更加平整，便于水贴附着</li><li>贴水贴，利用之前的两个点定位</li><li>使用粉色珐琅漆补上嘴部的颜色，或者直接使用粉色珐琅漆给嘴部渗线</li><li>再喷三层左右的光油，目的是消除水贴与零件见的厚度差</li><li>喷一层消光，便于色粉附着</li><li>用色粉画出腮红</li><li>再上一层消光，完工</li></ol><h3 id="小小的吐槽"><a href="#小小的吐槽" class="headerlink" title="小小的吐槽"></a>小小的吐槽</h3><p>这室外喷涂灰是真多吧，本身又是懒狗不想返工，零件沾灰只能用爱包容了</p><p>以及喷涂后的机娘是真不能把玩吧，稍微掰几个动作就一堆刮蹭了，尤其是两个手臂和臀部，再次用爱包容（</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>做机娘真开心啊，有种任由xp飞翔的感觉（</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h1&gt;&lt;p&gt;五一去上海玩，路过寿屋官方线下店时偶然看到了这款，之前种草已久但一直犹豫要不要入。查了查定价和pdd，发现线下店带特典价格居然低于线上价格。</summary>
      
    
    
    
    <category term="模型制作" scheme="https://atelieryu.site/categories/%E6%A8%A1%E5%9E%8B%E5%88%B6%E4%BD%9C/"/>
    
    
    <category term="模型" scheme="https://atelieryu.site/tags/%E6%A8%A1%E5%9E%8B/"/>
    
    <category term="机娘" scheme="https://atelieryu.site/tags/%E6%9C%BA%E5%A8%98/"/>
    
  </entry>
  
  <entry>
    <title>分布式入门——基础理论</title>
    <link href="https://atelieryu.site/%E5%88%86%E5%B8%83%E5%BC%8F%E5%85%A5%E9%97%A8%E7%90%86%E8%AE%BA.html"/>
    <id>https://atelieryu.site/%E5%88%86%E5%B8%83%E5%BC%8F%E5%85%A5%E9%97%A8%E7%90%86%E8%AE%BA.html</id>
    <published>2025-04-14T08:12:00.000Z</published>
    <updated>2025-08-06T04:50:00.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="什么是CAP？"><a href="#什么是CAP？" class="headerlink" title="什么是CAP？"></a>什么是CAP？</h1><div class="note purple flat"><p><strong>CAP 理论</strong>：一个分布式系统最多只能同时满足<strong>一致性（Consistency）</strong>、<strong>可用性（Availability）</strong>和<strong>分区容错性（Partition tolerance）</strong>这三项中的两项</p></div><p>注意： CAP 是学术理论，并不是工程理论，它本身基于状态，基于瞬态，是一个描述性的理论，它并不解决工程问题，会舍弃很多现实世界的问题。比如网络的时长，比如节点内部的处理速度不一致，比如节点间存储方式和速度的不一致。它说的一致性就是客户端是否能拿到最新数据，它说的可用性就是允许客户端拿不到最新数据。</p><h1 id="CAP的三个特性"><a href="#CAP的三个特性" class="headerlink" title="CAP的三个特性"></a>CAP的三个特性</h1><h2 id="C：数据一致性（Consistency）"><a href="#C：数据一致性（Consistency）" class="headerlink" title="C：数据一致性（Consistency）"></a>C：数据一致性<strong>（Consistency）</strong></h2><div class="note blue flat"><p><strong>简单理解</strong>：特指强一致性，即任何时刻都可以读到最近一次成功更新的副本数据（所有节点中的数据在任何时刻都保持一致）。当某个节点的数据发生变化后，所有其他节点都需要同步更新这个数据，确保数据始终一致</p></div><p>假设我们的分布式存储系统有两个节点，每个节点都包含了一部分需要被变化的数据。如果经过一次<strong>写请求</strong>后，两个节点都发生了数据变化。然后，读请求把这些变化后的数据都读取到了，我们就把这次数据修改称为数据发生了一致性变化</p><p><img src="https://atelieryu.xyz/elog/202508/9111459441b40cc67f8542b2f3246f55.png" alt="一致性"></p><p>强一致性：如果系统内部发生了问题从而导致系统的节点无法发生一致性变化，此时，为了保证分布式系统对外的数据一致性，系统会选择不返回任何数据。</p><p><img src="https://atelieryu.xyz/elog/202508/594d16118c42748b234059137e510702.png" alt="一致性"></p><p>注意：CAP 定理是在说在某种状态下的选择，和实际工程的理论是有差别的。上面描述的一致性和 ACID 事务中的一致性是两回事。事务中的一致性包含了实际工程对状态的后续处理。但是 CAP 定理并不涉及到状态的后续处理，对于这些问题，后续出现了 BASE 理论等工程结论去处理</p><h2 id="A：可用性（Availability）"><a href="#A：可用性（Availability）" class="headerlink" title="A：可用性（Availability）"></a>A：可用性<strong>（Availability）</strong></h2><div class="note blue flat"><p><strong>简单理解</strong>：指的是即使系统中的一部分节点出现故障，整个系统仍然能够继续对外提供服务（部分节点的故障不会影响整体服务的响应）</p></div><p>可用性在是 CAP 里对结果的要求。它要求系统内的节点们接收到了无论是写请求还是读请求，都要能处理并给回响应结果。它有两点必须满足的条件：</p><ol><li>返回结果必须<strong>在合理的时间以内</strong>，这个合理的时间是根据业务来定的。业务说必须 100 毫秒内返回，合理的时间就是 100 毫秒。如果业务定的 100 毫秒，结果却在 1 秒才返回，那么这个系统就不满足可用性。</li><li>需要系统内<strong>能正常接收请求的所有节点都返回结果</strong>。这包含了两重含义：<ul><li>如果节点不能正常接收请求了，比如宕机了，系统崩溃了，而其他节点依然能正常接收请求，那么，我们说系统依然是可用的，也就是说，<u>部分宕机没事，不影响可用性指标</u>。</li><li>如果节点能正常接收请求，那么<u>即使发现节点内部数据有问题，也必须返回结果</u>，哪怕返回的结果是有问题的。比如，系统有两个节点，其中有一个节点数据是三天前的，另一个节点是两分钟前的。如果一个读请求跑到了包含了三天前数据的那个节点上，抱歉，这个节点不能拒绝，必须返回这个三天前的数据，即使它可能不太合理</li></ul></li></ol><p><img src="https://atelieryu.xyz/elog/202508/1b8191d0b8d49fc0103b6e58bbb6e04a.png" alt="可用性"></p><h2 id="P：分区容忍性（Partition-tolerance）"><a href="#P：分区容忍性（Partition-tolerance）" class="headerlink" title="P：分区容忍性（Partition tolerance）"></a>P：分区容忍性<strong>（Partition tolerance）</strong></h2><div class="note blue flat"><p><strong>简单理解</strong>：指系统能够在网络分区的情况下还能提供服务</p></div><p>什么是网络分区？</p><ul><li>分布式的存储系统会有很多的节点，这些节点都是通过网络进行通信。而网络是不可靠的，当节点和节点之间的通信出现了问题，此时，就称当前的分布式存储系统出现了分区。</li><li>值得一提的是，分区并不一定是由网络故障引起的，也可能是因为机器故障（如节点宕机，或路由器、交换机等底层网络设备出现了故障）。</li><li>总之，只要在分布式系统中，<span class='p red'>节点通信出现了问题，那么就出现了分区</span></li></ul><p>什么是分区容忍性？</p><ul><li>分区容忍性指如果出现了分区问题，我们的分布式存储系统也必须继续运行。不能因为出现了分区问题，整个分布式节点全部就熄火了，罢工了，不做事情了</li></ul><p>A和P好像都是描述系统的可用性，它们有什么差别？</p><ul><li>A关注的是<strong>每个请求能否被响应</strong>（无论数据是否最新），是客户端视角的可用性，是对分布式系统结果的要求。</li><li>P关注的是<strong>系统能否在网络分区时继续运行</strong>，是系统架构视角的容错能力，是对分布式系统前提的要求。</li></ul><h1 id="CAP的选择"><a href="#CAP的选择" class="headerlink" title="CAP的选择"></a>CAP的选择</h1><h2 id="为什么CAP只能满足其二？"><a href="#为什么CAP只能满足其二？" class="headerlink" title="为什么CAP只能满足其二？"></a>为什么CAP只能满足其二？</h2><p>如前文所述，P是分布式系统的前提要求，因为实际情况中网络分区是不可避免的。如果不选 P，一旦发生分区错误，整个分布式系统就完全无法使用了，这是不符合实际需求的</p><p>而<span class='p red'>在网络分区不可避免的情况下，一致性与可用性无法同时满足</span>：</p><ul><li>想满足一致性就需要等待到分区恢复，即牺牲了可用性</li><li>想满足可用性，就无法保证不同节点读取数据的一致性</li></ul><p>所以，CAP的三选二，其实是二选一（<strong>C or A</strong>）</p><h2 id="典型系统的CAP选择"><a href="#典型系统的CAP选择" class="headerlink" title="典型系统的CAP选择"></a>典型系统的CAP选择</h2><ol><li><strong>CP系统</strong>（牺牲A）<ul><li>例子：ZooKeeper、HBase、传统数据库（如MySQL主从同步）。</li><li>场景：金融交易等对一致性要求极高的场景，允许部分节点在分区时不可用。</li></ul></li><li><strong>AP系统</strong>（牺牲C）<ul><li>例子：Cassandra、Dynamo、Redis Cluster。</li><li>场景：高可用优先的场景（如社交媒体），接受短暂的数据不一致。由于CAP 定理本身是没有考虑网络延迟的问题的（它认为一致性是立即生效的），但是，要保持一致性，是需要时间成本的，这就导致往往分布式系统多选择 AP 方式。</li></ul></li><li><strong>CA系统</strong>（牺牲P）<ul><li>例子：单机数据库（如单节点MySQL）。</li><li>场景：不适用于分布式环境，仅在网络绝对可靠的假设下成立。</li></ul></li></ol><h1 id="CAP的一些注意点"><a href="#CAP的一些注意点" class="headerlink" title="CAP的一些注意点"></a>CAP的一些注意点</h1><h2 id="C或A在没有网络分区时可以共存"><a href="#C或A在没有网络分区时可以共存" class="headerlink" title="C或A在没有网络分区时可以共存"></a>C或A在没有网络分区时可以共存</h2><p>CAP理论的核心限制是：在网络分区（节点间通信中断或延迟）时，系统必须在一致性和可用性之间二选一。</p><p>由此可见，CAP的核心矛盾仅在网络分区（P）发生时才被触发。所以，当网络正常（无分区）时，系统必须同时保证一致性（C）和可用性（A）。具体而言，系统既可以通过同步机制保证一致性，又能及时响应请求，保证可用性。此时<strong>C和A可以共存</strong>。</p><p>总而言之，<span class='p red'>当没有出现分区问题的时候，系统就应该有完美的数据一致性和可用性。</span></p><h2 id="C和A的抉择是局部性的"><a href="#C和A的抉择是局部性的" class="headerlink" title="C和A的抉择是局部性的"></a>C和A的抉择是局部性的</h2><p>当分区发生的时候，其实对一致性和可用性的抉择是局部性的，而不是针对整个系统的：可能是在一些子系统做一些抉择，甚至很可能只需要对某个事件或者数据，做一致性和可用性的抉择而已。</p><p>比如，当我们做一套支付系统的时候，会员的财务相关像账户余额，账务流水是必须强一致性的。这时候，你就要考虑选 C。但是，会员的名字，会员的支付设置就不必考虑强一致性，可以选择可用性 A。</p><h2 id="CAP特性是一个范围，而非是与否的极端选择"><a href="#CAP特性是一个范围，而非是与否的极端选择" class="headerlink" title="CAP特性是一个范围，而非是与否的极端选择"></a>CAP特性是一个范围，而非是与否的极端选择</h2><p>CAP 理论的三种特性不是 Boolean 类型的，不是一致和不一致，可用和不可用，分区和没分区的这类二选一的选项。而是强一致性和弱一致性，高可用和低可用，高容错和低容错的范围变化：</p><ul><li>一致性<ul><li><strong>强一致性</strong>：所有节点数据实时一致，但牺牲可用性。如ZooKeeper 的线性一致性</li><li><strong>弱一致性</strong>：允许短暂不一致，但最终收敛。如DNS 系统的数据传播延迟可能长达数小时</li></ul></li><li>可用性<ul><li><strong>完全可用</strong>：所有请求始终响应，如 AP 系统的无超时设计</li><li><strong>部分可用</strong>：系统降级处理部分请求，如限流、熔断机制等服务降级机制</li><li><strong>不可用</strong>：完全拒绝请求，如 CP 系统在分区时停止写入</li></ul></li><li>分区容忍性<ul><li><strong>完全容错</strong>：系统能容忍任意规模的分区，如Cassandra 的多数据中心复制</li><li><strong>部分容错</strong>：仅容忍短时间或小范围分区，如MongoDB 副本集容忍少数节点离线</li><li><strong>无容错</strong>：依赖单点或同步网络，如单机 MySQL 无法应对网络中断</li></ul></li></ul><h1 id="其他分布式理论"><a href="#其他分布式理论" class="headerlink" title="其他分布式理论"></a>其他分布式理论</h1><h2 id="BASE理论"><a href="#BASE理论" class="headerlink" title="BASE理论"></a>BASE理论</h2><div class="note blue flat"><p><strong>Base的核心思想</strong>：即使无法做到强一致性（Strong Consistency），但应用可以采用适合的方式达到最终一致性（Eventual Consistency）</p></div><h3 id="BASE的概念"><a href="#BASE的概念" class="headerlink" title="BASE的概念"></a>BASE的概念</h3><ul><li><strong>BA（Basically Available，基本可用）</strong>：当系统出现故障或意外情况时，允许放弃掉部分可用性，保证核心功能可用，例如大促时的服务降级策略等。</li><li><strong>S（Soft state，软状态）</strong>：允许系统存在中间状态，并且该中间状态不会影响系统整体可用性。</li><li><strong>E（Eventually consistent，最终一致性）</strong>：指系统中的所有数据副本经过一定时间后，最终能够达到一致的状态。即意味着中间状态，只会短暂存在，在一定时间后，肯定会变成最终状态。</li></ul><h3 id="Base理论的指导意义"><a href="#Base理论的指导意义" class="headerlink" title="Base理论的指导意义"></a>Base理论的指导意义</h3><p>BASE 中的基本可用指的是保障核心功能的基本可用，其实是做了“可用性”方面的妥协，比如：</p><ul><li>系统访问压力较大的时候，关闭次要功能的展示，从而保证主流程的可用性，这也是我们常说的<strong>服务降级</strong>。</li><li>为了错开高峰期，网站会将预售商品的支付时间延后十到二十分钟，这就是<strong>流量削峰</strong>。</li><li>在抢购商品的时候，往往会在队列中等待处理，这也是常用的<strong>延迟队列</strong>。</li></ul><p>软状态和最终一致性指的是允许系统中的数据存在中间状态，这同样是为了系统可用性而牺牲一段时间窗内的数据一致性，从而保证最终的数据一致性的做法</p><h2 id="PACELC理论"><a href="#PACELC理论" class="headerlink" title="PACELC理论"></a>PACELC理论</h2><p>CAP理论并不能很好的指导现实的系统架构：大部分情况下，系统分区都是平稳运行的，系统设计要权衡延迟与数据一致性的问题。为了保证数据一致性，读写的延迟必然升高，于是就有了PACELC理论</p><div class="note blue flat"><p>PACELC理论是对CAP定理的<strong>扩展与细化</strong>，旨在更细致地描述分布式系统在<strong>分区</strong>和<strong>无分区</strong>场景下的性能与一致性权衡</p></div><p>PACELC理论是建立在CAP理论之上的，二者都描述了一致性(Consistency)、可用性(Availability)和分区容忍性(Partition Tolerance)之间的约束与取舍。而PACELC理论则更进一步描述了即使在没有Partition的场景下，也存在Latency和Consistency之间的取舍，从而为分布式系统的Consistency模型提供了一个更为完整的理论依据</p><p><strong>PAC-Else-LC</strong>：</p><ul><li><strong>分区</strong>时：PAC<ul><li>P：分区容忍性</li><li>A：可用性</li><li>C：一致性</li></ul></li><li>（Else）</li><li><strong>无分区</strong>时：LC<ul><li>L：Latency，延迟</li><li>C：Consistency，一致性</li></ul></li></ul><p><img src="https://atelieryu.xyz/elog/202508/e2d5a3df9e19ca2823e94e6336fd423a.png" alt="PACELC理论是对CAP理论的扩展与细化"></p><p>根据 PACELC 模型的定义，如果有网络分区产生，系统就必须在AP系统与CP系统之间做权衡（PAC），否则（Else）当系统运行在无网络分区的正常情况下，系统则需要在 L（延迟）和 C（一致性）之间取得平衡</p><h2 id="NWR多数派理论"><a href="#NWR多数派理论" class="headerlink" title="NWR多数派理论"></a>NWR多数派理论</h2><p>NWR是一种在分布式存储系统中用于控制一致性级别的一种策略</p><ul><li><strong>N</strong>： 数据在系统中总共维护多少个副本。例如，N&#x3D;3 表示数据被复制存储在不同的3个节点（服务器）上</li><li><strong>W</strong>：写操作被视为成功的最小副本数。只有当写操作成功更新了至少 W 个副本后，系统才会向客户端返回“写成功”</li><li><strong>R</strong>：读操作被视为成功的最小副本数。只有当读操作成功从至少 R 个副本读取到数据后，系统才会向客户端返回“读成功”</li></ul><p>NWR分别设置不同的值时，将会产生不同的一致性效果。</p><ul><li><code>W+R&gt;N</code>：整个系统对于客户端的请求能保证<strong>强一致性</strong>。<ul><li>因为写请求和读请求一定存在一个相交的副本，这个副本一定包含了最新的数据（它是成功写入的 W 个副本之一），而读操作也必定能从这个副本中读取到最新的数据</li><li>所以，即使其他副本上的数据可能暂时是旧的（由于网络延迟或节点故障），只要从这个重叠副本中读到最新值，就能确保该读操作返回的是最新数据</li></ul></li><li><code>W+R&lt;=N</code>：整个系统对于客户端的请求则不能保证强一致性</li></ul><h3 id="NWR的一些配置示例"><a href="#NWR的一些配置示例" class="headerlink" title="NWR的一些配置示例"></a>NWR的一些配置示例</h3><ul><li><code>W = 1, R = 1, N &gt; 1</code>：写入最快（只需一个副本），读取最快（只需一个副本），但可能读到旧值（脏读），也可能丢失写入（如果唯一更新的副本后来故障了）。一致性最弱。</li><li><code>W = 1, R = N</code>：写入最快，但读取必须访问所有副本。读取能看到最新的写入，但读取延迟高、可用性低（一个读副本故障则读失败）。</li><li><code>W = N, R = 1</code>：如上面所述，强一致，但写入慢且可用性低。</li><li>折中配置 <code>W + R &gt; N</code>（例如 <code>N=3, W=2, R=2</code>或 <code>N=3, W=3, R=2</code>等）<ul><li>性能&#x2F;延迟： 比 <code>W=N, R=1</code>延迟低（尤其是写）。</li><li>可用性： 可以容忍 <code>N - W</code>个写副本故障 和<code>N - R</code>个读副本故障。比<code>W=N</code>或 <code>R=N</code>可用性更高。</li><li>一致性<strong>：</strong> 保证强读写一致性（总能读到最近一次成功写入的数据）</li></ul></li></ul><h3 id="与CAP理论的联系"><a href="#与CAP理论的联系" class="headerlink" title="与CAP理论的联系"></a>与CAP理论的联系</h3><p>NWR是实践中实现<strong>AP系统</strong>时，控制**一致性(C)**的重要手段：</p><ul><li>无分区时：<code>W + R &gt; N</code>能保证强读写一致性。</li><li>发生分区时：系统可能无法满足 <code>W</code>或 <code>R</code>的要求，此时<code>W</code>和 <code>R</code>值的选择直接决定了在分区场景下系统更倾向于A还是C</li></ul><h1 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h1><ul><li>（强推）<a href="https://cloud.tencent.com/developer/article/1860632">一文看懂｜分布式系统之CAP理论</a></li><li>（内容十分详细，推荐）<a href="https://github.com/erdengk/notes/blob/main/%E7%AC%94%E8%AE%B0/3%20%E5%88%86%E5%B8%83%E5%BC%8F/1%20%E5%88%86%E5%B8%83%E5%BC%8F%E7%90%86%E8%AE%BA%E7%9B%B8%E5%85%B3.md">分布式理论相关</a></li><li>（学习框架）<a href="https://erdengk.top/archives/fen-bu-shi-jian-dan-ru-men-zhi-shi">分布式简单入门知识</a></li><li><a href="https://artisan.blog.csdn.net/article/details/146461688">架构思维：从CAP到PACELC到BASE</a></li><li><a href="https://www.changping.me/2020/04/10/distributed-theory-cap-pacelc/">分布式系统架构设计 – 第21式 - 基础理论 - 从CAP到PACELC</a></li><li><a href="https://cloud.tencent.com/developer/article/1752382">分布式一致性协议 - CAP、BASE、NWR</a></li><li><a href="https://blog.csdn.net/yangshangwei/article/details/144519911">分布式协同 - 分布式事务一二事儿</a></li><li><a href="https://blog.csdn.net/hzf0701/article/details/142931603">分布式系统理论详解：CAP、BASE、PACELC</a></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;什么是CAP？&quot;&gt;&lt;a href=&quot;#什么是CAP？&quot; class=&quot;headerlink&quot; title=&quot;什么是CAP？&quot;&gt;&lt;/a&gt;什么是CAP？&lt;/h1&gt;&lt;div class=&quot;note purple flat&quot;&gt;&lt;p&gt;&lt;strong&gt;CAP 理论&lt;/stro</summary>
      
    
    
    
    <category term="学习笔记" scheme="https://atelieryu.site/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="分布式" scheme="https://atelieryu.site/tags/%E5%88%86%E5%B8%83%E5%BC%8F/"/>
    
  </entry>
  
  <entry>
    <title>C++高性能服务器框架Sylar（三）——线程模块</title>
    <link href="https://atelieryu.site/Sylar%E7%BA%BF%E7%A8%8B%E6%A8%A1%E5%9D%97.html"/>
    <id>https://atelieryu.site/Sylar%E7%BA%BF%E7%A8%8B%E6%A8%A1%E5%9D%97.html</id>
    <published>2025-04-02T06:40:00.000Z</published>
    <updated>2025-04-03T08:34:00.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="模块概述"><a href="#模块概述" class="headerlink" title="模块概述"></a>模块概述</h1><p>本模块主要进行线程及各类线程同步机制的封装，提供线程类（thread.h）和线程同步类（mutex.h）。</p><p>线程类：</p><ul><li><code>Thread</code>：构造函数传入线程入口函数和线程名称。线程类构造之后线程即开始运行，构造函数在确保线程真正开始运行之后返回。</li></ul><p>线程同步类：</p><ul><li><code>Semaphore</code>: 计数信号量，基于<code>sem_t</code>实现</li><li><code>Mutex</code>: 互斥锁，基于<code>pthread_mutex_t</code>实现</li><li><code>RWMutex</code>: 读写锁，基于<code>pthread_rwlock_t</code>实现</li><li><code>Spinlock</code>: 自旋锁，基于<code>pthread_spinlock_t</code>实现</li></ul><h2 id="几个注意点"><a href="#几个注意点" class="headerlink" title="几个注意点"></a>几个注意点</h2><p>Q：为什么不直接使用C++提供的std::thread，而是选择封装pthread_t？</p><p>A：C++的std::thread也是基于pthread实现的，但它的线程同步机制不完善，没有提供互斥量，RWMutex，Spinlock等应对高并发场景的线程同步机制。所以选择自己封装</p><hr><p>Q：线程入口函数只支持void(void)吗？</p><p>A：实际使用时可以结合std::bind来绑定参数，这样就相当于支持任何类型和数量的参数</p><hr><p>Q：有了协程，为什么还需要线程？</p><p>A：只有协程无法利用多Cpu，需要使用线程弥补协程的一些缺点，进行互补</p><h1 id="线程类Thread"><a href="#线程类Thread" class="headerlink" title="线程类Thread"></a>线程类Thread</h1><h2 id="thread-h"><a href="#thread-h" class="headerlink" title="thread.h"></a>thread.h</h2><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Thread</span>: <span class="keyword">public</span> Noncopyable&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="keyword">using</span> ptr = std::shared_ptr&lt;Thread&gt;;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">Thread</span>(std::function&lt;<span class="built_in">void</span>()&gt; cb, <span class="type">const</span> std::string&amp; name);</span><br><span class="line"></span><br><span class="line">    ~<span class="built_in">Thread</span>();</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="type">pid_t</span> <span class="title">getId</span><span class="params">()</span> <span class="type">const</span> </span>&#123; <span class="keyword">return</span> m_id; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function">std::string <span class="title">getName</span><span class="params">()</span> <span class="type">const</span> </span>&#123; <span class="keyword">return</span> m_name; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 线程的join操作：阻塞等待线程执行完成</span></span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">join</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="function"><span class="type">static</span> Thread* <span class="title">GetThis</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="function"><span class="type">static</span> std::string <span class="title">GetName</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="function"><span class="type">static</span> <span class="type">void</span> <span class="title">SetName</span><span class="params">(<span class="type">const</span> std::string&amp; name)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 初始化线程属性并执行线程的任务函数</span></span><br><span class="line">    <span class="function"><span class="type">static</span> <span class="type">void</span>* <span class="title">run</span><span class="params">(<span class="type">void</span>* arg)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="comment">// 全局的线程id（内核态id），主要用于日志等操作</span></span><br><span class="line">    <span class="type">pid_t</span> m_id = <span class="number">-1</span>;</span><br><span class="line">    <span class="comment">// pthread返回的线程id标识（用户态id）</span></span><br><span class="line">    <span class="type">pthread_t</span> m_thread = <span class="number">0</span>;</span><br><span class="line">    <span class="comment">// 线程需要执行的任务函数</span></span><br><span class="line">    std::function&lt;<span class="type">void</span>()&gt; m_cb;</span><br><span class="line">    <span class="comment">// 线程名称</span></span><br><span class="line">    std::string m_name;</span><br><span class="line">    <span class="comment">// 信号量</span></span><br><span class="line">    Semaphore m_semaphore;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h3 id="Noncopyable"><a href="#Noncopyable" class="headerlink" title="Noncopyable"></a>Noncopyable</h3><p>线程对象理论上是不能拷贝的，所以需要禁用它的拷贝构造与拷贝赋值。通过继承一个Noncopyable类来实现这一操作</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Noncopyable</span>&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">Noncopyable</span>() = <span class="keyword">default</span>;</span><br><span class="line"></span><br><span class="line">    ~<span class="built_in">Noncopyable</span>() = <span class="keyword">default</span>;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">Noncopyable</span>(<span class="type">const</span> Noncopyable&amp;) = <span class="keyword">delete</span>;</span><br><span class="line"></span><br><span class="line">    Noncopyable&amp; <span class="keyword">operator</span>=(<span class="type">const</span> Noncopyable&amp;) = <span class="keyword">delete</span>;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h2 id="构造函数"><a href="#构造函数" class="headerlink" title="构造函数"></a>构造函数</h2><p>调用pthread_create创建子线程，传入<code>Thread::run()</code>作为回调函数，传入Thread实例自身（this）作为run()的参数。实际的初始化操作在<code>run()</code>中完成（<code>run()</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></pre></td><td class="code"><pre><span class="line">Thread::<span class="built_in">Thread</span>(std::function&lt;<span class="built_in">void</span>()&gt; cb, <span class="type">const</span> std::string&amp; name)</span><br><span class="line">    : <span class="built_in">m_cb</span>(cb)</span><br><span class="line">    , <span class="built_in">m_name</span>(name)&#123;</span><br><span class="line">    <span class="keyword">if</span>(name.<span class="built_in">empty</span>())&#123;</span><br><span class="line">        m_name = <span class="string">&quot;UNKNOW&quot;</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// pthread_create：创建线程，创建成功返回0</span></span><br><span class="line">    <span class="comment">//  通过传递 this 指针，可以在新线程的上下文中访问调用 Thread 的当前实例对象，</span></span><br><span class="line">    <span class="comment">//  从而允许线程函数 run() 操作该对象的成员变量和成员函数</span></span><br><span class="line">    <span class="type">int</span> rt = <span class="built_in">pthread_create</span>(&amp;m_thread, <span class="literal">nullptr</span>, &amp;Thread::run, <span class="keyword">this</span>);</span><br><span class="line">    <span class="keyword">if</span>(rt)&#123;</span><br><span class="line">        <span class="built_in">SYLAR_LOG_ERROR</span>(g_logger) &lt;&lt; <span class="string">&quot;pthread_create fail, errnum=&quot;</span> &lt;&lt; rt</span><br><span class="line">                                  &lt;&lt; <span class="string">&quot; name=&quot;</span> &lt;&lt; name;</span><br><span class="line">        <span class="keyword">throw</span> std::<span class="built_in">logic_error</span>(<span class="string">&quot;pthread_create error&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 阻塞信号量，等待run()中线程初始化完成</span></span><br><span class="line">    m_semaphore.<span class="built_in">wait</span>();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="信号量"><a href="#信号量" class="headerlink" title="信号量"></a>信号量</h3><p>线程类需要保证在构造完成之后线程函数一定已经处于运行状态，这里通过一个信号量来实现的，构造函数在创建线程后会一直阻塞，直到线程函数运行并且通知信号量，构造函数才会返回，而构造函数一旦返回，就说明线程函数已经在执行了。细节如下：</p><ul><li>构造函数中对信号量上锁，<code>run()</code>中完成线程初始化后再对信号量解锁</li><li>这样操作保证了只有Thread初始化完成后主线程才能调用该线程实例</li><li>虽然wait操作和notify操作是分别在主线程和子线程中执行的，但他们操作的是同一个Thread实例的m_semaphore成员变量</li></ul><h2 id="run"><a href="#run" class="headerlink" title="run"></a>run</h2><p><code>run()</code>是<strong>子线程的入口函数</strong>，执行实际的线程初始化操作，初始化线程属性并运行真正的子线程任务函数</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="function"><span class="type">void</span>* <span class="title">Thread::run</span><span class="params">(<span class="type">void</span>* arg)</span></span>&#123;</span><br><span class="line">    <span class="comment">// pthread_create中，run接收一个this，即Thread*参数</span></span><br><span class="line">    Thread* thread = (Thread*)arg;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 初始化当前线程的属性并绑定到线程局部存储</span></span><br><span class="line">    t_thread = thread;</span><br><span class="line">    t_thread_name = thread-&gt;m_name;</span><br><span class="line">    thread-&gt;m_id = sylar::<span class="built_in">GetThreadId</span>();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// pthread_setname_np：设置线程名称，只取m_name的前15个字符（Linux 限制线程名称最长为 16 字符，包括末尾&#x27;\0&#x27;）</span></span><br><span class="line">    <span class="built_in">pthread_setname_np</span>(<span class="built_in">pthread_self</span>(), thread-&gt;m_name.<span class="built_in">substr</span>(<span class="number">0</span>, <span class="number">15</span>).<span class="built_in">c_str</span>());</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 将线程的任务回调函数移动到局部变量 cb，避免线程对象中的 m_cb 被多线程并发访问</span></span><br><span class="line">    std::function&lt;<span class="type">void</span>()&gt; cb;</span><br><span class="line">    cb.<span class="built_in">swap</span>(thread-&gt;m_cb);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 线程初始化完成后对信号量解锁</span></span><br><span class="line">    thread-&gt;m_semaphore.<span class="built_in">notify</span>();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 执行任务函数</span></span><br><span class="line">    <span class="built_in">cb</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">&#125;</span><br></pre></td></tr></table></figure><p>几个注意点：</p><ul><li><p>run的参数：主线程的Thread构造函数中，<code>pthread_create</code>给run指定了一个参数this，即主线程中创建的Thread对象自身，这一操作使得run<strong>可以在子线程中操作主线程中创建的Thread对象</strong></p></li><li><p><code>GetThreadId()</code>：通过系统调用返回一个系统范围内唯一的线程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="function"><span class="type">pid_t</span> <span class="title">GetThreadId</span><span class="params">()</span></span>&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">syscall</span>(SYS_gettid);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p><code>cb.swap</code>：这一步实际执行了两步操作：</p><ol><li>获取回调函数</li><li>清空m_cb，避免它被多线程并发访问</li></ol></li></ul><h3 id="线程局部量"><a href="#线程局部量" class="headerlink" title="线程局部量"></a>线程局部量</h3><p><code>thread_local static</code>变量：</p><ul><li>static：生命周期为静态的（直到程序结束才销毁）</li><li>thread_local：线程局部存储变量，每个线程有独立的实例</li></ul><p>声明两个线程局部量：</p><ul><li><code>t_thread</code>：为每个线程提供一个指向当前 Thread 对象的指针，便于在全局范围内访问与当前线程关联的 Thread 实例，而无需显式传递线程实例</li><li><code>t_thread_name</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></pre></td><td class="code"><pre><span class="line"><span class="type">static</span> <span class="keyword">thread_local</span> Thread* t_thread = <span class="literal">nullptr</span>;</span><br><span class="line"><span class="type">static</span> <span class="keyword">thread_local</span> std::string t_thread_name = <span class="string">&quot;UNKNOW&quot;</span>;</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></pre></td><td class="code"><pre><span class="line"><span class="function">Thread* <span class="title">Thread::GetThis</span><span class="params">()</span></span>&#123;</span><br><span class="line">    <span class="keyword">return</span> t_thread;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function">std::string <span class="title">Thread::GetName</span><span class="params">()</span></span>&#123;</span><br><span class="line">    <span class="keyword">return</span> t_thread_name;</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">Thread::SetName</span><span class="params">(<span class="type">const</span> std::string&amp; name)</span></span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(name.<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 class="comment">// 如果当前线程已经创建成功，就设置其name</span></span><br><span class="line">    <span class="keyword">if</span>(t_thread)&#123;</span><br><span class="line">        t_thread-&gt;m_name = name;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 设置全局的线程name值</span></span><br><span class="line">    t_thread_name = name;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="线程资源回收"><a href="#线程资源回收" class="headerlink" title="线程资源回收"></a>线程资源回收</h2><h3 id="join"><a href="#join" class="headerlink" title="join"></a>join</h3><p>封装pthread_join，阻塞等待工作线程完成，并重置m_thread：</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="function"><span class="type">void</span> <span class="title">Thread::join</span><span class="params">()</span></span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(m_thread)&#123;</span><br><span class="line">        <span class="comment">// 对当前线程执行join</span></span><br><span class="line">        <span class="type">int</span> rt = <span class="built_in">pthread_join</span>(m_thread, <span class="literal">nullptr</span>);</span><br><span class="line">        <span class="keyword">if</span>(rt)&#123;</span><br><span class="line">            <span class="built_in">SYLAR_LOG_ERROR</span>(g_logger) &lt;&lt; <span class="string">&quot;pthread_joib fail, errnum=&quot;</span> &lt;&lt; rt</span><br><span class="line">                                    &lt;&lt; <span class="string">&quot; name=&quot;</span> &lt;&lt; m_name;</span><br><span class="line">            <span class="keyword">throw</span> std::<span class="built_in">logic_error</span>(<span class="string">&quot;pthread_join error&quot;</span>);            </span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 重置m_thread</span></span><br><span class="line">        m_thread = <span class="number">0</span>;        </span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="析构函数"><a href="#析构函数" class="headerlink" title="析构函数"></a>析构函数</h3><p>析构函数中调用pthread_detach进行线程分离，实现析构时自动回收线程资源：</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">Thread::~<span class="built_in">Thread</span>()&#123;</span><br><span class="line">    <span class="comment">// 对当前线程进行detach</span></span><br><span class="line">    <span class="keyword">if</span>(m_thread)&#123;</span><br><span class="line">        <span class="built_in">pthread_detach</span>(m_thread);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h1 id="线程同步类Mutex"><a href="#线程同步类Mutex" class="headerlink" title="线程同步类Mutex"></a>线程同步类Mutex</h1><p>该部分主要使用RAII机制封装各类线程同步机制，包括信号量、读写锁、自旋锁</p><h2 id="信号量Semaphore"><a href="#信号量Semaphore" class="headerlink" title="信号量Semaphore"></a>信号量Semaphore</h2><p>RAII封装<code>semaphore_t</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="keyword">class</span> <span class="title class_">Semaphore</span>: <span class="keyword">public</span> Noncopyable&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="keyword">explicit</span> <span class="title">Semaphore</span><span class="params">(<span class="type">uint32_t</span> count = <span class="number">0</span>)</span></span>;</span><br><span class="line"></span><br><span class="line">    ~<span class="built_in">Semaphore</span>();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 信号量P操作：获取/等待信号量</span></span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">wait</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 信号量V操作：释放信号量</span></span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">notify</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="comment">// 信号量变量</span></span><br><span class="line">    <span class="type">sem_t</span> m_semaphore;</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></pre></td><td class="code"><pre><span class="line">Semaphore::<span class="built_in">Semaphore</span>(<span class="type">uint32_t</span> count)&#123;</span><br><span class="line">    <span class="keyword">if</span>(<span class="built_in">sem_init</span>(&amp;m_semaphore, <span class="number">0</span>, count))&#123;</span><br><span class="line">        <span class="built_in">SYLAR_LOG_ERROR</span>(g_logger) &lt;&lt; <span class="string">&quot;sem_init error&quot;</span>;</span><br><span class="line">        <span class="keyword">throw</span> std::<span class="built_in">logic_error</span>(<span class="string">&quot;sem_init error&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">Semaphore::~<span class="built_in">Semaphore</span>()&#123;</span><br><span class="line">    <span class="built_in">sem_destroy</span>(&amp;m_semaphore);</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">Semaphore::wait</span><span class="params">()</span></span>&#123;</span><br><span class="line">    <span class="comment">// sem_t不需要注意虚假唤醒问题</span></span><br><span class="line">    <span class="keyword">if</span>(<span class="built_in">sem_wait</span>(&amp;m_semaphore))&#123;</span><br><span class="line">        <span class="built_in">SYLAR_LOG_ERROR</span>(g_logger) &lt;&lt; <span class="string">&quot;sem_wait error&quot;</span>;</span><br><span class="line">        <span class="keyword">throw</span> std::<span class="built_in">logic_error</span>(<span class="string">&quot;sem_wait error&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="function"><span class="type">void</span> <span class="title">Semaphore::notify</span><span class="params">()</span></span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(<span class="built_in">sem_post</span>(&amp;m_semaphore))&#123;</span><br><span class="line">        <span class="built_in">SYLAR_LOG_ERROR</span>(g_logger) &lt;&lt; <span class="string">&quot;sem_post error&quot;</span>;</span><br><span class="line">        <span class="keyword">throw</span> std::<span class="built_in">logic_error</span>(<span class="string">&quot;sem_post error&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>可能的优化点：改为使用std::condition_variable实现信号量</p><ul><li>sem_t的wait和notify操作会发生用户态与内核态间的切换，而std::condition_variable为纯用户态，无须切换到内核态</li><li>对于旨在避免内核态的上下文切换的协程，选用std::condition_variable可能更为合适</li><li>sem_t主要在跨进程间操作上有着不可替代性</li></ul><h2 id="局部锁ScopedLockImpl"><a href="#局部锁ScopedLockImpl" class="headerlink" title="局部锁ScopedLockImpl"></a>局部锁ScopedLockImpl</h2><p>RAII封装局部锁：接收一个锁，构造时自动加锁，析构时自动解锁</p><p>功能类似<code>std::lock_guard</code>，但支持手动上锁&#x2F;解锁</p><p>后续每一个锁都可以<strong>声明一个Lock类型成员，用于实现范围锁</strong>，如：</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="keyword">using</span> Lock = ScopedLockImpl&lt;Mutex&gt;</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T&gt;</span><br><span class="line"><span class="keyword">struct</span> <span class="title class_">ScopedLockImpl</span>&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">ScopedLockImpl</span>(T&amp; mutex)</span><br><span class="line">        : <span class="built_in">m_mutex</span>(mutex)</span><br><span class="line">        , <span class="built_in">m_locked</span>(<span class="literal">false</span>)&#123;</span><br><span class="line">        <span class="built_in">lock</span>();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    ~<span class="built_in">ScopedLockImpl</span>()&#123;</span><br><span class="line">        <span class="built_in">unlock</span>();</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="function"><span class="type">void</span> <span class="title">lock</span><span class="params">()</span></span>&#123;</span><br><span class="line">        <span class="keyword">if</span>(!m_locked)&#123;</span><br><span class="line">            m_mutex.<span class="built_in">lock</span>();</span><br><span class="line">            m_locked = <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">    <span class="comment">// 解锁操作</span></span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">unlock</span><span class="params">()</span></span>&#123;</span><br><span class="line">        <span class="keyword">if</span>(m_locked)&#123;</span><br><span class="line">            m_mutex.<span class="built_in">unlock</span>();</span><br><span class="line">            m_locked = <span class="literal">false</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">private</span>:</span><br><span class="line">    T&amp; m_mutex;</span><br><span class="line">    <span class="comment">// 当前锁的状态</span></span><br><span class="line">    <span class="type">bool</span> m_locked;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h2 id="读写锁RWMutex"><a href="#读写锁RWMutex" class="headerlink" title="读写锁RWMutex"></a>读写锁RWMutex</h2><p>RAII封装<code>pthread_rwlock_t</code></p><p>读写锁可实现<strong>高并发读</strong>，支持多个线程同时读取，**<span class='p red'>适合读多写少的场景</span>**</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">RWMutex</span>: <span class="keyword">public</span> Noncopyable&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="keyword">using</span> ReadLock = ReadScopedLockImpl&lt;RWMutex&gt;;</span><br><span class="line">    <span class="keyword">using</span> WriteLock = WriteScopedLockImpl&lt;RWMutex&gt;;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">RWMutex</span>()&#123;</span><br><span class="line">        <span class="built_in">pthread_rwlock_init</span>(&amp;m_mutex, <span class="literal">nullptr</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    ~<span class="built_in">RWMutex</span>()&#123;</span><br><span class="line">        <span class="built_in">pthread_rwlock_destroy</span>(&amp;m_mutex);</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">rdlock</span><span class="params">()</span></span>&#123;</span><br><span class="line">        <span class="built_in">pthread_rwlock_rdlock</span>(&amp;m_mutex);</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">wrlock</span><span class="params">()</span></span>&#123;</span><br><span class="line">        <span class="built_in">pthread_rwlock_wrlock</span>(&amp;m_mutex);</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">unlock</span><span class="params">()</span></span>&#123;</span><br><span class="line">        <span class="built_in">pthread_rwlock_unlock</span>(&amp;m_mutex);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="type">pthread_rwlock_t</span> m_mutex;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h2 id="读写局部锁"><a href="#读写局部锁" class="headerlink" title="读写局部锁"></a>读写局部锁</h2><p>接收一个锁（一般是一个RWMutex锁），构造时自动上锁，析构时自动解锁</p><p>使用atomic变量来保证对<code>m_locked</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// RAII类封装局部写锁</span></span><br><span class="line"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T&gt;</span><br><span class="line"><span class="keyword">struct</span> <span class="title class_">ReadScopedLockImpl</span>&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">ReadScopedLockImpl</span>(T&amp; mutex)</span><br><span class="line">        : <span class="built_in">m_mutex</span>(mutex)</span><br><span class="line">        , <span class="built_in">m_locked</span>(<span class="literal">false</span>)&#123;</span><br><span class="line">        <span class="built_in">lock</span>();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    ~<span class="built_in">ReadScopedLockImpl</span>()&#123;</span><br><span class="line">        <span class="built_in">unlock</span>();</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="function"><span class="type">void</span> <span class="title">lock</span><span class="params">()</span></span>&#123;</span><br><span class="line">        <span class="keyword">if</span>(!m_locked)&#123;</span><br><span class="line">            m_mutex.<span class="built_in">rdlock</span>();</span><br><span class="line">            m_locked = <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">    <span class="comment">// 解锁操作</span></span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">unlock</span><span class="params">()</span></span>&#123;</span><br><span class="line">        <span class="keyword">if</span>(m_locked)&#123;</span><br><span class="line">            m_mutex.<span class="built_in">unlock</span>();</span><br><span class="line">            m_locked = <span class="literal">false</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">private</span>:</span><br><span class="line">    T&amp; m_mutex;</span><br><span class="line">    <span class="comment">// 使用atomic变量来保证m_locked的多线程安全性</span></span><br><span class="line">    std::atomic&lt;<span class="type">bool</span>&gt; m_locked;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// RAII类封装范围锁</span></span><br><span class="line"><span class="comment">// 接收一个锁，构造时自动加锁，析构时自动解锁</span></span><br><span class="line"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T&gt;</span><br><span class="line"><span class="keyword">struct</span> <span class="title class_">WriteScopedLockImpl</span>&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">WriteScopedLockImpl</span>(T&amp; mutex)</span><br><span class="line">        : <span class="built_in">m_mutex</span>(mutex)</span><br><span class="line">        , <span class="built_in">m_locked</span>(<span class="literal">false</span>)&#123;</span><br><span class="line">        <span class="built_in">lock</span>();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    ~<span class="built_in">WriteScopedLockImpl</span>()&#123;</span><br><span class="line">        <span class="built_in">unlock</span>();</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="function"><span class="type">void</span> <span class="title">lock</span><span class="params">()</span></span>&#123;</span><br><span class="line">        <span class="keyword">if</span>(!m_locked)&#123;</span><br><span class="line">            m_mutex.<span class="built_in">wrlock</span>();</span><br><span class="line">            m_locked = <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">    <span class="comment">// 解锁操作</span></span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">unlock</span><span class="params">()</span></span>&#123;</span><br><span class="line">        <span class="keyword">if</span>(m_locked)&#123;</span><br><span class="line">            m_mutex.<span class="built_in">unlock</span>();</span><br><span class="line">            m_locked = <span class="literal">false</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">private</span>:</span><br><span class="line">    T&amp; m_mutex;</span><br><span class="line">    <span class="comment">// 使用atomic变量来保证m_locked的多线程安全性</span></span><br><span class="line">    std::atomic&lt;<span class="type">bool</span>&gt; m_locked;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h2 id="互斥量Mutex"><a href="#互斥量Mutex" class="headerlink" title="互斥量Mutex"></a>互斥量Mutex</h2><p>RAII封装<code>pthread_mutex_t</code></p><p>互斥量与读写锁的对比：</p><ul><li>读写锁：可实现<strong>高并发读</strong>，支持多个线程同时读取，适合<strong>读多写少</strong>的场景</li><li>互斥量：更简单高效，适合 <strong><span class='p red'>读写均衡或写多读少</span></strong> 的场景</li><li>如果初始需求不明确, <strong><span class='p red'>优先使用互斥量，后续可以根据性能瓶颈替换为读写锁</span></strong></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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Mutex</span>&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="keyword">using</span> Lock = ScopedLockImpl&lt;Mutex&gt;;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">Mutex</span>()&#123;</span><br><span class="line">        <span class="built_in">pthread_mutex_init</span>(&amp;m_mutex, <span class="number">0</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    ~<span class="built_in">Mutex</span>()&#123;</span><br><span class="line">        <span class="built_in">pthread_mutex_destroy</span>(&amp;m_mutex);</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">lock</span><span class="params">()</span></span>&#123;</span><br><span class="line">        <span class="built_in">pthread_mutex_lock</span>(&amp;m_mutex);</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">unlock</span><span class="params">()</span></span>&#123;</span><br><span class="line">        <span class="built_in">pthread_mutex_unlock</span>(&amp;m_mutex);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="type">pthread_mutex_t</span> m_mutex;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h2 id="自旋锁SpinLock"><a href="#自旋锁SpinLock" class="headerlink" title="自旋锁SpinLock"></a>自旋锁SpinLock</h2><p>RAII封装<code>pthread_spinlock_t</code></p><p>自旋锁使锁被占用时，线程进入忙等状态，所以自旋锁适合 <strong><span class='p red'>短时间持有锁的场景</span></strong></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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">SpinLock</span>&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="keyword">using</span> Lock = ScopedLockImpl&lt;SpinLock&gt;;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">SpinLock</span>()&#123;</span><br><span class="line">        <span class="built_in">pthread_spin_init</span>(&amp;m_mutex, <span class="number">0</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    ~<span class="built_in">SpinLock</span>()&#123;</span><br><span class="line">        <span class="built_in">pthread_spin_destroy</span>(&amp;m_mutex);</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">lock</span><span class="params">()</span></span>&#123;</span><br><span class="line">        <span class="built_in">pthread_spin_lock</span>(&amp;m_mutex);</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">unlock</span><span class="params">()</span></span>&#123;</span><br><span class="line">        <span class="built_in">pthread_spin_unlock</span>(&amp;m_mutex);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="type">pthread_spinlock_t</span> m_mutex;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;模块概述&quot;&gt;&lt;a href=&quot;#模块概述&quot; class=&quot;headerlink&quot; title=&quot;模块概述&quot;&gt;&lt;/a&gt;模块概述&lt;/h1&gt;&lt;p&gt;本模块主要进行线程及各类线程同步机制的封装，提供线程类（thread.h）和线程同步类（mutex.h）。&lt;/p&gt;
&lt;p&gt;线</summary>
      
    
    
    
    <category term="学习笔记" scheme="https://atelieryu.site/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="后端" scheme="https://atelieryu.site/tags/%E5%90%8E%E7%AB%AF/"/>
    
    <category term="C++" scheme="https://atelieryu.site/tags/C/"/>
    
    <category term="服务器框架" scheme="https://atelieryu.site/tags/%E6%9C%8D%E5%8A%A1%E5%99%A8%E6%A1%86%E6%9E%B6/"/>
    
    <category term="线程" scheme="https://atelieryu.site/tags/%E7%BA%BF%E7%A8%8B/"/>
    
  </entry>
  
  <entry>
    <title>C++高性能服务器框架Sylar（二）——配置模块</title>
    <link href="https://atelieryu.site/Sylar%E9%85%8D%E7%BD%AE.html"/>
    <id>https://atelieryu.site/Sylar%E9%85%8D%E7%BD%AE.html</id>
    <published>2025-04-01T03:40:00.000Z</published>
    <updated>2025-04-02T08:07:00.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="模块概述"><a href="#模块概述" class="headerlink" title="模块概述"></a>模块概述</h1><p>基于YAML配置文件实现的一个配置模块</p><ul><li>难点：Lexical_cast的泛化与偏特化实现</li><li>设计点：<ul><li>将配置项ConfigVar抽象出虚基类ConfigVarBase，使得Config可以通过多态机制统一管理配置类</li><li>通过Lexical_cast，将“支持更多的类型”，变为“支持更多类型的Lexcial_cast偏特化实现”</li></ul></li></ul><h2 id="配置项概述"><a href="#配置项概述" class="headerlink" title="配置项概述"></a>配置项概述</h2><p>一项配置应该包括以下要素：</p><ol><li>名称：对应一个字符串，必须唯一，不能与其他配置项产生冲突。支持名称的层级，如<code>tcp.connect.timeout</code></li><li>类型：支持基本类型、复杂类型（STL）和自定义类型。</li><li>值：配置项的值</li><li>默认值：考虑到用户不一定总是会显式地给配置项赋值，所以配置项最好有一个默认值（约定优于配置）</li><li>配置变更通知：一旦用户更新了配置值，那么应该通知所有使用了这项配置的代码，以便于进行一些具体的操作，比如重新打开文件，重新起监听端口等。</li><li>校验方法：更新配置时会调用校验方法进行校验，以保证用户不会给配置项设置一个非法的值。</li></ol><h2 id="配置模块功能概述"><a href="#配置模块功能概述" class="headerlink" title="配置模块功能概述"></a>配置模块功能概述</h2><p>本项目配置模块具备的基本功能：</p><ol><li>支持定义&#x2F;声明配置项：也就是在提供配置名称、类型以及可选的默认值的情况下生成一个可用的配置项。</li><li>支持更新配置项的值：用户可以使用新的值来覆盖掉原来的值。</li><li>支持从预置的途径中加载配置项：一般是配置文件。支持基本数据类型与复杂数据类型的加载，比如直接从配置文件中加载一个map类型的配置项，或是直接从一个预定格式的配置文件中加载一个自定义结构体。</li><li>支持给配置项注册配置变更通知：用户可以为配置项注册回调函数，让程序知道某项配置被修改了，以便于进行一些操作。比如对于网络服务器而言，如果服务器端口配置变化了，那程序应该重新起监听端口。由于一项配置可能在多个地方引用，所以配置变更回调函数是一个数组的形式。</li><li>支持导出当前配置：将当前配置导出为YAML字符串</li></ol><p>待实现的功能：</p><ol><li>支持给配置项设置校验方法：配置项在定义时也可以指定一个校验方法，以保证该项配置不会被设置成一个非法的值，比如对于文件路径类的配置，可以通过校验方法来确保该路径一定存在。</li><li>从命令行参数或是网络服务器中加载配置项</li></ol><h1 id="Yaml"><a href="#Yaml" class="headerlink" title="Yaml"></a>Yaml</h1><h2 id="基本语法"><a href="#基本语法" class="headerlink" title="基本语法"></a>基本语法</h2><p>Yaml书写方式比较简单，主要注意缩进问题</p><figure class="highlight yaml"><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"># Person information</span></span><br><span class="line"><span class="attr">person:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">John</span> <span class="string">Doe</span></span><br><span class="line">  <span class="attr">age:</span> <span class="number">30</span></span><br><span class="line">  <span class="attr">address:</span></span><br><span class="line">    <span class="attr">street:</span> <span class="number">123</span> <span class="string">Main</span> <span class="string">St</span></span><br><span class="line">    <span class="attr">city:</span> <span class="string">Somewhere</span></span><br><span class="line">    <span class="attr">country:</span> <span class="string">USA</span></span><br><span class="line">  <span class="attr">hobbies:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">reading</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">hiking</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">coding</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># A list of items</span></span><br><span class="line"><span class="attr">items:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">apple</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">banana</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">orange</span></span><br></pre></td></tr></table></figure><h3 id="键值对（Mapping）"><a href="#键值对（Mapping）" class="headerlink" title="键值对（Mapping）"></a><strong>键值对（Mapping）</strong></h3><p>键值对使用 <code>:</code> 进行分隔，键和值之间用空格分开。</p><figure class="highlight yaml"><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="attr">name:</span> <span class="string">John</span> <span class="string">Doe</span></span><br><span class="line"><span class="attr">age:</span> <span class="number">30</span></span><br></pre></td></tr></table></figure><h3 id="列表（Sequence）"><a href="#列表（Sequence）" class="headerlink" title="列表（Sequence）"></a><strong>列表（Sequence）</strong></h3><p>列表元素前使用 <code>-</code> 符号进行标记。</p><figure class="highlight yaml"><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="attr">fruits:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">apple</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">banana</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">cherry</span></span><br></pre></td></tr></table></figure><h3 id="嵌套结构"><a href="#嵌套结构" class="headerlink" title="嵌套结构"></a><strong>嵌套结构</strong></h3><p>YAML 使用<strong>缩进</strong>来表示嵌套结构。通常使用<strong>2个空格</strong>作为缩进。</p><figure class="highlight yaml"><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="attr">person:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">Alice</span></span><br><span class="line">  <span class="attr">age:</span> <span class="number">28</span></span><br><span class="line">  <span class="attr">address:</span></span><br><span class="line">    <span class="attr">street:</span> <span class="number">123</span> <span class="string">Main</span> <span class="string">St</span></span><br><span class="line">    <span class="attr">city:</span> <span class="string">Wonderland</span></span><br></pre></td></tr></table></figure><h3 id="注释（Comments）"><a href="#注释（Comments）" class="headerlink" title="注释（Comments）"></a><strong>注释（Comments）</strong></h3><p>注释以 <code>#</code> 开头，注释内容会被忽略。</p><figure class="highlight yaml"><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"># This is a comment</span></span><br><span class="line"><span class="attr">name:</span> <span class="string">John</span> <span class="string">Doe</span>  <span class="comment"># Inline comment</span></span><br></pre></td></tr></table></figure><h3 id="多行字符串"><a href="#多行字符串" class="headerlink" title="多行字符串"></a><strong>多行字符串</strong></h3><p>使用 <code>|</code> 或 <code>&gt;</code> 来表示多行字符串。</p><figure class="highlight yaml"><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="attr">description:</span> <span class="string">|</span></span><br><span class="line"><span class="string">  This is a multi-line</span></span><br><span class="line"><span class="string">  string that keeps line breaks.</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="attr">note:</span> <span class="string">&gt;</span></span><br><span class="line"><span class="string">  This is a folded</span></span><br><span class="line"><span class="string">  multi-line string that</span></span><br><span class="line"><span class="string">  turns into a single line.</span></span><br></pre></td></tr></table></figure><h3 id="复杂数据类型"><a href="#复杂数据类型" class="headerlink" title="复杂数据类型"></a><strong>复杂数据类型</strong></h3><p>YAML 支持复杂数据类型，如时间、日期、布尔值等。</p><figure class="highlight yaml"><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="attr">date:</span> <span class="number">2024-12-25</span></span><br><span class="line"><span class="attr">boolean:</span> <span class="literal">true</span></span><br></pre></td></tr></table></figure><h2 id="yaml-cpp"><a href="#yaml-cpp" class="headerlink" title="yaml-cpp"></a>yaml-cpp</h2><p>项目中主要使用yaml-cpp来进行yaml文件的解析</p><h3 id="依赖安装"><a href="#依赖安装" class="headerlink" title="依赖安装"></a>依赖安装</h3><ul><li>方法1：apt安装依赖：</li></ul><figure class="highlight bash"><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">sudo</span> apt install libyaml-cpp-dev</span><br></pre></td></tr></table></figure><ul><li>方法2：git从源码安装最新版本</li></ul><figure class="highlight bash"><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">git <span class="built_in">clone</span> https://github.com/jbeder/yaml-cpp.git</span><br><span class="line"><span class="built_in">cd</span> yaml-cpp</span><br><span class="line"><span class="built_in">mkdir</span> build</span><br><span class="line"><span class="built_in">cd</span> build</span><br><span class="line">cmake ..</span><br><span class="line">make &amp;&amp; make install</span><br></pre></td></tr></table></figure><h3 id="用法"><a href="#用法" class="headerlink" title="用法"></a>用法</h3><p>本项目中只使用部分Api，对于yaml-cpp掌握下面这些即可：</p><blockquote><p>YAML::NodeType</p></blockquote><p>yaml-cpp会自动解析Yaml字符串，并将解析结果变为以下几种YAML::NodeType：</p><ul><li>Scalar：简单类型</li><li>Map：键值对</li><li>Sequence：列表</li></ul><blockquote><p>Yaml的加载</p></blockquote><ul><li>YAML::Load：从字符串中加载YAML::Node</li><li>YAML::LoadFile：从文件中加载YAML::Node</li></ul><h1 id="配置模块设计"><a href="#配置模块设计" class="headerlink" title="配置模块设计"></a>配置模块设计</h1><h2 id="约定优于配置"><a href="#约定优于配置" class="headerlink" title="约定优于配置"></a>约定优于配置</h2><div class="note info modern"><p>约定优于配置的方式可以减少程序员做决定的数量，获得简单的好处，同时兼顾灵活性。</p></div><p>配置模块采用约定优于配置的思想，参考资料：</p><ul><li><a href="https://zh.wikipedia.org/wiki/%E7%BA%A6%E5%AE%9A%E4%BC%98%E4%BA%8E%E9%85%8D%E7%BD%AE">维基百科：约定优于配置</a></li><li><a href="https://cloud.tencent.com/developer/article/1424634">如何理解 SpringBoot 中的约定优于配置 - 云+社区 - 腾讯云</a></li></ul><p>简单来说，约定优于配置的背景条件是，一般来说，程序所依赖的配置项都有一个公认的默认值，也就是所谓的约定。这点有可许多可以参考的例子，比如对于一个http网络服务器，服务端口通常都是80端口，对于配置文件夹路径，一般都是conf文件夹，对于数据库目录，一般都是db或data文件夹。对于这些具有公认约定的配置，就不需要麻烦程序员在程序跑起来后再一项一项地指定了，而是可以初始时就将配置项设置成对应的值。这样，程序员就可以**<span class='p red'>只修改那些约定之外的配置项，然后以最小的代价让程序跑起来</span>**。</p><p>在代码上，约定优于配置的思路体现为所有的配置项在定义时都带一个的默认值，以下是一个sylar配置项的示例，这是一个int类型的配置项，名称为<code>tcp.connect.timeout</code>，初始值为5000。</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="type">static</span> sylar::ConfigVar&lt;<span class="type">int</span>&gt;::ptr g_tcp_connect_timeout = \</span><br><span class="line">sylar::Config::<span class="built_in">Lookup</span>(<span class="string">&quot;tcp.connect.timeout&quot;</span>, <span class="number">5000</span>, <span class="string">&quot;tcp connect timeout&quot;</span>);</span><br></pre></td></tr></table></figure><p>该配置项转换为YAML配置文件后如下：</p><figure class="highlight yaml"><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="attr">tcp:</span></span><br><span class="line">  <span class="attr">connect:</span></span><br><span class="line">    <span class="attr">timeout:</span> <span class="number">10000</span></span><br></pre></td></tr></table></figure><h2 id="模块中的类"><a href="#模块中的类" class="headerlink" title="模块中的类"></a>模块中的类</h2><h3 id="ConfigVarBase"><a href="#ConfigVarBase" class="headerlink" title="ConfigVarBase"></a>ConfigVarBase</h3><p>配置项抽象类，定义了配置项公有的成员和方法：</p><ul><li>配置项的公有成员：<ul><li><code>name</code>：配置项的名称</li><li><code>description</code>：配置项的描述</li></ul></li><li>配置项的公有方法，两个纯虚函数，由派生类负责实现：<ul><li><code>toString</code>：将配置项转换为YAML字符串</li><li><code>fromString</code>：从YAML字符串中载入配置项</li></ul></li></ul><h3 id="ConfigVar："><a href="#ConfigVar：" class="headerlink" title="ConfigVar："></a>ConfigVar：</h3><p>每个配置项对应一个ConfigVar对象</p><p>是一个模板类，有三个模板参数：</p><ul><li><code>T</code>：配置项的类型</li><li><code>FromStr</code>、<code>ToStr</code>：两个仿函数，用于进行YAML字符串与配置项类型T之间的转换，是<strong>实现的重点</strong>，不同类型的配置项需要各自实现它们的偏特化版本</li></ul><p>继承自ConfigVarBase（相当于定义了ConfigVarBase的一系列派生类），在其基础上实现了还需要实现以下功能：</p><ul><li>对配置项数值的更新&#x2F;获取操作：<code>setValue</code> &#x2F; <code>getValue</code></li><li>对配置项的监听回调：<code>addListener</code> &#x2F; <code>delListener</code></li></ul><h3 id="Config："><a href="#Config：" class="headerlink" title="Config："></a>Config：</h3><p>一个单例类（但单例的实现方式与普通的单例模式有所不同），使用map管理ConfigVar配置项</p><p>实现了以下方法：</p><ul><li><code>Lookup</code>：可以在容器中查找&#x2F;新增配置项</li><li><code>LoadFromYaml</code>：从YAML文件中载入配置项</li></ul><h1 id="模块实现"><a href="#模块实现" class="headerlink" title="模块实现"></a>模块实现</h1><h2 id="ConfigVarBase-1"><a href="#ConfigVarBase-1" class="headerlink" title="ConfigVarBase"></a>ConfigVarBase</h2><p>抽象基类，简单定义了get&#x2F;set方法，以及两个纯虚函数<code>fromString</code>和<code>toString</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">ConfigVarBase</span>&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="keyword">using</span> ptr = std::shared_ptr&lt;ConfigVarBase&gt;;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">ConfigVarBase</span>(<span class="type">const</span> std::string&amp; name, <span class="type">const</span> std::string&amp; description = <span class="string">&quot;&quot;</span>)</span><br><span class="line">        :<span class="built_in">m_name</span>(name)</span><br><span class="line">        ,<span class="built_in">m_description</span>(description)&#123;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">virtual</span> ~<span class="built_in">ConfigVarBase</span>()&#123;&#125;</span><br><span class="line"></span><br><span class="line">    <span class="function">std::string <span class="title">getName</span><span class="params">()</span> <span class="type">const</span> </span>&#123; <span class="keyword">return</span> m_name; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function">std::string <span class="title">getDescription</span><span class="params">()</span> <span class="type">const</span> </span>&#123; <span class="keyword">return</span> m_description; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">virtual</span> std::string <span class="title">getTypename</span><span class="params">()</span> <span class="type">const</span> </span>= <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 将配置参数的值转换为字符串</span></span><br><span class="line">    <span class="function"><span class="keyword">virtual</span> std::string <span class="title">toString</span><span class="params">()</span> </span>= <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 从字符串中获取配置参数初始值</span></span><br><span class="line">    <span class="function"><span class="keyword">virtual</span> <span class="type">bool</span> <span class="title">fromString</span><span class="params">(<span class="type">const</span> std::string&amp; val)</span> </span>= <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="comment">// 配置参数的名称</span></span><br><span class="line">    std::string m_name;</span><br><span class="line">    <span class="comment">// 配置参数的描述</span></span><br><span class="line">    std::string m_description;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h2 id="ConfigVar"><a href="#ConfigVar" class="headerlink" title="ConfigVar"></a>ConfigVar</h2><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><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">// FromStr和ToStr：两个仿函数，用于将字符串转换为数值类型 / 将数值转换为字符串</span></span><br><span class="line"><span class="comment">// FromStr: T operator(const std::string&amp;)</span></span><br><span class="line"><span class="comment">// ToStr: std::string operator(const T&amp;)</span></span><br><span class="line"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T, <span class="keyword">typename</span> FromStr = Lexical_cast&lt;std::string, T&gt;</span><br><span class="line">                   , <span class="keyword">typename</span> ToStr = Lexical_cast&lt;T, std::string&gt;&gt;</span><br><span class="line"><span class="keyword">class</span> ConfigVar: <span class="keyword">public</span> ConfigVarBase&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="keyword">using</span> ptr = std::shared_ptr&lt;ConfigVar&gt;;</span><br><span class="line">    <span class="keyword">using</span> on_change_cb = std::function&lt;<span class="built_in">void</span>(<span class="type">const</span> T&amp; oldVal, <span class="type">const</span> T&amp; newVal)&gt;;</span><br><span class="line">    <span class="keyword">using</span> RWMutexType = RWMutex;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">ConfigVar</span>(<span class="type">const</span> std::string&amp; name</span><br><span class="line">            , <span class="type">const</span> T&amp; val</span><br><span class="line">            , <span class="type">const</span> std::string&amp; description = <span class="string">&quot;&quot;</span>)</span><br><span class="line">        : <span class="built_in">ConfigVarBase</span>(name, description)</span><br><span class="line">        , <span class="built_in">m_val</span>(val)&#123;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 调用ToStr()将配置值转换为Yaml字符串返回</span></span><br><span class="line">    <span class="function">std::string <span class="title">toString</span><span class="params">()</span> <span class="keyword">override</span> </span>&#123;</span><br><span class="line">        <span class="keyword">try</span>&#123;</span><br><span class="line">            <span class="function">RWMutexType::ReadLock <span class="title">lock</span><span class="params">(m_mutex)</span></span>;</span><br><span class="line">            <span class="comment">// return boost::lexical_cast&lt;std::string&gt;(m_val);</span></span><br><span class="line">            <span class="keyword">return</span> <span class="built_in">ToStr</span>()(m_val);</span><br><span class="line">        &#125; <span class="built_in">catch</span>(std::exception&amp; e) &#123;</span><br><span class="line">            <span class="built_in">SYLAR_LOG_ERROR</span>(<span class="built_in">SYLAR_LOG_ROOT</span>()) &lt;&lt; <span class="string">&quot;ConfigVar::toString exception: &quot;</span></span><br><span class="line">                &lt;&lt; e.<span class="built_in">what</span>() &lt;&lt; <span class="string">&quot; convert: &quot;</span> &lt;&lt; <span class="built_in">typeid</span>(m_val).<span class="built_in">name</span>() &lt;&lt; <span class="string">&quot; to string&quot;</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;&quot;</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 调用FromStr()将Yaml字符串解析为配置值</span></span><br><span class="line">    <span class="function"><span class="type">bool</span> <span class="title">fromString</span><span class="params">(<span class="type">const</span> std::string&amp; val)</span> <span class="keyword">override</span> </span>&#123;</span><br><span class="line">        <span class="keyword">try</span>&#123;</span><br><span class="line">            <span class="comment">// setValue 中进行了加锁操作，这里不需要额外加锁</span></span><br><span class="line">            <span class="comment">// m_val = boost::lexical_cast&lt;T&gt;(val);</span></span><br><span class="line">            <span class="built_in">setValue</span>(<span class="built_in">FromStr</span>()(val));</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">        &#125; <span class="built_in">catch</span>(std::exception&amp; e) &#123;</span><br><span class="line">            <span class="built_in">SYLAR_LOG_ERROR</span>(<span class="built_in">SYLAR_LOG_ROOT</span>()) &lt;&lt; <span class="string">&quot;ConfiVar::fromString exception: &quot;</span></span><br><span class="line">                &lt;&lt; e.<span class="built_in">what</span>() &lt;&lt; <span class="string">&quot; convert string to &quot;</span> &lt;&lt; <span class="built_in">typeid</span>(m_val).<span class="built_in">name</span>(); </span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</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="function"><span class="type">const</span> T <span class="title">getValue</span><span class="params">()</span> <span class="type">const</span> </span>&#123;</span><br><span class="line">        <span class="function">RWMutexType::ReadLock <span class="title">lock</span><span class="params">(m_mutex)</span></span>;</span><br><span class="line">        <span class="keyword">return</span> m_val;</span><br><span class="line">    &#125;</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">setValue</span><span class="params">(<span class="type">const</span> T&amp; val)</span></span>&#123;</span><br><span class="line">        <span class="comment">// 先读后写</span></span><br><span class="line">        &#123;</span><br><span class="line">            <span class="comment">// 使用&#123;&#125;来控制RAII类的作用范围</span></span><br><span class="line">            <span class="function">RWMutexType::ReadLock <span class="title">lock</span><span class="params">(m_mutex)</span></span>;</span><br><span class="line">            <span class="keyword">if</span>(val == m_val)&#123;</span><br><span class="line">                <span class="keyword">return</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">// 如果配置项的值发生了变化，通知所有回调函数</span></span><br><span class="line">            <span class="keyword">for</span>(<span class="keyword">auto</span> cb : m_cbs)&#123;</span><br><span class="line">                cb.<span class="built_in">second</span>(m_val, val);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="function">RWMutexType::WriteLock <span class="title">lock</span><span class="params">(m_mutex)</span></span>;</span><br><span class="line">        m_val = val;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function">std::string <span class="title">getTypename</span><span class="params">()</span> <span class="type">const</span> <span class="keyword">override</span> </span>&#123; <span class="keyword">return</span> <span class="built_in">typeid</span>(T).<span class="built_in">name</span>(); &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 添加回调函数，返回该回调函数的唯一id</span></span><br><span class="line">    <span class="function"><span class="type">uint64_t</span> <span class="title">addListener</span><span class="params">(on_change_cb cb)</span></span>&#123;</span><br><span class="line">        <span class="comment">// 自动创建回调函数对应的key</span></span><br><span class="line">        <span class="type">static</span> <span class="type">uint64_t</span> s_cbfun_id = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">        <span class="function">RWMutexType::WriteLock <span class="title">lock</span><span class="params">(m_mutex)</span></span>;</span><br><span class="line">        ++s_cbfun_id;</span><br><span class="line">        m_cbs[s_cbfun_id] = cb;</span><br><span class="line">        <span class="keyword">return</span> s_cbfun_id;</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">delListener</span><span class="params">(<span class="type">uint64_t</span> key)</span></span>&#123;</span><br><span class="line">        <span class="function">RWMutexType::WriteLock <span class="title">lock</span><span class="params">(m_mutex)</span></span>;</span><br><span class="line">        m_cbs.<span class="built_in">erase</span>(key);</span><br><span class="line">    &#125;;</span><br><span class="line"></span><br><span class="line">    <span class="function">on_change_cb <span class="title">getListener</span><span class="params">(<span class="type">uint64_t</span> key)</span></span>&#123;</span><br><span class="line">        <span class="function">RWMutexType::ReadLock <span class="title">lock</span><span class="params">(m_mutex)</span></span>;</span><br><span class="line">        <span class="keyword">auto</span> it = m_cbs.<span class="built_in">find</span>(key);</span><br><span class="line">        <span class="keyword">return</span> it == m_cbs.<span class="built_in">end</span>() ? <span class="literal">nullptr</span> : it-&gt;second;</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">clearListener</span><span class="params">()</span></span>&#123;</span><br><span class="line">        <span class="function">RWMutexType::WriteLock <span class="title">lock</span><span class="params">(m_mutex)</span></span>;</span><br><span class="line">        m_cbs.<span class="built_in">clear</span>();</span><br><span class="line">    &#125;;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="comment">// 配置项的值</span></span><br><span class="line">    T m_val;</span><br><span class="line">    <span class="comment">// 管理回调函数的map</span></span><br><span class="line">    std::map&lt;<span class="type">uint64_t</span>, on_change_cb&gt; m_cbs;</span><br><span class="line">    <span class="comment">// 读写锁</span></span><br><span class="line">    <span class="keyword">mutable</span> RWMutexType m_mutex;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h3 id="多线程安全"><a href="#多线程安全" class="headerlink" title="多线程安全"></a>多线程安全</h3><p>由于配置项一般为<strong>读多写少</strong>，所以使用<strong>读写锁</strong>保证ConfigVar的线程安全</p><h3 id="Lexical-cast"><a href="#Lexical-cast" class="headerlink" title="Lexical_cast"></a>Lexical_cast</h3><div class="note info modern"><p>支持更多类型，在于支持更多类型的Lexical_cast特化版本</p></div><p>一个仿函数类，用于封装<code>boost::lexical_cast</code>，支持<code>ToStr</code>和<code>FromStr</code>的实现</p><pre><code><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="keyword">typename</span> FromStr = Lexical_cast&lt;std::string, T&gt;;</span><br><span class="line"><span class="keyword">typename</span> ToStr = Lexical_cast&lt;T, std::string&gt;;</span><br></pre></td></tr></table></figure></code></pre><p>Lexical_cast泛化与特化两个版本</p><ul><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><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">// F：转换源类型；T：转换目标类型</span></span><br><span class="line"><span class="comment">// 泛化的版本，可直接用作简单类型的转换，或被其他特化版本递归调用</span></span><br><span class="line"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> F, <span class="keyword">typename</span> T&gt;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Lexical_cast</span>&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function">T <span class="title">operator</span><span class="params">()</span><span class="params">(<span class="type">const</span> F&amp; val)</span></span>&#123;</span><br><span class="line">        <span class="keyword">return</span> boost::<span class="built_in">lexical_cast</span>&lt;T&gt;(val);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure></li><li><p>特化版本，用于<strong>扩展支持更多类型</strong>的配置项，如STL、用户自定义类型</p></li></ul><h3 id="toString-fromString"><a href="#toString-fromString" class="headerlink" title="toString &#x2F; fromString"></a>toString &#x2F; fromString</h3><p>调用模板传入的仿函数<code>ToStr</code>&#x2F;<code>FromStr</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">// 调用ToStr()将配置值转换为Yaml字符串返回</span></span><br><span class="line">  <span class="function">std::string <span class="title">toString</span><span class="params">()</span> <span class="keyword">override</span> </span>&#123;</span><br><span class="line">      <span class="keyword">try</span>&#123;</span><br><span class="line">          <span class="function">RWMutexType::ReadLock <span class="title">lock</span><span class="params">(m_mutex)</span></span>;</span><br><span class="line">          <span class="comment">// return boost::lexical_cast&lt;std::string&gt;(m_val);</span></span><br><span class="line">          <span class="keyword">return</span> <span class="built_in">ToStr</span>()(m_val);</span><br><span class="line">      &#125; <span class="built_in">catch</span>(std::exception&amp; e) &#123;</span><br><span class="line">          <span class="built_in">SYLAR_LOG_ERROR</span>(<span class="built_in">SYLAR_LOG_ROOT</span>()) &lt;&lt; <span class="string">&quot;ConfigVar::toString exception: &quot;</span></span><br><span class="line">              &lt;&lt; e.<span class="built_in">what</span>() &lt;&lt; <span class="string">&quot; convert: &quot;</span> &lt;&lt; <span class="built_in">typeid</span>(m_val).<span class="built_in">name</span>() &lt;&lt; <span class="string">&quot; to string&quot;</span>;</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">return</span> <span class="string">&quot;&quot;</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 调用FromStr()将Yaml字符串解析为配置值</span></span><br><span class="line">  <span class="function"><span class="type">bool</span> <span class="title">fromString</span><span class="params">(<span class="type">const</span> std::string&amp; val)</span> <span class="keyword">override</span> </span>&#123;</span><br><span class="line">      <span class="keyword">try</span>&#123;</span><br><span class="line">          <span class="comment">// setValue 中进行了加锁操作，这里不需要额外加锁</span></span><br><span class="line">          <span class="comment">// m_val = boost::lexical_cast&lt;T&gt;(val);</span></span><br><span class="line">          <span class="built_in">setValue</span>(<span class="built_in">FromStr</span>()(val));</span><br><span class="line">          <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">      &#125; <span class="built_in">catch</span>(std::exception&amp; e) &#123;</span><br><span class="line">          <span class="built_in">SYLAR_LOG_ERROR</span>(<span class="built_in">SYLAR_LOG_ROOT</span>()) &lt;&lt; <span class="string">&quot;ConfiVar::fromString exception: &quot;</span></span><br><span class="line">              &lt;&lt; e.<span class="built_in">what</span>() &lt;&lt; <span class="string">&quot; convert string to &quot;</span> &lt;&lt; <span class="built_in">typeid</span>(m_val).<span class="built_in">name</span>(); </span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure><h3 id="回调函数"><a href="#回调函数" class="headerlink" title="回调函数"></a>回调函数</h3><p>ConfigVar支持添加回调函数，并在配置项数值更新时自动通知所有的回调函数。ConfigVar使用一个map管理所有的回调函数，在添加回调函数时会自动分配一个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><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="comment">// 添加回调函数，返回该回调函数的唯一id</span></span><br><span class="line">  <span class="function"><span class="type">uint64_t</span> <span class="title">addListener</span><span class="params">(on_change_cb cb)</span></span>&#123;</span><br><span class="line">      <span class="comment">// 自动创建回调函数对应的key</span></span><br><span class="line">      <span class="type">static</span> <span class="type">uint64_t</span> s_cbfun_id = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">      <span class="function">RWMutexType::WriteLock <span class="title">lock</span><span class="params">(m_mutex)</span></span>;</span><br><span class="line">      ++s_cbfun_id;</span><br><span class="line">      m_cbs[s_cbfun_id] = cb;</span><br><span class="line">      <span class="keyword">return</span> s_cbfun_id;</span><br><span class="line">  &#125;;</span><br></pre></td></tr></table></figure><p>setValue中配置项值更新完成后通知所有回调：</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="comment">// 设置配置项的值</span></span><br><span class="line">  <span class="function"><span class="type">void</span> <span class="title">setValue</span><span class="params">(<span class="type">const</span> T&amp; val)</span></span>&#123;</span><br><span class="line">      <span class="comment">// 先读后写</span></span><br><span class="line">      &#123;</span><br><span class="line">          <span class="comment">// 使用&#123;&#125;来控制RAII类的作用范围</span></span><br><span class="line">          <span class="function">RWMutexType::ReadLock <span class="title">lock</span><span class="params">(m_mutex)</span></span>;</span><br><span class="line">          <span class="keyword">if</span>(val == m_val)&#123;</span><br><span class="line">              <span class="keyword">return</span>;</span><br><span class="line">          &#125;</span><br><span class="line">          <span class="comment">// 如果配置项的值发生了变化，通知所有回调函数</span></span><br><span class="line">          <span class="keyword">for</span>(<span class="keyword">auto</span> cb : m_cbs)&#123;</span><br><span class="line">              cb.<span class="built_in">second</span>(m_val, val);</span><br><span class="line">          &#125;</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="function">RWMutexType::WriteLock <span class="title">lock</span><span class="params">(m_mutex)</span></span>;</span><br><span class="line">      m_val = val;</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure><h2 id="Config"><a href="#Config" class="headerlink" title="Config"></a>Config</h2><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Config</span>&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="keyword">using</span> ConfigVarMap = std::unordered_map&lt;std::string, ConfigVarBase::ptr&gt;;</span><br><span class="line">    <span class="keyword">using</span> RWMutexType = RWMutex;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 根据name在容器中查找配置项</span></span><br><span class="line">    <span class="keyword">template</span>&lt;<span class="keyword">typename</span> T&gt;</span><br><span class="line">    <span class="type">static</span> <span class="keyword">typename</span> ConfigVar&lt;T&gt;::<span class="function">ptr <span class="title">Lookup</span><span class="params">(<span class="type">const</span> std::string&amp; name)</span></span>&#123;</span><br><span class="line">        <span class="comment">// 这个Lookup版本只进行读操作，所以上读锁</span></span><br><span class="line">        <span class="function">RWMutexType::ReadLock <span class="title">lock</span><span class="params">(GetMutex())</span></span>;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">auto</span> it = <span class="built_in">GetDatas</span>().<span class="built_in">find</span>(name);</span><br><span class="line">        <span class="keyword">if</span>(it != <span class="built_in">GetDatas</span>().<span class="built_in">end</span>())&#123;</span><br><span class="line">            <span class="comment">// dynamic_pointer_cast可以理解为作用于shared_ptr上的dynamic_cast</span></span><br><span class="line">            <span class="keyword">return</span> std::dynamic_pointer_cast&lt;ConfigVar&lt;T&gt;&gt;(it-&gt;second);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">nullptr</span>;</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="keyword">template</span>&lt;<span class="keyword">typename</span> T&gt;</span><br><span class="line">    <span class="type">static</span> <span class="keyword">typename</span> ConfigVar&lt;T&gt;::<span class="function">ptr <span class="title">Lookup</span><span class="params">(<span class="type">const</span> std::string&amp; name, </span></span></span><br><span class="line"><span class="params"><span class="function">            <span class="type">const</span> T&amp; default_value, <span class="type">const</span> std::string&amp; description = <span class="string">&quot;&quot;</span>)</span></span>&#123;</span><br><span class="line">        <span class="keyword">auto</span>&amp; mp = <span class="built_in">GetDatas</span>();</span><br><span class="line">        <span class="comment">// 先上一个读锁，进行查询操作</span></span><br><span class="line">        &#123;</span><br><span class="line">            <span class="function">RWMutexType::ReadLock <span class="title">lock</span><span class="params">(GetMutex())</span></span>;</span><br><span class="line">            <span class="keyword">auto</span> it = mp.<span class="built_in">find</span>(name);</span><br><span class="line">            <span class="keyword">if</span>(it != <span class="built_in">GetDatas</span>().<span class="built_in">end</span>())&#123;</span><br><span class="line">                <span class="keyword">return</span> Config::<span class="built_in">pointer_cast</span>&lt;T&gt;(name, it-&gt;second);</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">// 检查name中是否所有字符都合法</span></span><br><span class="line">        <span class="keyword">if</span>(name.<span class="built_in">find_first_not_of</span>(<span class="string">&quot;1234567890._abcdefghijklmnopqrstuvwxyz&quot;</span>)</span><br><span class="line">            != std::string::npos)&#123;</span><br><span class="line">            <span class="comment">// name中存在非法字符，写入错误log并抛出invalid_argument异常</span></span><br><span class="line">            <span class="built_in">SYLAR_LOG_ERROR</span>(<span class="built_in">SYLAR_LOG_ROOT</span>()) &lt;&lt; <span class="string">&quot;Lookup name invalid: &quot;</span> &lt;&lt; name;</span><br><span class="line">            <span class="keyword">throw</span> std::<span class="built_in">invalid_argument</span>(name);</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">// 上写锁，进行插入操作</span></span><br><span class="line">        <span class="function">RWMutexType::WriteLock <span class="title">lock</span><span class="params">(GetMutex())</span></span>;</span><br><span class="line">        <span class="comment">// 再次检查 name 是否存在，避免多线程重复插入的问题</span></span><br><span class="line">        <span class="keyword">auto</span> it = mp.<span class="built_in">find</span>(name);</span><br><span class="line">        <span class="keyword">if</span>(it != mp.<span class="built_in">end</span>())&#123;</span><br><span class="line">            <span class="keyword">return</span> Config::<span class="built_in">pointer_cast</span>&lt;T&gt;(name, it-&gt;second);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">typename</span> ConfigVar&lt;T&gt;::ptr newConfigVar = std::make_shared&lt;ConfigVar&lt;T&gt;&gt;(name, default_value, description);</span><br><span class="line">        mp[name] = newConfigVar;</span><br><span class="line">        <span class="comment">// SYLAR_LOG_DEBUG(SYLAR_LOG_ROOT()) &lt;&lt; &quot;Add config name: &quot; &lt;&lt; name &lt;&lt; &quot;, type: &quot;&lt;&lt; typeid(T).name();</span></span><br><span class="line">        <span class="comment">// SYLAR_LOG_DEBUG(SYLAR_LOG_ROOT()) &lt;&lt; &quot; -Add config: &quot; &lt;&lt; name;</span></span><br><span class="line">        <span class="keyword">return</span> newConfigVar;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 查找name，并返回其父类指针（利用多态性）</span></span><br><span class="line">    <span class="function"><span class="type">static</span> ConfigVarBase::ptr <span class="title">LookupBase</span><span class="params">(<span class="type">const</span> std::string&amp; name)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 从Yaml中加载配置</span></span><br><span class="line">    <span class="function"><span class="type">static</span> <span class="type">void</span> <span class="title">LoadFromYaml</span><span class="params">(<span class="type">const</span> YAML::Node&amp; root)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Visit接收一个回调函数cb，对Config管理的所有配置项应用该回调函数</span></span><br><span class="line">    <span class="function"><span class="type">static</span> <span class="type">void</span> <span class="title">Visit</span><span class="params">(std::function&lt;<span class="type">void</span>(ConfigVarBase::ptr)&gt; cb)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="comment">// 封装 dynamic_pointer_cast 和类型检查逻辑操作</span></span><br><span class="line">    <span class="keyword">template</span>&lt;<span class="keyword">typename</span> T&gt;</span><br><span class="line">    <span class="type">static</span> <span class="keyword">typename</span> ConfigVar&lt;T&gt;::<span class="function">ptr <span class="title">pointer_cast</span><span class="params">(<span class="type">const</span> std::string&amp; name, ConfigVarBase::ptr val)</span></span>&#123;</span><br><span class="line">        <span class="keyword">typename</span> ConfigVar&lt;T&gt;::ptr tmp = std::dynamic_pointer_cast&lt;ConfigVar&lt;T&gt;&gt;(val);;</span><br><span class="line">        <span class="keyword">if</span>(tmp)&#123;</span><br><span class="line">            <span class="built_in">SYLAR_LOG_INFO</span>(<span class="built_in">SYLAR_LOG_ROOT</span>()) &lt;&lt; <span class="string">&quot;Lookup name=&quot;</span> &lt;&lt; name &lt;&lt; <span class="string">&quot;exists.&quot;</span>;</span><br><span class="line">            <span class="keyword">return</span> tmp;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="built_in">SYLAR_LOG_ERROR</span>(<span class="built_in">SYLAR_LOG_ROOT</span>()) &lt;&lt; <span class="string">&quot;Lookup name=&quot;</span> &lt;&lt; name &lt;&lt; <span class="string">&quot;, but type not &lt;&quot;</span></span><br><span class="line">                &lt;&lt; <span class="built_in">typeid</span>(T).<span class="built_in">name</span>() &lt;&lt; <span class="string">&quot;&gt;, real type=&lt;&quot;</span> &lt;&lt; val-&gt;<span class="built_in">getTypename</span>() &lt;&lt; <span class="string">&quot;&gt;&quot;</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">nullptr</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 使用静态成员函数获取数据，避免静态变量的初始化顺序问题（见Effective C++条款4）</span></span><br><span class="line">    <span class="function"><span class="type">static</span> ConfigVarMap&amp; <span class="title">GetDatas</span><span class="params">()</span></span>&#123;</span><br><span class="line">        <span class="type">static</span> ConfigVarMap s_datas;</span><br><span class="line">        <span class="keyword">return</span> s_datas;</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="function"><span class="type">static</span> RWMutexType&amp; <span class="title">GetMutex</span><span class="params">()</span></span>&#123;</span><br><span class="line">        <span class="type">static</span> RWMutexType s_mutex;</span><br><span class="line">        <span class="keyword">return</span> s_mutex;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h3 id="单例类实现"><a href="#单例类实现" class="headerlink" title="单例类实现"></a>单例类实现</h3><p>Config的单例实现方式有所不同。它不通过Sington来实现单例，而是将所有的成员函数都设置为静态</p><p>注意静态数据成员变量也转变为通过静态成员函数获取</p><ul><li><p>复习一下Effective C++条款4：</p><p>  <img src="https://atelieryu.xyz/elog/202504/efce89d3c10e4cc7badc0686f51899a2.png" alt="Effective C++条款4——确定对象被使用前已被初始化"></p></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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 使用静态成员函数获取数据，避免静态变量的初始化顺序问题</span></span><br><span class="line">  <span class="function"><span class="type">static</span> ConfigVarMap&amp; <span class="title">GetDatas</span><span class="params">()</span></span>&#123;</span><br><span class="line">      <span class="type">static</span> ConfigVarMap s_datas;</span><br><span class="line">      <span class="keyword">return</span> s_datas;</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="function"><span class="type">static</span> RWMutexType&amp; <span class="title">GetMutex</span><span class="params">()</span></span>&#123;</span><br><span class="line">      <span class="type">static</span> RWMutexType s_mutex;</span><br><span class="line">      <span class="keyword">return</span> s_mutex;</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure><h3 id="Lookup"><a href="#Lookup" class="headerlink" title="Lookup"></a>Lookup</h3><p>Lookup用于向map中查找&#x2F;添加配置项，整体逻辑比较简单。注意一下几点即可：</p><ul><li><p>有两个重载版本的Lookup：</p><ul><li><p>版本1：只用于读操作</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="type">static</span> <span class="keyword">typename</span> ConfigVar&lt;T&gt;::<span class="function">ptr <span class="title">Lookup</span><span class="params">(<span class="type">const</span> std::string&amp; name)</span></span></span><br></pre></td></tr></table></figure></li><li><p>版本2：接受数值与描述，如果没有查询到对应的配置项则使用该数值与描述新增一个配置项</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="type">static</span> <span class="keyword">typename</span> ConfigVar&lt;T&gt;::<span class="function">ptr <span class="title">Lookup</span><span class="params">(<span class="type">const</span> std::string&amp; name, </span></span></span><br><span class="line"><span class="params"><span class="function">            <span class="type">const</span> T&amp; default_value, <span class="type">const</span> std::string&amp; description = <span class="string">&quot;&quot;</span>)</span></span></span><br></pre></td></tr></table></figure></li></ul></li><li><p>版本2在新增配置项前需要对名称与数值类型进行检查：</p><ul><li><p>名称检查：仅支持名称由 数字、字母（大小写不敏感）、‘.’和’_’构成</p></li><li><p>类型检查：通过封装pointer_cast进行类型检查</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">// 封装 dynamic_pointer_cast 和类型检查逻辑操作</span></span><br><span class="line"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T&gt;</span><br><span class="line"><span class="type">static</span> <span class="keyword">typename</span> ConfigVar&lt;T&gt;::<span class="function">ptr <span class="title">pointer_cast</span><span class="params">(<span class="type">const</span> std::string&amp; name, ConfigVarBase::ptr val)</span></span>&#123;</span><br><span class="line">    <span class="keyword">typename</span> ConfigVar&lt;T&gt;::ptr tmp = std::dynamic_pointer_cast&lt;ConfigVar&lt;T&gt;&gt;(val);;</span><br><span class="line">    <span class="keyword">if</span>(tmp)&#123;</span><br><span class="line">        <span class="built_in">SYLAR_LOG_INFO</span>(<span class="built_in">SYLAR_LOG_ROOT</span>()) &lt;&lt; <span class="string">&quot;Lookup name=&quot;</span> &lt;&lt; name &lt;&lt; <span class="string">&quot;exists.&quot;</span>;</span><br><span class="line">        <span class="keyword">return</span> tmp;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="built_in">SYLAR_LOG_ERROR</span>(<span class="built_in">SYLAR_LOG_ROOT</span>()) &lt;&lt; <span class="string">&quot;Lookup name=&quot;</span> &lt;&lt; name &lt;&lt; <span class="string">&quot;, but type not &lt;&quot;</span></span><br><span class="line">            &lt;&lt; <span class="built_in">typeid</span>(T).<span class="built_in">name</span>() &lt;&lt; <span class="string">&quot;&gt;, real type=&lt;&quot;</span> &lt;&lt; val-&gt;<span class="built_in">getTypename</span>() &lt;&lt; <span class="string">&quot;&gt;&quot;</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">nullptr</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ul></li></ul><h3 id="Visit"><a href="#Visit" class="headerlink" title="Visit"></a>Visit</h3><p><code>Visit</code>接收一个回调函数cb，对Config管理的所有配置项应用该回调函数</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">Config::Visit</span><span class="params">(std::function&lt;<span class="type">void</span>(ConfigVarBase::ptr)&gt; cb)</span></span>&#123;</span><br><span class="line">    <span class="function">RWMutexType::ReadLock <span class="title">lock</span><span class="params">(GetMutex())</span></span>;</span><br><span class="line">    <span class="keyword">auto</span>&amp; mp = <span class="built_in">GetDatas</span>();</span><br><span class="line">    <span class="keyword">for</span>(<span class="keyword">auto</span>&amp; i : mp)&#123;</span><br><span class="line">        <span class="built_in">cb</span>(i.second);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="LoadFromYaml"><a href="#LoadFromYaml" class="headerlink" title="LoadFromYaml"></a>LoadFromYaml</h3><p><code>LoadFromYaml</code>实现从YAML::Node中载入配置</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="comment">// 从Yaml中导入配置</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">Config::LoadFromYaml</span><span class="params">(<span class="type">const</span> YAML::Node&amp; root)</span></span>&#123;</span><br><span class="line">    <span class="comment">// 将Yaml的树状结构拍平为list（方便遍历）</span></span><br><span class="line">    <span class="comment">// &lt;name, yaml_node&gt;</span></span><br><span class="line">    std::list&lt;std::pair&lt;std::string, <span class="type">const</span> YAML::Node&gt;&gt; all_nodes;</span><br><span class="line">    <span class="built_in">ListAllMember</span>(<span class="string">&quot;&quot;</span>, root, all_nodes);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 遍历Yaml，逐个解析配置项</span></span><br><span class="line">    <span class="keyword">for</span>(<span class="keyword">auto</span>&amp; item : all_nodes)&#123;</span><br><span class="line">        std::string key = item.first;</span><br><span class="line">        <span class="keyword">if</span>(key.<span class="built_in">empty</span>())&#123;</span><br><span class="line">            <span class="keyword">continue</span>;</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">        std::<span class="built_in">transform</span>(key.<span class="built_in">begin</span>(), key.<span class="built_in">end</span>(), key.<span class="built_in">begin</span>(), ::tolower);</span><br><span class="line">        <span class="comment">// std::cout &lt;&lt; key &lt;&lt; std::endl;</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">// 在Config容器中查找一项配置项var（只查找注册过的配置项，如果没有找到不会自动添加配置项）</span></span><br><span class="line">        ConfigVarBase::ptr var = <span class="built_in">LookupBase</span>(item.first);</span><br><span class="line">        <span class="comment">// 从字符串获取配置项var的值</span></span><br><span class="line">        <span class="keyword">if</span>(var)&#123;</span><br><span class="line">            <span class="keyword">if</span>(item.second.<span class="built_in">IsScalar</span>())&#123;</span><br><span class="line">                var-&gt;<span class="built_in">fromString</span>(item.second.<span class="built_in">Scalar</span>());</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                std::stringstream ss;</span><br><span class="line">                ss &lt;&lt; item.second;</span><br><span class="line">                <span class="comment">// ss.str()为Yaml格式的字符串，交给fromString来解析</span></span><br><span class="line">                var-&gt;<span class="built_in">fromString</span>(ss.<span class="built_in">str</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></pre></td></tr></table></figure><p>步骤：</p><ol><li><p>将Yaml树结构拍平为list，便于后面遍历</p> <figure class="highlight plaintext"><figcaption><span>text</span></figcaption><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">tcp:</span><br><span class="line">  connect:                 ----拍平----&gt;       tcp.connect.timeout</span><br><span class="line">    timeout: 10000</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 将Yaml的树结果拍平为list</span></span><br><span class="line"><span class="comment">//  ·prefix：配置项名称的前缀</span></span><br><span class="line"><span class="comment">//  ·output：传出参数</span></span><br><span class="line"><span class="function"><span class="type">static</span> <span class="type">void</span> <span class="title">ListAllMember</span><span class="params">(<span class="type">const</span> std::string&amp; prefix,</span></span></span><br><span class="line"><span class="params"><span class="function">                          <span class="type">const</span> YAML::Node&amp; node,</span></span></span><br><span class="line"><span class="params"><span class="function">                          std::list&lt;std::pair&lt;std::string, <span class="type">const</span> YAML::Node&gt;&gt;&amp; output)</span></span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(prefix.<span class="built_in">find_first_not_of</span>(<span class="string">&quot;1234567890._abcdefghijklmnopqrstuvwxyz&quot;</span>)</span><br><span class="line">        != std::string::npos)&#123;</span><br><span class="line">        <span class="built_in">SYLAR_LOG_ERROR</span>(<span class="built_in">SYLAR_LOG_ROOT</span>()) &lt;&lt; <span class="string">&quot;Lookup name invalid: &quot;</span> &lt;&lt; prefix;</span><br><span class="line">        <span class="keyword">throw</span> std::<span class="built_in">invalid_argument</span>(prefix);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    output.<span class="built_in">push_back</span>(std::<span class="built_in">make_pair</span>(prefix, node));</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span>(node.<span class="built_in">IsMap</span>())&#123;</span><br><span class="line">        <span class="comment">// 递归地处理map</span></span><br><span class="line">        <span class="keyword">for</span>(<span class="keyword">auto</span>&amp; it : node)&#123;</span><br><span class="line">            <span class="built_in">ListAllMember</span>(prefix.<span class="built_in">empty</span>() ? it.first.<span class="built_in">Scalar</span>() : prefix + <span class="string">&quot;.&quot;</span> + it.first.<span class="built_in">Scalar</span>(),</span><br><span class="line">                          it.second, output);</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></li><li><p>遍历YAML::Node，逐个解析配置项，有两个注意点</p><ul><li>将配置项的键（名称）转换为全小写（大小写不敏感）</li><li>只有已经存在于Config中的配置项才会被载入</li></ul></li></ol><h2 id="STL支持"><a href="#STL支持" class="headerlink" title="STL支持"></a>STL支持</h2><blockquote><p>支持更多类型，在于支持更多类型的Lexical_cast特化版本</p></blockquote><ul><li>实现方法：定义一个仿函数类Lexical_cast，以及它的一系列偏特化</li><li>由于Lexical_cast的递归调用，实现天生<strong>支持STL的嵌套</strong></li></ul><p>STL的实现方式分为以下两大类</p><ul><li>将容器转化为<code>YAML::NodeType::Sequence</code><ul><li>包括vector、list、set、unordered_set</li></ul></li><li>将容器转化为<code>YAML::NodeType::Map</code><ul><li>map、unordered_map（目前的实现只支持key为std::string）</li></ul></li></ul><h3 id="vector、list、set、unordered-set"><a href="#vector、list、set、unordered-set" class="headerlink" title="vector、list、set、unordered_set"></a>vector、list、set、unordered_set</h3><p>转化为<code>YAML::NodeType::Sequence</code>，以vector为例：</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// vector -&gt; Yaml格式字符串</span></span><br><span class="line"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T&gt;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Lexical_cast</span>&lt;std::vector&lt;T&gt;, std::string&gt;&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function">std::string <span class="title">operator</span><span class="params">()</span><span class="params">(<span class="type">const</span> std::vector&lt;T&gt; vec)</span></span>&#123;</span><br><span class="line">        <span class="function">YAML::Node <span class="title">node</span><span class="params">(YAML::NodeType::Sequence)</span></span>;</span><br><span class="line">        <span class="keyword">for</span>(<span class="keyword">auto</span>&amp; i : vec)&#123;</span><br><span class="line">            <span class="comment">// 递归地将vec的元素转换为Yaml字符串格式</span></span><br><span class="line">            node.<span class="built_in">push_back</span>(YAML::<span class="built_in">Load</span>(<span class="built_in">Lexical_cast</span>&lt;T, std::string&gt;()(i)));</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 使用stringstream来接收Yaml格式字符串</span></span><br><span class="line">        std::stringstream ss;</span><br><span class="line">        ss &lt;&lt; node;</span><br><span class="line">        <span class="keyword">return</span> ss.<span class="built_in">str</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="comment">// Yaml格式字符串 -&gt; vector</span></span><br><span class="line"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T&gt;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Lexical_cast</span>&lt;std::string, std::vector&lt;T&gt;&gt;&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function">std::vector&lt;T&gt; <span class="title">operator</span><span class="params">()</span><span class="params">(<span class="type">const</span> std::string str)</span></span>&#123;</span><br><span class="line">        <span class="comment">// 使用YAML::Load解析Yaml格式字符串</span></span><br><span class="line">        YAML::Node nodes = YAML::<span class="built_in">Load</span>(str);</span><br><span class="line">        std::vector&lt;T&gt; vec;</span><br><span class="line">        std::stringstream ss;</span><br><span class="line">        <span class="keyword">for</span>(<span class="type">const</span> <span class="keyword">auto</span>&amp; node : nodes)&#123;</span><br><span class="line">            <span class="comment">// 使用使用stringstream来接收Yaml格式字符串</span></span><br><span class="line">            ss.<span class="built_in">str</span>(<span class="string">&quot;&quot;</span>);</span><br><span class="line">            ss &lt;&lt; node;</span><br><span class="line">            <span class="comment">// 递归地将Yaml格式字符串转换为vec的元素</span></span><br><span class="line">            vec.<span class="built_in">push_back</span>(<span class="built_in">Lexical_cast</span>&lt;std::string, T&gt;()(ss.<span class="built_in">str</span>()));</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> vec;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h3 id="map、unordered-map"><a href="#map、unordered-map" class="headerlink" title="map、unordered_map"></a>map、unordered_map</h3><p>转化为<code>YAML::NodeType::Map</code>，以map为例：</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="comment">// map，只支持key类型为std::string</span></span><br><span class="line"><span class="comment">// map -&gt; Yaml格式字符串</span></span><br><span class="line"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T&gt;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Lexical_cast</span>&lt;std::map&lt;std::string, T&gt;, std::string&gt;&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function">std::string <span class="title">operator</span><span class="params">()</span><span class="params">(<span class="type">const</span> std::map&lt;std::string, T&gt; mp)</span></span>&#123;</span><br><span class="line">        <span class="function">YAML::Node <span class="title">node</span><span class="params">(YAML::NodeType::Map)</span></span>;</span><br><span class="line">        <span class="keyword">for</span>(<span class="keyword">auto</span>&amp; i : mp)&#123;</span><br><span class="line">            node[i.first] = YAML::<span class="built_in">Load</span>(<span class="built_in">Lexical_cast</span>&lt;T, std::string&gt;()(i.second));</span><br><span class="line">        &#125;</span><br><span class="line">        std::stringstream ss;</span><br><span class="line">        ss &lt;&lt; node;</span><br><span class="line">        <span class="keyword">return</span> ss.<span class="built_in">str</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="comment">// Yaml格式字符串 -&gt; map</span></span><br><span class="line"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T&gt;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Lexical_cast</span>&lt;std::string, std::map&lt;std::string, T&gt;&gt;&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function">std::map&lt;std::string, T&gt; <span class="title">operator</span><span class="params">()</span><span class="params">(<span class="type">const</span> std::string str)</span></span>&#123;</span><br><span class="line">        YAML::Node nodes = YAML::<span class="built_in">Load</span>(str);</span><br><span class="line">        std::map&lt;std::string, T&gt; mp;</span><br><span class="line">        std::stringstream ss;</span><br><span class="line">        <span class="keyword">for</span>(<span class="type">const</span> <span class="keyword">auto</span>&amp; node : nodes)&#123;</span><br><span class="line">            ss.<span class="built_in">str</span>(<span class="string">&quot;&quot;</span>);</span><br><span class="line">            ss &lt;&lt; node.second;</span><br><span class="line">            mp.<span class="built_in">insert</span>(std::<span class="built_in">make_pair</span>(node.first.<span class="built_in">Scalar</span>(),</span><br><span class="line">                                     <span class="built_in">Lexical_cast</span>&lt;std::string, T&gt;()(ss.<span class="built_in">str</span>())));</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> mp;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h2 id="自定义类型支持"><a href="#自定义类型支持" class="headerlink" title="自定义类型支持"></a>自定义类型支持</h2><p>同STL，只要提供自定义类型的fromString和toString，就可以让配置系统支持该自定义类型</p><p>例子：</p><ul><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><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">// 一个自定义类</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Person</span>&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">Person</span>(<span class="type">const</span> std::string&amp; name = <span class="string">&quot;&quot;</span>, <span class="type">int</span> age = <span class="number">0</span>, <span class="type">bool</span> sex = <span class="literal">false</span>)</span><br><span class="line">        : <span class="built_in">m_name</span>(name), <span class="built_in">m_age</span>(age), <span class="built_in">m_sex</span>(sex)&#123;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function">std::string <span class="title">toString</span><span class="params">()</span> <span class="type">const</span> </span>&#123;</span><br><span class="line">        std::stringstream ss;</span><br><span class="line">        ss &lt;&lt; <span class="string">&quot;[Person: name=&quot;</span> &lt;&lt; m_name &lt;&lt; <span class="string">&quot;, age=&quot;</span> &lt;&lt; m_age &lt;&lt; <span class="string">&quot;, sex=&quot;</span> &lt;&lt; (m_sex ? <span class="string">&quot;female&quot;</span> : <span class="string">&quot;male&quot;</span>) &lt;&lt; <span class="string">&quot;]&quot;</span>;</span><br><span class="line">        <span class="keyword">return</span> ss.<span class="built_in">str</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">getName</span><span class="params">()</span> <span class="type">const</span> </span>&#123; <span class="keyword">return</span> m_name; &#125;</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">getAge</span><span class="params">()</span> <span class="type">const</span> </span>&#123; <span class="keyword">return</span> m_age; &#125;</span><br><span class="line">    <span class="function"><span class="type">bool</span> <span class="title">getSex</span><span class="params">()</span> <span class="type">const</span> </span>&#123; <span class="keyword">return</span> m_sex; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="type">bool</span> <span class="keyword">operator</span>==(<span class="type">const</span> Person&amp; oth) <span class="type">const</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> m_name == oth.m_name</span><br><span class="line">            &amp;&amp; m_age == oth.m_age</span><br><span class="line">            &amp;&amp; m_sex == oth.m_sex;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    std::string m_name;</span><br><span class="line">    <span class="type">int</span> m_age;</span><br><span class="line">    <span class="type">bool</span> m_sex;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure></li><li><p>fromString和toString</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">// 定义Person类的Lexical_cast偏特化版本，使得配置系统支持该自定义类型</span></span><br><span class="line"><span class="keyword">namespace</span> sylar&#123;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Yaml字符串 -&gt; Person类</span></span><br><span class="line"><span class="keyword">template</span>&lt;&gt;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Lexical_cast</span>&lt;std::string, Person&gt;&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function">Person <span class="title">operator</span><span class="params">()</span><span class="params">(<span class="type">const</span> std::string&amp; val)</span></span>&#123;</span><br><span class="line">        YAML::Node node = YAML::<span class="built_in">Load</span>(val);</span><br><span class="line">        <span class="function">Person <span class="title">p</span><span class="params">(node[<span class="string">&quot;name&quot;</span>].as&lt;std::string&gt;(),</span></span></span><br><span class="line"><span class="params"><span class="function">                 node[<span class="string">&quot;age&quot;</span>].as&lt;<span class="type">int</span>&gt;(),</span></span></span><br><span class="line"><span class="params"><span class="function">                 node[<span class="string">&quot;sex&quot;</span>].as&lt;<span class="type">bool</span>&gt;())</span></span>;</span><br><span class="line">        <span class="keyword">return</span> p;</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">// Person类 -&gt; Yaml字符串</span></span><br><span class="line"><span class="keyword">template</span>&lt;&gt;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Lexical_cast</span>&lt;Person, std::string&gt;&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function">std::string <span class="title">operator</span><span class="params">()</span><span class="params">(<span class="type">const</span> Person&amp; val)</span></span>&#123;</span><br><span class="line">        <span class="function">YAML::Node <span class="title">node</span><span class="params">(YAML::NodeType::Map)</span></span>;</span><br><span class="line">        node[<span class="string">&quot;name&quot;</span>] = val.<span class="built_in">getName</span>();</span><br><span class="line">        node[<span class="string">&quot;age&quot;</span>] = val.<span class="built_in">getAge</span>();</span><br><span class="line">        node[<span class="string">&quot;sex&quot;</span>] = val.<span class="built_in">getSex</span>();</span><br><span class="line">        std::stringstream ss;</span><br><span class="line">        ss &lt;&lt; node;</span><br><span class="line">        <span class="keyword">return</span> ss.<span class="built_in">str</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></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><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 自定义类</span></span><br><span class="line">sylar::ConfigVar&lt;Person&gt;::ptr g_person_config = </span><br><span class="line">    sylar::Config::<span class="built_in">Lookup</span>(<span class="string">&quot;class.person&quot;</span>, <span class="built_in">Person</span>(<span class="string">&quot;y&quot;</span>, <span class="number">0</span>, <span class="literal">false</span>), <span class="string">&quot;Person class&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 自定义类与STL的嵌套</span></span><br><span class="line">sylar::ConfigVar&lt;std::map&lt;std::string, Person&gt;&gt;::ptr g_person_map_config = </span><br><span class="line">    sylar::Config::<span class="built_in">Lookup</span>(<span class="string">&quot;class.persons&quot;</span>, std::<span class="built_in">map</span>&lt;std::string, Person&gt;(&#123;&#123;<span class="string">&quot;nobody&quot;</span>, <span class="built_in">Person</span>()&#125;&#125;), <span class="string">&quot;Person class map&quot;</span>);</span><br></pre></td></tr></table></figure></li></ul><h1 id="模块使用"><a href="#模块使用" class="headerlink" title="模块使用"></a>模块使用</h1><h2 id="载入配置项"><a href="#载入配置项" class="headerlink" title="载入配置项"></a>载入配置项</h2><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// YAML文件载入为YAML::Node</span></span><br><span class="line">YAML::Node root = YAML::<span class="built_in">LoadFile</span>(<span class="string">&quot;/conf/test.yml&quot;</span>);</span><br><span class="line"><span class="comment">// 从YAML::Node中载入配置项</span></span><br><span class="line">sylar::Config::<span class="built_in">LoadFromYaml</span>(root);</span><br></pre></td></tr></table></figure><h2 id="添加-获取配置项"><a href="#添加-获取配置项" class="headerlink" title="添加&#x2F;获取配置项"></a>添加&#x2F;获取配置项</h2><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">sylar::ConfigVar&lt;<span class="type">int</span>&gt;::ptr g_int_value_config = </span><br><span class="line">    sylar::Config::<span class="built_in">Lookup</span>&lt;<span class="type">int</span>&gt;(<span class="string">&quot;system.port&quot;</span>, <span class="number">8080</span>, <span class="string">&quot;system port&quot;</span>);</span><br><span class="line"></span><br><span class="line">sylar::ConfigVar&lt;std::vector&lt;<span class="type">int</span>&gt; &gt;::ptr g_int_vec_value_config =</span><br><span class="line">    sylar::Config::<span class="built_in">Lookup</span>(<span class="string">&quot;system.int_vec&quot;</span>, std::vector&lt;<span class="type">int</span>&gt;&#123;<span class="number">1</span>, <span class="number">2</span>&#125;, <span class="string">&quot;system int vector&quot;</span>);</span><br><span class="line"></span><br><span class="line">sylar::ConfigVar&lt;std::list&lt;<span class="type">int</span>&gt; &gt;::ptr g_int_list_value_config =</span><br><span class="line">    sylar::Config::<span class="built_in">Lookup</span>(<span class="string">&quot;system.int_list&quot;</span>, std::list&lt;<span class="type">int</span>&gt;&#123;<span class="number">1</span>, <span class="number">2</span>&#125;, <span class="string">&quot;system int list&quot;</span>);</span><br><span class="line"></span><br><span class="line">sylar::ConfigVar&lt;std::set&lt;<span class="type">int</span>&gt; &gt;::ptr g_int_set_value_config =</span><br><span class="line">    sylar::Config::<span class="built_in">Lookup</span>(<span class="string">&quot;system.int_set&quot;</span>, std::set&lt;<span class="type">int</span>&gt;&#123;<span class="number">1</span>, <span class="number">2</span>&#125;, <span class="string">&quot;system int set&quot;</span>);</span><br><span class="line"></span><br><span class="line">sylar::ConfigVar&lt;std::unordered_set&lt;<span class="type">int</span>&gt; &gt;::ptr g_int_uset_value_config =</span><br><span class="line">    sylar::Config::<span class="built_in">Lookup</span>(<span class="string">&quot;system.int_uset&quot;</span>, std::unordered_set&lt;<span class="type">int</span>&gt;&#123;<span class="number">1</span>, <span class="number">2</span>&#125;, <span class="string">&quot;system int unordered_set&quot;</span>);</span><br><span class="line"></span><br><span class="line">sylar::ConfigVar&lt;std::map&lt;std::string, <span class="type">int</span>&gt; &gt;::ptr g_str_int_map_value_config =</span><br><span class="line">    sylar::Config::<span class="built_in">Lookup</span>(<span class="string">&quot;system.str_int_map&quot;</span>, std::map&lt;std::string, <span class="type">int</span>&gt;&#123;&#123;<span class="string">&quot;k&quot;</span>,<span class="number">2</span>&#125;&#125;, <span class="string">&quot;system str int map&quot;</span>);</span><br><span class="line"></span><br><span class="line">sylar::ConfigVar&lt;std::unordered_map&lt;std::string, <span class="type">int</span>&gt; &gt;::ptr g_str_int_umap_value_config =</span><br><span class="line">    sylar::Config::<span class="built_in">Lookup</span>(<span class="string">&quot;system.str_int_umap&quot;</span>, std::unordered_map&lt;std::string, <span class="type">int</span>&gt;&#123;&#123;<span class="string">&quot;k&quot;</span>,<span class="number">2</span>&#125;&#125;, <span class="string">&quot;system str int map&quot;</span>);</span><br></pre></td></tr></table></figure><h1 id="与日志系统的整合"><a href="#与日志系统的整合" class="headerlink" title="与日志系统的整合"></a>与日志系统的整合</h1><h2 id="LogAppenderDefine"><a href="#LogAppenderDefine" class="headerlink" title="LogAppenderDefine"></a>LogAppenderDefine</h2><p><code>LogAppenderDefine</code>为定义Appender信息的结构体，结构体需要包含以下内容：</p><ol><li><code>type</code>：表示Appender的类型，目前日志系统支持输出到终端&#x2F;文件两种appender，type取值如下：<ul><li>0：未初始化</li><li>1：输出到文件</li><li>2：输出到终端</li></ul></li><li><code>pattern</code>：表示输出格式的模板</li><li><code>file</code>：在type &#x3D;&#x3D; 1时有效，记录输出文件的路径</li><li><code>operator==</code>：为配置系统提供结构体的比较方法，上面三个数据成员全相等才返回true</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">struct</span> <span class="title class_">LogAppenderDefine</span>&#123;</span><br><span class="line">    <span class="type">int</span> type = <span class="number">0</span>;   <span class="comment">// 1: File, 2: Stdout</span></span><br><span class="line">    std::string pattern;</span><br><span class="line">    std::string file;</span><br><span class="line"></span><br><span class="line">    <span class="type">bool</span> <span class="keyword">operator</span>==(<span class="type">const</span> LogAppenderDefine&amp; oth) <span class="type">const</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> type == oth.type</span><br><span class="line">            &amp;&amp; pattern == oth.pattern</span><br><span class="line">            &amp;&amp; file == oth.file;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h2 id="LoggerDefine"><a href="#LoggerDefine" class="headerlink" title="LoggerDefine"></a>LoggerDefine</h2><p><code>LoggerDefine</code>为记录Logger信息的结构体，结构体需要包含以下内容：</p><ol><li><code>name</code>：Logger的名称</li><li><code>level</code>：Logger的级别，类型为LogLevel::Level</li><li><code>appenders</code>：管理LogAppenderDefine结构体的数组</li><li>重载关系运算符，包括operator&#x3D;&#x3D;、operator!&#x3D;、operator&lt;</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 记录Logger相关信息的结构体</span></span><br><span class="line"><span class="keyword">struct</span> <span class="title class_">LoggerDefine</span>&#123;</span><br><span class="line">    std::string name;</span><br><span class="line">    LogLevel::Level level = LogLevel::UNKNOW;</span><br><span class="line">    std::vector&lt;LogAppenderDefine&gt; appenders;</span><br><span class="line"></span><br><span class="line">    <span class="type">bool</span> <span class="keyword">operator</span>==(<span class="type">const</span> LoggerDefine&amp; oth) <span class="type">const</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> name == oth.name</span><br><span class="line">            &amp;&amp; level == oth.level</span><br><span class="line">            &amp;&amp; appenders == oth.appenders;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="type">bool</span> <span class="keyword">operator</span>!=(<span class="type">const</span> LoggerDefine&amp; oth) <span class="type">const</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> !(*<span class="keyword">this</span> == oth);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="type">bool</span> <span class="keyword">operator</span>&lt;(<span class="type">const</span> LoggerDefine&amp; oth) <span class="type">const</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> name &lt; oth.name;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h2 id="Lexcial-cast偏特化"><a href="#Lexcial-cast偏特化" class="headerlink" title="Lexcial_cast偏特化"></a>Lexcial_cast偏特化</h2><p>实现Lexical_cast偏特化，使用YAML::NodeType::Map管理。整体逻辑不难理解，注意一下YAML::Node的操作方法即可</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 偏特化Lexical_cast</span></span><br><span class="line"><span class="comment">// Yaml字符串 -&gt; LoggerDefine</span></span><br><span class="line"><span class="keyword">template</span>&lt;&gt;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Lexical_cast</span>&lt;std::string, LoggerDefine&gt;&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function">LoggerDefine <span class="title">operator</span><span class="params">()</span><span class="params">(<span class="type">const</span> std::string&amp; val)</span></span>&#123;</span><br><span class="line">        YAML::Node node = YAML::<span class="built_in">Load</span>(val);</span><br><span class="line">        LoggerDefine ld;</span><br><span class="line">        <span class="keyword">if</span>(!node[<span class="string">&quot;name&quot;</span>].<span class="built_in">IsDefined</span>())&#123;</span><br><span class="line">            std::cerr &lt;&lt; <span class="string">&quot;log config error: name is null&quot;</span> &lt;&lt; node &lt;&lt; std::endl;</span><br><span class="line">            <span class="keyword">throw</span> std::<span class="built_in">logic_error</span>(<span class="string">&quot;log config name is null&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 处理name和level</span></span><br><span class="line">        ld.name = node[<span class="string">&quot;name&quot;</span>].<span class="built_in">as</span>&lt;std::string&gt;();</span><br><span class="line">        ld.level = LogLevel::<span class="built_in">FromString</span>(node[<span class="string">&quot;level&quot;</span>].<span class="built_in">IsDefined</span>() ? node[<span class="string">&quot;level&quot;</span>].<span class="built_in">as</span>&lt;std::string&gt;() : <span class="string">&quot;&quot;</span>);</span><br><span class="line">        <span class="comment">// 处理appenders</span></span><br><span class="line">        <span class="keyword">if</span>(node[<span class="string">&quot;appenders&quot;</span>].<span class="built_in">IsDefined</span>())&#123;</span><br><span class="line">            <span class="keyword">for</span>(<span class="keyword">auto</span> a : node[<span class="string">&quot;appenders&quot;</span>])&#123;</span><br><span class="line">                <span class="keyword">if</span>(!a[<span class="string">&quot;type&quot;</span>].<span class="built_in">IsDefined</span>())&#123;</span><br><span class="line">                    std::cerr &lt;&lt; <span class="string">&quot;log config error: appender type is null&quot;</span> &lt;&lt; a &lt;&lt; std::endl;</span><br><span class="line">                    <span class="keyword">continue</span>;</span><br><span class="line">                &#125;</span><br><span class="line">                LogAppenderDefine lad;</span><br><span class="line"></span><br><span class="line">                <span class="comment">// 不同的appender类型设置不同的type，FileLogAppender还需要获取输出的文件路径</span></span><br><span class="line">                std::string type = a[<span class="string">&quot;type&quot;</span>].<span class="built_in">as</span>&lt;std::string&gt;();</span><br><span class="line">                <span class="keyword">if</span>(type == <span class="string">&quot;FileLogAppender&quot;</span>)&#123;</span><br><span class="line">                    lad.type = <span class="number">1</span>;</span><br><span class="line">                    <span class="keyword">if</span>(!a[<span class="string">&quot;file&quot;</span>].<span class="built_in">IsDefined</span>())&#123;</span><br><span class="line">                        std::cerr &lt;&lt; <span class="string">&quot;log config error: FileLogAppender filename is null&quot;</span> &lt;&lt; a &lt;&lt; std::endl;</span><br><span class="line">                        <span class="keyword">continue</span>;</span><br><span class="line">                    &#125;</span><br><span class="line">                    lad.file = a[<span class="string">&quot;file&quot;</span>].<span class="built_in">as</span>&lt;std::string&gt;();</span><br><span class="line">                &#125; <span class="keyword">else</span> <span class="keyword">if</span>(type == <span class="string">&quot;StdoutLogAppender&quot;</span>)&#123;</span><br><span class="line">                    lad.type = <span class="number">2</span>;</span><br><span class="line">                &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                    std::cerr &lt;&lt; <span class="string">&quot;log config error: appender type is invalid&quot;</span> &lt;&lt; a &lt;&lt; std::endl;</span><br><span class="line">                    <span class="keyword">continue</span>;</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="comment">// 以上appender都需要检测是否配置了formatter的pattern</span></span><br><span class="line">                <span class="keyword">if</span>(a[<span class="string">&quot;pattern&quot;</span>].<span class="built_in">IsDefined</span>())&#123;</span><br><span class="line">                    lad.pattern = a[<span class="string">&quot;pattern&quot;</span>].<span class="built_in">as</span>&lt;std::string&gt;();</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="comment">// 将处理完成的LogAppenderDefine加入appenders中</span></span><br><span class="line">                ld.appenders.<span class="built_in">push_back</span>(lad);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> ld;</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">// LoggerDefine -&gt; Yaml字符串</span></span><br><span class="line"><span class="keyword">template</span>&lt;&gt;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Lexical_cast</span>&lt;LoggerDefine, std::string&gt;&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function">std::string <span class="title">operator</span><span class="params">()</span><span class="params">(<span class="type">const</span> LoggerDefine&amp; ld)</span></span>&#123;</span><br><span class="line">        <span class="function">YAML::Node <span class="title">node</span><span class="params">(YAML::NodeType::Map)</span></span>;</span><br><span class="line">        node[<span class="string">&quot;name&quot;</span>] = ld.name;</span><br><span class="line">        node[<span class="string">&quot;level&quot;</span>] = LogLevel::<span class="built_in">ToString</span>(ld.level);</span><br><span class="line">        std::stringstream ss_appenders;</span><br><span class="line">        <span class="keyword">for</span>(<span class="type">const</span> <span class="keyword">auto</span>&amp; lad : ld.appenders)&#123;</span><br><span class="line">            <span class="function">YAML::Node <span class="title">nlad</span><span class="params">(YAML::NodeType::Map)</span></span>;</span><br><span class="line">            <span class="keyword">if</span>(lad.type == <span class="number">1</span>)&#123;</span><br><span class="line">                nlad[<span class="string">&quot;type&quot;</span>] = <span class="string">&quot;FileLogAppender&quot;</span>;</span><br><span class="line">                nlad[<span class="string">&quot;file&quot;</span>] = lad.file;</span><br><span class="line">            &#125; <span class="keyword">else</span> <span class="keyword">if</span> (lad.type == <span class="number">2</span>)&#123;</span><br><span class="line">                nlad[<span class="string">&quot;type&quot;</span>] = <span class="string">&quot;StdoutLogAppender&quot;</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span>(!lad.pattern.<span class="built_in">empty</span>())&#123;</span><br><span class="line">                nlad[<span class="string">&quot;pattern&quot;</span>] = lad.pattern;</span><br><span class="line">            &#125;</span><br><span class="line">            node[<span class="string">&quot;appenders&quot;</span>].<span class="built_in">push_back</span>(nlad);</span><br><span class="line">        &#125;</span><br><span class="line">        std::stringstream ss;</span><br><span class="line">        ss &lt;&lt; node;</span><br><span class="line">        <span class="keyword">return</span> ss.<span class="built_in">str</span>();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h2 id="日志模块配置项g-log-config"><a href="#日志模块配置项g-log-config" class="headerlink" title="日志模块配置项g_log_config"></a>日志模块配置项g_log_config</h2><p>在log.h中注册日志模块的配置项：</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="comment">// 注册 logs 配置项</span></span><br><span class="line">ConfigVar&lt;std::set&lt;LoggerDefine&gt;&gt;::ptr g_log_config = </span><br><span class="line">        Config::<span class="built_in">Lookup</span>(<span class="string">&quot;logs&quot;</span>, std::<span class="built_in">set</span>&lt;LoggerDefine&gt;(), <span class="string">&quot;logs config&quot;</span>);</span><br></pre></td></tr></table></figure><h3 id="回调函数-1"><a href="#回调函数-1" class="headerlink" title="回调函数"></a>回调函数</h3><p>**<span class='p red'>全局对象在main函数前构造（其构造函数在main函数之前触发）</span>**，我们可以利用这一特性为 g_log_config 配置项添加回调函数</p><p>回调函数接收两个set<LoggerDefine>，一个为配置项旧值<code>oldVal</code>，一个为新值<code>newVal</code>。对比配置项的新旧值，完成logger的创建 &#x2F; 修改 &#x2F; 删除工作：</p><ul><li>创建 &#x2F; 修改：创建&#x2F;获取<code>newVal</code>中对应的日志器，根据LoggerDefine的新值设置日志器属性</li><li>删除：如果日志器在<code>oldVal</code>中但不在<code>newVal</code>中，则说明该日志器需要被删除。进行的操作为逻辑删除（将Level设置为无法访问到的级别，并情况其appender）</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">struct</span> <span class="title class_">LogIniter</span>&#123;</span><br><span class="line">    <span class="built_in">LogIniter</span>()&#123;</span><br><span class="line">        <span class="comment">// SYLAR_LOG_INFO(SYLAR_LOG_ROOT()) &lt;&lt; &quot;g_log_config add callback&quot;;</span></span><br><span class="line">        g_log_config-&gt;<span class="built_in">addListener</span>([](<span class="type">const</span> std::set&lt;LoggerDefine&gt;&amp; oldVal,</span><br><span class="line">                                     <span class="type">const</span> std::set&lt;LoggerDefine&gt;&amp; newVal)&#123;</span><br><span class="line">            <span class="comment">// 监测以下三种情况：新增 / 修改 / 删除</span></span><br><span class="line">            <span class="keyword">for</span>(<span class="keyword">auto</span>&amp; i : newVal)&#123;</span><br><span class="line">                <span class="keyword">auto</span> it = oldVal.<span class="built_in">find</span>(i);</span><br><span class="line">                Logger::ptr logger;</span><br><span class="line">                <span class="keyword">if</span>(it == oldVal.<span class="built_in">end</span>())&#123;</span><br><span class="line">                    <span class="comment">// &lt;新增&gt;</span></span><br><span class="line">                    logger = <span class="built_in">SYLAR_LOG_NAME</span>(i.name);</span><br><span class="line">                &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                    <span class="keyword">if</span>(i != *it)&#123;</span><br><span class="line">                        <span class="comment">// &lt;修改&gt;</span></span><br><span class="line">                        logger = <span class="built_in">SYLAR_LOG_NAME</span>(i.name);</span><br><span class="line">                    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                        <span class="keyword">continue</span>;</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="comment">// 根据newVal中的数据成员修改logger配置</span></span><br><span class="line">                <span class="comment">// 设置level</span></span><br><span class="line">                logger-&gt;<span class="built_in">setLevel</span>(i.level);</span><br><span class="line">                <span class="comment">// 设置appenders</span></span><br><span class="line">                logger-&gt;<span class="built_in">clearAppenders</span>();</span><br><span class="line">                <span class="keyword">for</span>(<span class="keyword">auto</span> &amp;a : i.appenders)&#123;</span><br><span class="line">                    LogAppender::ptr ap;</span><br><span class="line">                    <span class="keyword">if</span>(a.type == <span class="number">1</span>)&#123;</span><br><span class="line">                        ap.<span class="built_in">reset</span>(<span class="keyword">new</span> <span class="built_in">FileLogAppender</span>(a.file));</span><br><span class="line">                    &#125; <span class="keyword">else</span> <span class="keyword">if</span>(a.type == <span class="number">2</span>)&#123;</span><br><span class="line">                        ap.<span class="built_in">reset</span>(<span class="keyword">new</span> <span class="built_in">StdoutLogAppender</span>());</span><br><span class="line">                    &#125;</span><br><span class="line">                    <span class="comment">// pattern非空时设置自定义formatter，否则使用默认formatter</span></span><br><span class="line">                    <span class="keyword">if</span>(!a.pattern.<span class="built_in">empty</span>())&#123;</span><br><span class="line">                        ap-&gt;<span class="built_in">setFormatter</span>(LogFormatter::<span class="built_in">ptr</span>(<span class="keyword">new</span> <span class="built_in">LogFormatter</span>(a.pattern)));</span><br><span class="line">                    &#125;</span><br><span class="line">                    logger-&gt;<span class="built_in">addAppender</span>(ap);</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">// &lt;删除&gt;</span></span><br><span class="line">            <span class="keyword">for</span>(<span class="keyword">auto</span>&amp; i : oldVal)&#123;</span><br><span class="line">                <span class="keyword">auto</span> it = newVal.<span class="built_in">find</span>(i);</span><br><span class="line">                <span class="keyword">if</span>(it == newVal.<span class="built_in">end</span>())&#123;</span><br><span class="line">                    <span class="comment">// 对配置文件中被删除的logger执行逻辑删除</span></span><br><span class="line">                    Logger::ptr logger = <span class="built_in">SYLAR_LOG_NAME</span>(i.name);</span><br><span class="line">                    <span class="comment">// 逻辑删除：将Level设置为访问不到的级别，并清空所有appender</span></span><br><span class="line">                    logger-&gt;<span class="built_in">setLevel</span>(LogLevel::NOTSET);</span><br><span class="line">                    logger-&gt;<span class="built_in">clearAppenders</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">&#125;;</span><br><span class="line"><span class="comment">// 全局对象在main函数前构造</span></span><br><span class="line"><span class="type">static</span> LogIniter __log_init;</span><br></pre></td></tr></table></figure><h2 id="配置文件"><a href="#配置文件" class="headerlink" title="配置文件"></a>配置文件</h2><p>日志系统的配置系统可以如下形式：</p><figure class="highlight yaml"><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="attr">logs:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">root</span></span><br><span class="line">      <span class="attr">level:</span> <span class="string">info</span></span><br><span class="line">      <span class="attr">appenders:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="attr">type:</span> <span class="string">FileLogAppender</span></span><br><span class="line">            <span class="attr">file:</span> <span class="string">/home/mySylar/log.txt</span></span><br><span class="line">          <span class="bullet">-</span> <span class="attr">type:</span> <span class="string">StdoutLogAppender</span></span><br><span class="line">            <span class="attr">pattern:</span> <span class="string">&quot;%d%T%m%n&quot;</span></span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;模块概述&quot;&gt;&lt;a href=&quot;#模块概述&quot; class=&quot;headerlink&quot; title=&quot;模块概述&quot;&gt;&lt;/a&gt;模块概述&lt;/h1&gt;&lt;p&gt;基于YAML配置文件实现的一个配置模块&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;难点：Lexical_cast的泛化与偏特化实现&lt;/li&gt;</summary>
      
    
    
    
    <category term="学习笔记" scheme="https://atelieryu.site/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="后端" scheme="https://atelieryu.site/tags/%E5%90%8E%E7%AB%AF/"/>
    
    <category term="C++" scheme="https://atelieryu.site/tags/C/"/>
    
    <category term="服务器框架" scheme="https://atelieryu.site/tags/%E6%9C%8D%E5%8A%A1%E5%99%A8%E6%A1%86%E6%9E%B6/"/>
    
    <category term="YAML" scheme="https://atelieryu.site/tags/YAML/"/>
    
  </entry>
  
  <entry>
    <title>C++高性能服务器框架Sylar（一）——日志模块</title>
    <link href="https://atelieryu.site/Sylar%E6%97%A5%E5%BF%97%E6%A8%A1%E5%9D%97.html"/>
    <id>https://atelieryu.site/Sylar%E6%97%A5%E5%BF%97%E6%A8%A1%E5%9D%97.html</id>
    <published>2025-03-29T07:12:00.000Z</published>
    <updated>2025-03-29T10:01:00.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="模块概述"><a href="#模块概述" class="headerlink" title="模块概述"></a>模块概述</h1><p>模仿log4j日志框架实现一个的日志模块</p><ul><li>难点：Logformatter的init()，引入了一个简单的状态机机制，实现了将一个字符串解析为模板</li><li>设计点：<ul><li><strong>LogEventWrap</strong>：一个封装LogEvent和Logger的<strong>RAII类</strong></li><li><strong>LoggerManager</strong>：一个管理Logger的<strong>单例类</strong></li><li><strong>LogAppender</strong>：一个<strong>抽象基类</strong>，可以方便地扩展日志的输出地</li><li><strong>一系列辅助函数</strong>：<ul><li>优化用户使用方式，用户可使用<strong>流式方式或格式化方式</strong>进行日志内容的写入</li><li>包括一系列宏函数和inline，辅助函数创建LogEventWrap来实现日志的输出</li></ul></li></ul></li></ul><h1 id="模块UML类图"><a href="#模块UML类图" class="headerlink" title="模块UML类图"></a>模块UML类图</h1><p><img src="https://atelieryu.xyz/elog/202503/49d699bf874ff79e3f2cf7a2245d2330.png" alt="日志模块类图"></p><h2 id="Logger：日志器"><a href="#Logger：日志器" class="headerlink" title="Logger：日志器"></a>Logger：日志器</h2><ul><li>负责进行日志输出</li><li>一个日志器可指定一个日志级别（LogLevel）和多个日志输出地（LogAppender）</li><li>提供log方法，传入日志事件，如果该日志事件的级别高于日志器本身的级别则将其输出，否则将该日志抛弃。</li></ul><h2 id="LogAppender：日志输出器"><a href="#LogAppender：日志输出器" class="headerlink" title="LogAppender：日志输出器"></a>LogAppender：日志输出器</h2><ul><li>用于将一个日志事件输出到对应的输出地。</li><li>类内部包含一个日志格式器（LogFormatter）成员和一个log方法，日志事件经过格式化后输出到对应的输出地。</li><li>由于有多种输出地，所以该类成为一个<strong>抽象基类</strong>，其派生类必须重写它的log方法<ul><li>比如StdoutLogAppender和FileLogAppender为LogAppender的派生类，分别表示输出到终端和文件。</li></ul></li></ul><h2 id="LogFormatter：日志格式器"><a href="#LogFormatter：日志格式器" class="headerlink" title="LogFormatter：日志格式器"></a>LogFormatter：日志格式器</h2><ul><li>与log4cpp的PatternLayout对应，用于格式化一个日志事件（输出log4j日志格式）。</li><li>该类构建时可以指定pattern，表示如何进行格式化。提供format方法，用于将日志事件格式化成字符串。<ul><li>pattern中每个模板参数或字符串对应一个FormatItem派生类</li></ul></li></ul><h2 id="LogEvent：日志事件"><a href="#LogEvent：日志事件" class="headerlink" title="LogEvent：日志事件"></a>LogEvent：日志事件</h2><ul><li>用于记录日志现场，比如该日志的级别，文件名&#x2F;行号，日志消息，线程&#x2F;协程号，所属日志器名称等。</li><li>可以理解为一个日志事件对应一条日志</li></ul><h2 id="LogEventWrap：日志事件包装类"><a href="#LogEventWrap：日志事件包装类" class="headerlink" title="LogEventWrap：日志事件包装类"></a>LogEventWrap：日志事件包装类</h2><ul><li>LogEventWrap是一个<strong>RAII类</strong><ul><li>LogEventWrap在构造时指定日志事件和日志器</li><li>在析构时调用日志器的log方法将日志事件进行输出</li></ul></li><li>LogEventWrap将日志事件和日志器包装到一起<ul><li>一条日志只会在一个日志器上进行输出</li><li>将日志事件和日志器包装到一起后，方便通过宏定义来简化日志模块的使用。</li></ul></li><li>为什么需要LogEventWrap？<ul><li>由于LogEvent::ptr是智能指针，所以如果直接创建LogEvent::ptr，智能指针将在主函数结束时才释放。</li><li>LogEventWrap的getSS()方法支持以流式方式将日志写入logger</li><li>使用RAII类管理LogEvent，可以<strong>在LogEventWrap析构时自动提交日志事件，并将其释放</strong></li></ul></li></ul><h2 id="LogManager：日志器管理类"><a href="#LogManager：日志器管理类" class="headerlink" title="LogManager：日志器管理类"></a>LogManager：日志器管理类</h2><ul><li><strong>单例模式</strong>，用于统一管理所有的日志器，提供日志器的创建与获取方法<ul><li><strong>LogManager应成为创建Logger的唯一方式，所有的Logger都应该经由LogManager获取</strong></li><li>通过singleton.h中定义的单例类GetInstance方法返回LoggerManager的单例对象</li></ul></li><li>getRoot方法：LogManager自带一个root Logger，用于为日志模块提供一个初始可用的日志器。</li><li>getLogger方法：根据一个名字搜索容器中已有的logger或新建一个logger</li></ul><h2 id="调用栈"><a href="#调用栈" class="headerlink" title="调用栈"></a>调用栈</h2><p><code>~LogEventWrap()</code> → <code>Logger::log()</code> → <code>LogAppender::log()</code> → <code>LogFormatter::format()</code></p><h1 id="代码解析"><a href="#代码解析" class="headerlink" title="代码解析"></a>代码解析</h1><h2 id="LogLevel"><a href="#LogLevel" class="headerlink" title="LogLevel"></a>LogLevel</h2><blockquote><p>LogLevel表示日志级别</p></blockquote><ul><li>类中定义了7种日志级别，实现了日志的分级输出</li><li>提供了<code>ToString</code>和<code>FromString</code>两个静态方法，支持将日志级别名称转换为字符串，或将字符串转换为日志级别<ul><li><code>FromString</code>对输入字符串的大小写不敏感</li></ul></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></pre></td><td class="code"><pre><span class="line"><span class="comment">// .h</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 日志级别</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">LogLevel</span>&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="keyword">enum</span> <span class="title class_">Level</span>&#123;</span><br><span class="line">        UNKNOW = <span class="number">0</span>,     <span class="comment">// 未知级别</span></span><br><span class="line">        DEBUG = <span class="number">1</span>,      <span class="comment">// DEBUG级别</span></span><br><span class="line">        INFO = <span class="number">2</span>,       <span class="comment">// INFO级别</span></span><br><span class="line">        WARN = <span class="number">3</span>,       <span class="comment">// WARN级别</span></span><br><span class="line">        ERROR = <span class="number">4</span>,      <span class="comment">// ERROR级别</span></span><br><span class="line">        FATAL = <span class="number">5</span>,      <span class="comment">// FATAL级别</span></span><br><span class="line">        NOTSET = <span class="number">100</span>    <span class="comment">// NOSET级别，表示不可访问</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">const</span> <span class="type">char</span>* <span class="title">ToString</span><span class="params">(Level level)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="type">static</span> Level <span class="title">FromString</span><span class="params">(<span class="type">const</span> std::string&amp; str)</span></span>;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>通过宏函数简化了<code>ToString</code>和<code>FromString</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// .cpp</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">const</span> <span class="type">char</span>* <span class="title">LogLevel::ToString</span><span class="params">(Level level)</span></span>&#123;</span><br><span class="line">    <span class="keyword">switch</span> (level)&#123;</span><br><span class="line"><span class="meta">#<span class="keyword">define</span> XX(name) case LogLevel::name: return #name;</span></span><br><span class="line">        <span class="built_in">XX</span>(DEBUG);</span><br><span class="line">        <span class="built_in">XX</span>(INFO);</span><br><span class="line">        <span class="built_in">XX</span>(WARN);</span><br><span class="line">        <span class="built_in">XX</span>(ERROR);</span><br><span class="line">        <span class="built_in">XX</span>(FATAL);</span><br><span class="line"><span class="meta">#<span class="keyword">undef</span> XX</span></span><br><span class="line">        <span class="keyword">default</span>:</span><br><span class="line">            <span class="keyword">return</span> <span class="string">&quot;UNKNOW&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="function">LogLevel::Level <span class="title">LogLevel::FromString</span><span class="params">(<span class="type">const</span> std::string&amp; str)</span></span>&#123;</span><br><span class="line">    <span class="function">std::string <span class="title">strlower</span><span class="params">(str.size(), <span class="string">&#x27;\0&#x27;</span>)</span></span>;</span><br><span class="line">    std::<span class="built_in">transform</span>(str.<span class="built_in">begin</span>(), str.<span class="built_in">end</span>(), strlower.<span class="built_in">begin</span>(), ::tolower);</span><br><span class="line"><span class="meta">#<span class="keyword">define</span> XX(level, v) <span class="keyword">if</span>(strlower == #v)&#123; return LogLevel::level; &#125;</span></span><br><span class="line">    <span class="built_in">XX</span>(DEBUG, debug);</span><br><span class="line">    <span class="built_in">XX</span>(INFO, info);</span><br><span class="line">    <span class="built_in">XX</span>(WARN, warn);</span><br><span class="line">    <span class="built_in">XX</span>(ERROR, error);</span><br><span class="line">    <span class="built_in">XX</span>(FATAL, fatal);</span><br><span class="line"><span class="meta">#<span class="keyword">undef</span> XX</span></span><br><span class="line">    <span class="keyword">return</span> UNKNOW;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="LogEvent"><a href="#LogEvent" class="headerlink" title="LogEvent"></a>LogEvent</h2><blockquote><p>LogEvent用于记录日志现场</p></blockquote><p>一个日志事件具体包括以下内容：</p><ul><li>日志内容</li><li>日志器名称</li><li>日志级别</li><li>文件名，对应__FILE__宏</li><li>行号，对应__LINE__宏</li><li>程序运行时间，通过sylar::GetElapsedMS()获取</li><li>线程ID</li><li>协程ID</li><li>UTC时间戳，对应time(0)</li><li>线程名称</li></ul><p>LogEvent提供了<code>format</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// .h</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 日志事件，用于记录日志现场</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">LogEvent</span>&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="keyword">using</span> ptr = std::shared_ptr&lt;LogEvent&gt;;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">LogEvent</span>(std::string loggerName, LogLevel::Level level, <span class="type">const</span> <span class="type">char</span> *file, <span class="type">int32_t</span> line</span><br><span class="line">        , <span class="type">int64_t</span> elapse, <span class="type">uint32_t</span> thread_id, <span class="type">uint64_t</span> fiber_id, <span class="type">time_t</span> time</span><br><span class="line">        , <span class="type">const</span> std::string &amp;thread_name);</span><br><span class="line"></span><br><span class="line">    <span class="function">std::string <span class="title">getFile</span><span class="params">()</span> <span class="type">const</span> </span>&#123; <span class="keyword">return</span> m_file; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="type">int32_t</span> <span class="title">getLine</span><span class="params">()</span> <span class="type">const</span> </span>&#123; <span class="keyword">return</span> m_line; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="type">uint32_t</span> <span class="title">getElapse</span><span class="params">()</span> <span class="type">const</span> </span>&#123; <span class="keyword">return</span> m_elapse; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="type">uint32_t</span> <span class="title">getThreadId</span><span class="params">()</span> <span class="type">const</span> </span>&#123; <span class="keyword">return</span> m_threadId; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="type">uint32_t</span> <span class="title">getFiberId</span><span class="params">()</span> <span class="type">const</span> </span>&#123; <span class="keyword">return</span> m_fiberId; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="type">uint64_t</span> <span class="title">getTime</span><span class="params">()</span> <span class="type">const</span> </span>&#123; <span class="keyword">return</span> m_time; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="type">const</span> std::string&amp; <span class="title">getThreadName</span><span class="params">()</span> <span class="type">const</span> </span>&#123; <span class="keyword">return</span> m_threadName; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function">std::string <span class="title">getContent</span><span class="params">()</span> <span class="type">const</span> </span>&#123; <span class="keyword">return</span> m_ss.<span class="built_in">str</span>(); &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function">std::stringstream&amp; <span class="title">getSS</span><span class="params">()</span> </span>&#123; <span class="keyword">return</span> m_ss; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function">std::string <span class="title">getLoggerName</span><span class="params">()</span> <span class="type">const</span> </span>&#123; <span class="keyword">return</span> m_loggerName; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function">LogLevel::Level <span class="title">getLevel</span><span class="params">()</span> <span class="type">const</span> </span>&#123; <span class="keyword">return</span> m_level; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 以格式化方式将内容写入m_ss</span></span><br><span class="line">    <span class="keyword">template</span> &lt;<span class="keyword">typename</span>... Args&gt;</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">format</span><span class="params">(<span class="type">const</span> <span class="type">char</span>* fmt, Args&amp;&amp;... args)</span></span>&#123;</span><br><span class="line">        <span class="comment">// 获取格式化字符串的长度</span></span><br><span class="line">        <span class="type">int</span> len = <span class="built_in">snprintf</span>(<span class="literal">nullptr</span>, <span class="number">0</span>, fmt, args...);</span><br><span class="line">        <span class="keyword">if</span>(len &gt;= <span class="number">0</span>)&#123;</span><br><span class="line">            <span class="comment">// 调用snprintf，将格式化字符串写入缓冲区</span></span><br><span class="line">            <span class="type">char</span>* buf = <span class="keyword">new</span> <span class="type">char</span>[len + <span class="number">1</span>];</span><br><span class="line">            <span class="built_in">snprintf</span>(buf, len + <span class="number">1</span>, fmt, args...);</span><br><span class="line">            m_ss &lt;&lt; std::<span class="built_in">string</span>(buf, len);</span><br><span class="line">            <span class="keyword">delete</span>[] buf;</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">private</span>:</span><br><span class="line">    std::string m_loggerName;           <span class="comment">// 日志器</span></span><br><span class="line">    LogLevel::Level m_level;            <span class="comment">// 日志级别</span></span><br><span class="line">    <span class="type">const</span> <span class="type">char</span>* m_file = <span class="literal">nullptr</span>;       <span class="comment">// 文件名</span></span><br><span class="line">    <span class="type">int32_t</span> m_line = <span class="number">0</span>;                 <span class="comment">// 行号</span></span><br><span class="line">    <span class="type">uint32_t</span> m_elapse = <span class="number">0</span>;              <span class="comment">// 程序启动到现在的毫秒数</span></span><br><span class="line">    <span class="type">uint32_t</span> m_threadId = <span class="number">0</span>;            <span class="comment">// 线程ID</span></span><br><span class="line">    <span class="type">uint32_t</span> m_fiberId = <span class="number">0</span>;             <span class="comment">// 协程ID</span></span><br><span class="line">    <span class="type">uint64_t</span> m_time = <span class="number">0</span>;                <span class="comment">// 时间戳</span></span><br><span class="line">    std::string m_threadName;           <span class="comment">// 线程名称</span></span><br><span class="line">    std::stringstream m_ss;             <span class="comment">// 日志内容流</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// .cpp</span></span><br><span class="line"></span><br><span class="line">LogEvent::<span class="built_in">LogEvent</span>(std::string loggerName, LogLevel::Level level, <span class="type">const</span> <span class="type">char</span> *file, <span class="type">int32_t</span> line</span><br><span class="line">        , <span class="type">int64_t</span> elapse, <span class="type">uint32_t</span> thread_id, <span class="type">uint64_t</span> fiber_id, <span class="type">time_t</span> time</span><br><span class="line">        , <span class="type">const</span> std::string &amp;thread_name)</span><br><span class="line">    : <span class="built_in">m_loggerName</span>(loggerName) </span><br><span class="line">    , <span class="built_in">m_level</span>(level)</span><br><span class="line">    , <span class="built_in">m_file</span>(file)</span><br><span class="line">    , <span class="built_in">m_line</span>(line)</span><br><span class="line">    , <span class="built_in">m_elapse</span>(elapse)</span><br><span class="line">    , <span class="built_in">m_threadId</span>(thread_id)</span><br><span class="line">    , <span class="built_in">m_fiberId</span>(fiber_id)</span><br><span class="line">    , <span class="built_in">m_time</span>(time)</span><br><span class="line">    , <span class="built_in">m_threadName</span>(thread_name) &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="LogFormatter"><a href="#LogFormatter" class="headerlink" title="LogFormatter"></a>LogFormatter</h2><blockquote><p>LogFormatter用于将日志事件LogEvent格式化（模仿log4j日志框架）</p></blockquote><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// .h</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 日志格式器，将日志事件格式化为字符串</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">LogFormatter</span>&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="keyword">using</span> ptr = std::shared_ptr&lt;LogFormatter&gt;;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">LogFormatter</span>(<span class="type">const</span> std::string&amp; pattern = <span class="string">&quot;%d&#123;%Y-%m-%d %H:%M:%S&#125;%T%t%T%N%T%F%T[%p]%T[%c]%T%f:%l%T%m%n&quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="function">std::ostream&amp; <span class="title">format</span><span class="params">(std::ostream&amp; os, LogEvent::ptr event)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="type">bool</span> <span class="title">isError</span><span class="params">()</span> <span class="type">const</span> </span>&#123; <span class="keyword">return</span> m_error; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function">std::string <span class="title">getPattern</span><span class="params">()</span> <span class="type">const</span> </span>&#123; <span class="keyword">return</span> m_pattern; &#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">init</span><span class="params">()</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">// 抽象基类FormatItem，用于格式化某一项日志内容</span></span><br><span class="line">    <span class="keyword">class</span> <span class="title class_">FormatItem</span>&#123;</span><br><span class="line">    <span class="keyword">public</span>:</span><br><span class="line">        <span class="keyword">using</span> ptr = std::shared_ptr&lt;FormatItem&gt;;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">virtual</span> ~<span class="built_in">FormatItem</span>()&#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">format</span><span class="params">(std::ostream&amp; os, LogEvent::ptr event)</span> </span>= <span class="number">0</span>;</span><br><span class="line">    &#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    std::string m_pattern;</span><br><span class="line">    std::vector&lt;FormatItem::ptr&gt; m_items;</span><br><span class="line">    <span class="type">bool</span> m_error;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h3 id="pattern"><a href="#pattern" class="headerlink" title="pattern"></a>pattern</h3><p>由于一个日志事件包括了很多的内容（参考LogEvent），但实际上用户并不希望每次输出日志时都将这些信息全部进行输出，而是希望可以自由地选择要输出的信息。并且，用户还可能需要在每条日志里增加一些指定的字符，比如在文件名和行号之间加上一个冒号的情况。</p><p>为了实现这项功能，LogFormatter使用了一个模板字符串来指定格式化的方式。模板字符串由普通字符和转义字符构成，<strong>转义字符以%开头</strong>，比如%m，%p等。除了转义字符，剩下的全部都是普通字符，包括空格，当前支持以下转义字符：</p><figure class="highlight plaintext"><figcaption><span>text</span></figcaption><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">%m 消息</span><br><span class="line">%p 日志级别</span><br><span class="line">%r 累计毫秒数</span><br><span class="line">%c 日志名称</span><br><span class="line">%t 线程id</span><br><span class="line">%n 换行</span><br><span class="line">%d 时间</span><br><span class="line">%f 文件名</span><br><span class="line">%l 行号</span><br><span class="line">%T 制表符</span><br><span class="line">%F 协程id</span><br><span class="line">%N 线程名称</span><br></pre></td></tr></table></figure><blockquote><p>默认格式</p></blockquote><ul><li>默认格式： <code>&quot;%d&#123;%Y-%m-%d %H:%M:%S&#125;%T%t%T%N%T%F%T[%p]%T[%c]%T%f:%l%T%m%n&quot;</code></li><li>描述：年-月-日 时:分:秒 [累计运行毫秒数] \t 线程id \t 线程名称 \t 协程id \t [日志级别] \t [日志器名称] \t 文件名:行号 \t 日志消息 换行符</li></ul><h3 id="嵌套类FormatItem"><a href="#嵌套类FormatItem" class="headerlink" title="嵌套类FormatItem"></a>嵌套类FormatItem</h3><ul><li><p>FormatItem：日志内容格式化项，一个抽象基类，每个格式模板参数实现一个FormatItem的派生类</p><ul><li><code>void format(std::ostream os, LogEvent::ptr event)</code>：将event中对应模板参数的内容格式化后写入os</li></ul></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><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// .cpp</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// %m</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">MessageFormatItem</span>: <span class="keyword">public</span> LogFormatter::FormatItem&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">MessageFormatItem</span>(<span class="type">const</span> std::string&amp; str = <span class="string">&quot;&quot;</span>)&#123;&#125;</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">format</span><span class="params">(std::ostream&amp; os, LogEvent::ptr event)</span> <span class="keyword">override</span> </span>&#123;</span><br><span class="line">        os &lt;&lt; event-&gt;<span class="built_in">getContent</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="comment">// %p</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">LevelFormatItem</span>: <span class="keyword">public</span> LogFormatter::FormatItem&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">LevelFormatItem</span>(<span class="type">const</span> std::string&amp; str = <span class="string">&quot;&quot;</span>)&#123;&#125;</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">format</span><span class="params">(std::ostream&amp; os, LogEvent::ptr event)</span> <span class="keyword">override</span> </span>&#123;</span><br><span class="line">        os &lt;&lt; LogLevel::<span class="built_in">ToString</span>(event-&gt;<span class="built_in">getLevel</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="comment">// %c</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">LoggerNameFormatItem</span>: <span class="keyword">public</span> LogFormatter::FormatItem&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">LoggerNameFormatItem</span>(<span class="type">const</span> std::string&amp; str = <span class="string">&quot;&quot;</span>)&#123;&#125;</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">format</span><span class="params">(std::ostream&amp; os, LogEvent::ptr event)</span> <span class="keyword">override</span> </span>&#123;</span><br><span class="line">        os &lt;&lt; event-&gt;<span class="built_in">getLoggerName</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="comment">// %d</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">DateTimeFormatItem</span>: <span class="keyword">public</span> LogFormatter::FormatItem&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">DateTimeFormatItem</span>(<span class="type">const</span> std::string&amp; format = <span class="string">&quot;%Y-%m-%d %H:%M:%S&quot;</span>)</span><br><span class="line">    : <span class="built_in">m_format</span>(format)&#123;</span><br><span class="line">        <span class="keyword">if</span>(m_format.<span class="built_in">empty</span>())&#123;</span><br><span class="line">            m_format = <span class="string">&quot;%Y-%m-%d %H:%M:%S&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="function"><span class="type">void</span> <span class="title">format</span><span class="params">(std::ostream&amp; os, LogEvent::ptr event)</span> <span class="keyword">override</span> </span>&#123;    </span><br><span class="line">        <span class="type">time_t</span> time = event-&gt;<span class="built_in">getTime</span>();</span><br><span class="line">        <span class="keyword">struct</span> <span class="title class_">tm</span>* tm = <span class="built_in">localtime</span>(&amp;time);</span><br><span class="line">        <span class="type">char</span> buf[<span class="number">64</span>];</span><br><span class="line">        <span class="built_in">strftime</span>(buf, <span class="built_in">sizeof</span>(buf), m_format.<span class="built_in">c_str</span>(), tm);</span><br><span class="line">        os &lt;&lt; buf;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    std::string m_format;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// %r</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">ElapseFormatItem</span>: <span class="keyword">public</span> LogFormatter::FormatItem&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">ElapseFormatItem</span>(<span class="type">const</span> std::string&amp; str = <span class="string">&quot;&quot;</span>)&#123;&#125;</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">format</span><span class="params">(std::ostream&amp; os, LogEvent::ptr event)</span> <span class="keyword">override</span> </span>&#123;</span><br><span class="line">        os &lt;&lt; event-&gt;<span class="built_in">getElapse</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="comment">// %f</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">FilenameFormatItem</span>: <span class="keyword">public</span> LogFormatter::FormatItem&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">FilenameFormatItem</span>(<span class="type">const</span> std::string&amp; str = <span class="string">&quot;&quot;</span>)&#123;&#125;</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">format</span><span class="params">(std::ostream&amp; os, LogEvent::ptr event)</span> <span class="keyword">override</span> </span>&#123;</span><br><span class="line">        os &lt;&lt; event-&gt;<span class="built_in">getFile</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="comment">// %l</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">LineFormatItem</span>: <span class="keyword">public</span> LogFormatter::FormatItem&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">LineFormatItem</span>(<span class="type">const</span> std::string&amp; str = <span class="string">&quot;&quot;</span>)&#123;&#125;</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">format</span><span class="params">(std::ostream&amp; os, LogEvent::ptr event)</span> <span class="keyword">override</span> </span>&#123;</span><br><span class="line">        os &lt;&lt; event-&gt;<span class="built_in">getLine</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="comment">// %t</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">ThreadIdFormatItem</span>: <span class="keyword">public</span> LogFormatter::FormatItem&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">ThreadIdFormatItem</span>(<span class="type">const</span> std::string&amp; str = <span class="string">&quot;&quot;</span>)&#123;&#125;</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">format</span><span class="params">(std::ostream&amp; os, LogEvent::ptr event)</span> <span class="keyword">override</span> </span>&#123;</span><br><span class="line">        os &lt;&lt; event-&gt;<span class="built_in">getThreadId</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="comment">// %f</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">FiberIdFormatItem</span>: <span class="keyword">public</span> LogFormatter::FormatItem&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">FiberIdFormatItem</span>(<span class="type">const</span> std::string&amp; str = <span class="string">&quot;&quot;</span>)&#123;&#125;</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">format</span><span class="params">(std::ostream&amp; os, LogEvent::ptr event)</span> <span class="keyword">override</span> </span>&#123;</span><br><span class="line">        os &lt;&lt; event-&gt;<span class="built_in">getFiberId</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="comment">// %N</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">ThreadNameFormatItem</span>: <span class="keyword">public</span> LogFormatter::FormatItem&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">ThreadNameFormatItem</span>(<span class="type">const</span> std::string&amp; str = <span class="string">&quot;&quot;</span>)&#123;&#125;</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">format</span><span class="params">(std::ostream&amp; os, LogEvent::ptr event)</span> <span class="keyword">override</span> </span>&#123;</span><br><span class="line">        os &lt;&lt; event-&gt;<span class="built_in">getThreadName</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="comment">// %%</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">PercentSignFormatItem</span>: <span class="keyword">public</span> LogFormatter::FormatItem&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">PercentSignFormatItem</span>(<span class="type">const</span> std::string&amp; str = <span class="string">&quot;&quot;</span>)&#123;&#125;</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">format</span><span class="params">(std::ostream&amp; os, LogEvent::ptr event)</span> <span class="keyword">override</span> </span>&#123;</span><br><span class="line">        os &lt;&lt; <span class="string">&quot;%&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="comment">// %T</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">TabFormatItem</span>: <span class="keyword">public</span> LogFormatter::FormatItem&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">TabFormatItem</span>(<span class="type">const</span> std::string&amp; str = <span class="string">&quot;&quot;</span>)&#123;&#125;</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">format</span><span class="params">(std::ostream&amp; os, LogEvent::ptr event)</span> <span class="keyword">override</span> </span>&#123;</span><br><span class="line">        os &lt;&lt; <span class="string">&#x27;\t&#x27;</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="comment">// %n</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">NewLineFormatItem</span>: <span class="keyword">public</span> LogFormatter::FormatItem&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">NewLineFormatItem</span>(<span class="type">const</span> std::string&amp; str = <span class="string">&quot;&quot;</span>)&#123;&#125;</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">format</span><span class="params">(std::ostream&amp; os, LogEvent::ptr event)</span> <span class="keyword">override</span> </span>&#123;</span><br><span class="line">        os &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"><span class="comment">// 格式化模板参数外的字符串</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">StringFormatItem</span>: <span class="keyword">public</span> LogFormatter::FormatItem&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">StringFormatItem</span>(<span class="type">const</span> std::string&amp; str = <span class="string">&quot;&quot;</span>): <span class="built_in">m_string</span>(str)&#123;&#125;</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">format</span><span class="params">(std::ostream&amp; os, LogEvent::ptr event)</span> <span class="keyword">override</span> </span>&#123;</span><br><span class="line">        os &lt;&lt; m_string;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    std::string m_string;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure></li></ul><h3 id="format"><a href="#format" class="headerlink" title="format"></a>format</h3><p>format使用多态，逐个调用FormatItem派生类的format方法，将日志信息写入os</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">std::ostream&amp; <span class="title">LogFormatter::format</span><span class="params">(std::ostream&amp; os, LogEvent::ptr event)</span></span>&#123;</span><br><span class="line">    <span class="keyword">for</span>(<span class="keyword">auto</span>&amp; formatItem : m_items)&#123;</span><br><span class="line">        formatItem-&gt;format(os, event);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> os;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="init"><a href="#init" class="headerlink" title="init"></a>init</h3><blockquote><p><code>init()</code>是LogFormatter的实现重点，其作用是将m_pattern解析为一个FormatItem::ptr数组，并存入m_item</p></blockquote><p>init的逻辑大致只有以下两步，但实现方式需要多琢磨</p><ol><li><p>使用一个简单的基于状态机的解析方案，解析模板字符串与常规字符</p><ul><li>支持解析上面列举的转义字符，并且支持将连续的普通字符合并成一个字符串</li></ul> <figure class="highlight plaintext"><figcaption><span>text</span></figcaption><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><br><span class="line">    --&gt;是</span><br><span class="line">        --&gt;状态机状态为正在解析模板参数</span><br><span class="line">            - 当前字符为转义%，将模板参数记录到数组</span><br><span class="line">        --&gt;状态机状态为正在解析常规字符串</span><br><span class="line">            - 当前常规字符串解析完成，保存已解析的常规字符串</span><br><span class="line">            - 准备开始解析模板参数，状态机状态变为正在解析模板参数</span><br><span class="line">    --&gt;否</span><br><span class="line">        --&gt;状态机状态为正在解析模板参数</span><br><span class="line">            - 将模板参数记录到数组（对于日期格式(模板参数为d)，还需要进一步进行处理）</span><br><span class="line">            - 解析完模板参数，状态机状态变为正在解析常规字符串</span><br><span class="line">        --&gt;状态机状态为正在解析常规字符串</span><br><span class="line">            - 记录字符到字符串</span><br></pre></td></tr></table></figure></li><li><p>创建模板参数对应的<code>FormatItem</code>对象（使用宏函数）</p></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><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">LogFormatter::init</span><span class="params">()</span></span>&#123;</span><br><span class="line">    <span class="comment">// &lt;type, str&gt;</span></span><br><span class="line">    <span class="comment">// type: 为0时表示为普通字符串，为1时表示为需要解析的模板参数</span></span><br><span class="line">    <span class="comment">// str: 存储字符串内容（普通字符串 or 模板参数）</span></span><br><span class="line">    std::vector&lt;std::pair&lt;<span class="type">int</span>, std::string&gt;&gt; vec;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 模板参数中只有%d（日期时间）需要额外存储模板字符串，所以使用一个单独的变量dataFormat来存储</span></span><br><span class="line">    std::string dateFormat;</span><br><span class="line">    <span class="comment">// 存储常规字符串</span></span><br><span class="line">    std::string normalString;</span><br><span class="line">    <span class="comment">// 状态机，true表示正在解析普通字符串，false表示正在解析模板参数或模板字符串</span></span><br><span class="line">    <span class="type">bool</span> parsing_string = <span class="literal">true</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span>(<span class="type">size_t</span> i = <span class="number">0</span>; i &lt; m_pattern.<span class="built_in">size</span>(); ++i)&#123;</span><br><span class="line">        std::string c = std::<span class="built_in">string</span>(<span class="number">1</span>, m_pattern[i]);</span><br><span class="line">        <span class="keyword">if</span>(c == <span class="string">&quot;%&quot;</span>)&#123;</span><br><span class="line">            <span class="keyword">if</span>(parsing_string)&#123;</span><br><span class="line">                <span class="comment">// 解析常规字符串时碰到%，本段常规字符串的解析完成，状态机变为解析模板字符串模式</span></span><br><span class="line">                <span class="keyword">if</span>(!normalString.<span class="built_in">empty</span>())&#123;</span><br><span class="line">                    vec.<span class="built_in">push_back</span>(std::<span class="built_in">make_pair</span>(<span class="number">0</span>, normalString));</span><br><span class="line">                    normalString.<span class="built_in">clear</span>();</span><br><span class="line">                &#125;</span><br><span class="line">                parsing_string = <span class="literal">false</span>;</span><br><span class="line">                <span class="keyword">continue</span>;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="comment">// 解析模板参数时碰到%，说明是转义%</span></span><br><span class="line">                vec.<span class="built_in">push_back</span>(std::<span class="built_in">make_pair</span>(<span class="number">1</span>, c));</span><br><span class="line">                parsing_string = <span class="literal">true</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="keyword">if</span>(parsing_string)&#123;</span><br><span class="line">                <span class="comment">// 解析常规字符串时，不断将当前字符加入normalString，直到遇到%</span></span><br><span class="line">                normalString += c;</span><br><span class="line">                <span class="keyword">continue</span>;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="comment">// 解析模板字符：直接将模板字符加入vec</span></span><br><span class="line">                vec.<span class="built_in">push_back</span>(std::<span class="built_in">make_pair</span>(<span class="number">1</span>, c));   </span><br><span class="line"></span><br><span class="line">                <span class="comment">// 对日期时间（%d），还需要另外解析dateFormat</span></span><br><span class="line">                <span class="keyword">if</span>(c == <span class="string">&quot;d&quot;</span>)&#123;</span><br><span class="line">                    ++i;</span><br><span class="line">                    <span class="keyword">if</span>(m_pattern[i] != <span class="string">&#x27;&#123;&#x27;</span>)&#123;</span><br><span class="line">                        <span class="comment">// %d后没有跟 &#123;&#125;，dateFormat为空，解析结果依赖DataFormatItem类的默认实现</span></span><br><span class="line">                        <span class="keyword">continue</span>;</span><br><span class="line">                    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                        ++i;</span><br><span class="line">                        <span class="comment">// 将 &#123;&#125; 之间的所有字符都加入dateFormat</span></span><br><span class="line">                        <span class="keyword">while</span>(i &lt; m_pattern.<span class="built_in">size</span>() &amp;&amp; m_pattern[i] != <span class="string">&#x27;&#125;&#x27;</span>)&#123;</span><br><span class="line">                            dateFormat.<span class="built_in">push_back</span>(m_pattern[i]);</span><br><span class="line">                            ++i;</span><br><span class="line">                        &#125;</span><br><span class="line">                        <span class="comment">// 如果遍历到m_pattern尾部都没有遇到&#x27;&#125;&#x27;，说明大括号没有闭合，解析错误</span></span><br><span class="line">                        <span class="keyword">if</span>(i == m_pattern.<span class="built_in">size</span>() &amp;&amp; m_pattern[i - <span class="number">1</span>] != <span class="string">&#x27;&#125;&#x27;</span>)&#123;</span><br><span class="line">                            <span class="comment">// log中加入错误信息</span></span><br><span class="line">                            vec.<span class="built_in">push_back</span>(std::<span class="built_in">make_pair</span>(<span class="number">0</span>, <span class="string">&quot;&lt;&lt;Pattern Error&gt;&gt;&quot;</span>));</span><br><span class="line">                            dateFormat.<span class="built_in">clear</span>();</span><br><span class="line">                            m_error = <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 class="comment">// 模板字符串解析完成，状态机转为默认状态（即解析常规字符串）</span></span><br><span class="line">                parsing_string = <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 class="comment">// 将尾部的常规字符串也加入vec</span></span><br><span class="line">    <span class="keyword">if</span>(!normalString.<span class="built_in">empty</span>())&#123;</span><br><span class="line">        vec.<span class="built_in">push_back</span>(std::<span class="built_in">pair</span>(<span class="number">0</span>, normalString));</span><br><span class="line">        normalString.<span class="built_in">clear</span>();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 字符串到具体FormatItem类的映射，相当于一个简易工厂类</span></span><br><span class="line">    <span class="comment">// 利用宏函数初始化map的成员，两个宏参数：</span></span><br><span class="line">    <span class="comment">// str：通过#str将str的内容字符串化，指定std::function需要生成FormatItem的哪个派生类</span></span><br><span class="line">    <span class="comment">// std::function：接收一个字符串，用该字符串初始化指定的派生类</span></span><br><span class="line">    <span class="type">static</span> std::map&lt;std::string, std::function&lt;FormatItem::ptr(<span class="type">const</span> std::string&amp; str)&gt;&gt; s_format_items = &#123;</span><br><span class="line"><span class="meta">#<span class="keyword">define</span> XX(str, C) &#123;#str, [](const std::string&amp; fmt)&#123; return FormatItem::ptr(new C(fmt)); &#125; &#125;</span></span><br><span class="line"></span><br><span class="line">        <span class="built_in">XX</span>(m, MessageFormatItem),       <span class="comment">// m: 消息</span></span><br><span class="line">        <span class="built_in">XX</span>(p, LevelFormatItem),         <span class="comment">// p: 日志级别</span></span><br><span class="line">        <span class="built_in">XX</span>(c, LoggerNameFormatItem),    <span class="comment">// c: 日志器名称</span></span><br><span class="line">        <span class="built_in">XX</span>(d, DateTimeFormatItem),      <span class="comment">// d: 日期时间</span></span><br><span class="line">        <span class="built_in">XX</span>(r, ElapseFormatItem),        <span class="comment">// r: 累计毫秒数</span></span><br><span class="line">        <span class="built_in">XX</span>(f, FilenameFormatItem),      <span class="comment">// f: 文件名</span></span><br><span class="line">        <span class="built_in">XX</span>(l, LineFormatItem),          <span class="comment">// l: 行号</span></span><br><span class="line">        <span class="built_in">XX</span>(t, ThreadIdFormatItem),      <span class="comment">// t: 线程号</span></span><br><span class="line">        <span class="built_in">XX</span>(F, FiberIdFormatItem),       <span class="comment">// F: 协程号</span></span><br><span class="line">        <span class="built_in">XX</span>(N, ThreadNameFormatItem),    <span class="comment">// N: 线程名称</span></span><br><span class="line">        <span class="built_in">XX</span>(%, PercentSignFormatItem),   <span class="comment">// %: 百分号</span></span><br><span class="line">        <span class="built_in">XX</span>(T, TabFormatItem),           <span class="comment">// T: 制表符</span></span><br><span class="line">        <span class="built_in">XX</span>(n, NewLineFormatItem),       <span class="comment">// n: 换行符</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">undef</span> XX</span></span><br><span class="line">    &#125;;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span>(<span class="keyword">auto</span>&amp; item : vec)&#123;</span><br><span class="line">        <span class="comment">// 常规字符串</span></span><br><span class="line">        <span class="keyword">if</span>(item.first == <span class="number">0</span>)&#123;</span><br><span class="line">            m_items.<span class="built_in">push_back</span>(FormatItem::<span class="built_in">ptr</span>(<span class="keyword">new</span> <span class="built_in">StringFormatItem</span>(item.second)));</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 模板参数</span></span><br><span class="line">        <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="comment">// 日期时间进行特殊处理，使用dateFormat初始化，其他模板参数使用item.second初始化</span></span><br><span class="line">            <span class="keyword">if</span>(item.second == <span class="string">&quot;d&quot;</span>)&#123;</span><br><span class="line">                m_items.<span class="built_in">push_back</span>(FormatItem::<span class="built_in">ptr</span>(<span class="keyword">new</span> <span class="built_in">DateTimeFormatItem</span>(dateFormat)));</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="keyword">auto</span> it = s_format_items.<span class="built_in">find</span>(item.second);</span><br><span class="line">                <span class="keyword">if</span>(it != s_format_items.<span class="built_in">end</span>())&#123;</span><br><span class="line">                    m_items.<span class="built_in">push_back</span>(FormatItem::<span class="built_in">ptr</span>(it-&gt;<span class="built_in">second</span>(item.second)));</span><br><span class="line">                &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                    <span class="comment">// 不合法的模板参数，log记录错误信息</span></span><br><span class="line">                    m_items.<span class="built_in">push_back</span>(FormatItem::<span class="built_in">ptr</span>(<span class="keyword">new</span> <span class="built_in">StringFormatItem</span>(<span class="string">&quot;&lt;&lt;error_format %&quot;</span> + item.second + <span class="string">&quot;&gt;&gt;&quot;</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">&#125;</span><br></pre></td></tr></table></figure><h2 id="LogAppender"><a href="#LogAppender" class="headerlink" title="LogAppender"></a>LogAppender</h2><blockquote><p>LogAppender用于将一个日志事件输出到对应的输出地</p></blockquote><p>LogAppender是一个虚类，可以派生出不同的具体实现。不同类型的Appender通过重载log方法来实现往不同的目的地进行输出</p><p>基类指定了格式化器（LogFormatter），用户可以自定义格式化器，如果用户没有定义，则使用默认格式化器</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// .h</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 日志输出地</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">LogAppender</span>&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="keyword">using</span> ptr = std::shared_ptr&lt;LogAppender&gt;;</span><br><span class="line">    <span class="keyword">using</span> MutexType = SpinLock;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">LogAppender</span>(LogFormatter::ptr defaultFormatter)</span><br><span class="line">        : <span class="built_in">m_defaultFormatter</span>(defaultFormatter)&#123;&#125;;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">virtual</span> ~<span class="built_in">LogAppender</span>()&#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">log</span><span class="params">(LogEvent::ptr event)</span> </span>= <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">setFormatter</span><span class="params">(LogFormatter::ptr formatter)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="function">LogFormatter::ptr <span class="title">getFormatter</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="keyword">virtual</span> std::string <span class="title">toYamlString</span><span class="params">()</span> <span class="type">const</span> </span>= <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">protected</span>:</span><br><span class="line">    <span class="comment">// Logger在调用Appender之前已经判断了日志级别，这里不需要再重复判断level</span></span><br><span class="line">    <span class="comment">// LogLevel::Level m_level;</span></span><br><span class="line">    LogFormatter::ptr m_formatter;          <span class="comment">// 自定义formatter</span></span><br><span class="line">    LogFormatter::ptr m_defaultFormatter;   <span class="comment">// 默认formatter</span></span><br><span class="line">    <span class="keyword">mutable</span> MutexType m_mutex;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h3 id="基类的get-set方法"><a href="#基类的get-set方法" class="headerlink" title="基类的get&#x2F;set方法"></a>基类的get&#x2F;set方法</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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">LogAppender::setFormatter</span><span class="params">(LogFormatter::ptr formatter)</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 创建一个MutexType的范围锁</span></span><br><span class="line">    <span class="function">MutexType::Lock <span class="title">lock</span><span class="params">(m_mutex)</span></span>;   </span><br><span class="line">    m_formatter = formatter;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function">LogFormatter::ptr <span class="title">LogAppender::getFormatter</span><span class="params">()</span> <span class="type">const</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 创建一个MutexType的范围锁</span></span><br><span class="line">    <span class="function">MutexType::Lock <span class="title">lock</span><span class="params">(m_mutex)</span></span>;</span><br><span class="line">    <span class="keyword">return</span> m_formatter; </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="StdoutLogAppender——输出到终端"><a href="#StdoutLogAppender——输出到终端" class="headerlink" title="StdoutLogAppender——输出到终端"></a>StdoutLogAppender——输出到终端</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// .h</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">StdoutLogAppender</span>: <span class="keyword">public</span> LogAppender&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">StdoutLogAppender</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">using</span> ptr = std::shared_ptr&lt;StdoutLogAppender&gt;;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">log</span><span class="params">(LogEvent::ptr event)</span> <span class="keyword">override</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="function">std::string <span class="title">toYamlString</span><span class="params">()</span> <span class="type">const</span> <span class="keyword">override</span></span>;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// .cpp</span></span><br><span class="line">StdoutLogAppender::<span class="built_in">StdoutLogAppender</span>()</span><br><span class="line">    : <span class="built_in">LogAppender</span>(LogFormatter::<span class="built_in">ptr</span>(<span class="keyword">new</span> LogFormatter))&#123;</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">StdoutLogAppender::log</span><span class="params">(LogEvent::ptr event)</span></span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(m_formatter)&#123;</span><br><span class="line">        <span class="function">MutexType::Lock <span class="title">lock</span><span class="params">(m_mutex)</span></span>;</span><br><span class="line">        m_formatter-&gt;format(std::cout, event);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        MutexType::Lock <span class="built_in">lock</span>(m_mutex);</span><br><span class="line">        m_defaultFormatter-&gt;format(std::cout, event);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="FileLogAppender——输出到文件"><a href="#FileLogAppender——输出到文件" class="headerlink" title="FileLogAppender——输出到文件"></a>FileLogAppender——输出到文件</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><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// .h</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">FileLogAppender</span>: <span class="keyword">public</span> LogAppender&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="keyword">using</span> ptr = std::shared_ptr&lt;FileLogAppender&gt;;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">FileLogAppender</span>(<span class="type">const</span> std::string&amp; filename);</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">log</span><span class="params">(LogEvent::ptr event)</span> <span class="keyword">override</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="function">std::string <span class="title">toYamlString</span><span class="params">()</span> <span class="type">const</span> <span class="keyword">override</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="comment">// 重新打开文件（如果文件是打开状态，则先关闭它再打开）</span></span><br><span class="line">    <span class="comment">// 打开成功返回true，否则返回false</span></span><br><span class="line">    <span class="function"><span class="type">bool</span> <span class="title">reopen</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    std::string m_filename;         <span class="comment">// 输出目的地的文件名</span></span><br><span class="line">    std::ofstream m_filestream;     <span class="comment">// 输出目的地的文件流</span></span><br><span class="line">    <span class="type">uint64_t</span> m_lastTime;            <span class="comment">// 上次打开时间</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// .cpp</span></span><br><span class="line">FileLogAppender::<span class="built_in">FileLogAppender</span>(<span class="type">const</span> std::string&amp; filename)</span><br><span class="line">    : <span class="built_in">LogAppender</span>(LogFormatter::<span class="built_in">ptr</span>(<span class="keyword">new</span> LogFormatter))</span><br><span class="line">    , <span class="built_in">m_filename</span>(filename)&#123;</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">FileLogAppender::log</span><span class="params">(LogEvent::ptr event)</span></span>&#123;</span><br><span class="line">    <span class="comment">// 定时reopen文件，防止因文件被其他线程删除等原因而log失败</span></span><br><span class="line">    <span class="type">uint64_t</span> now = event-&gt;<span class="built_in">getTime</span>();</span><br><span class="line">    <span class="keyword">if</span>(now &gt;= (m_lastTime + <span class="number">3</span>))&#123;</span><br><span class="line">        <span class="keyword">if</span>(!<span class="built_in">reopen</span>())&#123;</span><br><span class="line">            std::cerr &lt;&lt; <span class="string">&quot;reopen fail, file name=&quot;</span> &lt;&lt; m_filename &lt;&lt; std::endl;</span><br><span class="line">        &#125;</span><br><span class="line">        m_lastTime = now;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 上范围锁</span></span><br><span class="line">    <span class="function">MutexType::Lock <span class="title">lock</span><span class="params">(m_mutex)</span></span>;</span><br><span class="line">    <span class="comment">// log</span></span><br><span class="line">    <span class="keyword">if</span>(m_filestream)&#123;</span><br><span class="line">        <span class="keyword">if</span>(m_formatter)&#123;</span><br><span class="line">            m_formatter-&gt;format(m_filestream, event);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            m_defaultFormatter-&gt;format(m_filestream, event);</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><h2 id="Logger"><a href="#Logger" class="headerlink" title="Logger"></a>Logger</h2><blockquote><p>Logger负责进行日志输出</p></blockquote><ul><li>Logger的多线程安全：因为log操作的阻塞时间较短，所以使用<strong>自旋锁</strong>来保证多线程安全</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 日志器</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Logger</span>&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="keyword">friend</span> LogManager;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">using</span> ptr = std::shared_ptr&lt;Logger&gt;;</span><br><span class="line">    <span class="keyword">using</span> MutexType = SpinLock;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">Logger</span>(<span class="type">const</span> std::string&amp; name = <span class="string">&quot;root&quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">log</span><span class="params">(LogEvent::ptr event)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">addAppender</span><span class="params">(LogAppender::ptr appender)</span></span>;</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">delAppender</span><span class="params">(LogAppender::ptr appender)</span></span>;</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">clearAppenders</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="function">LogLevel::Level <span class="title">getLevel</span><span class="params">()</span> <span class="type">const</span> </span>&#123; <span class="keyword">return</span> m_level; &#125;;</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">setLevel</span><span class="params">(LogLevel::Level level)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="function">std::string <span class="title">getName</span><span class="params">()</span> <span class="type">const</span> </span>&#123; <span class="keyword">return</span> m_name; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function">std::string <span class="title">toYamlString</span><span class="params">()</span> <span class="type">const</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="comment">// 日志器的名称</span></span><br><span class="line">    std::string m_name;</span><br><span class="line">    <span class="comment">// 日志级别，只有高于该级别的日志事件才会被输出</span></span><br><span class="line">    LogLevel::Level m_level;</span><br><span class="line">    <span class="comment">// 日志输出地的集合</span></span><br><span class="line">    std::list&lt;LogAppender::ptr&gt; m_appenders;</span><br><span class="line">    <span class="comment">// 主日志器，当logger没有定义appender时，其行为与该主日志器一致</span></span><br><span class="line">    Logger::ptr m_root;</span><br><span class="line">    <span class="comment">// 锁</span></span><br><span class="line">    <span class="keyword">mutable</span> MutexType m_mutex;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h3 id="log"><a href="#log" class="headerlink" title="log"></a>log</h3><p>log接受一个日志事件，先判断该日志事件的级别是否大于本日志器的日志级别。如果日志级别满足条件，则调用appender的log方法将日志事件进行输出</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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">Logger::log</span><span class="params">(LogEvent::ptr event)</span></span>&#123;</span><br><span class="line">    <span class="comment">// 仅输出大于m_level的日志事件</span></span><br><span class="line">    <span class="keyword">if</span>(event-&gt;<span class="built_in">getLevel</span>() &gt;= m_level)&#123;</span><br><span class="line">        <span class="comment">// 上范围锁</span></span><br><span class="line">        <span class="function">MutexType::Lock <span class="title">lock</span><span class="params">(m_mutex)</span></span>;</span><br><span class="line">        <span class="comment">// 对所有appender进行log</span></span><br><span class="line">        <span class="keyword">if</span>(!m_appenders.<span class="built_in">empty</span>())&#123;</span><br><span class="line">            <span class="keyword">for</span>(<span class="keyword">auto</span>&amp; appender: m_appenders)&#123;</span><br><span class="line">                appender-&gt;<span class="built_in">log</span>(event);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span>(m_root) &#123;</span><br><span class="line">            <span class="comment">// 如果没有配置appender，就使用主日志器m_root的appenders进行log</span></span><br><span class="line">            <span class="keyword">for</span>(<span class="keyword">auto</span>&amp; appender : m_root-&gt;m_appenders)&#123;</span><br><span class="line">                appender-&gt;<span class="built_in">log</span>(event);</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></pre></td></tr></table></figure><h3 id="appender操作"><a href="#appender操作" class="headerlink" title="appender操作"></a>appender操作</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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">Logger::addAppender</span><span class="params">(LogAppender::ptr appender)</span></span>&#123;</span><br><span class="line">    <span class="function">MutexType::Lock <span class="title">lock</span><span class="params">(m_mutex)</span></span>;</span><br><span class="line">    m_appenders.<span class="built_in">push_back</span>(appender);</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">Logger::delAppender</span><span class="params">(LogAppender::ptr appender)</span></span>&#123;</span><br><span class="line">    <span class="function">MutexType::Lock <span class="title">lock</span><span class="params">(m_mutex)</span></span>;</span><br><span class="line">    <span class="keyword">for</span>(<span class="keyword">auto</span> it = m_appenders.<span class="built_in">begin</span>(); it != m_appenders.<span class="built_in">end</span>(); ++it)&#123;</span><br><span class="line">        <span class="keyword">if</span>(*it == appender)&#123;</span><br><span class="line">            m_appenders.<span class="built_in">erase</span>(it);</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"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">Logger::clearAppenders</span><span class="params">()</span></span>&#123;</span><br><span class="line">    <span class="function">MutexType::Lock <span class="title">lock</span><span class="params">(m_mutex)</span></span>;</span><br><span class="line">    m_appenders.<span class="built_in">clear</span>();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="toYamlString"><a href="#toYamlString" class="headerlink" title="toYamlString"></a>toYamlString</h3><p>将Logger信息输出为Yaml格式的字符串</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::string <span class="title">Logger::toYamlString</span><span class="params">()</span> <span class="type">const</span> </span>&#123;</span><br><span class="line">    <span class="function">YAML::Node <span class="title">node</span><span class="params">(YAML::NodeType::Map)</span></span>;</span><br><span class="line">    node[<span class="string">&quot;name&quot;</span>] = m_name;</span><br><span class="line">    node[<span class="string">&quot;level&quot;</span>] = LogLevel::<span class="built_in">ToString</span>(m_level);</span><br><span class="line">    <span class="comment">// 读取m_appenders，上范围锁</span></span><br><span class="line">    <span class="function">MutexType::Lock <span class="title">lock</span><span class="params">(m_mutex)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span>(<span class="type">const</span> <span class="keyword">auto</span>&amp; a : m_appenders)&#123;</span><br><span class="line">        node[<span class="string">&quot;appenders&quot;</span>].<span class="built_in">push_back</span>(YAML::<span class="built_in">Load</span>(a-&gt;<span class="built_in">toYamlString</span>()));</span><br><span class="line">    &#125;</span><br><span class="line">    std::stringstream ss;</span><br><span class="line">    ss &lt;&lt; node;</span><br><span class="line">    <span class="keyword">return</span> ss.<span class="built_in">str</span>();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="LogEventWrap"><a href="#LogEventWrap" class="headerlink" title="LogEventWrap"></a>LogEventWrap</h2><blockquote><p>LogEventWrap是一个<strong>RAII类</strong>，它封装了一个Logger和LogEvent，在其析构时调用Logger的log方法提交日志事件</p></blockquote><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// .h</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">LogEventWrap</span>&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">LogEventWrap</span>(Logger::ptr logger, LogEvent::ptr event);</span><br><span class="line"></span><br><span class="line">    ~<span class="built_in">LogEventWrap</span>();</span><br><span class="line"></span><br><span class="line">    <span class="function">LogEvent::ptr <span class="title">getEvent</span><span class="params">()</span> <span class="type">const</span> </span>&#123; <span class="keyword">return</span> m_event; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function">std::stringstream&amp; <span class="title">getSS</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    Logger::ptr m_logger;</span><br><span class="line">    LogEvent::ptr m_event;</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// .cpp</span></span><br><span class="line"></span><br><span class="line">LogEventWrap::<span class="built_in">LogEventWrap</span>(Logger::ptr logger, LogEvent::ptr event)</span><br><span class="line">    : <span class="built_in">m_logger</span>(logger)</span><br><span class="line">    , <span class="built_in">m_event</span>(event)&#123;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// LogEventWrap析构时对日志事件进行log</span></span><br><span class="line">LogEventWrap::~<span class="built_in">LogEventWrap</span>()&#123;</span><br><span class="line">    m_logger-&gt;<span class="built_in">log</span>(m_event);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function">std::stringstream&amp; <span class="title">LogEventWrap::getSS</span><span class="params">()</span></span>&#123;</span><br><span class="line">    <span class="keyword">return</span> m_event-&gt;<span class="built_in">getSS</span>();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="LogManager"><a href="#LogManager" class="headerlink" title="LogManager"></a>LogManager</h2><blockquote><p>LogManager用于统一管理所有的日志器，提供日志器的创建与获取方法</p></blockquote><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Logger的管理器，管理主日志器以及所有Logger</span></span><br><span class="line"><span class="comment">// LogManager是创建Logger的唯一方式</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">LogManager</span>&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="keyword">friend</span> Singleton&lt;LogManager&gt;;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">using</span> MutexType = SpinLock;</span><br><span class="line"></span><br><span class="line">    <span class="function">Logger::ptr <span class="title">getLogger</span><span class="params">(std::string loggerName)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// todo: init实现从配置文件中加载日志配置</span></span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">init</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="function">Logger::ptr <span class="title">getRoot</span><span class="params">()</span> <span class="type">const</span> </span>&#123; <span class="keyword">return</span> m_root; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function">std::string <span class="title">toYamlString</span><span class="params">()</span> <span class="type">const</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="comment">// 单例模式，私有的构造函数</span></span><br><span class="line">    <span class="built_in">LogManager</span>();</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="comment">// 主日志器</span></span><br><span class="line">    Logger::ptr m_root;</span><br><span class="line">    <span class="comment">// 管理包括m_root在内所有日志器的容器</span></span><br><span class="line">    std::map&lt;std::string, Logger::ptr&gt; m_loggers;</span><br><span class="line">    <span class="comment">// Mutex</span></span><br><span class="line">    <span class="keyword">mutable</span> MutexType m_mutex;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用单例模式管理LogManager</span></span><br><span class="line"><span class="keyword">using</span> LoggerMgr = Singleton&lt;LogManager&gt;;</span><br></pre></td></tr></table></figure><h3 id="Singleton单例类"><a href="#Singleton单例类" class="headerlink" title="Singleton单例类"></a>Singleton单例类</h3><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// Singleton.h</span></span><br><span class="line"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T, <span class="keyword">typename</span> X = <span class="type">void</span>, <span class="type">int</span> N = <span class="number">0</span>&gt;</span><br><span class="line"><span class="keyword">class</span> Singleton&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="type">static</span> T* <span class="built_in">GetInstance</span>()&#123;</span><br><span class="line">        <span class="type">static</span> T v;</span><br><span class="line">        <span class="keyword">return</span> &amp;v; </span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>通过类型别名为用户提供创建LogManager单例的方法：</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">// log.h</span></span><br><span class="line"><span class="keyword">using</span> LoggerMgr = Singleton&lt;LogManager&gt;;</span><br></pre></td></tr></table></figure><h3 id="构造函数"><a href="#构造函数" class="headerlink" title="构造函数"></a>构造函数</h3><p>构造时默认创建一个root日志器，将日志信息输出到终端</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">LogManager::<span class="built_in">LogManager</span>()&#123;</span><br><span class="line">    m_root.<span class="built_in">reset</span>(<span class="keyword">new</span> <span class="built_in">Logger</span>(<span class="string">&quot;root&quot;</span>));</span><br><span class="line">    m_root-&gt;<span class="built_in">addAppender</span>(std::<span class="built_in">make_shared</span>&lt;StdoutLogAppender&gt;());</span><br><span class="line">    m_loggers[m_root-&gt;<span class="built_in">getName</span>()] = m_root;</span><br><span class="line">    <span class="built_in">init</span>();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="getLogger"><a href="#getLogger" class="headerlink" title="getLogger"></a>getLogger</h3><p>getLogger根据日志器名从管理的日志器中返回一个Logger，否则以该名字创建一个Logger</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">Logger::ptr <span class="title">LogManager::getLogger</span><span class="params">(std::string loggerName)</span></span>&#123;</span><br><span class="line">    <span class="comment">// 需要读取m_loggers，上范围锁</span></span><br><span class="line">    <span class="function">MutexType::Lock <span class="title">lock</span><span class="params">(m_mutex)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">auto</span> it = m_loggers.<span class="built_in">find</span>(loggerName);</span><br><span class="line">    <span class="comment">// 找到logger就返回该logger</span></span><br><span class="line">    <span class="keyword">if</span>(it != m_loggers.<span class="built_in">end</span>())&#123;</span><br><span class="line">        <span class="keyword">return</span> it-&gt;second;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 否则以该名字创建一个logger，其主日志器指向LogManager的主日志器</span></span><br><span class="line">    <span class="function">Logger::ptr <span class="title">newLogger</span><span class="params">(std::make_shared&lt;Logger&gt;(loggerName))</span></span>;</span><br><span class="line">    newLogger-&gt;m_root = m_root;</span><br><span class="line">    m_loggers[loggerName] = newLogger;</span><br><span class="line">    <span class="keyword">return</span> newLogger;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="toYamlString-1"><a href="#toYamlString-1" class="headerlink" title="toYamlString"></a>toYamlString</h3><p>将<code>LoggerManager</code>的信息输出为Yaml格式的字符串</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></pre></td><td class="code"><pre><span class="line"><span class="function">std::string <span class="title">LogManager::toYamlString</span><span class="params">()</span> <span class="type">const</span> </span>&#123;</span><br><span class="line">    <span class="function">MutexType::Lock <span class="title">lock</span><span class="params">(m_mutex)</span></span>;</span><br><span class="line">    <span class="function">YAML::Node <span class="title">node</span><span class="params">(YAML::NodeType::Map)</span></span>;</span><br><span class="line">    <span class="keyword">for</span>(<span class="type">const</span> <span class="keyword">auto</span>&amp; l : m_loggers)&#123;</span><br><span class="line">        node[<span class="string">&quot;logs&quot;</span>].<span class="built_in">push_back</span>(YAML::<span class="built_in">Load</span>(l.second-&gt;<span class="built_in">toYamlString</span>()));</span><br><span class="line">    &#125;</span><br><span class="line">    std::stringstream ss;</span><br><span class="line">    ss &lt;&lt; node;</span><br><span class="line">    <span class="keyword">return</span> ss.<span class="built_in">str</span>();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="实用辅助函数"><a href="#实用辅助函数" class="headerlink" title="实用辅助函数"></a>实用辅助函数</h2><h3 id="使用流式方式写入日志"><a href="#使用流式方式写入日志" class="headerlink" title="使用流式方式写入日志"></a>使用流式方式写入日志</h3><p>使用流式方式将日志级别level的日志写入logger</p><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// .h</span></span><br><span class="line"><span class="comment">// 使用流式方式将日志级别level的日志写入logger</span></span><br><span class="line"><span class="comment">// 这里宏函数替换的是代码段，所以不能使用inline替代宏函数</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> SYLAR_LOG_LEVEL(logger, level) \</span></span><br><span class="line"><span class="meta">    <span class="keyword">if</span>(logger-&gt;getLevel() &lt;= level) \</span></span><br><span class="line"><span class="meta">        sylar::LogEventWrap(logger, std::make_shared<span class="string">&lt;sylar::LogEvent&gt;</span>(logger-&gt;getName(), level, __FILE__, __LINE__\</span></span><br><span class="line"><span class="meta">                            , 0, sylar::GetThreadId(), sylar::GetFiberId(), time(0), sylar::GetThreadName())).getSS()</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> SYLAR_LOG_DEBUG(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::DEBUG)</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> SYLAR_LOG_INFO(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::INFO)</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> SYLAR_LOG_WARN(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::WARN)</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> SYLAR_LOG_ERROR(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::ERROR)</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> SYLAR_LOG_FATAL(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::FATAL)</span></span><br></pre></td></tr></table></figure><h3 id="使用格式化方式写入日志"><a href="#使用格式化方式写入日志" class="headerlink" title="使用格式化方式写入日志"></a>使用格式化方式写入日志</h3><p>基于inline实现</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">// 使用格式化方式将日志级别level的日志写入logger</span></span><br><span class="line"><span class="comment">// 使用可变参数模板+inline替代宏函数</span></span><br><span class="line"><span class="function"><span class="keyword">template</span>&lt;<span class="keyword">typename</span>... Arg&gt;</span></span><br><span class="line"><span class="function"><span class="keyword">inline</span> <span class="type">void</span> <span class="title">SYLAR_LOG_FMT_LEVEL</span><span class="params">(sylar::Logger::ptr logger, sylar::LogLevel::Level level, <span class="type">const</span> <span class="type">char</span>* fmt, Arg&amp;&amp;... args)</span></span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(logger-&gt;<span class="built_in">getLevel</span>() &lt;= level)&#123;</span><br><span class="line">        sylar::<span class="built_in">LogEventWrap</span>(logger, std::<span class="built_in">make_shared</span>&lt;sylar::LogEvent&gt;(logger-&gt;<span class="built_in">getName</span>(), level, __FILE__, __LINE__</span><br><span class="line">                            , <span class="number">0</span>, sylar::<span class="built_in">GetThreadId</span>(), <span class="number">42</span>, <span class="built_in">time</span>(<span class="number">0</span>), <span class="string">&quot;Thread&quot;</span>)).<span class="built_in">getEvent</span>()-&gt;format(fmt, args...);</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="keyword">template</span>&lt;<span class="keyword">typename</span>... Arg&gt;</span></span><br><span class="line"><span class="function"><span class="keyword">inline</span> <span class="type">void</span> <span class="title">SYLAR_LOG_FMT_DEBUG</span><span class="params">(sylar::Logger::ptr logger, <span class="type">const</span> <span class="type">char</span>* fmt, Arg&amp;&amp;... args)</span></span>&#123;</span><br><span class="line">    <span class="built_in">SYLAR_LOG_FMT_LEVEL</span>(logger, sylar::LogLevel::DEBUG, fmt, args...);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">template</span>&lt;<span class="keyword">typename</span>... Arg&gt;</span></span><br><span class="line"><span class="function"><span class="keyword">inline</span> <span class="type">void</span> <span class="title">SYLAR_LOG_FMT_INFO</span><span class="params">(sylar::Logger::ptr logger, <span class="type">const</span> <span class="type">char</span>* fmt, Arg&amp;&amp;... args)</span></span>&#123;</span><br><span class="line">    <span class="built_in">SYLAR_LOG_FMT_LEVEL</span>(logger, sylar::LogLevel::INFO, fmt, args...);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">template</span>&lt;<span class="keyword">typename</span>... Arg&gt;</span></span><br><span class="line"><span class="function"><span class="keyword">inline</span> <span class="type">void</span> <span class="title">SYLAR_LOG_FMT_WARN</span><span class="params">(sylar::Logger::ptr logger, <span class="type">const</span> <span class="type">char</span>* fmt, Arg&amp;&amp;... args)</span></span>&#123;</span><br><span class="line">    <span class="built_in">SYLAR_LOG_FMT_LEVEL</span>(logger, sylar::LogLevel::WARN, fmt, args...);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">template</span>&lt;<span class="keyword">typename</span>... Arg&gt;</span></span><br><span class="line"><span class="function"><span class="keyword">inline</span> <span class="type">void</span> <span class="title">SYLAR_LOG_FMT_ERROR</span><span class="params">(sylar::Logger::ptr logger, <span class="type">const</span> <span class="type">char</span>* fmt, Arg&amp;&amp;... args)</span></span>&#123;</span><br><span class="line">    <span class="built_in">SYLAR_LOG_FMT_LEVEL</span>(logger, sylar::LogLevel::ERROR, fmt, args...);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">template</span>&lt;<span class="keyword">typename</span>... Arg&gt;</span></span><br><span class="line"><span class="function"><span class="keyword">inline</span> <span class="type">void</span> <span class="title">SYLAR_LOG_FMT_FATAL</span><span class="params">(sylar::Logger::ptr logger, <span class="type">const</span> <span class="type">char</span>* fmt, Arg&amp;&amp;... args)</span></span>&#123;</span><br><span class="line">    <span class="built_in">SYLAR_LOG_FMT_LEVEL</span>(logger, sylar::LogLevel::FATAL, fmt, args...);</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 获取LogManager中的主日志器（root）</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> SYLAR_LOG_ROOT() sylar::LoggerMgr::GetInstance()-&gt;getRoot()</span></span><br><span class="line"><span class="comment">// 根据名字获取logger</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> SYLAR_LOG_NAME(name) sylar::LoggerMgr::GetInstance()-&gt;getLogger(name)</span></span><br></pre></td></tr></table></figure><h1 id="模块使用"><a href="#模块使用" class="headerlink" title="模块使用"></a>模块使用</h1><h2 id="例子1"><a href="#例子1" class="headerlink" title="例子1"></a>例子1</h2><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></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="comment">// 自定义输出地与输出格式</span></span><br><span class="line">    sylar::<span class="function">FileLogAppender::ptr <span class="title">file_appender</span><span class="params">(<span class="keyword">new</span> sylar::FileLogAppender(<span class="string">&quot;./log.txt&quot;</span>))</span></span>;</span><br><span class="line">    sylar::<span class="function">LogFormatter::ptr <span class="title">fmt</span><span class="params">(<span class="keyword">new</span> sylar::LogFormatter(<span class="string">&quot;%d%T%p%T%m%n&quot;</span>))</span></span>;</span><br><span class="line">    file_appender-&gt;<span class="built_in">setFormatter</span>(fmt);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取LogerManager单例</span></span><br><span class="line">    <span class="keyword">auto</span> mgr = sylar::LoggerMgr::<span class="built_in">GetInstance</span>();</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 根日志器新增appender</span></span><br><span class="line">    mgr-&gt;<span class="built_in">getRoot</span>()-&gt;<span class="built_in">addAppender</span>(file_appender);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 根据LogManager单例创建Logger</span></span><br><span class="line">    <span class="keyword">auto</span> logger = mgr-&gt;<span class="built_in">getLogger</span>(<span class="string">&quot;Logger01&quot;</span>);</span><br><span class="line">    logger-&gt;<span class="built_in">addAppender</span>(std::<span class="built_in">make_shared</span>&lt;sylar::StdoutLogAppender&gt;());</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 使用宏函数流式输出日志信息</span></span><br><span class="line">    <span class="function">std::string <span class="title">str</span><span class="params">(<span class="string">&quot;LoggerManager&quot;</span>)</span></span>;</span><br><span class="line">    <span class="built_in">SYLAR_LOG_ERROR</span>(mgr-&gt;<span class="built_in">getRoot</span>()) &lt;&lt; <span class="string">&quot;test &quot;</span> &lt;&lt; str &lt;&lt; <span class="string">&quot; Error&quot;</span>;</span><br><span class="line">    <span class="built_in">SYLAR_LOG_INFO</span>(logger) &lt;&lt; <span class="string">&quot;test logger01&quot;</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 实用格式化方式输出日志信息</span></span><br><span class="line">    <span class="built_in">SYLAR_LOG_FMT_DEBUG</span>(mgr-&gt;<span class="built_in">getRoot</span>(), <span class="string">&quot;test %s Debug &lt;%s&gt;&quot;</span>, str.<span class="built_in">c_str</span>(), <span class="string">&quot;using inline and template&quot;</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">&#125;</span><br></pre></td></tr></table></figure><h2 id="例子2"><a href="#例子2" class="headerlink" title="例子2"></a>例子2</h2><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 获取system日志器</span></span><br><span class="line"><span class="type">static</span> sylar::Logger::ptr g_logger1 = <span class="built_in">SYLAR_LOG_NAME</span>(<span class="string">&quot;system&quot;</span>);</span><br><span class="line"><span class="comment">// 使用system日志器流式打印INFO级别的日志</span></span><br><span class="line"><span class="built_in">SYLAR_LOG_INFO</span>(g_logger1) &lt;&lt; <span class="string">&quot;test logger01&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取root日志器</span></span><br><span class="line"><span class="type">static</span> sylar::Logger::ptr g_logger2 = <span class="built_in">SYLAR_LOG_ROOT</span>();</span><br><span class="line"><span class="comment">// 使用root日志器流式打印INFO级别的日志</span></span><br><span class="line"><span class="built_in">SYLAR_LOG_INFO</span>(g_logger2) &lt;&lt; <span class="string">&quot;test logger02&quot;</span>;</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;模块概述&quot;&gt;&lt;a href=&quot;#模块概述&quot; class=&quot;headerlink&quot; title=&quot;模块概述&quot;&gt;&lt;/a&gt;模块概述&lt;/h1&gt;&lt;p&gt;模仿log4j日志框架实现一个的日志模块&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;难点：Logformatter的init()，引入了一个</summary>
      
    
    
    
    <category term="学习笔记" scheme="https://atelieryu.site/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="后端" scheme="https://atelieryu.site/tags/%E5%90%8E%E7%AB%AF/"/>
    
    <category term="C++" scheme="https://atelieryu.site/tags/C/"/>
    
    <category term="日志库" scheme="https://atelieryu.site/tags/%E6%97%A5%E5%BF%97%E5%BA%93/"/>
    
    <category term="服务器框架" scheme="https://atelieryu.site/tags/%E6%9C%8D%E5%8A%A1%E5%99%A8%E6%A1%86%E6%9E%B6/"/>
    
  </entry>
  
  <entry>
    <title>C++实现轻量级RPC分布式网络通信框架</title>
    <link href="https://atelieryu.site/RPC.html"/>
    <id>https://atelieryu.site/RPC.html</id>
    <published>2025-03-18T12:38:00.000Z</published>
    <updated>2025-03-25T03:23:00.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><h2 id="RPC简介"><a href="#RPC简介" class="headerlink" title="RPC简介"></a>RPC简介</h2><p>RPC（Remote Procedure Call）是一种使程序能够像调用本地函数一样调用远程服务的方法。它屏蔽了底层的通信细节，使得<strong>开发人员无需关注远程调用的复杂性，只需像操作本地方法一样调用远程方法</strong>。</p><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><ul><li><a href="https://blog.csdn.net/T_Solotov/article/details/124107667?spm=1001.2014.3001.5501">参考博客</a></li><li><a href="https://github.com/youngyangyang04/Krpc">参考项目</a></li></ul><h2 id="项目仓库"><a href="#项目仓库" class="headerlink" title="项目仓库"></a>项目仓库</h2><a class="ghcard" rel="external nofollow noopener noreferrer" href="https://github.com/YVollerei/RPC"><img src="https://github-readme-stats.vercel.app/api/pin/?username=YVollerei&repo=RPC&show_owner=true"/></a><h1 id="项目概述"><a href="#项目概述" class="headerlink" title="项目概述"></a>项目概述</h1><h2 id="框架流程图"><a href="#框架流程图" class="headerlink" title="框架流程图"></a>框架流程图</h2><p><img src="https://atelieryu.xyz/elog/202503/658296c43b32f9ef93f5847831247355.png" alt="image.png"></p><h2 id="代码调用流程"><a href="#代码调用流程" class="headerlink" title="代码调用流程"></a>代码调用流程</h2><p><img src="https://atelieryu.xyz/elog/202503/25f25fec56e09f89f5fd76fd8d2bcc2e.png" alt="image.png"></p><h3 id="三个主体"><a href="#三个主体" class="headerlink" title="三个主体"></a>三个主体</h3><ul><li><p><strong>zookeeper服务端</strong></p><ul><li>zooKeeper在这里作为服务方法的管理配置中心，负责管理服务方法提供者对外提供的服务方法</li><li>Rpc服务端与客户端的身份都是zookeeper客户端</li><li>zookeeper存储服务对象与服务方法的方式：</li></ul><p>  <img src="https://atelieryu.xyz/elog/202503/d9567a2a7c46ad22233b0c41cef598ff.png" alt="image.png"></p></li><li><p><strong>Rpc服务端</strong></p><ul><li>Rpc服务端需要向zookeeper注册服务对象与服务方法，注册的内容是本机上提供该服务的ip+端口</li><li>注册完服务后，启动epoll监听客户端的远端调用请求</li><li>接收到Rpc客户端的远端调用后，先对调用参数进行反序列化，再调用本地方法处理该调用，最后将处理结果包装为响应，序列化后发出</li></ul></li><li><p><strong>Rpc客户端</strong></p><ul><li>Rpc客户端需要先从zookeeper中查询提供目标服务的Rpc服务端ip与端口</li><li>查询到Rpc服务端的ip与端口向目标端口发起连接，之后就是send - recv的流程，区别是send前需要对请求内容进行序列化，recv后需要对响应内容进行反序列化</li></ul></li></ul><h1 id="Protobuf"><a href="#Protobuf" class="headerlink" title="Protobuf"></a>Protobuf</h1><p>定义了两个proto文件</p><h2 id="Krpcheader-proto"><a href="#Krpcheader-proto" class="headerlink" title="Krpcheader.proto"></a>Krpcheader.proto</h2><p>该文件定义了<strong>RPC调用的元数据头部</strong></p><figure class="highlight protobuf"><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">syntax = <span class="string">&quot;proto3&quot;</span>;</span><br><span class="line"><span class="keyword">package</span> Krpc;</span><br><span class="line"></span><br><span class="line"><span class="keyword">message </span><span class="title class_">RpcHeader</span> &#123;</span><br><span class="line">    <span class="type">bytes</span> service_name = <span class="number">1</span>;</span><br><span class="line">    <span class="type">bytes</span> method_name = <span class="number">2</span>;</span><br><span class="line">    <span class="type">uint32</span> args_size = <span class="number">3</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>service_name：用于标识目标服务（如 <code>UserServiceRpc</code>）</li><li>method_name：用于标识目标方法（如 <code>Login</code>）</li><li>args_size：表示后续参数数据的字节长度，避免粘包问题</li></ul><p>服务端接收到请求后，通过service_name和method_name找到对应的服务</p><h2 id="user-proto"><a href="#user-proto" class="headerlink" title="user.proto"></a>user.proto</h2><p>该文件定义了<code>UserServiceRpc</code>服务，以及它的两个方法<code>Login</code>和<code>Register</code></p><p>可以根据具体需求改动<code>LoginRequest</code>和<code>RegisterRequest</code>中的参数</p><figure class="highlight protobuf"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line">syntax = <span class="string">&quot;proto3&quot;</span>;</span><br><span class="line"><span class="keyword">package</span> Kuser;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 控制生成基于protobuf的通用RPC服务基类</span></span><br><span class="line"><span class="keyword">option</span> cc_generic_services = <span class="literal">true</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">message </span><span class="title class_">ResultCode</span>&#123;</span><br><span class="line">    <span class="type">int32</span> errcode = <span class="number">1</span>;</span><br><span class="line">    <span class="type">bytes</span> errmsg = <span class="number">2</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">message </span><span class="title class_">LoginRequest</span> &#123;</span><br><span class="line">    <span class="type">bytes</span> name = <span class="number">1</span>;</span><br><span class="line">    <span class="type">bytes</span> pwd = <span class="number">2</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">message </span><span class="title class_">LoginResponse</span> &#123;</span><br><span class="line">    ResultCode result = <span class="number">1</span>;</span><br><span class="line">    <span class="type">bool</span> success = <span class="number">2</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">message </span><span class="title class_">RegisterRequest</span> &#123;</span><br><span class="line">    <span class="type">uint32</span> id = <span class="number">1</span>;</span><br><span class="line">    <span class="type">bytes</span> name = <span class="number">2</span>;</span><br><span class="line">    <span class="type">bytes</span> pwd = <span class="number">3</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">message </span><span class="title class_">RegisterResponse</span> &#123;</span><br><span class="line">    ResultCode result = <span class="number">1</span>;</span><br><span class="line">    <span class="type">bool</span> success = <span class="number">2</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">service </span><span class="title class_">UserServiceRpc</span>&#123;</span><br><span class="line">    <span class="function"><span class="keyword">rpc</span> Login(LoginRequest) <span class="keyword">returns</span>(LoginResponse)</span>;</span><br><span class="line">    <span class="function"><span class="keyword">rpc</span> Register(RegisterRequest) <span class="keyword">returns</span>(RegisterResponse)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Login和Register"><a href="#Login和Register" class="headerlink" title="Login和Register"></a>Login和Register</h3><ul><li>定义Login方法接收两个参数：name和pwd</li><li>定义Register方法接收三个参数：id、name、pwd</li></ul><h3 id="cc-generic-services"><a href="#cc-generic-services" class="headerlink" title="cc_generic_services"></a>cc_generic_services</h3><p>启用<code>cc_generic_services</code>后，proto会生成两个C++类</p><ul><li><code>UserServiceRpc</code>：callee需要继承此类并实现 <code>Login</code> 和 <code>Register</code> 方法</li><li><code>UserServiceRpc_Stub</code>：客户端存根类，由caller继承，通过 <code>RpcChannel</code> 发起调用</li></ul><h2 id="生成代码"><a href="#生成代码" class="headerlink" title="生成代码"></a>生成代码</h2><p>调用以下命令生成proto文件对应的C++代码</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">protoc user.proto -I ./ --cpp_out=./user</span><br></pre></td></tr></table></figure><p>生成的 user.h 和 user.cc 会被保存到 .&#x2F;user 文件夹中</p><h1 id="RPC服务端"><a href="#RPC服务端" class="headerlink" title="RPC服务端"></a>RPC服务端</h1><h2 id="服务端主文件Kserver-cpp"><a href="#服务端主文件Kserver-cpp" class="headerlink" title="服务端主文件Kserver.cpp"></a>服务端主文件Kserver.cpp</h2><p>主文件由UserService和main函数两部分组成</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></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 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">&quot;../user.pb.h&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;KrpcProvider.h&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;KrpcApplication.h&quot;</span></span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// 服务端上的自定义UserService，继承自proto生成的UserServiceRpc</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">UserService</span>: <span class="keyword">public</span> Kuser::UserServiceRpc&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="comment">// 这个版本执行Login的本地任务 </span></span><br><span class="line">    <span class="function"><span class="type">bool</span> <span class="title">Login</span><span class="params">(std::string name, std::string pwd)</span></span>&#123;</span><br><span class="line">        std::cout &lt;&lt; <span class="string">&quot;doing local service: Login&quot;</span> &lt;&lt; std::endl;</span><br><span class="line">        std::cout &lt;&lt; <span class="string">&quot;name=&quot;</span> &lt;&lt; name &lt;&lt; <span class="string">&quot; pwd=&quot;</span> &lt;&lt; pwd &lt;&lt; std::endl;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 重写UserServiceRpc的虚函数</span></span><br><span class="line">    <span class="comment">// 这个版本的Login执行：接收远端调用 - 调用执行本地任务 - 写入响应 - 执行回调</span></span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">Login</span><span class="params">(::google::protobuf::RpcController* controller,</span></span></span><br><span class="line"><span class="params"><span class="function">                    <span class="type">const</span> ::Kuser::LoginRequest* request,</span></span></span><br><span class="line"><span class="params"><span class="function">                    ::Kuser::LoginResponse* response,</span></span></span><br><span class="line"><span class="params"><span class="function">                    ::google::protobuf::Closure* done)</span></span>&#123;</span><br><span class="line">        <span class="comment">// 接收远端调用的参数</span></span><br><span class="line">        std::string name = request-&gt;<span class="built_in">name</span>();</span><br><span class="line">        std::string pwd = request-&gt;<span class="built_in">pwd</span>();</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 调用重载版本，执行本地任务</span></span><br><span class="line">        <span class="type">bool</span> login_result = <span class="built_in">Login</span>(name, pwd);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 写入响应，参考user.proto中的定义</span></span><br><span class="line">        Kuser::ResultCode *code = response-&gt;<span class="built_in">mutable_result</span>();</span><br><span class="line">        code-&gt;<span class="built_in">set_errcode</span>(<span class="number">0</span>);</span><br><span class="line">        code-&gt;<span class="built_in">set_errmsg</span>(<span class="string">&quot;&quot;</span>);</span><br><span class="line">        response-&gt;<span class="built_in">set_success</span>(login_result);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 执行回调（执行响应对象数据的序列化和网络发送，交给框架来完成）</span></span><br><span class="line">        done-&gt;<span class="built_in">Run</span>();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">(<span class="type">int</span> argc, <span class="type">char</span>** argv)</span></span>&#123;</span><br><span class="line">    <span class="comment">// 调用框架的初始化操作</span></span><br><span class="line">    KrpcApplication::<span class="built_in">Init</span>(argc, argv);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// provider将UserService发布到rpc节点上</span></span><br><span class="line">    KrpcProvider provider;</span><br><span class="line">    provider.<span class="built_in">NotifyService</span>(<span class="keyword">new</span> <span class="built_in">UserService</span>());</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 启动rpc服务发布节点。Run以后进程进入阻塞状态，等待远程的rpc调用请求</span></span><br><span class="line">    provider.<span class="built_in">Run</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">&#125;</span><br></pre></td></tr></table></figure><h3 id="UserService"><a href="#UserService" class="headerlink" title="UserService"></a>UserService</h3><p>UserService继承自proto文件生成的UserServiceRpc，为具体服务，其中定义了服务中Login方法的两个具体实现：</p><ul><li>一个版本的Login负责执行本地任务</li><li>一个版本的Login负责接收远端调用，之后调用重载版本执行本地任务，最后将执行结果写入响应，并阻塞等待回调</li></ul><h3 id="main"><a href="#main" class="headerlink" title="main"></a>main</h3><p>main函数主要执行以下工作：</p><ol><li>调用框架的初始化操作，从配置文件中读取必要的配置信息</li><li>将上面定义的UserService发布到Rpc节点上</li><li>启动Rpc服务发布节点，阻塞等待远端客户端调用该服务</li></ol><h2 id="为服务端提供服务的类"><a href="#为服务端提供服务的类" class="headerlink" title="为服务端提供服务的类"></a>为服务端提供服务的类</h2><h3 id="KrpcApplication"><a href="#KrpcApplication" class="headerlink" title="KrpcApplication"></a>KrpcApplication</h3><p>KrpcApplication主要负责框架的初始化操作，有以下几个注意点：</p><ul><li>KrpcApplication为一个单例类</li><li>初始化操作具体为：从命令行中解析出配置文件路径 → 调用KrpcConfig类载入配置文件，完成各参数的初始化</li><li>初始化的配置项包括zookeeper服务器的ip和端口，以及Rpc服务端自己的ip和端口</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// .h</span></span><br><span class="line"><span class="meta">#<span class="keyword">pragma</span> once</span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;KrpcConfig.h&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;KrpcChannel.h&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;KrpcController.h&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;mutex&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Krpc基础类，负责框架的初始化操作</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">KrpcApplication</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="function"><span class="type">static</span> <span class="type">void</span> <span class="title">Init</span><span class="params">(<span class="type">int</span> argc, <span class="type">char</span>** argv)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 单例模式，返回单例对象</span></span><br><span class="line">    <span class="function"><span class="type">static</span> KrpcApplication&amp; <span class="title">GetInstance</span><span class="params">()</span></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">DeleteInstance</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="function"><span class="type">static</span> KrpcConfig&amp; <span class="title">GetConfig</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="comment">// 单例模式，私有的构造函数</span></span><br><span class="line">    <span class="built_in">KrpcApplication</span>()&#123;&#125;;</span><br><span class="line">    ~<span class="built_in">KrpcApplication</span>()&#123;&#125;;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 删除拷贝与移动构造</span></span><br><span class="line">    <span class="built_in">KrpcApplication</span>(<span class="type">const</span> KrpcApplication&amp;) = <span class="keyword">delete</span>;</span><br><span class="line">    <span class="built_in">KrpcApplication</span>(KrpcApplication&amp;&amp;) = <span class="keyword">delete</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="comment">// 配置</span></span><br><span class="line">    <span class="type">static</span> KrpcConfig m_config;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 单例对象</span></span><br><span class="line">    <span class="type">static</span> KrpcApplication* m_application;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 互斥量</span></span><br><span class="line">    <span class="type">static</span> std::mutex m_mutex;</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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// .cpp</span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;KrpcApplication.h&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;unistd.h&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><br><span class="line"></span><br><span class="line"><span class="comment">// 再次声明静态变量</span></span><br><span class="line">KrpcConfig KrpcApplication::m_config;</span><br><span class="line">std::mutex KrpcApplication::m_mutex;</span><br><span class="line"><span class="comment">// 懒汉模式初始化单例对象</span></span><br><span class="line">KrpcApplication* KrpcApplication::m_application = <span class="literal">nullptr</span>;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// 初始化框架</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">KrpcApplication::Init</span><span class="params">(<span class="type">int</span> argc, <span class="type">char</span>** argv)</span></span>&#123;</span><br><span class="line">    std::cout &lt;&lt; <span class="string">&quot;KrpcApplication::Init&quot;</span> &lt;&lt; std::endl;</span><br><span class="line">    <span class="keyword">if</span>(argc &lt; <span class="number">2</span>)&#123;</span><br><span class="line">        <span class="comment">// -i后必须有配置文件路径，文件中记录zookeeper服务器的ip和端口，以及服务器的ip和端口</span></span><br><span class="line">        std::cout &lt;&lt; <span class="string">&quot;format::command -i &lt;configfile&gt;&quot;</span> &lt;&lt; std::endl;</span><br><span class="line">        <span class="built_in">exit</span>(EXIT_FAILURE);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="type">int</span> opt;</span><br><span class="line">    std::string config_file;</span><br><span class="line">    <span class="comment">// getopt用于解析命令行字符，第三个参数表示接收的参数，这里只指定i</span></span><br><span class="line">    <span class="keyword">while</span>(<span class="number">-1</span> != (opt = <span class="built_in">getopt</span>(argc, argv, <span class="string">&quot;i:&quot;</span>)))&#123;</span><br><span class="line">        <span class="keyword">switch</span>(opt)&#123;</span><br><span class="line">            <span class="keyword">case</span> <span class="string">&#x27;i&#x27;</span>:&#123;</span><br><span class="line">                std::cout &lt;&lt; <span class="string">&quot;KrpcApplication: case -i&quot;</span> &lt;&lt; std::endl;</span><br><span class="line">                <span class="comment">// -i表示指定配置文件路径</span></span><br><span class="line">                <span class="comment">// optarg为命令行参数对应的值，这里即为指定的配置文件路径</span></span><br><span class="line">                config_file = optarg;</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">case</span> <span class="string">&#x27;?&#x27;</span>:&#123;</span><br><span class="line">                <span class="comment">// 不接受i以外的命令行参数</span></span><br><span class="line">                std::cout &lt;&lt; <span class="string">&quot;format::command -i &lt;configfile&gt;&quot;</span> &lt;&lt; std::endl;</span><br><span class="line">                <span class="built_in">exit</span>(EXIT_FAILURE);</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">case</span> <span class="string">&#x27;:&#x27;</span>:&#123;</span><br><span class="line">                <span class="comment">// 出现了i但后面没有对应的值</span></span><br><span class="line">                std::cout &lt;&lt; <span class="string">&quot;format::command -i &lt;configfile&gt;&quot;</span> &lt;&lt; std::endl;</span><br><span class="line">                <span class="built_in">exit</span>(EXIT_FAILURE);</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">default</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">    <span class="comment">// 从配置文件中载入配置项</span></span><br><span class="line">    m_config.<span class="built_in">LoadConfigFile</span>(config_file.<span class="built_in">c_str</span>());</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="function">KrpcApplication&amp; <span class="title">KrpcApplication::GetInstance</span><span class="params">()</span></span>&#123;</span><br><span class="line">    <span class="comment">// 获取单例时上锁</span></span><br><span class="line">    <span class="function">std::lock_guard&lt;std::mutex&gt; <span class="title">lock</span><span class="params">(m_mutex)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span>(!m_application)&#123;</span><br><span class="line">        m_application = <span class="keyword">new</span> <span class="built_in">KrpcApplication</span>();</span><br><span class="line">        <span class="comment">// 设置程序退出时自动销毁单例对象</span></span><br><span class="line">        <span class="built_in">atexit</span>(DeleteInstance);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> *m_application;</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="function"><span class="type">void</span> <span class="title">KrpcApplication::DeleteInstance</span><span class="params">()</span></span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(m_application)&#123;</span><br><span class="line">        <span class="keyword">delete</span> m_application;</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="function">KrpcConfig&amp; <span class="title">KrpcApplication::GetConfig</span><span class="params">()</span></span>&#123;</span><br><span class="line">    <span class="keyword">return</span> m_config;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="KrpcConfig"><a href="#KrpcConfig" class="headerlink" title="KrpcConfig"></a>KrpcConfig</h3><p>KrpcConfig主要负责实际的配置项初始化操作，具体就是从配置文件中逐行读入配置项，逻辑比较简单</p><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// .h</span></span><br><span class="line"><span class="meta">#<span class="keyword">pragma</span> once</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;string&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">KrpcConfig</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="function"><span class="type">void</span> <span class="title">LoadConfigFile</span><span class="params">(<span class="type">const</span> <span class="type">char</span>* config_file)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 查找配置项对应的值</span></span><br><span class="line">    <span class="function">std::string <span class="title">Load</span><span class="params">(<span class="type">const</span> std::string&amp; key)</span> <span class="type">const</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</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">Trim</span><span class="params">(std::string&amp; read_buf)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="comment">// 存放配置项的容器</span></span><br><span class="line">    std::unordered_map&lt;std::string, std::string&gt; m_configs;</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// .cpp</span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;KrpcConfig.h&quot;</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><br><span class="line"><span class="comment">// 加载配置文件</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">KrpcConfig::LoadConfigFile</span><span class="params">(<span class="type">const</span> <span class="type">char</span>* config_file)</span></span>&#123;</span><br><span class="line">    <span class="comment">// 使用智能指针管理文件指针，并指定删除器为fclose</span></span><br><span class="line">    <span class="function">std::unique_ptr&lt;FILE, <span class="title">decltype</span><span class="params">(&amp;fclose)</span>&gt; <span class="title">pf</span><span class="params">(fopen(config_file, <span class="string">&quot;r&quot;</span>), fclose)</span></span>;</span><br><span class="line">    <span class="keyword">if</span>(pf == <span class="literal">nullptr</span>)&#123;</span><br><span class="line">        <span class="built_in">perror</span>(<span class="string">&quot;fopen&quot;</span>);</span><br><span class="line">        <span class="built_in">exit</span>(EXIT_FAILURE);</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="type">char</span> buf[<span class="number">1024</span>];</span><br><span class="line">    <span class="keyword">while</span>(<span class="built_in">fgets</span>(buf, <span class="number">1024</span>, pf.<span class="built_in">get</span>()) != <span class="literal">nullptr</span>)&#123;</span><br><span class="line">        <span class="function">std::string <span class="title">read_buf</span><span class="params">(buf)</span></span>;</span><br><span class="line">        <span class="built_in">Trim</span>(read_buf);</span><br><span class="line">        <span class="comment">// 跳过空行与注释行</span></span><br><span class="line">        <span class="keyword">if</span>(read_buf.<span class="built_in">empty</span>() || read_buf[<span class="number">0</span>] == <span class="string">&#x27;#&#x27;</span>)&#123;</span><br><span class="line">            <span class="keyword">continue</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// &#x27;=&#x27;前为配置名，后为配置值</span></span><br><span class="line">        <span class="type">int</span> ind = read_buf.<span class="built_in">find</span>(<span class="string">&#x27;=&#x27;</span>);</span><br><span class="line">        <span class="keyword">if</span>(ind == <span class="number">-1</span>)&#123;</span><br><span class="line">            <span class="keyword">continue</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 解析key</span></span><br><span class="line">        std::string key = read_buf.<span class="built_in">substr</span>(<span class="number">0</span>, ind);</span><br><span class="line">        <span class="built_in">Trim</span>(key);</span><br><span class="line">        <span class="comment">// 解析value</span></span><br><span class="line">        <span class="type">int</span> endInd = read_buf.<span class="built_in">find</span>(<span class="string">&#x27;\n&#x27;</span>, ind);</span><br><span class="line">        std::string value = read_buf.<span class="built_in">substr</span>(ind + <span class="number">1</span>, endInd - ind - <span class="number">1</span>);</span><br><span class="line">        <span class="built_in">Trim</span>(value);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 插入配置项</span></span><br><span class="line">        m_configs[key] = value;</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="function">std::string <span class="title">KrpcConfig::Load</span><span class="params">(<span class="type">const</span> std::string&amp; key)</span> <span class="type">const</span> </span>&#123;</span><br><span class="line">    <span class="keyword">auto</span> it = m_configs.<span class="built_in">find</span>(key);</span><br><span class="line">    <span class="keyword">if</span>(it == m_configs.<span class="built_in">end</span>())&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;&quot;</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> it-&gt;second;</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="function"><span class="type">void</span> <span class="title">KrpcConfig::Trim</span><span class="params">(std::string&amp; read_buf)</span></span>&#123;</span><br><span class="line">    <span class="comment">// 去除前导空格</span></span><br><span class="line">    <span class="type">int</span> ind = read_buf.<span class="built_in">find_first_not_of</span>(<span class="string">&#x27; &#x27;</span>);</span><br><span class="line">    <span class="keyword">if</span>(ind != <span class="number">-1</span>)&#123;</span><br><span class="line">        read_buf = read_buf.<span class="built_in">substr</span>(ind, read_buf.<span class="built_in">size</span>() - ind);</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">    ind = read_buf.<span class="built_in">find_last_not_of</span>(<span class="string">&#x27; &#x27;</span>);</span><br><span class="line">    <span class="keyword">if</span>(ind != <span class="number">-1</span>)&#123;</span><br><span class="line">        read_buf = read_buf.<span class="built_in">substr</span>(<span class="number">0</span>, ind + <span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="KrpcProvider"><a href="#KrpcProvider" class="headerlink" title="KrpcProvider"></a>KrpcProvider</h2><div class="note info modern"><p>Rpc服务端的核心函数类，提供发布Rpc方法、启动Rpc服务节点等功能</p></div><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// .h</span></span><br><span class="line"><span class="meta">#<span class="keyword">pragma</span> once</span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;google/protobuf/service.h&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;zookeeperutil.h&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;muduo/net/TcpServer.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;muduo/net/EventLoop.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;muduo/net/InetAddress.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;muduo/net/TcpConnection.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;google/protobuf/descriptor.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;functional&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;unordered_map&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">KrpcProvider</span>&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    ~<span class="built_in">KrpcProvider</span>();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 提供给外部使用，用于发布rpc方法</span></span><br><span class="line">    <span class="comment">// 多态：所有服务都继承自google::protobuf::Service</span></span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">NotifyService</span><span class="params">(google::protobuf::Service* service)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 启动Rpc服务节点，开始提供Rpc远程调用服务</span></span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">Run</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="comment">// 服务结构体，用于保存具体的服务对象和它的方法</span></span><br><span class="line">    <span class="keyword">struct</span> <span class="title class_">ServiceInfo</span>&#123;</span><br><span class="line">        google::protobuf::Service* service;</span><br><span class="line">        std::unordered_map&lt;std::string, <span class="type">const</span> google::protobuf::MethodDescriptor*&gt; method_map;</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="function"><span class="type">void</span> <span class="title">OnConnection</span><span class="params">(<span class="type">const</span> muduo::net::TcpConnectionPtr&amp; conn)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 处理已有连接上发来的消息</span></span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">OnMessage</span><span class="params">(<span class="type">const</span> muduo::net::TcpConnectionPtr&amp; conn, muduo::net::Buffer* buffer, muduo::Timestamp receive_time)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 发送Rpc响应</span></span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">SendRpcResponse</span><span class="params">(<span class="type">const</span> muduo::net::TcpConnectionPtr&amp; conn, google::protobuf::Message* response)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    muduo::net::EventLoop m_eventloop;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 保存服务对象的容器</span></span><br><span class="line">    std::unordered_map&lt;std::string, ServiceInfo&gt; m_services;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><blockquote><p>KrpcProvider如何管理服务对象与其方法？</p></blockquote><ul><li>使用unordered_map存放ServiceInfo，管理所有服务</li><li>每个ServiceInfo也使用一个unordered_map存放method描述符，管理该服务下的所有方法</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></pre></td><td class="code"><pre><span class="line">KrpcProvider::~<span class="built_in">KrpcProvider</span>()&#123;</span><br><span class="line">    <span class="comment">// 析构函数，停止Event Loop</span></span><br><span class="line">    std::cout &lt;&lt; <span class="string">&quot;~KrpcProvider()&quot;</span> &lt;&lt; std::endl;</span><br><span class="line">    m_eventloop.<span class="built_in">quit</span>();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="NotifyService"><a href="#NotifyService" class="headerlink" title="NotifyService"></a>NotifyService</h3><p><code>NotifyService</code>提供给外部使用，用于<strong>在Rpc服务端上注册RPC服务</strong>：</p><ul><li>将传入的服务保存到<code>m_services</code>中，等待之后调用Run时发布到zookeeper服务器上</li></ul><p>多态思想的利用：</p><ul><li>所有服务都继承自<code>google::protobuf::Service</code>，所以<code>NotifyService</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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">KrpcProvider::NotifyService</span><span class="params">(google::protobuf::Service* service)</span></span>&#123;</span><br><span class="line">    <span class="comment">// 存放服务对象及其方法的结构体</span></span><br><span class="line">    ServiceInfo service_info;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 利用多态返回服务类的描述信息</span></span><br><span class="line">    <span class="type">const</span> google::protobuf::ServiceDescriptor* psd = service-&gt;<span class="built_in">GetDescriptor</span>();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 将服务对象的方法都存入service_info</span></span><br><span class="line">    <span class="type">int</span> method_cnt = psd-&gt;<span class="built_in">method_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; method_cnt; ++i)&#123;</span><br><span class="line">        <span class="type">const</span> google::protobuf::MethodDescriptor* pmd = psd-&gt;<span class="built_in">method</span>(i);</span><br><span class="line">        service_info.method_map.<span class="built_in">emplace</span>(pmd-&gt;<span class="built_in">name</span>(), pmd);</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">    m_services.<span class="built_in">emplace</span>(psd-&gt;<span class="built_in">name</span>(), service_info);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Run"><a href="#Run" class="headerlink" title="Run"></a>Run</h3><p><code>Run</code>的主要工作：</p><ol><li>从配置文件中读取Rpc服务器的ip和端口</li><li>调用muduo库接口创建TcpServer对象，并分别绑定连接事件和消息事件，实现网络连接业务和消息处理业务的分离</li><li>将<code>m_services</code>中<strong>注册的服务全部发布到zookeeper服务器上</strong>，让Rpc客户端可以从zookeeper上发现Rpc服务端提供的服务</li><li>所有服务都完成发布后，<strong>启动muduo库网络服务</strong></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><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">KrpcProvider::Run</span><span class="params">()</span></span>&#123;</span><br><span class="line">    <span class="comment">// 从配置文件中读取Rpc服务端的ip和端口</span></span><br><span class="line">    std::string ip = KrpcApplication::<span class="built_in">GetInstance</span>().<span class="built_in">GetConfig</span>().<span class="built_in">Load</span>(<span class="string">&quot;rpcserverip&quot;</span>);</span><br><span class="line">    <span class="type">int</span> port = <span class="built_in">atoi</span>(KrpcApplication::<span class="built_in">GetInstance</span>().<span class="built_in">GetConfig</span>().<span class="built_in">Load</span>(<span class="string">&quot;rpcserverport&quot;</span>).<span class="built_in">c_str</span>());</span><br><span class="line">    <span class="comment">// 创建地址</span></span><br><span class="line">    muduo::<span class="function">net::InetAddress <span class="title">address</span><span class="params">(ip, port)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 创建TcpServer对象</span></span><br><span class="line">    <span class="keyword">auto</span> server = std::<span class="built_in">make_shared</span>&lt;muduo::net::TcpServer&gt;(&amp;m_eventloop, address, <span class="string">&quot;KrpcProvider&quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 分别绑定连接事件和消息事件</span></span><br><span class="line">    server-&gt;<span class="built_in">setConnectionCallback</span>(std::<span class="built_in">bind</span>(&amp;KrpcProvider::OnConnection, <span class="keyword">this</span>, std::placeholders::_1));</span><br><span class="line">    server-&gt;<span class="built_in">setMessageCallback</span>(std::<span class="built_in">bind</span>(&amp;KrpcProvider::OnMessage, <span class="keyword">this</span>, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 设置线程数量为4</span></span><br><span class="line">    server-&gt;<span class="built_in">setThreadNum</span>(<span class="number">4</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 把当前Rpc服务端的服务全部注册到zookeeper上，使得Rpc客户端能够从zookeeper上发现服务</span></span><br><span class="line">    ZkClient zkclient;</span><br><span class="line">    zkclient.<span class="built_in">Start</span>();</span><br><span class="line">    std::cout &lt;&lt; <span class="string">&quot;KrpcProvider: zkclient Start success!&quot;</span> &lt;&lt; std::endl;</span><br><span class="line">    <span class="comment">// service_name为永久节点，method_name为临时节点</span></span><br><span class="line">    <span class="keyword">for</span>(<span class="keyword">auto</span>&amp; service: m_services)&#123;</span><br><span class="line">        <span class="comment">// service_name的路径: /service_name</span></span><br><span class="line">        std::string service_path = <span class="string">&quot;/&quot;</span> + service.first;</span><br><span class="line">        std::cout &lt;&lt; <span class="string">&quot;KrpcProvider: zkclient Create znode: &quot;</span> &lt;&lt; service_path &lt;&lt; std::endl;</span><br><span class="line">        zkclient.<span class="built_in">Create</span>(service_path.<span class="built_in">c_str</span>(), <span class="literal">nullptr</span>, <span class="number">0</span>);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 创建method_name节点</span></span><br><span class="line">        <span class="keyword">for</span>(<span class="keyword">auto</span>&amp; method: service.second.method_map)&#123;</span><br><span class="line">            std::string method_path = service_path + <span class="string">&quot;/&quot;</span> + method.first;</span><br><span class="line">            <span class="type">char</span> method_path_data[<span class="number">128</span>] = &#123;<span class="number">0</span>&#125;;</span><br><span class="line">            <span class="comment">// 写入节点内容：ip + 端口</span></span><br><span class="line">            <span class="built_in">sprintf</span>(method_path_data, <span class="string">&quot;%s:%d&quot;</span>, ip.<span class="built_in">c_str</span>(), port);</span><br><span class="line">            <span class="comment">// zookeeper上创建临时节点</span></span><br><span class="line">            zkclient.<span class="built_in">Create</span>(method_path.<span class="built_in">c_str</span>(), method_path_data, <span class="built_in">strlen</span>(method_path_data), ZOO_EPHEMERAL);</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">// Rpc服务端准备启动</span></span><br><span class="line">    std::cout &lt;&lt; <span class="string">&quot;RpcProvider start service at ip:&quot;</span> &lt;&lt; ip &lt;&lt; <span class="string">&quot;, port:&quot;</span> &lt;&lt; port &lt;&lt; std::endl;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 启动muduo网络服务</span></span><br><span class="line">    server-&gt;<span class="built_in">start</span>();</span><br><span class="line">    m_eventloop.<span class="built_in">loop</span>();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="OnConnection"><a href="#OnConnection" class="headerlink" title="OnConnection"></a>OnConnection</h3><p>如果连接失效，则调用shutdown断开连接，除此不对连接事件做特殊处理</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">void</span> <span class="title">KrpcProvider::OnConnection</span><span class="params">(<span class="type">const</span> muduo::net::TcpConnectionPtr&amp; conn)</span></span>&#123;</span><br><span class="line">    <span class="comment">// 不做特别处理</span></span><br><span class="line">    <span class="keyword">if</span>(!conn-&gt;<span class="built_in">connected</span>())&#123;</span><br><span class="line">        conn-&gt;<span class="built_in">shutdown</span>();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="OnMessage"><a href="#OnMessage" class="headerlink" title="OnMessage"></a>OnMessage</h3><p><code>OnMessage</code>的主要工作：</p><ol><li>处理Tcp粘包问题，反序列化从字节流中解析出参数</li><li>根据参数找到对应的服务与方法</li><li>生成Rpc方法调用的请求和响应，调用本地的方法，并通过回调函数发送响应</li></ol><blockquote><p>如何处理Tcp粘包问题?</p></blockquote><p>将字节流分割为以下几部分：</p><ul><li><code>header_size</code>: 固定4字节，记录header_str的长度</li><li><code>header_str</code>: 记录服务名、方法名、参数长度（KrpcHeader.proto中定义）</li><li><code>arg_str</code>: 用于调用方法的参数</li></ul><blockquote><p>什么是<code>NewCallback</code>？</p></blockquote><p><code>NewCallback</code>函数会返回一个google::protobuf::Closure类的对象，可以理解为定义了一个<strong>回调函数</strong></p><p>Closure类对象相当于一个闭包，它捕获了以下内容：</p><ul><li>一个成员对象的成员函数（这里为<code>SendRpcResponse</code>）</li><li>以及这个成员函数需要的参数（这里为<code>conn</code>、<code>response</code>）</li></ul><blockquote><p>什么是<code>CallMethod</code> ？</p></blockquote><p>CallMethod在<code>UserServiceRpc</code>中实现（proto自动生成），功能为<strong>根据远端Rpc请求，调用当前Rpc节点上发布的方法</strong></p><p><code>request</code>与<code>response</code>中包含了调用method的参数，<code>done</code>是执行完method后的回调函数，这里指定了<code>SendRpcResponse</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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">KrpcProvider::OnMessage</span><span class="params">(<span class="type">const</span> muduo::net::TcpConnectionPtr&amp; conn, muduo::net::Buffer* buffer, muduo::Timestamp receive_time)</span></span>&#123;</span><br><span class="line">    std::cout &lt;&lt; <span class="string">&quot;OnMessage&quot;</span> &lt;&lt; std::endl;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 接收远端调用的字节流</span></span><br><span class="line">    std::string recv_buf = buffer-&gt;<span class="built_in">retrieveAllAsString</span>();</span><br><span class="line">    <span class="comment">// ArrayInputStream: 将字节流包装为一个可读取的输入溜</span></span><br><span class="line">    google::protobuf::<span class="function">io::ArrayInputStream <span class="title">raw_input</span><span class="params">(recv_buf.data(), recv_buf.size())</span></span>;</span><br><span class="line">    <span class="comment">// CodedInputStream: 提供高效的二进制流解析工具</span></span><br><span class="line">    google::protobuf::<span class="function">io::CodedInputStream <span class="title">coded_input</span><span class="params">(&amp;raw_input)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 读取4字节的header_size</span></span><br><span class="line">    <span class="type">uint32_t</span> header_size&#123;&#125;;</span><br><span class="line">    coded_input.<span class="built_in">ReadVarint32</span>(&amp;header_size);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 根据header_size的值读取header_str，并对其反序列化，得到Rpc请求的详细信息（既服务名、方法名、参数大小）</span></span><br><span class="line">    std::string rpc_header_str;</span><br><span class="line">    Krpc::RpcHeader krpcHeader;</span><br><span class="line">    std::string service_name;</span><br><span class="line">    std::string method_name;</span><br><span class="line">    <span class="type">uint32_t</span> args_size&#123;&#125;;</span><br><span class="line">    <span class="comment">// 设置读取规则，读取header_str</span></span><br><span class="line">    google::protobuf::io::CodedInputStream::Limit msg_limit = coded_input.<span class="built_in">PushLimit</span>(header_size);</span><br><span class="line">    coded_input.<span class="built_in">ReadString</span>(&amp;rpc_header_str, header_size);</span><br><span class="line">    <span class="comment">// 恢复规则，便于之后安全地读取其他数据</span></span><br><span class="line">    coded_input.<span class="built_in">PopLimit</span>(msg_limit);</span><br><span class="line">    <span class="comment">// 反序列化，解析KrpcHeader</span></span><br><span class="line">    <span class="keyword">if</span>(krpcHeader.<span class="built_in">ParseFromString</span>(rpc_header_str))&#123;</span><br><span class="line">        service_name = krpcHeader.<span class="built_in">service_name</span>();</span><br><span class="line">        method_name = krpcHeader.<span class="built_in">method_name</span>();</span><br><span class="line">        args_size = krpcHeader.<span class="built_in">args_size</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;Error: krpcHeader parse error&quot;</span> &lt;&lt; std::endl;</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="comment">// 用于调用Rpc方法的参数</span></span><br><span class="line">    std::string args_str;</span><br><span class="line">    <span class="comment">// 读取args_size长度的字符串</span></span><br><span class="line">    <span class="keyword">if</span>(!coded_input.<span class="built_in">ReadString</span>(&amp;args_str, args_size))&#123;</span><br><span class="line">        std::cout &lt;&lt; <span class="string">&quot;Error: read args error&quot;</span> &lt;&lt; std::endl;</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="comment">// 在Rpc服务端中搜索service对象和method对象</span></span><br><span class="line">    <span class="keyword">auto</span> sit = m_services.<span class="built_in">find</span>(service_name);</span><br><span class="line">    <span class="keyword">if</span>(sit == m_services.<span class="built_in">end</span>())&#123;</span><br><span class="line">        std::cout &lt;&lt; service_name &lt;&lt; <span class="string">&quot; is no exist!&quot;</span> &lt;&lt; std::endl;</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">auto</span> mit = sit-&gt;second.method_map.<span class="built_in">find</span>(method_name);</span><br><span class="line">    <span class="keyword">if</span>(mit == sit-&gt;second.method_map.<span class="built_in">end</span>())&#123;</span><br><span class="line">        std::cout &lt;&lt; method_name &lt;&lt; <span class="string">&quot; is no exist!&quot;</span> &lt;&lt; std::endl;</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 获取服务对象与方法对象</span></span><br><span class="line">    google::protobuf::Service* service = sit-&gt;second.service;</span><br><span class="line">    <span class="type">const</span> google::protobuf::MethodDescriptor* method = mit-&gt;second;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 生成Rpc方法调用的请求（request）和响应（response）</span></span><br><span class="line">    <span class="comment">// 通过 GetRequestPrototype，可以根据方法描述符动态获取对应的请求消息类型，并New（）实例化该类型的对象</span></span><br><span class="line">    google::protobuf::Message* request = service-&gt;<span class="built_in">GetRequestPrototype</span>(method).<span class="built_in">New</span>();</span><br><span class="line">    <span class="keyword">if</span>(!request-&gt;<span class="built_in">ParseFromString</span>(args_str))&#123;</span><br><span class="line">        std::cout &lt;&lt; service_name &lt;&lt; <span class="string">&#x27;.&#x27;</span> &lt;&lt; method_name &lt;&lt; <span class="string">&quot; parse error!&quot;</span> &lt;&lt; std::endl;</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    google::protobuf::Message* response = service-&gt;<span class="built_in">GetResponsePrototype</span>(method).<span class="built_in">New</span>();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// NewCallback函数会返回一个google::protobuf::Closure类的对象</span></span><br><span class="line">    <span class="comment">// Closure类对象相当于一个闭包，它捕获了一个成员对象的成员函数(SendRpcResponse)，以及这个成员函数需要的参数(conn、response)）</span></span><br><span class="line">    google::protobuf::Closure* done = google::protobuf::<span class="built_in">NewCallback</span>&lt;KrpcProvider,</span><br><span class="line">                                                                    <span class="type">const</span> muduo::net::TcpConnectionPtr&amp;,</span><br><span class="line">                                                                    google::protobuf::Message*&gt;(<span class="keyword">this</span>, &amp;KrpcProvider::SendRpcResponse,</span><br><span class="line">                                                                                                conn,</span><br><span class="line">                                                                                                response);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// CallMethod在UserServiceRpc实现，功能为根据远端Rpc请求，调用当前Rpc节点上发布的方法</span></span><br><span class="line">    <span class="comment">// request与response中包含了调用method的参数，done是执行完method后的回调函数，这里指定了SendRpcResponse</span></span><br><span class="line">    service-&gt;<span class="built_in">CallMethod</span>(method, <span class="literal">nullptr</span>, request, response, done);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="SendRpcResponse"><a href="#SendRpcResponse" class="headerlink" title="SendRpcResponse"></a>SendRpcResponse</h3><p>SendRpcResponse作为OnMessage中CallMethod的回调函数，在执行完远端Rpc调用的方法后调用。其功能是<strong>序列化响应信息，并通过send发送给Rpc客户端</strong></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="function"><span class="type">void</span> <span class="title">KrpcProvider::SendRpcResponse</span><span class="params">(<span class="type">const</span> muduo::net::TcpConnectionPtr&amp; conn, google::protobuf::Message* response)</span></span>&#123;</span><br><span class="line">    <span class="comment">// 序列化响应字符串，并将其发送给Rpc调用方</span></span><br><span class="line">    std::string response_str;</span><br><span class="line">    <span class="keyword">if</span>(response-&gt;<span class="built_in">SerializeToString</span>(&amp;response_str))&#123;</span><br><span class="line">        conn-&gt;<span class="built_in">send</span>(response_str);</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;Serialize Error!&quot;</span> &lt;&lt; std::endl;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="zookeeperutil"><a href="#zookeeperutil" class="headerlink" title="zookeeperutil"></a>zookeeperutil</h2><p><code>zookeeperutil</code>主要对zookeeper提供的一些api进行封装</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">// .h</span></span><br><span class="line"><span class="meta">#<span class="keyword">pragma</span> once</span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;semaphore.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;zookeeper/zookeeper.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="comment">// zookeeper客户端，主要封装一些zookeeper相关的api</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">ZkClient</span>&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">ZkClient</span>();</span><br><span class="line">    ~<span class="built_in">ZkClient</span>();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// zk客户端启动，连接zk服务器。封装zookeeper_init</span></span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">Start</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 在zk服务器中根据path新建一个节点。封装zoo_create</span></span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">Create</span><span class="params">(<span class="type">const</span> <span class="type">char</span>* path, <span class="type">const</span> <span class="type">char</span>* data, <span class="type">int</span> datalen, <span class="type">int</span> state = <span class="number">0</span>)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 根据指定的路径，获取znode节点值。封装zoo_get</span></span><br><span class="line">    <span class="function">std::string <span class="title">GetData</span><span class="params">(<span class="type">const</span> <span class="type">char</span>* path)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="comment">// zk客户端的句柄</span></span><br><span class="line">    <span class="type">zhandle_t</span>* m_zhandle;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h3 id="global-watcher"><a href="#global-watcher" class="headerlink" title="global_watcher"></a>global_watcher</h3><blockquote><p>watcher机制</p></blockquote><p>global_watcher定义了一个全局的watcher观察器，当znode节点发生变化时，zk服务端会通过该回调函数通知zk客户端</p><p>这里的global_watcher只处理 type&#x3D;&#x3D;ZOO_SESSION_EVENT &amp;&amp; state&#x3D;&#x3D;ZOO_CONNECTED_STATE 的watcher事件。目的是<strong>保证Start()调用完成后zk客户端（即Rpc服务端）与zk服务器的连接已经建立完成了</strong></p><blockquote><p>为什么需要watcher机制提供保证？</p></blockquote><p>因为<strong>zk客户端与zk服务器的连接建立过程是异步的</strong>。zookeeper_mt库的zookeeper客户端使用了以下三个线程：</p><ul><li>主线程：用户调用API的线程。</li><li>IO线程：负责网络通信的线程。</li><li>completion线程：对于异步请求（Zookeeper中提供的异步API，一般都是以zoo_a开头的api）以及<strong>watcher的响应回调</strong>，io线程会发送给completion线程完成处理。</li></ul><p>主线程在zk客户端调用api后返回zk句柄，而此时IO线程可能还没有完成连接的建立。所以需要watcher机制配合条件变量来保证Start()调用结束前连接的建立。</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// .cpp</span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;zookeeperutil.h&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;mutex&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;condition_variable&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">&quot;KrpcApplication.h&quot;</span></span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// 全局锁</span></span><br><span class="line">std::mutex cv_mutex;</span><br><span class="line"><span class="comment">// 条件变量</span></span><br><span class="line">std::condition_variable cv;</span><br><span class="line"><span class="comment">// 标记zk客户端是否已经连接到zk服务器</span></span><br><span class="line"><span class="type">bool</span> isConnected = <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 全局的watcher观察器，当节点发生变化时，zk服务端会通过该回调函数通知zk客户端</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">global_watcher</span><span class="params">(<span class="type">zhandle_t</span>* zh, <span class="type">int</span> type, <span class="type">int</span> state, <span class="type">const</span> <span class="type">char</span>* path, <span class="type">void</span>* watcher_ctx)</span></span>&#123;</span><br><span class="line">    <span class="comment">// 只处理 type==ZOO_SESSION_EVENT &amp;&amp; state==ZOO_CONNECTED_STATE 的watcher事件</span></span><br><span class="line">    <span class="keyword">if</span>(type == ZOO_SESSION_EVENT)&#123;</span><br><span class="line">        <span class="keyword">if</span>(state == ZOO_CONNECTED_STATE)&#123;</span><br><span class="line">            <span class="function">std::lock_guard&lt;std::mutex&gt; <span class="title">lock</span><span class="params">(cv_mutex)</span></span>;</span><br><span class="line">            <span class="comment">// 标记zk客户端已经与zk服务端建立连接</span></span><br><span class="line">            isConnected = <span class="literal">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 唤醒条件变量</span></span><br><span class="line">    cv.<span class="built_in">notify_all</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 构造函数，初始化zk客户端句柄</span></span><br><span class="line">ZkClient::<span class="built_in">ZkClient</span>(): <span class="built_in">m_zhandle</span>(<span class="literal">nullptr</span>)&#123;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 析构函数，关闭zk客户端句柄</span></span><br><span class="line">ZkClient::~<span class="built_in">ZkClient</span>()&#123;</span><br><span class="line">    <span class="keyword">if</span>(m_zhandle != <span class="literal">nullptr</span>)&#123;</span><br><span class="line">        <span class="built_in">zookeeper_close</span>(m_zhandle);</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">// zk客户端启动，连接zk服务器。封装zookeeper_init</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">ZkClient::Start</span><span class="params">()</span></span>&#123;</span><br><span class="line">    <span class="comment">// 从配置文件中获取zookeeper服务器的ip和端口</span></span><br><span class="line">    std::string host = KrpcApplication::<span class="built_in">GetInstance</span>().<span class="built_in">GetConfig</span>().<span class="built_in">Load</span>(<span class="string">&quot;zookeeperip&quot;</span>);</span><br><span class="line">    std::string port = KrpcApplication::<span class="built_in">GetInstance</span>().<span class="built_in">GetConfig</span>().<span class="built_in">Load</span>(<span class="string">&quot;zookeeperport&quot;</span>);</span><br><span class="line">    <span class="comment">// 拼接 ip + port</span></span><br><span class="line">    std::string conn_str = host + port;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 初始化zk对象，异步建立zk客户端（即Rpc服务端）与zk服务器的连接</span></span><br><span class="line">    m_zhandle = <span class="built_in">zookeeper_init</span>(conn_str.<span class="built_in">c_str</span>(), global_watcher, <span class="number">6000</span>, <span class="literal">nullptr</span>, <span class="literal">nullptr</span>, <span class="number">0</span>);</span><br><span class="line">    <span class="keyword">if</span>(m_zhandle)&#123;</span><br><span class="line">        <span class="comment">// 初始化失败</span></span><br><span class="line">        std::cout &lt;&lt; <span class="string">&quot;zookeeper_init error!&quot;</span> &lt;&lt; std::endl;</span><br><span class="line">        <span class="built_in">exit</span>(EXIT_FAILURE);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 等待global_watcher回调通知连接已经建立完成(isConnected == true)</span></span><br><span class="line">    <span class="function">std::unique_lock&lt;std::mutex&gt; <span class="title">lock</span><span class="params">(cv_mutex)</span></span>;</span><br><span class="line">    <span class="comment">// 第二个参数用于防止虚假唤醒</span></span><br><span class="line">    cv.<span class="built_in">wait</span>(lock, []&#123;<span class="keyword">return</span> isConnected;&#125;);</span><br><span class="line">    std::cout &lt;&lt; <span class="string">&quot;zookeeper_init success!&quot;</span> &lt;&lt; std::endl;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 在zk服务器中根据path新建一个节点。封装zoo_create</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">ZkClient::Create</span><span class="params">(<span class="type">const</span> <span class="type">char</span>* path, <span class="type">const</span> <span class="type">char</span>* data, <span class="type">int</span> datalen, <span class="type">int</span> state = <span class="number">0</span>)</span></span>&#123;</span><br><span class="line">    <span class="comment">// 创建znode节点，可以选择永久性节点还是临时性节点</span></span><br><span class="line">    <span class="type">char</span> path_buffer[<span class="number">128</span>];</span><br><span class="line">    <span class="type">int</span> bufferlen = <span class="built_in">sizeof</span>(path_buffer);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 检查指定的节点是否存在，只有不存在时才创建节点</span></span><br><span class="line">    <span class="type">int</span> flag = <span class="built_in">zoo_exists</span>(m_zhandle, path, <span class="number">0</span>, <span class="literal">nullptr</span>);</span><br><span class="line">    <span class="keyword">if</span>(flag == ZNONODE)&#123;</span><br><span class="line">        <span class="comment">// 创建指定path的znode节点</span></span><br><span class="line">        flag = <span class="built_in">zoo_create</span>(m_zhandle, path, data, datalen, &amp;ZOO_OPEN_ACL_UNSAFE, state, path_buffer, bufferlen);</span><br><span class="line">        <span class="keyword">if</span>(flag == ZOK)&#123;</span><br><span class="line">            std::cout &lt;&lt; <span class="string">&quot;znode create success, path=&quot;</span> &lt;&lt; path &lt;&lt; std::endl;</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;znode create fail, path=&quot;</span> &lt;&lt; path &lt;&lt; std::endl;</span><br><span class="line">            <span class="built_in">exit</span>(EXIT_FAILURE);</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">// 根据指定的路径，获取znode节点值。封装zoo_get</span></span><br><span class="line"><span class="function">std::string <span class="title">ZkClient::GetData</span><span class="params">(<span class="type">const</span> <span class="type">char</span>* path)</span></span>&#123;</span><br><span class="line">    <span class="type">char</span> buffer[<span class="number">64</span>];</span><br><span class="line">    <span class="type">int</span> bufferlen = <span class="built_in">sizeof</span>(buffer);</span><br><span class="line">    <span class="type">int</span> flag = <span class="built_in">zoo_get</span>(m_zhandle, path, <span class="number">0</span>, buffer, &amp;bufferlen, <span class="literal">nullptr</span>);</span><br><span class="line">    <span class="keyword">if</span>(flag == ZOK)&#123;</span><br><span class="line">        <span class="keyword">return</span> buffer;</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;zoo_get error!&quot;</span> &lt;&lt; std::endl;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;&quot;</span>;</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;&quot;</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="KrpcController"><a href="#KrpcController" class="headerlink" title="KrpcController"></a>KrpcController</h2><p>KrpcController的主要作用是跟踪RPC方法调用的状态、错误信息并提供控制功能（如取消调用）。这里只实现了其最基本的功能</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">// .h</span></span><br><span class="line"><span class="meta">#<span class="keyword">pragma</span> once</span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;google/protobuf/service.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="comment">// 描述Rpc调用的控制器，主要作用是跟踪RPC方法调用的状态、错误信息并提供控制功能（如取消调用）</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">KrpcController</span>&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">KrpcController</span>();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 重置状态与错误信息</span></span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">Reset</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="function"><span class="type">bool</span> <span class="title">Failed</span><span class="params">()</span> <span class="type">const</span> </span>&#123; <span class="keyword">return</span> m_failed; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 返回错误信息</span></span><br><span class="line">    <span class="function">std::string <span class="title">ErrorText</span><span class="params">()</span> <span class="type">const</span> </span>&#123; m_errmsg; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 设置状态与错误信息</span></span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">SetFailed</span><span class="params">(<span class="type">const</span> std::string&amp; reason)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 下面是一些还未实现的功能</span></span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">StartCancel</span><span class="params">()</span></span>;</span><br><span class="line">    <span class="function"><span class="type">bool</span> <span class="title">IsCanceled</span><span class="params">()</span> <span class="type">const</span></span>;</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">NotifyOnCancel</span><span class="params">(google::protobuf::Closure* callback)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="comment">// RPC方法执行过程中的状态</span></span><br><span class="line">    <span class="type">bool</span> m_failed;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// RPC方法执行过程中的错误信息</span></span><br><span class="line">    std::string m_errmsg;</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// .cpp</span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;KrpcController.h&quot;</span></span></span><br><span class="line"></span><br><span class="line">KrpcController::<span class="built_in">KrpcController</span>()</span><br><span class="line">    : <span class="built_in">m_failed</span>(<span class="literal">false</span>)</span><br><span class="line">    , <span class="built_in">m_errmsg</span>(<span class="string">&quot;&quot;</span>)&#123;</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="function"><span class="type">void</span> <span class="title">KrpcController::Reset</span><span class="params">()</span></span>&#123;</span><br><span class="line">    m_failed = <span class="literal">false</span>;</span><br><span class="line">    m_errmsg = <span class="string">&quot;&quot;</span>;</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="function"><span class="type">void</span> <span class="title">KrpcController::SetFailed</span><span class="params">(<span class="type">const</span> std::string&amp; reason)</span></span>&#123;</span><br><span class="line">    m_failed = <span class="literal">true</span>;</span><br><span class="line">    m_errmsg = reason;</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="function"><span class="type">void</span> <span class="title">KrpcController::StartCancel</span><span class="params">()</span></span>&#123;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">bool</span> <span class="title">KrpcController::IsCanceled</span><span class="params">()</span> <span class="type">const</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">KrpcController::NotifyOnCancel</span><span class="params">(google::protobuf::Closure* callback)</span></span>&#123;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h1 id="RPC客户端"><a href="#RPC客户端" class="headerlink" title="RPC客户端"></a>RPC客户端</h1><h2 id="KrpcChannel"><a href="#KrpcChannel" class="headerlink" title="KrpcChannel"></a>KrpcChannel</h2><blockquote><p>多态的应用</p></blockquote><p><code>UserServiceRpc_Stub</code>的构造函数必须传入一个<code>google::protobuf::RpcChannel</code>。所以我们必须自己实现一个<code>KrpcChannel</code>继承自<code>google::protobuf::RpcChannel</code> ，并实现它的<code>CallMethod</code>方法</p><blockquote><p><code>CallMethod</code>如何理解？</p></blockquote><ul><li><p><strong>客户端视角</strong>：<code>CallMethod</code> 是客户端存根（Stub）类调用的入口，负责将本地方法调用（如这里的<code>Login</code>）的参数序列化为网络传输格式，并通过网络发送给服务端（简单理解就是<strong>将本地方法调用转换为远程过程调用</strong>）</p><ul><li>客户端所有服务方法的调用最终都会转变为对<code>CallMethod</code>的调用，如Login：</li></ul><p>  <img src="https://atelieryu.xyz/elog/202503/1234ff92ae5a0fac62f2656fddd6f9d0.png" alt="UserServiceRpc_stub::Login源码"></p></li><li><p><strong>服务端视角</strong>：服务端通过 <code>CallMethod</code> 接收请求后，根据消息头中的服务名和方法名路由到具体的服务实现，并触发本地方法执行</p></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></pre></td><td class="code"><pre><span class="line"><span class="comment">// .h</span></span><br><span class="line"><span class="meta">#<span class="keyword">pragma</span> once</span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;google/protobuf/service.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;zookeeperutil.h&quot;</span></span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// 继承自google::protobuf::RpcChannel</span></span><br><span class="line"><span class="comment">// 目的是为了给客户端进行方法调用的时候，统一接收的</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">KrpcChannel</span>: <span class="keyword">public</span> google::protobuf::RpcChannel&#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="built_in">KrpcChannel</span>(<span class="type">bool</span> connectNow);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 析构函数</span></span><br><span class="line">    <span class="keyword">virtual</span> ~<span class="built_in">KrpcChannel</span>()&#123;&#125;;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 重写继承的CallMethod</span></span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">CallMethod</span><span class="params">(<span class="type">const</span> ::google::protobuf::MethodDescriptor* method,</span></span></span><br><span class="line"><span class="params"><span class="function">                    ::google::protobuf::RpcController* controller, </span></span></span><br><span class="line"><span class="params"><span class="function">                    <span class="type">const</span> ::google::protobuf::Message* request,</span></span></span><br><span class="line"><span class="params"><span class="function">                    ::google::protobuf::Message* response, </span></span></span><br><span class="line"><span class="params"><span class="function">                    ::google::protobuf::Closure* done)</span> <span class="keyword">override</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="comment">// 建立与Rpc服务端的连接</span></span><br><span class="line">    <span class="function"><span class="type">bool</span> <span class="title">newConnect</span><span class="params">(<span class="type">const</span> <span class="type">char</span>* ip, <span class="type">uint16_t</span> port)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 向zookeeper服务器查询服务方法对应的Rpc服务端ip和端口</span></span><br><span class="line">    <span class="function">std::string <span class="title">QueryServiceHost</span><span class="params">(ZkClient* zkclient, <span class="type">const</span> std::string&amp; service_name, </span></span></span><br><span class="line"><span class="params"><span class="function">                                 <span class="type">const</span> std::string&amp; method_name, <span class="type">int</span>&amp; idx)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="comment">// 客户端通信的socket</span></span><br><span class="line">    <span class="type">int</span> m_clientSock;</span><br><span class="line">    <span class="comment">// 服务名</span></span><br><span class="line">    std::string m_service_name; </span><br><span class="line">    <span class="comment">// 方法名</span></span><br><span class="line">    std::string m_method_name;</span><br><span class="line">    <span class="comment">// Rpc服务端的ip和端口</span></span><br><span class="line">    std::string m_ip;</span><br><span class="line">    <span class="type">uint16_t</span> m_port;</span><br><span class="line">    <span class="comment">// 划分服务器ip和端口的下标</span></span><br><span class="line">    <span class="type">int</span> m_idx;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h3 id="构造函数"><a href="#构造函数" class="headerlink" title="构造函数"></a>构造函数</h3><p>如果已经处于连接状态（connectNow &#x3D;&#x3D; True），则尝试与Rpc服务端进行连接</p><ul><li><strong>重试机制</strong>：当连接失败时，可以重试3次</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></pre></td><td class="code"><pre><span class="line">KrpcChannel::<span class="built_in">KrpcChannel</span>(<span class="type">bool</span> connectNow)</span><br><span class="line">    : <span class="built_in">m_clientSock</span>(<span class="number">-1</span>)</span><br><span class="line">    , <span class="built_in">m_idx</span>(<span class="number">0</span>)&#123;</span><br><span class="line">    <span class="keyword">if</span>(!connectNow)&#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="comment">// 尝试与Rpc服务端进行连接，可以重试3次</span></span><br><span class="line">    <span class="keyword">auto</span> rt = <span class="built_in">newConnect</span>(m_ip.<span class="built_in">c_str</span>(), m_port);</span><br><span class="line">    <span class="type">int</span> cnt = <span class="number">3</span>;</span><br><span class="line">    <span class="keyword">while</span>(!rt &amp;&amp; cnt--)&#123;</span><br><span class="line">        rt = <span class="built_in">newConnect</span>(m_ip.<span class="built_in">c_str</span>(), m_port);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="CallMethod"><a href="#CallMethod" class="headerlink" title="CallMethod"></a>CallMethod</h3><p>CallMethod的主要工作：</p><ol><li>连接Rpc服务器 ：查询zookeeper服务器获取ip和端口 → 调用<code>newConnect</code>连接服务端</li><li>序列化请求：打包header_size、header_str、args_size、args_str</li><li>发送请求：<code>send</code></li><li>接受响应：<code>recv</code></li><li>解析响应数据</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><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">KrpcChannel::CallMethod</span><span class="params">(<span class="type">const</span> ::google::protobuf::MethodDescriptor* method,</span></span></span><br><span class="line"><span class="params"><span class="function">                            ::google::protobuf::RpcController* controller, </span></span></span><br><span class="line"><span class="params"><span class="function">                            <span class="type">const</span> ::google::protobuf::Message* request,</span></span></span><br><span class="line"><span class="params"><span class="function">                            ::google::protobuf::Message* response, </span></span></span><br><span class="line"><span class="params"><span class="function">                            ::google::protobuf::Closure* done)</span></span>&#123;</span><br><span class="line">    <span class="comment">// 建立与Rpc服务端的连接</span></span><br><span class="line">    <span class="keyword">if</span>(m_clientSock == <span class="number">-1</span>)&#123;</span><br><span class="line">        <span class="comment">// 获取服务对象名和方法名</span></span><br><span class="line">        <span class="type">const</span> google::protobuf::ServiceDescriptor* sd = method-&gt;<span class="built_in">service</span>();</span><br><span class="line">        m_service_name = sd-&gt;<span class="built_in">name</span>();</span><br><span class="line">        m_method_name = method-&gt;<span class="built_in">name</span>();</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 向zookeeper服务器查询服务对象和方法对应的服务端host</span></span><br><span class="line">        ZkClient zkCli;</span><br><span class="line">        zkCli.<span class="built_in">Start</span>();</span><br><span class="line">        std::string server_host = <span class="built_in">QueryServiceHost</span>(&amp;zkCli, m_service_name, m_method_name, m_idx);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 解析host</span></span><br><span class="line">        m_ip = server_host.<span class="built_in">substr</span>(<span class="number">0</span>, m_idx);</span><br><span class="line">        m_port = <span class="built_in">atoi</span>(server_host.<span class="built_in">substr</span>(m_idx + <span class="number">1</span>, server_host.<span class="built_in">size</span>() - m_idx).<span class="built_in">c_str</span>());</span><br><span class="line">        std::cout &lt;&lt; <span class="string">&quot;Server ip: &quot;</span> &lt;&lt; m_ip &lt;&lt; <span class="string">&quot;, port: &quot;</span> &lt;&lt; m_port &lt;&lt; std::endl;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 建立连接</span></span><br><span class="line">        <span class="type">bool</span> rt = <span class="built_in">newConnect</span>(m_ip.<span class="built_in">c_str</span>(), m_port);</span><br><span class="line">        <span class="keyword">if</span>(rt) &#123;</span><br><span class="line">            std::cout &lt;&lt; <span class="string">&quot;connect server success&quot;</span> &lt;&lt; std::endl;</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;connect server error&quot;</span> &lt;&lt; std::endl;</span><br><span class="line">            <span class="comment">// 连接建立失败时返回</span></span><br><span class="line">            <span class="keyword">return</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="comment">// 封装KrpcHeader</span></span><br><span class="line">    Krpc::RpcHeader krpcHeader;</span><br><span class="line">    <span class="comment">// - service_name</span></span><br><span class="line">    krpcHeader.<span class="built_in">set_service_name</span>(m_service_name);</span><br><span class="line">    <span class="comment">// - method_name</span></span><br><span class="line">    krpcHeader.<span class="built_in">set_method_name</span>(m_method_name);</span><br><span class="line">    <span class="comment">// - args_size</span></span><br><span class="line">    <span class="type">uint32_t</span> args_size&#123;&#125;;</span><br><span class="line">    std::string args_str;</span><br><span class="line">    <span class="comment">// 序列化参数到字符串</span></span><br><span class="line">    <span class="keyword">if</span>(request-&gt;<span class="built_in">SerializeToString</span>(&amp;args_str))&#123;</span><br><span class="line">        args_size = args_str.<span class="built_in">size</span>();</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">// 设置KrpcController的错误信息</span></span><br><span class="line">        controller-&gt;<span class="built_in">SetFailed</span>(<span class="string">&quot;serialize request fail&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    krpcHeader.<span class="built_in">set_args_size</span>(args_size);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 将(header_size、header_str、args_size、args_str)打包到send_rpc_str</span></span><br><span class="line">    std::string send_rpc_str;</span><br><span class="line">    <span class="type">uint32_t</span> header_size = <span class="number">0</span>;</span><br><span class="line">    std::string rpc_header_str;</span><br><span class="line">    <span class="comment">// 序列化KrpcHeader到字符串</span></span><br><span class="line">    <span class="keyword">if</span>(krpcHeader.<span class="built_in">SerializeToString</span>(&amp;rpc_header_str))&#123;</span><br><span class="line">        header_size = rpc_header_str.<span class="built_in">size</span>();</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        controller-&gt;<span class="built_in">SetFailed</span>(<span class="string">&quot;serialize rpc header fail&quot;</span>);</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="comment">// 流式写入send_rpc_str</span></span><br><span class="line">    &#123;</span><br><span class="line">        google::protobuf::<span class="function">io::StringOutputStream <span class="title">string_output</span><span class="params">(&amp;send_rpc_str)</span></span>;</span><br><span class="line">        google::protobuf::<span class="function">io::CodedOutputStream <span class="title">coded_output</span><span class="params">(&amp;string_output)</span></span>;</span><br><span class="line">        coded_output.<span class="built_in">WriteVarint32</span>(<span class="built_in">static_cast</span>&lt;<span class="type">uint32_t</span>&gt;(header_size));</span><br><span class="line">        coded_output.<span class="built_in">WriteString</span>(rpc_header_str);</span><br><span class="line">    &#125;</span><br><span class="line">    send_rpc_str += args_str;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 发送Rpc请求</span></span><br><span class="line">    <span class="keyword">if</span>(<span class="built_in">send</span>(m_clientSock, send_rpc_str.<span class="built_in">c_str</span>(), send_rpc_str.<span class="built_in">size</span>(), <span class="number">0</span>) == <span class="number">-1</span>)&#123;</span><br><span class="line">        <span class="comment">// 发送请求失败</span></span><br><span class="line">        <span class="built_in">close</span>(m_clientSock);</span><br><span class="line">        <span class="built_in">perror</span>(<span class="string">&quot;send&quot;</span>);</span><br><span class="line">        controller-&gt;<span class="built_in">SetFailed</span>(<span class="string">&quot;send error&quot;</span>);</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="comment">// 接受Rpc请求的响应</span></span><br><span class="line">    <span class="type">char</span> recv_buf[<span class="number">1024</span>] = &#123;<span class="number">0</span>&#125;;</span><br><span class="line">    <span class="type">int</span> recv_size = <span class="built_in">recv</span>(m_clientSock, recv_buf, <span class="built_in">sizeof</span>(recv_buf), <span class="number">0</span>);</span><br><span class="line">    <span class="keyword">if</span>(recv_size == <span class="number">-1</span>)&#123;</span><br><span class="line">        <span class="built_in">perror</span>(<span class="string">&quot;recv&quot;</span>);</span><br><span class="line">        controller-&gt;<span class="built_in">SetFailed</span>(<span class="string">&quot;recv error&quot;</span>);</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="comment">// 反序列化解析响应数据</span></span><br><span class="line">    <span class="keyword">if</span>(!response-&gt;<span class="built_in">ParseFromArray</span>(recv_buf, recv_size))&#123;</span><br><span class="line">        <span class="comment">// 解析失败</span></span><br><span class="line">        <span class="built_in">close</span>(m_clientSock);</span><br><span class="line">        <span class="built_in">perror</span>(<span class="string">&quot;parse&quot;</span>);</span><br><span class="line">        controller-&gt;<span class="built_in">SetFailed</span>(<span class="string">&quot;parse error&quot;</span>);</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="comment">// 请求-响应完毕，关闭连接</span></span><br><span class="line">    <span class="built_in">close</span>(m_clientSock);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="newConnect"><a href="#newConnect" class="headerlink" title="newConnect"></a>newConnect</h3><p>获得服务端的ip和port后，建立与Rpc服务端的TCP连接。常规的socket编程客户端connect流程</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">bool</span> <span class="title">KrpcChannel::newConnect</span><span class="params">(<span class="type">const</span> <span class="type">char</span>* ip, <span class="type">uint16_t</span> port)</span></span>&#123;</span><br><span class="line">    <span class="comment">// socket编程的客户端connect流程</span></span><br><span class="line">    <span class="type">int</span> clientfd = <span class="built_in">socket</span>(AF_INET, SOCK_STREAM, <span class="number">0</span>);</span><br><span class="line">    <span class="keyword">if</span>(clientfd == <span class="number">-1</span>)&#123;</span><br><span class="line">        <span class="built_in">perror</span>(<span class="string">&quot;socket&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">struct</span> <span class="title class_">sockaddr_in</span> server_addr;</span><br><span class="line">    server_addr.sin_family = AF_INET;</span><br><span class="line">    server_addr.sin_port = <span class="built_in">htons</span>(port);</span><br><span class="line">    server_addr.sin_addr.s_addr = <span class="built_in">inet_addr</span>(ip);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span>(<span class="built_in">connect</span>(clientfd, (<span class="keyword">struct</span> sockaddr*)&amp;server_addr, <span class="built_in">sizeof</span>(server_addr)) == <span class="number">-1</span>)&#123;</span><br><span class="line">        <span class="built_in">perror</span>(<span class="string">&quot;KrpcChannel::newConnect: connect&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    m_clientSock = clientfd;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="QueryServiceHost"><a href="#QueryServiceHost" class="headerlink" title="QueryServiceHost"></a>QueryServiceHost</h3><p>向zookeeper服务器查询服务方法对应的Rpc服务端的host</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></pre></td><td class="code"><pre><span class="line"><span class="function">std::string <span class="title">KrpcChannel::QueryServiceHost</span><span class="params">(ZkClient* zkclient, <span class="type">const</span> std::string&amp; service_name, </span></span></span><br><span class="line"><span class="params"><span class="function">                                          <span class="type">const</span> std::string&amp; method_name, <span class="type">int</span>&amp; idx)</span></span>&#123;</span><br><span class="line">    std::string method_path = <span class="string">&#x27;/&#x27;</span> + service_name + <span class="string">&#x27;/&#x27;</span> + method_name;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 上锁从zookeeper获取Rpc服务器host，保证多线程情况下每一个线程都能拿到信息</span></span><br><span class="line">    <span class="function">std::unique_lock&lt;std::mutex&gt; <span class="title">lock</span><span class="params">(g_mutex)</span></span>;</span><br><span class="line">    std::string server_host = zkclient-&gt;<span class="built_in">GetData</span>(method_path.<span class="built_in">c_str</span>());</span><br><span class="line">    lock.<span class="built_in">unlock</span>();  </span><br><span class="line"></span><br><span class="line">    <span class="comment">// 判断host合法性</span></span><br><span class="line">    <span class="keyword">if</span>(server_host == <span class="string">&quot;&quot;</span>)&#123;</span><br><span class="line">        std::cout &lt;&lt; <span class="string">&quot;ERROR: &quot;</span> &lt;&lt; method_path &lt;&lt; <span class="string">&quot; is no exist! \n&quot;</span>;</span><br><span class="line">        <span class="comment">// 不能返回空字符串，否则后续substr会出错</span></span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot; &quot;</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 以&quot;:&quot;为分隔符分隔ip和port</span></span><br><span class="line">    idx = server_host.<span class="built_in">find</span>(<span class="string">&quot;:&quot;</span>);</span><br><span class="line">    <span class="keyword">if</span>(idx == <span class="number">-1</span>)&#123;</span><br><span class="line">        std::cout &lt;&lt; <span class="string">&quot;ERROR: &quot;</span> &lt;&lt; method_path &lt;&lt; <span class="string">&quot; address is invalid! \n&quot;</span>;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot; &quot;</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> server_host;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="客户端主函数"><a href="#客户端主函数" class="headerlink" title="客户端主函数"></a>客户端主函数</h2><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;KrpcApplication.h&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;KrpcChannel.h&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;../user.pb.h&quot;</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;vector&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;atomic&gt;</span></span></span><br><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="meta">#<span class="keyword">include</span> <span class="string">&lt;chrono&gt;</span></span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// Rpc客户端远端调用Rpc服务端的服务方法</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">send_request</span><span class="params">(<span class="type">int</span> thread_id, std::atomic&lt;<span class="type">int</span>&gt;&amp; success_cnt, std::atomic&lt;<span class="type">int</span>&gt;&amp; fail_cnt)</span></span>&#123;</span><br><span class="line">    <span class="function">Kuser::UserServiceRpc_Stub <span class="title">stub</span><span class="params">(<span class="keyword">new</span> KrpcChannel(<span class="literal">false</span>))</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 设置Rpc请求参数</span></span><br><span class="line">    Kuser::LoginRequest request;</span><br><span class="line">    request.<span class="built_in">set_name</span>(<span class="string">&quot;yu&quot;</span>);</span><br><span class="line">    request.<span class="built_in">set_pwd</span>(<span class="string">&quot;123456&quot;</span>);</span><br><span class="line">    <span class="comment">// 创建Rpc响应参数</span></span><br><span class="line">    Kuser::LoginResponse response;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 远程调用Login方法</span></span><br><span class="line">    KrpcController controller;</span><br><span class="line">    <span class="comment">// 这里Login实际就是通过KrpcChannel::CallMethod间接调用的</span></span><br><span class="line">    stub.<span class="built_in">Login</span>(&amp;controller, &amp;request, &amp;response, <span class="literal">nullptr</span>);</span><br><span class="line">    <span class="comment">// 读取响应</span></span><br><span class="line">    <span class="keyword">if</span>(controller.Failed)&#123;</span><br><span class="line">        <span class="comment">// 调用失败</span></span><br><span class="line">        std::cout &lt;&lt; controller.<span class="built_in">ErrorText</span>() &lt;&lt; std::endl;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="keyword">if</span>(response.<span class="built_in">result</span>().<span class="built_in">errcode</span>() == <span class="number">0</span>)&#123;</span><br><span class="line">            std::cout &lt;&lt; <span class="string">&quot;Rpc login response success: &quot;</span> &lt;&lt; response.<span class="built_in">success</span>() &lt;&lt; std::endl;</span><br><span class="line">            ++success_cnt;</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;Rpc login response success: &quot;</span> &lt;&lt; response.<span class="built_in">result</span>().<span class="built_in">errmsg</span>() &lt;&lt; std::endl;</span><br><span class="line">            ++fail_cnt;</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><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="comment">// 程序启动后，调用KrpcApplication类进行初始化</span></span><br><span class="line">    KrpcApplication::<span class="built_in">Init</span>(argc, argv);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 设置线程配置属性</span></span><br><span class="line">    <span class="type">const</span> <span class="type">int</span> thread_cnt = <span class="number">1000</span>;        <span class="comment">// 并发线程数</span></span><br><span class="line">    <span class="type">const</span> <span class="type">int</span> request_per_thread = <span class="number">10</span>;  <span class="comment">// 每个线程发送的请求数</span></span><br><span class="line"></span><br><span class="line">    std::vector&lt;std::thread&gt; threads;</span><br><span class="line">    <span class="function">std::atomic&lt;<span class="type">int</span>&gt; <span class="title">success_cnt</span><span class="params">(<span class="number">0</span>)</span></span>;</span><br><span class="line">    <span class="function">std::atomic&lt;<span class="type">int</span>&gt; <span class="title">fail_cnt</span><span class="params">(<span class="number">0</span>)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取程序开始时间</span></span><br><span class="line">    <span class="keyword">auto</span> start_time = std::chrono::high_resolution_clock::<span class="built_in">now</span>();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 启动多线程并发测试</span></span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> i = <span class="number">0</span>; i &lt; thread_cnt; ++i)&#123;</span><br><span class="line">        threads.<span class="built_in">emplace_back</span>([argc, argv, i, &amp;success_cnt, &amp; fail_cnt]()&#123;</span><br><span class="line">            <span class="keyword">for</span>(<span class="type">int</span> j = <span class="number">0</span>; j &lt; request_per_thread; ++j)&#123;</span><br><span class="line">                <span class="built_in">send_request</span>(i, success_cnt, fail_cnt);</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="keyword">for</span>(<span class="keyword">auto</span>&amp; t : threads)&#123;</span><br><span class="line">        t.<span class="built_in">join</span>();</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="keyword">auto</span> end_time = std::chrono::high_resolution_clock::<span class="built_in">now</span>();</span><br><span class="line">    std::chrono::duration&lt;<span class="type">double</span>&gt; elapsed = end_time - start_time;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 输出统计结果</span></span><br><span class="line">    std::cout &lt;&lt; <span class="string">&quot;Total requests: &quot;</span> &lt;&lt; thread_cnt * request_per_thread &lt;&lt; std::endl;</span><br><span class="line">    std::cout &lt;&lt; <span class="string">&quot;Success count: &quot;</span> &lt;&lt; success_cnt &lt;&lt; std::endl;</span><br><span class="line">    std::cout &lt;&lt; <span class="string">&quot;Fail count: &quot;</span> &lt;&lt; fail_cnt &lt;&lt; std::endl;</span><br><span class="line">    std::cout &lt;&lt; <span class="string">&quot;Elapsed time: &quot;</span> &lt;&lt; elapsed.<span class="built_in">count</span>() &lt;&lt; <span class="string">&quot; seconds&quot;</span> &lt;&lt; std::end;</span><br><span class="line">    std::cout &lt;&lt; <span class="string">&quot;QPS: &quot;</span> &lt;&lt; (thread_cnt * request_per_thread) / elapsed.<span class="built_in">count</span>() &lt;&lt; std::endl;  </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><h3 id="send-request"><a href="#send-request" class="headerlink" title="send_request"></a>send_request</h3><p>客户端通过<code>send_request</code> 远端调用<code>Login</code>方法</p><h3 id="main-1"><a href="#main-1" class="headerlink" title="main"></a>main</h3><p>进行Rpc框架的多线程并发测试</p><h1 id="性能测试"><a href="#性能测试" class="headerlink" title="性能测试"></a>性能测试</h1><p>分别运行服务端、客户端即可：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">1.在example文件夹和src文件夹中为proto文件生成.h和.cc文件</span></span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">2.回到项目根目录，使用以下命令编译生成可执行文件</span></span><br><span class="line">mkdir build &amp;&amp; cd build &amp;&amp; cmake .. &amp;&amp; make -j$&#123;nproc&#125;</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">3.进入bin文件夹，在不同会话中分别运行server和client</span></span><br><span class="line">./server -i ./test.conf</span><br><span class="line">./client -i ./test.conf</span><br></pre></td></tr></table></figure><h2 id="测试环境"><a href="#测试环境" class="headerlink" title="测试环境"></a>测试环境</h2><ul><li>测试平台：阿里云服务器</li><li>操作系统：Ubuntu 22.04</li><li>CPU：2核</li><li>内存：2G</li><li>硬盘：40G</li></ul><h2 id="测试结果"><a href="#测试结果" class="headerlink" title="测试结果"></a>测试结果</h2><ul><li><p>并发线程数<code>200</code>，每个线程发送的请求数<code>10</code>：</p>  <figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">Total requests: 2000</span><br><span class="line">Success count: 2000</span><br><span class="line">Fail count: 0</span><br><span class="line">Elapsed time: 10.4225 seconds</span><br><span class="line">QPS: 191.892</span><br></pre></td></tr></table></figure></li><li><p>并发线程数<code>1000</code>，每个线程发送的请求数<code>10</code>：</p>  <figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">Total requests: 10000</span><br><span class="line">Success count: 10000</span><br><span class="line">Fail count: 0</span><br><span class="line">Elapsed time: 79.2326 seconds</span><br><span class="line">QPS: 126.21</span><br></pre></td></tr></table></figure></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h1&gt;&lt;h2 id=&quot;RPC简介&quot;&gt;&lt;a href=&quot;#RPC简介&quot; class=&quot;headerlink&quot; title=&quot;RPC简介&quot;&gt;&lt;/a&gt;RPC简</summary>
      
    
    
    
    <category term="学习笔记" scheme="https://atelieryu.site/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="后端" scheme="https://atelieryu.site/tags/%E5%90%8E%E7%AB%AF/"/>
    
    <category term="RPC" scheme="https://atelieryu.site/tags/RPC/"/>
    
    <category term="Protobuf" scheme="https://atelieryu.site/tags/Protobuf/"/>
    
    <category term="C++" scheme="https://atelieryu.site/tags/C/"/>
    
    <category term="分布式" scheme="https://atelieryu.site/tags/%E5%88%86%E5%B8%83%E5%BC%8F/"/>
    
  </entry>
  
  <entry>
    <title>Protobuf简易使用</title>
    <link href="https://atelieryu.site/Protobuf.html"/>
    <id>https://atelieryu.site/Protobuf.html</id>
    <published>2025-03-17T01:48:00.000Z</published>
    <updated>2025-03-19T02:06:00.000Z</updated>
    
    <content type="html"><![CDATA[<div class="note info modern"><p>ProtoBuf提供对数据的序列化和反序列化，ProtoBuf可以用于结构化数据的串行序列化，并且以Key-Value格式存储数据，因为采用二进制格式，所以序列化出来的数据比较少，作为网络传输的载体效率很高</p></div><h1 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h1><ul><li><a href="https://protobuf.com.cn/programming-guides/proto3/">proto3官方教程</a></li><li><a href="https://subingwen.cn/cpp/protobuf/">参考博客</a></li></ul><h1 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h1><h2 id="源码下载与安装"><a href="#源码下载与安装" class="headerlink" title="源码下载与安装"></a>源码下载与安装</h2><ul><li><a href="https://github.com/protocolbuffers/protobuf/releases">github源代码下载地址</a></li><li><a href="https://github.com/protocolbuffers/protobuf/releases">3.21.12版本安装较为简单</a></li></ul><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">以 protobuf 3.21.12 为例</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">自行下载源码包, 解压缩</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">tar zxvf protobuf-cpp-3.21.12.tar.gz</span> </span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">进入到解压目录</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">cd</span> protobuf-3.21.12/</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">构建并安装</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">./configure         <span class="comment"># 检查安装环境, 生成 makefile</span></span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">make                <span class="comment"># 编译</span></span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">sudo</span> make install   <span class="comment"># 安装</span></span></span><br></pre></td></tr></table></figure><h3 id="动态库链接失败处理"><a href="#动态库链接失败处理" class="headerlink" title="动态库链接失败处理"></a>动态库链接失败处理</h3><p>可以使用<code>$ protoc --version</code>测试是否安装成功</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">测试时发现动态库链接失败</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">protoc --version</span></span><br><span class="line">protoc: error while loading shared libraries: libprotoc.so.32: cannot open shared object file: No such file or directory</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">方法1：</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">find指令查找动态库路径</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">sudo</span> find /usr/local/ -name libprotoc.so</span>     </span><br><span class="line">/usr/local/lib/libprotoc.so</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">将查找到的目录添加到/etc/ld.so.conf配置文件中：</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">sudo</span> vim /etc/ld.so.conf</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">尾部追加/usr/local/lib/</span></span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">方法2</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">直接使用ldconfig指令</span></span><br><span class="line">sudo ldconfig</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">处理完成后：</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">protoc --version</span></span><br><span class="line">libprotoc 3.21.12</span><br></pre></td></tr></table></figure><h1 id="快速上手"><a href="#快速上手" class="headerlink" title="快速上手"></a>快速上手</h1><h2 id="使用流程"><a href="#使用流程" class="headerlink" title="使用流程"></a>使用流程</h2><ol><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><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">// 要序列化的数据</span></span><br><span class="line"><span class="comment">// 第一种: 单一数据类型</span></span><br><span class="line"><span class="type">int</span> number;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 第二种: 复合数据类型</span></span><br><span class="line"><span class="keyword">struct</span> <span class="title class_">Person</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="type">int</span> id;</span><br><span class="line">    string name;</span><br><span class="line">    string sex;</span><br><span class="line">    <span class="type">int</span> age;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure></li><li><p>创建一个新的文件, 文件名随意指定, 文件后缀为 .proto</p></li><li><p>根据protobuf的语法, 编辑.proto文件</p></li><li><p>使用 protoc 命令将 .proto 文件转化为相应的 C++ 文件</p><ul><li>源文件: xxx.pb.cc –&gt; xxx对应的名字和 .proto文件名相同</li><li>头文件: xxx.pb.h –&gt; xxx对应的名字和 .proto文件名相同</li></ul></li><li><p>需要将生成的c++文件添加到项目中, 通过文件中提供的类 API 实现数据的序列化&#x2F;反序列化</p><blockquote><p>Protobuf与C++的类型对照</p></blockquote></li></ol><ul><li>几个主要注意点：<ul><li>C++中的string到Protobuf中统一使用bytes比较好</li><li>C++中的整型到Protobuf中为 int+size，如int→int32</li><li>C++中的结构体与类对应到Protobuf中为message（消息体）<ul><li>Protobuf中其他类型最终也都要封装到消息体里</li></ul></li></ul></li></ul><p><img src="https://atelieryu.xyz/elog/202503/406ba403b533f2b5dadcd79db8664f31.png" alt="image.png"></p><h2 id="基本使用"><a href="#基本使用" class="headerlink" title="基本使用"></a>基本使用</h2><figure class="highlight protobuf"><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">syntax = <span class="string">&quot;proto3&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">message </span><span class="title class_">Person</span>&#123;</span><br><span class="line">    <span class="type">int32</span> id = <span class="number">1</span>;</span><br><span class="line">    <span class="type">bytes</span> name = <span class="number">2</span>;</span><br><span class="line">    <span class="type">bytes</span> sex = <span class="number">3</span>;</span><br><span class="line">    <span class="type">int32</span> age = <span class="number">4</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>生成对应的.h和.c文件</p></blockquote><figure class="highlight shell"><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="meta prompt_"># </span><span class="language-bash">protoc &lt;proto文件路径&gt; --cpp_out=&lt;输出路径&gt;</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">protoc Person.proto --cpp_out=./</span></span><br></pre></td></tr></table></figure><h3 id="repeated关键字"><a href="#repeated关键字" class="headerlink" title="repeated关键字"></a>repeated关键字</h3><ul><li>repeated标志对应的成员是动态数组</li></ul><figure class="highlight protobuf"><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">syntax = <span class="string">&quot;proto3&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">message </span><span class="title class_">Person</span>&#123;</span><br><span class="line">    <span class="keyword">repeated</span> <span class="type">int32</span> id = <span class="number">1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="枚举"><a href="#枚举" class="headerlink" title="枚举"></a>枚举</h3><ul><li>proto3 中的<strong>第一个枚举值必须为 0</strong>，第一个元素以外的元素值可以随意指定</li></ul><figure class="highlight protobuf"><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">// 定义枚举类型</span></span><br><span class="line"><span class="keyword">enum </span><span class="title class_">Color</span></span><br><span class="line">&#123;</span><br><span class="line">    Red = <span class="number">0</span>;</span><br><span class="line">    Green = <span class="number">3</span>;<span class="comment">// 第一个元素以外的元素值可以随意指定</span></span><br><span class="line">    Yellow = <span class="number">6</span>;</span><br><span class="line">    Blue = <span class="number">9</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 在该文件中对要序列化的结构体进行描述</span></span><br><span class="line"><span class="keyword">message </span><span class="title class_">Person</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="type">int32</span> id = <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">repeated</span> <span class="type">bytes</span> name = <span class="number">2</span>;</span><br><span class="line">    <span class="type">bytes</span> sex = <span class="number">3</span>;</span><br><span class="line">    <span class="type">int32</span> age = <span class="number">4</span>;</span><br><span class="line">    <span class="comment">// 枚举类型</span></span><br><span class="line">    Color color = <span class="number">5</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="proto文件的导入"><a href="#proto文件的导入" class="headerlink" title="proto文件的导入"></a>proto文件的导入</h3><ul><li>使用<code>import</code>语句在当前.ptoto中导入其它的.proto文件。这样就可以在一个.proto文件中引用并使用其它文件中定义的消息类型和枚举类型。</li></ul><figure class="highlight protobuf"><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">syntax = <span class="string">&quot;proto3&quot;</span>;</span><br><span class="line"><span class="comment">// 使用另外一个proto文件中的数类型, 需要导入这个文件</span></span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;Address.proto&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 在该文件中对要序列化的结构体进行描述</span></span><br><span class="line"><span class="keyword">message </span><span class="title class_">Person</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="type">int32</span> id = <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">repeated</span> <span class="type">bytes</span> name = <span class="number">2</span>;</span><br><span class="line">    <span class="type">bytes</span> sex = <span class="number">3</span>;</span><br><span class="line">    <span class="type">int32</span> age = <span class="number">4</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 添加地址信息, 使用的是外部proto文件中定义的数据类型</span></span><br><span class="line">    Address addr = <span class="number">6</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="包（package）"><a href="#包（package）" class="headerlink" title="包（package）"></a>包（package）</h3><ul><li>在 Protobuf 中，可以使用package关键字来定义一个消息所属的包（package）。包是用于组织和命名消息类型的一种机制，<strong>类似于命名空间的概念</strong></li><li>在一个.proto文件中，可以通过在顶层使用package关键字来定义包：</li></ul><figure class="highlight protobuf"><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">syntax = <span class="string">&quot;proto3&quot;</span>;</span><br><span class="line"><span class="comment">// 添加命名空间 Dabing</span></span><br><span class="line"><span class="keyword">package</span> Dabing;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 地址信息, 这个Address类属于命名空间: Dabing</span></span><br><span class="line"><span class="keyword">message </span><span class="title class_">Address</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="type">bytes</span> addr = <span class="number">1</span>;</span><br><span class="line">    <span class="type">bytes</span> number = <span class="number">2</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>其他.proto文件导入以上文件后，可以通过<code>Dabing.Address</code>使用该文件定义的消息体Address</li><li>.c文件使用该proto文件生成的对象时，通过<code>Dabing::Address</code>使用该文件定义的消息体</li></ul><h2 id="Protobuf-API"><a href="#Protobuf-API" class="headerlink" title="Protobuf API"></a>Protobuf API</h2><p>通过对象调用Protobuf API</p><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 获取成员的const版本（数组成员需要传入索引）</span></span><br><span class="line">&lt;成员名&gt;()</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取成员的可修改版本（数组成员需要传入索引）</span></span><br><span class="line"><span class="built_in">mutable_</span>&lt;成员名&gt;()</span><br><span class="line"></span><br><span class="line"><span class="comment">// 设置成员（数组成员需要传入索引）</span></span><br><span class="line"><span class="built_in">set_</span>&lt;成员名&gt;()</span><br><span class="line"></span><br><span class="line"><span class="comment">// 清空成员</span></span><br><span class="line"><span class="built_in">clear_</span>&lt;成员名&gt;()</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取数组成员的size</span></span><br><span class="line">&lt;成员名&gt;_size()</span><br><span class="line"></span><br><span class="line"><span class="comment">// 向数组中添加成员</span></span><br><span class="line"><span class="built_in">add_</span>&lt;成员名&gt;()</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 头文件目录: google\protobuf\message_lite.h</span></span><br><span class="line"><span class="comment">// --- 将序列化的数据 数据保存到内存中</span></span><br><span class="line"><span class="comment">// 将类对象中的数据序列化为字符串: C++风格的字符串, 参数是一个传出参数</span></span><br><span class="line"><span class="function"><span class="type">bool</span> <span class="title">SerializeToString</span><span class="params">(std::string* output)</span> <span class="type">const</span></span>;</span><br><span class="line"><span class="comment">// 将类对象中的数据序列化为字符串: C风格的字符串, 参数 data 是一个传出参数</span></span><br><span class="line"><span class="function"><span class="type">bool</span> <span class="title">SerializeToArray</span><span class="params">(<span class="type">void</span>* data, <span class="type">int</span> size)</span> <span class="type">const</span></span>;</span><br><span class="line"></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="comment">// 将数据序列化写入到磁盘文件中, c++ 风格</span></span><br><span class="line"><span class="comment">// ostream 子类 ofstream -&gt; 写文件</span></span><br><span class="line"><span class="function"><span class="type">bool</span> <span class="title">SerializeToOstream</span><span class="params">(std::ostream* output)</span> <span class="type">const</span></span>;</span><br><span class="line"><span class="comment">// 将数据序列化写入到磁盘文件中, c 风格</span></span><br><span class="line"><span class="function"><span class="type">bool</span> <span class="title">SerializeToFileDescriptor</span><span class="params">(<span class="type">int</span> file_descriptor)</span> <span class="type">const</span></span>;</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 头文件目录: google\protobuf\message_lite.h</span></span><br><span class="line"><span class="function"><span class="type">bool</span> <span class="title">ParseFromString</span><span class="params">(<span class="type">const</span> std::string&amp; data)</span> </span>;</span><br><span class="line"><span class="function"><span class="type">bool</span> <span class="title">ParseFromArray</span><span class="params">(<span class="type">const</span> <span class="type">void</span>* data, <span class="type">int</span> size)</span></span>;</span><br><span class="line"><span class="comment">// istream -&gt; 子类 ifstream -&gt; 读操作</span></span><br><span class="line"><span class="comment">// wo ri</span></span><br><span class="line"><span class="comment">// w-&gt;写 o: ofstream , r-&gt;读 i: ifstream</span></span><br><span class="line"><span class="function"><span class="type">bool</span> <span class="title">ParseFromIstream</span><span class="params">(std::istream* input)</span></span>;</span><br><span class="line"><span class="function"><span class="type">bool</span> <span class="title">ParseFromFileDescriptor</span><span class="params">(<span class="type">int</span> file_descriptor)</span></span>;</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;div class=&quot;note info modern&quot;&gt;&lt;p&gt;ProtoBuf提供对数据的序列化和反序列化，ProtoBuf可以用于结构化数据的串行序列化，并且以Key-Value格式存储数据，因为采用二进制格式，所以序列化出来的数据比较少，作为网络传输的载体效率很高&lt;/p&gt;</summary>
      
    
    
    
    <category term="学习笔记" scheme="https://atelieryu.site/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="后端" scheme="https://atelieryu.site/tags/%E5%90%8E%E7%AB%AF/"/>
    
    <category term="RPC" scheme="https://atelieryu.site/tags/RPC/"/>
    
    <category term="Protobuf" scheme="https://atelieryu.site/tags/Protobuf/"/>
    
  </entry>
  
  <entry>
    <title>2025一月番追番记录</title>
    <link href="https://atelieryu.site/%E8%BF%BD%E7%95%AA%E8%AE%B0%E5%BD%95-2025-Winter.html"/>
    <id>https://atelieryu.site/%E8%BF%BD%E7%95%AA%E8%AE%B0%E5%BD%95-2025-Winter.html</id>
    <published>2025-03-16T09:24:00.000Z</published>
    <updated>2025-03-30T04:09:00.000Z</updated>
    
    <content type="html"><![CDATA[<div class="note info modern"><p>食用说明</p></div><ol><li>本文更偏向追番体验的记录，可能与补番体验略有不同</li><li>本文评分体系近似bangumi，以下为分数区间参考含义<ul><li>&gt;9：全人类都应该来看</li><li>&gt;8：必看神作</li><li>&gt;7：值得一看</li><li>&gt;6：闲的没事可以看</li><li>&gt;5：平平无奇</li><li>&lt;5：献给小众赤石爱好者的一封情书</li></ul></li><li>本文完全由个人主观构成，没有客观</li></ol><h1 id="2025一月番简评"><a href="#2025一月番简评" class="headerlink" title="2025一月番简评"></a>2025一月番简评</h1><h2 id="8分段"><a href="#8分段" class="headerlink" title="8分段"></a>8分段</h2><h3 id="金牌得主"><a href="#金牌得主" class="headerlink" title="金牌得主"></a>金牌得主</h3><p><img src="https://atelieryu.xyz/elog/202503/dbe4984261187c28c97afa31519a9d10.jpg" alt="金牌得主"></p><blockquote><p>“花滑是见证奇迹的运动”</p></blockquote><p><del>没想到小女孩滑冰这么好看。</del>看一半没忍住把漫画全补完了，原作质量过于硬了（<del>原作得了mvp，engi就是躺赢狗，全人类都应该看这部漫画</del>），动画制作虽然还是差一些（比如部分文戏），但花滑部分是真用心做了。原作花滑的分镜固然震撼，但动画里能看到角色流畅地完成一个个花滑动作也是一种别样的享受。这部番的魅力很难用三言两语描述出来，总之都给我去看呀（</p><ul><li><strong>对于花滑的刻画</strong>：花滑真是优雅而又残酷啊。看了几集甚至主动去了解了一些花滑的规则，教练，我想学花滑！（</li><li><strong>对于人物的刻画</strong>：无论是主角还是每一位配角都有比较立体的刻画。尤其是主角的小祈和司教练，对过去的遗憾，对金牌的执着……，各种复杂的感情混杂在一起构成了人物。</li><li><strong>女女关系性</strong>：<del>小女孩也能有女女关系性？</del>漫画后期关系性发力后是真的美味啊（</li><li><strong>制作组对作品的热情</strong>：你是说，原作者厨的声优喜欢花滑，于是画了花滑为主题的漫画 → 八爷厨漫画主动请缨给漫画唱op → 花滑顶尖运动员羽生结弦欣赏八爷的才华同意合拍op的mv？就连平时和p9坐一桌的engi都开始发力了</li></ul><blockquote><p>综合打分——8</p></blockquote><ul><li>剧情：8.5</li><li>人设：8</li><li>动画制作：7.6</li></ul><h2 id="7分段"><a href="#7分段" class="headerlink" title="7分段"></a>7分段</h2><h3 id="喜欢的冲绳妹说方言"><a href="#喜欢的冲绳妹说方言" class="headerlink" title="喜欢的冲绳妹说方言"></a><strong>喜欢的冲绳妹说方言</strong></h3><p><img src="https://atelieryu.xyz/elog/202503/7f4a23f11ed367ec74ef117ee2aeac12.jpg" alt="喜欢的冲绳妹说方言"></p><blockquote><p>人文纪录片 + 恋爱喜剧</p></blockquote><p>近几年还挺流行通过动漫来进行地区宣传的，虽然好看的也有一些（比如佐贺偶像是传奇），但看完真正让我产生“好想去这里旅游啊”的想法的这还是第一部。人文纪录片和恋爱喜剧两个都是我比较喜欢的类型，本季个人最佳大脑按摩番。</p><ul><li><strong>人文纪录片</strong>：剧中把各种因为文化、自然环境等因素产生的习俗差异，自然地融入了角色的日常交流中。与其说是在地区宣传中加入角色互动，不如说是通过角色互动中自然产生的交流gap来科普地区特色，加上段子情节设计的也都挺有趣，看的乐呵的同时还能学到不少异域豆知识。</li><li><strong>恋爱喜剧</strong>：<del>女二好可爱呀好可爱呀。本来我对于黑皮是拒绝的，但女二真的好可爱啊（本季第二喜欢的女角色）</del>。男主喜欢女主，女二喜欢男主，看起来扭曲的设定实际却异常清爽（因为女二一个人贡献了全片90%以上的恋爱桥段），看女二一直自我拉扯还是挺有趣的（听漫画党说后面女二有望反杀女主，希望是真的）。</li></ul><blockquote><p>综合打分——7.6</p></blockquote><ul><li><del>剧情</del>有趣度：7.7</li><li>人设：7.7（7.2 女二再加0.5）</li><li>动画制作：7.2</li></ul><h3 id="群花绽放，彷如修罗"><a href="#群花绽放，彷如修罗" class="headerlink" title="群花绽放，彷如修罗"></a><strong>群花绽放，彷如修罗</strong></h3><p><img src="https://atelieryu.xyz/elog/202503/e73e378e85ae3f374b5a816e84a46b0e.jpg" alt="群花绽放，彷如修罗"></p><blockquote><p>青春+社团活动+苦呀西</p></blockquote><ul><li><strong>有点尬的朗读演出</strong>：说实在朗读这个题材是真不太能get到，平时没接触过这个领域，单纯听也听不出名堂（毕竟天天听各种专业声优的配音）。制作组选择用领域展开这种方式表现朗读在我看来是有点尬的，所以朗读方面对我来讲是减分项。</li><li><strong>意外的文戏水平</strong>：这番在文戏上的刻画倒是渐入佳境，特别是在三四集左右进入各个部员的个人回，每集都看的挺舒服的。每个人都有各自的追求和心结，但又不拧巴。第九集的重女登场更是为我提供了不少乐子。</li><li><strong>可爱的女主</strong>：本番还贡献了本季度我最喜欢的女主（<del>花奈像只小动物一样，真的好可爱呀好可爱呀好可爱呀</del>）</li></ul><p><img src="https://atelieryu.xyz/elog/202503/1c1b2dc84f2e71b63751487b5e3a18fa.png" alt="花奈可爱捏"></p><blockquote><p>综合打分——7.6</p></blockquote><ul><li>剧情：7.5</li><li>人设：8（7.5女主再加0.5）</li><li>动画制作：7.5</li></ul><h3 id="一杆青空"><a href="#一杆青空" class="headerlink" title="一杆青空"></a>一杆青空</h3><p><img src="https://atelieryu.xyz/elog/202503/84b2ab9241b002fe2da88dea8091d697.jpg" alt="一杆青空"></p><blockquote><p>你们三个把日子过好比啥都重要</p></blockquote><p>本来对高尔夫也没啥兴趣（刻板印象：中年有钱大叔们谈生意的娱乐活动），结果看着看着居然能get到一点乐趣了。</p><p>算向山进发&#x2F;摇曳露营类型片？看着比较空气系但却并不无聊。主角团三人关系的文戏刻画意外地出色（跟一家三口似的）。看美少女们开开心心做点啥真是治愈啊，没有任何阴暗的东西，大家都只是纯粹地享受这个运动，享受朋友间共度的时光，每集看完都心里暖暖的。本季第二大脑按摩番，最后完结看着特殊ed是真不舍啊。</p><p>此外女主也挺可爱的</p><p><img src="https://atelieryu.xyz/elog/202503/821b49634dc4ee2008d20f7ec8eed59e.png" alt="美波可爱捏"></p><blockquote><p>综合评分——7.4</p></blockquote><ul><li>日常感：7.4</li><li>人设：7.4</li><li>动画制作：7</li></ul><h3 id="中年大叔转生恶役大小姐"><a href="#中年大叔转生恶役大小姐" class="headerlink" title="中年大叔转生恶役大小姐"></a>中年大叔转生恶役大小姐</h3><p><img src="https://atelieryu.xyz/elog/202503/76d5a1d2eae80ad8a1b8bd91951e84cb.jpg" alt="中年大叔转生恶役大小姐"></p><blockquote><p>体制内老干部的优雅与从容</p></blockquote><p>恶役系除了第一部猴王其他都没看完，几乎都是一个模板套出来的，导致越看越觉得土。这部倒是有点特色，中年大叔转生，利用多年体制内混出来的情商俘获一群人心。段子设计的也挺好（怀疑作者真是中年体制内老干部），甚至设定里还有一丝悬疑味。每周打开前都觉得好土，但最后又都是开心地看完一集（</p><blockquote><p>综合打分——7.2</p></blockquote><ul><li>有趣度：7.5</li><li>人设：6.8</li><li>动画制作：6.9</li></ul><h2 id="6分段"><a href="#6分段" class="headerlink" title="6分段"></a>6分段</h2><h3 id="不想加班的公会柜台小姐单挑BOSS"><a href="#不想加班的公会柜台小姐单挑BOSS" class="headerlink" title="不想加班的公会柜台小姐单挑BOSS"></a>不想加班的公会柜台小姐单挑BOSS</h3><p><img src="https://atelieryu.xyz/elog/202503/f4bab2b9a92c88c3ffe0efd5ef15dc84.jpg" alt="不想加班的公会柜台小姐单挑BOSS"></p><blockquote><p>她太喜欢当公务员了</p></blockquote><p>女主实在是太爱国了，明明随手打一个boss的报酬就够躺平好久，但她还是选择了天天加班当公务员，除了爱国想不出别的理由（</p><p>经典凤傲天番，看点是同类型中不错的制作水平（cw你有空做这个不如早点凑齐人手把我孤独摇滚第二季吐出来）和无处不在的女主大腿特写（上面的宣传图就能看出端倪了吧）。总之不带大脑1.5倍速看还算能看</p><blockquote><p>综合评分——6.2</p></blockquote><ul><li>剧情：5.2</li><li>人设：6.5</li><li>动画制作：7</li></ul><h2 id="4分段"><a href="#4分段" class="headerlink" title="4分段"></a>4分段</h2><h3 id="颂乐人偶（BanG-Dream-Ave-Mujica）"><a href="#颂乐人偶（BanG-Dream-Ave-Mujica）" class="headerlink" title="颂乐人偶（BanG Dream! Ave Mujica）"></a>颂乐人偶（BanG Dream! Ave Mujica）</h3><p><img src="https://atelieryu.xyz/elog/202503/04ea7b35fd43ab92229f73fcea9b1a18.jpg" alt="Mujica"></p><blockquote><p>唉，人偶</p></blockquote><p>虽然想到大概率会翻车，但能烂到这种程度是真没想到。<del>我又幻想了，幻想独角兽老师归来重置母鸡卡，负责全部集数的脚本，所有爆点都处理的酣畅淋漓，剧本完成度甚至超越前作MyGo，一举拿下2025年度动画</del></p><ul><li><strong>逻辑混乱的剧情</strong>：团炸的莫名其妙，重组的也莫名其妙，角色性格转变的也莫名其妙。能看出很多地方剧情大纲是没大问题了，但具体实施到细节就充满了随意与混乱。</li><li><strong>崩坏无常的人设</strong>：角色没一个是人：只对熟人哈气的多重人格大祥，真多重人格的精神病睦、让30个乐队倒大霉的信用复读机海玲、强制soulmate的喵梦、地上狗爬的初华。编剧未免过于不爱惜自己笔下的角色了</li><li><strong>毫无底线的炒作</strong>：制作组没那实力又想炒波大的。前几集每集结尾都在炒，然后下一集开头又轻轻放下；中间几集围绕一个多重人格的烂活硬炒几集，还把前作角色的人设给整崩了；最后几集没活了又炒出个二次元雷雨。更过分的是这些炒作设定对剧情推进和人物塑造几乎没有贡献。评价是为了热度连🐎都不要了。</li></ul><p>（为什么13集的live又做的这么出色，不敢想要是能忘记前面的抽象剧情这集得看的多爽。欧内该，ob一串字母大人，让我忘记一切吧😭）</p><p><img src="https://atelieryu.xyz/elog/202503/bd8b81f3b85440da67c280b05d586cbb.png" alt="让我忘记一切吧"></p><blockquote><p>综合评分——4.2</p></blockquote><ul><li>剧情：3</li><li>人设：3</li><li>动画制作：7.3</li></ul><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p><img src="https://atelieryu.xyz/elog/202503/5bc14bd1a2dea71b40ea2ff3f912f073.png" alt="2025冬季番"></p><p>以为只是个过渡季，没想到黑马还挺多的，追番体验大概如上吧，比其往年平淡的一月，这季度看的还是比较开心的，有几部番甚至让我找回了那种完结时心里空落落的感觉</p><p>还有几部续作，比如百人女友、异修罗、石纪元，准备之后再补。上季度看一半搁置的甘神家和青之箱有空可能也会补回来。</p><p>（唉，mujica带给我的失望是真的，可追番过程每周磕假药溜二创带给我的快乐也是真的。或许这回它烂了，但下次碰到这类原创番我肯定还会追的，毕竟长颈鹿渴望的永远是无法预测的命运之舞台呀。）</p><p><img src="https://atelieryu.xyz/elog/202503/065352e21308516717b70d2f5f16cec5.png" alt="欧内该"></p><p><img src="https://atelieryu.xyz/elog/202503/3be78d9b2545dc33c7dfe5c8e1bf24ed.png" alt="瓦塔西"></p><p>（什么？还有续作？豪赤！再来一份！）</p><h1 id="2025四月番前瞻"><a href="#2025四月番前瞻" class="headerlink" title="2025四月番前瞻"></a>2025四月番前瞻</h1><p>度过了较为平淡的一月，四月就热闹起来了。数了数感兴趣的居然有16部</p><h3 id="夏日口袋"><a href="#夏日口袋" class="headerlink" title="夏日口袋"></a>夏日口袋</h3><p><img src="https://atelieryu.xyz/elog/202503/b750ead19b9bb7a15fc51e8d9a5f1302.jpg" alt="夏日口袋"></p><p>夏季是我最喜欢的季节，暑假是一年中我最喜欢的时期，而Summer Pocket则是我认为对暑期感描写的最好的作品之一。原作是我最喜欢的Gal之一，希望feel能给点劲把它做好吧。重振Gal改荣光（</p><h3 id="机动战士高达GQuuuuuuX"><a href="#机动战士高达GQuuuuuuX" class="headerlink" title="机动战士高达GQuuuuuuX"></a><strong>机动战士高达GQuuuuuuX</strong></h3><p><img src="https://atelieryu.xyz/elog/202503/441462356fded410db4427fc91d5dbe3.jpg" alt="机动战士高达GQuuuuuuX"></p><p>庵野秀明编剧 + 吉翁反杀联邦if线 + 过于标新立异的机设，反正噱头是已经拉满了，看肯定是会看的，但感觉翻车率拉满（得有个70%+的翻车率吧）。是时候把之前坑的Z高达补完了</p><h3 id="LAZARUS"><a href="#LAZARUS" class="headerlink" title="LAZARUS"></a><strong>LAZARUS</strong></h3><p><img src="https://atelieryu.xyz/elog/202503/d58312a81eb7f7be7e9c31a239ec864f.jpg" alt="LAZARUS"></p><p>渡边信一郎导演的新作 + 近年来稳定高质量的Mappa + 听说已经完成制作的超充裕工期，同样噱头拉满，但感觉比GQuuuX稳多了（至少能保住下限吧？）。</p><h3 id="赛马娘-芦毛灰姑娘"><a href="#赛马娘-芦毛灰姑娘" class="headerlink" title="赛马娘 芦毛灰姑娘"></a><strong>赛马娘 芦毛灰姑娘</strong></h3><p><img src="https://atelieryu.xyz/elog/202503/eb1f0a3356127d4947278ebc864b88d8.jpg" alt="赛马娘 芦毛灰姑娘"></p><p>听漫画原作党说是赛马娘系列最优秀的作品，小栗帽也是日本人气断层领先的赛马，Cy应该会拿出全力做吧，顶尖之路和新时代之扉做的都挺好的，还是值得一看的（虽然看制作人员好像有点危险）</p><h3 id="魔女与使魔"><a href="#魔女与使魔" class="headerlink" title="魔女与使魔"></a>魔女与使魔</h3><p><img src="https://atelieryu.xyz/elog/202503/f7514468c2e15d5167a4124b69faf28b.jpg" alt="魔女与使魔"></p><p>原作是jump中坚之一，看pv表现挺好的，制作不错，pv里都有不少笑点，校园恋爱喜剧一直是我比较喜欢的类型，应该是会追下去的（希望别是鹿乃子ver2）</p><h3 id="mono女孩"><a href="#mono女孩" class="headerlink" title="mono女孩"></a>mono女孩</h3><p><img src="https://atelieryu.xyz/elog/202503/87704935c7a772bc38b7da56d34fa0af.jpg" alt="mono女孩"></p><p>摇曳露营代餐其一，摇曳露营原作者的另一部作品，好像是摄影为主题的。没啥好说的，先追着</p><h3 id="杂旅"><a href="#杂旅" class="headerlink" title="杂旅"></a>杂旅</h3><p><img src="https://atelieryu.xyz/elog/202503/edda6f86ef1858c336da8a41b8c8a766.jpg" alt="杂旅"></p><p>摇曳露营代餐其二，以旅行为主题的作品，可能成为日本旅游guidebook，没啥好说的，先追着</p><h3 id="时光流逝-饭菜依旧美味"><a href="#时光流逝-饭菜依旧美味" class="headerlink" title="时光流逝 饭菜依旧美味"></a>时光流逝 饭菜依旧美味</h3><p><img src="https://atelieryu.xyz/elog/202503/efbc9571de508608f09524eb2076b0f6.jpg" alt="时光流逝 饭菜依旧美味"></p><p>PA的原创，以美食为主题，PA制作的下限一直都挺高，感觉是每一部都在认真做，但之前几部作品总是看着看着就觉得无聊弃坑了。这部也准备先追几集看看吧，毕竟PA的美少女画的都挺可爱的（</p><h3 id="忍者与杀手二人组的日常生活"><a href="#忍者与杀手二人组的日常生活" class="headerlink" title="忍者与杀手二人组的日常生活"></a>忍者与杀手二人组的日常生活</h3><p><img src="https://atelieryu.xyz/elog/202503/09116b1547639eceacb0964139a146cd.jpg" alt="忍者与杀手二人组的日常生活"></p><p><del>我超忍杀二</del>看pv女角色都蛮可爱的，但好像是主打反差的（指可爱的妹子下一集就惨死，<del>这也是末世之法的一个侧面</del>），看多了轻松日常番来点这类番调节一下也挺好？</p><h3 id="摇滚乐是淑女的嗜好"><a href="#摇滚乐是淑女的嗜好" class="headerlink" title="摇滚乐是淑女的嗜好"></a>摇滚乐是淑女的嗜好</h3><p><img src="https://atelieryu.xyz/elog/202503/5f397b019511285d4744731e99cddc39.jpg" alt="摇滚乐是淑女的嗜好"></p><p>最新的少女乐队番，看点好像是少女们边摇滚边颜艺彪垃圾话，emmm，这就是摇滚吗，总之先看几集吧</p><h3 id="启示录酒店"><a href="#启示录酒店" class="headerlink" title="启示录酒店"></a>启示录酒店</h3><p><img src="https://atelieryu.xyz/elog/202503/bfd937ede8da9855f132a7f05fb2cb40.jpg" alt="启示录酒店"></p><p>Cy的一部原创番，看设定有点像几年前的星之梦，但看pv2又有点闹腾，可能会是那种欢乐日常中带点刀的单元剧？制作看起来不错，期待表现（Cy主力不会在这吧）</p><h3 id="搞笑漫画日和-第5期"><a href="#搞笑漫画日和-第5期" class="headerlink" title="搞笑漫画日和 第5期"></a>搞笑漫画日和 第5期</h3><p><img src="https://atelieryu.xyz/elog/202503/9fca3b749d3f31e6217e171e45f373a0.jpg" alt="搞笑漫画日和 第5期"></p><p>这是真童年了啊，入动画坑前就看过的作品，产出的不少梗到今天还有生命力。不知道隔了这么久的动画化还是不是内味（也有可能我的口味变了，导致看不进去了，唉）</p><h3 id="乡下大叔成为剑圣"><a href="#乡下大叔成为剑圣" class="headerlink" title="乡下大叔成为剑圣"></a>乡下大叔成为剑圣</h3><p><img src="https://atelieryu.xyz/elog/202503/beb32691702746a2cd938a72a367afdb.jpg" alt="乡下大叔成为剑圣"></p><p>之前吃安利看过原作，剧情比较平庸，但画的分镜和动作相当出色，角色的动作真的能让人感受到武术的美感。动画不知道能不能体现好</p><h3 id="阳光马达棒球场"><a href="#阳光马达棒球场" class="headerlink" title="阳光马达棒球场"></a>阳光马达棒球场</h3><p><img src="https://atelieryu.xyz/elog/202503/2b670f8bceef549d680cc1f41ada8f37.jpg" alt="阳光马达棒球场"></p><p>好像讲的不是棒球，而是围绕棒球的各类人群（看客、售货员、解说等）的群像剧。有点兴趣，感觉有成为泛鸽鸽每周六直播间下播番的潜力</p><h3 id="拜托请穿上-鹰峰同学"><a href="#拜托请穿上-鹰峰同学" class="headerlink" title="拜托请穿上 鹰峰同学"></a>拜托请穿上 鹰峰同学</h3><p><img src="https://atelieryu.xyz/elog/202503/eca1c2b813c2442060670f8deb5a0181.jpg" alt="拜托请穿上 鹰峰同学"></p><p>福利番一号，还有表里版。十年前我还觉得看这种卖肉番不如去看里番，而在现在里番业界不断走下坡，真得表番来当里番代餐了。这部人设还挺好看的，可能会看？</p><h3 id="快藏起来-玛琪娜同学"><a href="#快藏起来-玛琪娜同学" class="headerlink" title="快藏起来 玛琪娜同学"></a>快藏起来 玛琪娜同学</h3><p><img src="https://atelieryu.xyz/elog/202503/a9e3bbafe2b4048dd06161a3c16fca65.jpg" alt="快藏起来 玛琪娜同学"></p><p>福利番二号，性x机器人，总觉得能看到未来（</p>]]></content>
    
    
      
      
    <summary type="html">&lt;div class=&quot;note info modern&quot;&gt;&lt;p&gt;食用说明&lt;/p&gt;
&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt;本文更偏向追番体验的记录，可能与补番体验略有不同&lt;/li&gt;
&lt;li&gt;本文评分体系近似bangumi，以下为分数区间参考含义&lt;ul&gt;
&lt;li&gt;&amp;gt;9：全人类都应</summary>
      
    
    
    
    <category term="ACGN" scheme="https://atelieryu.site/categories/ACGN/"/>
    
    
    <category term="追番记录" scheme="https://atelieryu.site/tags/%E8%BF%BD%E7%95%AA%E8%AE%B0%E5%BD%95/"/>
    
    <category term="动画" scheme="https://atelieryu.site/tags/%E5%8A%A8%E7%94%BB/"/>
    
  </entry>
  
  <entry>
    <title>基于Elog的 Notion-hexo 同步方案</title>
    <link href="https://atelieryu.site/Elog%E5%8D%9A%E5%AE%A2%E5%90%8C%E6%AD%A5.html"/>
    <id>https://atelieryu.site/Elog%E5%8D%9A%E5%AE%A2%E5%90%8C%E6%AD%A5.html</id>
    <published>2025-03-16T03:37:00.000Z</published>
    <updated>2025-03-31T03:20:00.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="博客工具"><a href="#博客工具" class="headerlink" title="博客工具"></a>博客工具</h1><ul><li>写作平台：<a href="https://www.notion.so/">Notion</a></li><li>博客平台：<a href="https://hexo.io/">Hexo</a></li><li>博客主题：<a href="https://github.com/jerryc127/hexo-theme-butterfly">Butterfly</a></li><li>博客文档同步：<a href="https://github.com/LetTTGACO/elog">Elog</a></li><li>原项目仓库：<a href="https://github.com/elog-x/notion-hexo">https://github.com/elog-x/notion-hexo</a></li></ul><h1 id="博客搭建指南"><a href="#博客搭建指南" class="headerlink" title="博客搭建指南"></a>博客搭建指南</h1><h2 id="Fork模板仓库"><a href="#Fork模板仓库" class="headerlink" title="Fork模板仓库"></a>Fork模板仓库</h2><p>将<a href="https://github.com/elog-x/notion-hexo">模板仓库</a> clone 到本地</p><h3 id="之前已经有博客的情况"><a href="#之前已经有博客的情况" class="headerlink" title="之前已经有博客的情况"></a>之前已经有博客的情况</h3><ol><li><p>使用旧博客文件<strong>覆盖</strong>博客仓库</p></li><li><p>修改<code>package.json</code>中的依赖：</p> <figure class="highlight diff"><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></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  &quot;name&quot;: &quot;hexo-site&quot;,</span><br><span class="line">  &quot;version&quot;: &quot;0.0.0&quot;,</span><br><span class="line">  &quot;private&quot;: true,</span><br><span class="line">  &quot;scripts&quot;: &#123;</span><br><span class="line">    &quot;build&quot;: &quot;hexo generate&quot;,</span><br><span class="line">    &quot;clean&quot;: &quot;hexo clean&quot;,</span><br><span class="line">    &quot;deploy&quot;: &quot;hexo deploy&quot;,</span><br><span class="line"><span class="addition">+   &quot;server&quot;: &quot;hexo clean &amp;&amp; hexo server&quot;,</span></span><br><span class="line"><span class="addition">+   &quot;sync:local&quot;: &quot;elog sync -e .elog.env&quot;,</span></span><br><span class="line"><span class="addition">+   &quot;elog:init&quot;: &quot;elog init&quot;,</span></span><br><span class="line"><span class="addition">+   &quot;sync&quot;: &quot;elog sync&quot;,</span></span><br><span class="line"><span class="addition">+   &quot;elog:clean&quot;: &quot;elog clean&quot;</span></span><br><span class="line">  &#125;,</span><br><span class="line">  &quot;hexo&quot;: &#123;</span><br><span class="line">    &quot;version&quot;: &quot;7.3.0&quot;</span><br><span class="line">  &#125;,</span><br><span class="line">  &quot;dependencies&quot;: &#123;</span><br><span class="line"><span class="addition">+   &quot;@elog/cli&quot;: &quot;^0.14.0&quot;,</span></span><br><span class="line">    &quot;hexo&quot;: &quot;^7.0.0&quot;,</span><br><span class="line">    &quot;hexo-abbrlink&quot;: &quot;^2.2.1&quot;,</span><br><span class="line">    &quot;hexo-bilibili-bangumi&quot;: &quot;^1.10.8&quot;,</span><br><span class="line">    &quot;hexo-butterfly-categories-card&quot;: &quot;^1.0.0&quot;,</span><br><span class="line">    &quot;hexo-butterfly-clock-anzhiyu&quot;: &quot;^1.1.8&quot;,</span><br><span class="line">    &quot;hexo-butterfly-envelope&quot;: &quot;^1.0.15&quot;,</span><br><span class="line">    &quot;hexo-butterfly-footer-beautify&quot;: &quot;^1.0.6&quot;,</span><br><span class="line">    &quot;hexo-butterfly-tag-plugins-plus&quot;: &quot;^1.0.18&quot;,</span><br><span class="line">    &quot;hexo-butterfly-wowjs&quot;: &quot;^1.0.5&quot;,</span><br><span class="line">    &quot;hexo-deployer-git&quot;: &quot;^4.0.0&quot;,</span><br><span class="line">    &quot;hexo-generator-archive&quot;: &quot;^2.0.0&quot;,</span><br><span class="line">    &quot;hexo-generator-category&quot;: &quot;^2.0.0&quot;,</span><br><span class="line">    &quot;hexo-generator-feed&quot;: &quot;^3.0.0&quot;,</span><br><span class="line">    &quot;hexo-generator-index&quot;: &quot;^3.0.0&quot;,</span><br><span class="line">    &quot;hexo-generator-search&quot;: &quot;^2.4.3&quot;,</span><br><span class="line">    &quot;hexo-generator-sitemap&quot;: &quot;^3.0.1&quot;,</span><br><span class="line">    &quot;hexo-generator-tag&quot;: &quot;^2.0.0&quot;,</span><br><span class="line">    &quot;hexo-renderer-ejs&quot;: &quot;^2.0.0&quot;,</span><br><span class="line">    &quot;hexo-renderer-kramed&quot;: &quot;^0.1.4&quot;,</span><br><span class="line"><span class="addition">+   &quot;hexo-renderer-marked&quot;: &quot;^6.0.0&quot;,</span></span><br><span class="line">    &quot;hexo-renderer-pug&quot;: &quot;^3.0.0&quot;,</span><br><span class="line">    &quot;hexo-renderer-stylus&quot;: &quot;^3.0.1&quot;,</span><br><span class="line">    &quot;hexo-server&quot;: &quot;^3.0.0&quot;,</span><br><span class="line">    &quot;hexo-tag-aplayer&quot;: &quot;^3.0.4&quot;,</span><br><span class="line">    &quot;hexo-theme-landscape&quot;: &quot;^1.0.0&quot;</span><br><span class="line">  &#125;,</span><br><span class="line">  &quot;devDependencies&quot;: &#123;</span><br><span class="line">    &quot;hexo-generator-baidu-sitemap&quot;: &quot;^0.1.9&quot;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>继续后续操作</p></li></ol><h2 id="安装依赖"><a href="#安装依赖" class="headerlink" title="安装依赖"></a>安装依赖</h2><p>在项目根目录下运行命令安装依赖</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install</span><br></pre></td></tr></table></figure><h2 id="新建-Elog-本地调试文件"><a href="#新建-Elog-本地调试文件" class="headerlink" title="新建 Elog 本地调试文件"></a><strong>新建 Elog 本地调试文件</strong></h2><ol><li>将<code>.elog.example.env</code>文件重命名为<code>.elog.env</code>，此文件将用于本地同步Notion 文档</li></ol><h2 id="配置-Notion-关键信息"><a href="#配置-Notion-关键信息" class="headerlink" title="配置 Notion 关键信息"></a>配置 Notion 关键信息</h2><h3 id="配置流程"><a href="#配置流程" class="headerlink" title="配置流程"></a>配置流程</h3><ol><li>使用 <a href="https://1874.notion.site/09ff9e1e141744c6af0a1f69d2a3d834?v=a09065f9266446afa745b475044daca6"><strong>Database 模板</strong></a> 创建数据库副本或增加必要属性到已有 Notion 数据库<ul><li>博客平台为Hexo时，可参考<a href="https://1874.notion.site/867486af567f4a8897427b15ffd10b3c?v=a25aec8e27d5415e8605e43034f822bd"><strong>elog-hexo-template</strong></a> 创建数据库副本或增加必要属性到已有 Notion 数据库</li></ul></li><li>创建 Integration Token，具体请参考 <a href="https://developers.notion.com/docs/create-a-notion-integration#create-your-integration-in-notion"><strong>Notion 官方教程</strong></a></li><li>将复制的数据库连接到刚创建的 Integration，具体请参考 <a href="https://developers.notion.com/docs/create-a-notion-integration#give-your-integration-page-permissions"><strong>Notion 官方教程</strong></a></li><li>获取数据库 DatabaseId</li></ol><p>按照<a href="https://elog.1874.cool/notion/gvnxobqogetukays#notion">文档提示</a>配置 Notion 并获取 <code>token</code> 和 <code>databaseId</code>，在本地<code>.elog.env</code>中写入</p><figure class="highlight plaintext"><figcaption><span>text</span></figcaption><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">NOTION_TOKEN=获取的token</span><br><span class="line">NOTION_DATABASE_ID=获取的databaseId</span><br></pre></td></tr></table></figure><h3 id="配置Notion-Database"><a href="#配置Notion-Database" class="headerlink" title="配置Notion Database"></a>配置Notion Database</h3><p>示例：</p><p><img src="https://atelieryu.xyz/elog/202503/a2be76ee2e6a013e213586a7e524b722.png" alt="image.png"></p><p>Database字段：</p><ul><li><code>permalink</code>为文档的永久链接，例如<code>https://notion-hexo.vercel.app/notion-hexo/</code>，注意记得在结尾加上<code>/</code></li><li><code>categories</code>为文档的分类</li><li><code>tags</code> 为文档的标签</li><li><code>description</code>为主题配置中可选的文档描述</li></ul><h3 id="获取token"><a href="#获取token" class="headerlink" title="获取token"></a>获取token</h3><ol><li><p>登录Notion 网页版 &#x3D;&gt;访问<a href="https://www.notion.so/my-integrations">My integrations</a> &#x3D;&gt;New Integration &#x3D;&gt; 生成 Internal Integration Token</p><p> <img src="https://atelieryu.xyz/elog/202503/2a01b8634fd5897a79a9fa367d6502c2.png" alt="image.png"></p></li><li><p>Internal Intergration Secret即为token</p><p> <img src="https://atelieryu.xyz/elog/202503/35735ef273abc3537dad0072dec232f5.png" alt="image.png"></p></li><li><p>在使用的Notion Database页面点击右上角设置→Connections，选择刚才创建的Integration</p><p> <img src="https://atelieryu.xyz/elog/202503/bc53f185657c5009ec62d22aafdb14ed.png" alt="image.png"></p></li></ol><p>其他注意事项：</p><ul><li>notion文章必须有封面（cover），否则同步会失败</li></ul><h3 id="获取DatabaseId"><a href="#获取DatabaseId" class="headerlink" title="获取DatabaseId"></a>获取DatabaseId</h3><p>图中url中红框部分（1b8a0f24b32980ca9c66cbc557236562）即为DatabaseId</p><p><img src="https://atelieryu.xyz/elog/202503/3ec5b264fea657120d4bf98a8801b77c.png" alt="029401505f8359601294e9d4af319792.png"></p><h2 id="本地调试"><a href="#本地调试" class="headerlink" title="本地调试"></a>本地调试</h2><p>在项目根目录运行同步命令，Notion Database中的文章就会被全部同步值本地</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">npm run sync:local</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">或</span></span><br><span class="line">elog sync -e .elog.env</span><br></pre></td></tr></table></figure><h1 id="Elog配置CloudFlare-R2图床"><a href="#Elog配置CloudFlare-R2图床" class="headerlink" title="Elog配置CloudFlare R2图床"></a>Elog配置CloudFlare R2图床</h1><p><a href="https://github.com/LetTTGACO/elog/tree/master/plugins/plugin-img-r2#readme">官方插件文档</a></p><h2 id="安装插件"><a href="#安装插件" class="headerlink" title="安装插件"></a><strong>安装插件</strong></h2><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install @elog/plugin-img-r2</span><br></pre></td></tr></table></figure><h2 id="配置-elog-config-js"><a href="#配置-elog-config-js" class="headerlink" title="配置 elog.config.js"></a><strong>配置 elog.config.js</strong></h2><figure class="highlight javascript"><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">// elog.config.js</span></span><br><span class="line"><span class="keyword">const</span> r2 = <span class="built_in">require</span>(<span class="string">&#x27;@elog/plugin-img-r2&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">  <span class="comment">// ...省略</span></span><br><span class="line">  <span class="attr">image</span>: &#123;</span><br><span class="line">    <span class="attr">enable</span>: <span class="literal">true</span>,</span><br><span class="line">    <span class="attr">plugin</span>: r2,</span><br><span class="line">    <span class="attr">r2</span>: &#123;</span><br><span class="line">      <span class="attr">accessKeyId</span>: process.<span class="property">env</span>.<span class="property">R2_ACCESSKEYID</span>,</span><br><span class="line">      <span class="attr">secretAccessKey</span>: process.<span class="property">env</span>.<span class="property">R2_SECRET_ACCESSKEY</span>,</span><br><span class="line">      <span class="attr">bucket</span>: process.<span class="property">env</span>.<span class="property">R2_BUCKET</span>,</span><br><span class="line">      <span class="attr">endpoint</span>: process.<span class="property">env</span>.<span class="property">R2_ENDPOINT</span>,</span><br><span class="line">      <span class="attr">host</span>: process.<span class="property">env</span>.<span class="property">R2_HOST</span>,</span><br><span class="line">      <span class="attr">prefixKey</span>: <span class="string">&#x27;elog-image-plugin-test&#x27;</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><h2 id="配置-elog-env文件"><a href="#配置-elog-env文件" class="headerlink" title="配置 .elog.env文件"></a>配置 .elog.env文件</h2><p>在 .elog.env 中插入以下信息</p><figure class="highlight yaml"><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="comment"># .elog.env 配置R2 相关账号参数</span></span><br><span class="line"><span class="comment">#R2</span></span><br><span class="line"><span class="comment"># 访问密钥 ID</span></span><br><span class="line"><span class="string">R2_ACCESSKEYID=</span></span><br><span class="line"><span class="comment"># 机密访问密钥</span></span><br><span class="line"><span class="string">R2_SECRET_ACCESSKEY=</span></span><br><span class="line"><span class="string">R2_ENDPOINT=</span></span><br><span class="line"><span class="comment"># R2 需要使r2.dev子域供网络访问或者绑定自己的域名</span></span><br><span class="line"><span class="string">R2_HOST=</span></span><br><span class="line"><span class="string">R2_BUCKET=</span></span><br></pre></td></tr></table></figure><ul><li>R2_ACCESSKEYID、R2_SECRET_ACCESSKEY、R2_ENDPOINT：API令牌中查看</li><li>R2_HOST：生成的R2.dev子域 或 自己的域名</li><li>R2_BUCKET：存储桶的名称</li></ul><p><img src="https://atelieryu.xyz/elog/202503/cb3779952158c30a0a2591b0797040e9.png" alt="image.png"></p><p>令牌信息仅在创建令牌时显示一次，之后无法再次查看，如需查看只能选择轮转重新生成令牌信息。</p><p><img src="https://atelieryu.xyz/elog/202503/76cd80a3e9d22864fee2c756afaea01f.png" alt="image.png"></p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;博客工具&quot;&gt;&lt;a href=&quot;#博客工具&quot; class=&quot;headerlink&quot; title=&quot;博客工具&quot;&gt;&lt;/a&gt;博客工具&lt;/h1&gt;&lt;ul&gt;
&lt;li&gt;写作平台：&lt;a href=&quot;https://www.notion.so/&quot;&gt;Notion&lt;/a&gt;&lt;/li&gt;
&lt;l</summary>
      
    
    
    
    <category term="技术随记" scheme="https://atelieryu.site/categories/%E6%8A%80%E6%9C%AF%E9%9A%8F%E8%AE%B0/"/>
    
    
    <category term="elog" scheme="https://atelieryu.site/tags/elog/"/>
    
    <category term="notion" scheme="https://atelieryu.site/tags/notion/"/>
    
    <category term="hexo" scheme="https://atelieryu.site/tags/hexo/"/>
    
  </entry>
  
</feed>
