<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>源码分析 - 她和她的猫</title>
    <link>https://her-cat.com/categories/%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/</link>
    <description>源码分析的文章列表 - 她和她的猫</description>
    <image>
      <title>她和她的猫</title>
      <url>https://her-cat.com/assets/favorite.jpeg</url>
      <link>https://her-cat.com/assets/favorite.jpeg</link>
    </image>
    <generator>Hugo -- 0.148.1</generator>
    <language>zh</language>
    <lastBuildDate>Wed, 15 Oct 2025 23:26:52 +0800</lastBuildDate>
    <atom:link href="https://her-cat.com/categories/%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>深入 Hyperf：Inject 注解是如何工作的？</title>
      <link>https://her-cat.com/posts/2024/08/25/how-does-hyperf-inject-annotation-work/</link>
      <pubDate>Sun, 25 Aug 2024 19:17:00 +0800</pubDate>
      <guid>https://her-cat.com/posts/2024/08/25/how-does-hyperf-inject-annotation-work/</guid>
      <description>本文详细介绍了 Hyperf 框架中 Inject 注解的工作原理，并深入剖析了代理类生成、类实例化过程的拦截机制以及属性值的自动注入操作。</description>
      <content:encoded><![CDATA[<p>周五的时候，我在 Hyperf 群里看到有群友提出了一个问题：为什么 Inject 注解在使用 <code>new</code> 关键字实例化类时依然能够生效？按理说，Inject 注解不是应该只在通过容器实例化类时才会起作用吗？这个问题引发了群友们的讨论和猜测，甚至有人感叹，Inject 注解的实现简直就是魔法！</p>
<p>对于这个问题，Hyperf 的作者作出了解答：新版本的注入机制通过代理类来实现，注解之所以在 <code>new</code> 关键字下依然有效，是因为实例化的实际上是代理类，而代理类的构造函数中包含了注入操作。</p>
<p><img decoding="async" height="926" loading="lazy" sizes="(max-width: 768px) 100vw, (max-width: 1024px) 768px, 1024px" src="/posts/2024/08/25/how-does-hyperf-inject-annotation-work/hyperf-group.png" srcset="/posts/2024/08/25/how-does-hyperf-inject-annotation-work/hyperf-group_hu_f13c5ae66257d7df.png 384w, /posts/2024/08/25/how-does-hyperf-inject-annotation-work/hyperf-group_hu_17e0cb5dd103087f.png 768w, /posts/2024/08/25/how-does-hyperf-inject-annotation-work/hyperf-group_hu_4660ec7950c17cc2.png 1024w, /posts/2024/08/25/how-does-hyperf-inject-annotation-work/hyperf-group_hu_1814150f258ee944.png 1536w, /posts/2024/08/25/how-does-hyperf-inject-annotation-work/hyperf-group.png 1738w" style="max-width: 100%; height: auto; aspect-ratio: 1.8769;" width="1738"></p>
<p>然而，如果我们继续深究，还会发现一些问题：Hyperf 是否为所有类都生成了代理类？又是如何在类实例化时拦截 <code>new</code> 关键字的行为，从而实现实例化的是代理类而非原始类？在属性值注入过程中，具体都执行了哪些操作？</p>
<p>由于微信群本身不太适合深入讨论这些复杂的问题，而且考虑到「深入 Hyperf」系列已经有半年多没更新了，所以我决定撰写这篇文章，逐一解答这些问题，带大家深入探索 Inject 注解的工作原理。</p>
<h2 id="是否为所有类生成了代理类">是否为所有类生成了代理类？</h2>
<p>Hyperf 生成的所有代理类都保存在 <code>runtime/container/proxy</code> 目录中，仔细观察一下就会发现，这个目录只包含了部分原始类的代理类。</p>
<p>由此可知，<strong>Hyperf 不会为所有类生成代理类</strong>。</p>
<p>那么，Hyperf 会为哪些类生成代理类呢？答案是，所有需要被切面（Aspect）介入的类。</p>
<p>在 Hyperf 中，可以通过切面介入到任意类的任意方法的执行流程中，从而改变或加强原方法的功能，这就是 AOP（Aspect Oriented Programming）面向切面编程。切面包含了要介入的目标，以及实现对原方法的修改加强处理。这里的介入目标包含了类/方法或者注解，意味着你可以通过类名/方法名称直接指定要介入的类/方法，或者通过注解间接地指定要介入的类/方法。</p>
<p>为了实现 Inject 自动注入的功能，Hyperf 添加了一个 Inject 切面，其介入的目标是使用了 Inject 注解的类。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">InjectAspect</span> <span class="k">extends</span> <span class="nx">AbstractAspect</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">public</span> <span class="k">array</span> <span class="nv">$annotations</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="nx">Inject</span><span class="o">::</span><span class="na">class</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">public</span> <span class="k">function</span> <span class="nf">process</span><span class="p">(</span><span class="nx">ProceedingJoinPoint</span> <span class="nv">$proceedingJoinPoint</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// Do nothing, just to mark the class should be generated to the proxy classes.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="k">return</span> <span class="nv">$proceedingJoinPoint</span><span class="o">-&gt;</span><span class="na">process</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>一旦我们在某个类中使用了 Inject 注解，Hyperf 就会为这个类生成代理类。并且从注释中可以看到，Inject 切面不会修改原始方法的任何行为，只是用来标记需要为其生成代理类。</p>
<p>除了 Inject 切面以外，Hyperf 中还包含了很多其它的切面，你可以使用 <code>AspectCollector::list()</code> 获取这些切面。你也可以查看 <a href="https://hyperf.wiki/3.1/#/zh-cn/aop?id=%e5%ae%9a%e4%b9%89%e5%88%87%e9%9d%a2aspect">Hyperf 文档</a> 学习如何自定义切面，提高程序的可重用性以及开发效率。</p>
<h2 id="为什么被实例化的是代理类">为什么被实例化的是代理类？</h2>
<p>这一切的关键在于 Hyperf 巧妙地利用了 Composer 类自动加载机制，让我们来了解一下其中的细节。</p>
<p>通常情况下，使用了 Composer 的框架都会在入口文件引入一个 <code>/vendor/autoload.php</code> 文件，以启用自动加载功能。在这个文件中，Composer 会通过 <a href="https://www.php.net/manual/zh/language.oop5.autoload.php"><code>spl_autoload_register</code></a> 函数向 PHP 注册自己的类加载器（ClassLoader）。当我们在 PHP 中使用了当前内存中尚未定义的类的时候（例如使用 <code>new</code> 关键字实例化某个类），PHP 会调用已注册的类加载器，将这个类文件加载到内存中，也就实现了我们常说的自动加载。</p>
<p>在 Composer 的类加载器中，有一个 <code>classMap</code> 数组属性，其键是包含命名空间的类名，值是类的文件路径。示例如下：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="k">array</span><span class="o">:</span><span class="mi">5</span> <span class="p">[</span>  
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;Hyperf\Cache\AnnotationManager&#34;</span> <span class="o">=&gt;</span> <span class="s2">&#34;/code/project/vendor/composer/../hyperf/cache/src/AnnotationManager.php&#34;</span>  
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;Hyperf\Cache\Annotation\CacheAhead&#34;</span> <span class="o">=&gt;</span> <span class="s2">&#34;/code/project/vendor/composer/../hyperf/cache/src/Annotation/CacheAhead.php&#34;</span>  
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;Hyperf\Cache\Annotation\CacheEvict&#34;</span> <span class="o">=&gt;</span> <span class="s2">&#34;/code/project/vendor/composer/../hyperf/cache/src/Annotation/CacheEvict.php&#34;</span>  
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;Hyperf\Cache\Annotation\CachePut&#34;</span> <span class="o">=&gt;</span> <span class="s2">&#34;/code/project/vendor/composer/../hyperf/cache/src/Annotation/CachePut.php&#34;</span>  
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;Hyperf\Cache\Annotation\Cacheable&#34;</span> <span class="o">=&gt;</span> <span class="s2">&#34;/code/project/vendor/composer/../hyperf/cache/src/Annotation/Cacheable.php&#34;</span>  
</span></span><span class="line"><span class="cl"><span class="p">]</span>
</span></span></code></pre></div><p>当 Composer 的类加载器运行时，它会先检查 <code>classMap</code> 数组中是否已经存在这个类。如果类不存在，加载器将按照 PSR-4 或 PSR-0 的规范依次查找类文件；如果存在或找到了类文件，就会使用 <code>include</code> 加载这个类文件。</p>
<p><img decoding="async" height="265" loading="lazy" sizes="(max-width: 768px) 100vw, (max-width: 1024px) 768px, 1024px" src="/posts/2024/08/25/how-does-hyperf-inject-annotation-work/load-class.png" srcset="/posts/2024/08/25/how-does-hyperf-inject-annotation-work/load-class_hu_cf869feef5f0795f.png 384w, /posts/2024/08/25/how-does-hyperf-inject-annotation-work/load-class_hu_3c42fece1530c263.png 768w, /posts/2024/08/25/how-does-hyperf-inject-annotation-work/load-class_hu_e817acc78667b4ac.png 1024w" style="max-width: 100%; height: auto; aspect-ratio: 4.3509;" width="1153"></p>
<p>从整个自动加载过程可以看出，只要在使用某个类之前，将其代理类的路径添加到 <code>classMap</code> 数组中，那么当我们在 PHP 中实例化这个类时，Composer 就会直接加载代理类，而不是原始类。</p>
<p><img decoding="async" height="728" loading="lazy" sizes="(max-width: 768px) 100vw, (max-width: 1024px) 768px, 1024px" src="/posts/2024/08/25/how-does-hyperf-inject-annotation-work/load-proxy-class.png" srcset="/posts/2024/08/25/how-does-hyperf-inject-annotation-work/load-proxy-class_hu_4da8481a703f8f78.png 384w, /posts/2024/08/25/how-does-hyperf-inject-annotation-work/load-proxy-class_hu_8627556eb60b00c9.png 768w, /posts/2024/08/25/how-does-hyperf-inject-annotation-work/load-proxy-class_hu_5fc1ecd8dcf9c8ac.png 1024w, /posts/2024/08/25/how-does-hyperf-inject-annotation-work/load-proxy-class_hu_642f779870e50710.png 1536w, /posts/2024/08/25/how-does-hyperf-inject-annotation-work/load-proxy-class.png 2094w" style="max-width: 100%; height: auto; aspect-ratio: 2.8764;" width="2094"></p>
<p>实际上，Hyperf 确实是这样实现的。在生成所有代理类后，Hyperf 会将原始类与代理类的映射关系添加到 Composer 的 <code>classMap</code> 数组中：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="nv">$proxyFileDirPath</span> <span class="o">=</span> <span class="nx">BASE_PATH</span> <span class="o">.</span> <span class="s1">&#39;/runtime/container/proxy/&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="nv">$composerLoader</span> <span class="o">=</span> <span class="nx">Composer</span><span class="o">::</span><span class="na">getLoader</span><span class="p">();</span>    
</span></span><span class="line"><span class="cl">  
</span></span><span class="line"><span class="cl"><span class="nv">$scanner</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Scanner</span><span class="p">(</span><span class="nv">$config</span><span class="p">,</span> <span class="nv">$handler</span><span class="p">);</span>  
</span></span><span class="line"><span class="cl"><span class="nv">$composerLoader</span><span class="o">-&gt;</span><span class="na">addClassMap</span><span class="p">(</span> 
</span></span><span class="line"><span class="cl">	<span class="c1">// 在 scan 方法中完成了扫描与生成代理类
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nv">$scanner</span><span class="o">-&gt;</span><span class="na">scan</span><span class="p">(</span><span class="nv">$composerLoader</span><span class="o">-&gt;</span><span class="na">getClassMap</span><span class="p">(),</span> <span class="nv">$proxyFileDirPath</span><span class="p">)</span>  
</span></span><span class="line"><span class="cl"><span class="p">);</span>
</span></span></code></pre></div><p>因此，当你在 Hyperf 中使用那些已经生成了代理类的类时，加载的就是 <code>/runtime/container/proxy</code> 目录下的代理类，而非原始类。</p>
<h2 id="inject-自动注入的过程">Inject 自动注入的过程</h2>
<p>属性值的注入操作是在代理类的构造函数中完成的，因此我们需要通过对比原始类与代理类的内容来进一步分析。</p>
<p><img decoding="async" height="992" loading="lazy" sizes="(max-width: 768px) 100vw, (max-width: 1024px) 768px, 1024px" src="/posts/2024/08/25/how-does-hyperf-inject-annotation-work/original-class-vs-proxy-class.png" srcset="/posts/2024/08/25/how-does-hyperf-inject-annotation-work/original-class-vs-proxy-class_hu_85fb85801f03e7e6.png 384w, /posts/2024/08/25/how-does-hyperf-inject-annotation-work/original-class-vs-proxy-class_hu_d438b1e913b8e37b.png 768w, /posts/2024/08/25/how-does-hyperf-inject-annotation-work/original-class-vs-proxy-class_hu_75b88c3dd3e490b4.png 1024w, /posts/2024/08/25/how-does-hyperf-inject-annotation-work/original-class-vs-proxy-class_hu_9beddd6691487bf0.png 1536w, /posts/2024/08/25/how-does-hyperf-inject-annotation-work/original-class-vs-proxy-class.png 2102w" style="max-width: 100%; height: auto; aspect-ratio: 2.1190;" width="2102"></p>
<p>从图片中可以看出，代理类相较于原始类增加了以下内容：</p>
<ul>
<li>引入了 <code>ProxyTrait</code> 和 <code>PropertyHandlerTrait</code></li>
<li>在构造函数中调用了 Trait 中的 <code>__handlePropertyHandler</code> 方法</li>
<li>将原始类 <code>user</code> 方法的内容封装为匿名函数，并作为 <code>self::__proxyCall</code> 方法的参数</li>
</ul>
<p>其中，<code>ProxyTrait</code> 和 <code>self::__proxyCall</code> 是 AOP 功能的核心部分。由于 Inject 切面并不会修改原始方法的行为，我们可以暂时忽略这部分内容，专注于属性值注入的过程。</p>
<p>当我们实例化某个类时，PHP 会自动调用这个类的构造函数。而构造函数中的 <code>__handlePropertyHandler</code> 方法也会随之被调用。由于类里面不仅仅包含当前类的属性，还可能包含 Trait 和继承自父类的属性，因此 <code>__handlePropertyHandler</code> 方法会通过 <code>PropertyHandlerTrait</code> 中的 <code>__handle</code> 方法，依次为当前类、Trait 以及父类的属性完成注入操作。</p>
<p>在 <code>__handle</code> 方法中，Hyperf 会遍历提供的属性列表，并根据属性上的 Inject 注解，从 <code>PropertyHandlerManager</code> 中找到相应的回调函数并调用。</p>
<p>Inject 注解的回调函数是在 Hyperf 启动时注册的，该函数会通过属性的类型名称（即类名）从容器中获取到相应的实例，然后通过反射将实例设置为该属性的值，从而完成注入。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="nv">$reflectionProperty</span> <span class="o">=</span> <span class="nx">ReflectionManager</span><span class="o">::</span><span class="na">reflectProperty</span><span class="p">(</span><span class="nv">$currentClassName</span><span class="p">,</span> <span class="nv">$property</span><span class="p">);</span>  
</span></span><span class="line"><span class="cl"><span class="nv">$reflectionProperty</span><span class="o">-&gt;</span><span class="na">setAccessible</span><span class="p">(</span><span class="k">true</span><span class="p">);</span>  
</span></span><span class="line"><span class="cl"><span class="nv">$container</span> <span class="o">=</span> <span class="nx">ApplicationContext</span><span class="o">::</span><span class="na">getContainer</span><span class="p">();</span>  
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="p">(</span><span class="nv">$container</span><span class="o">-&gt;</span><span class="na">has</span><span class="p">(</span><span class="nv">$annotation</span><span class="o">-&gt;</span><span class="na">value</span><span class="p">))</span> <span class="p">{</span>  
</span></span><span class="line"><span class="cl">    <span class="nv">$reflectionProperty</span><span class="o">-&gt;</span><span class="na">setValue</span><span class="p">(</span><span class="nv">$object</span><span class="p">,</span> <span class="nv">$container</span><span class="o">-&gt;</span><span class="na">get</span><span class="p">(</span><span class="nv">$annotation</span><span class="o">-&gt;</span><span class="na">value</span><span class="p">));</span>  
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="k">elseif</span> <span class="p">(</span><span class="nv">$annotation</span><span class="o">-&gt;</span><span class="na">required</span><span class="p">)</span> <span class="p">{</span>  
</span></span><span class="line"><span class="cl">    <span class="k">throw</span> <span class="k">new</span> <span class="nx">NotFoundException</span><span class="p">(</span><span class="s2">&#34;No entry or class found for &#39;</span><span class="si">{</span><span class="nv">$annotation</span><span class="o">-&gt;</span><span class="na">value</span><span class="si">}</span><span class="s2">&#39;&#34;</span><span class="p">);</span>  
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>当类实例化完成后，类里面所有使用了 Inject 注解的属性，也就完成了属性值的自动注入。</p>
<h2 id="总结">总结</h2>
<p>以上就是关于 Inject 的全部内容了，希望这篇文章能够帮助你更好地理解和使用这些组件。</p>
]]></content:encoded>
    </item>
    <item>
      <title>深入 Hyperf：HTTP 服务是如何处理请求的？</title>
      <link>https://her-cat.com/posts/2023/12/30/how-does-hyperf-http-server-handle-requests/</link>
      <pubDate>Sat, 30 Dec 2023 22:06:00 +0800</pubDate>
      <guid>https://her-cat.com/posts/2023/12/30/how-does-hyperf-http-server-handle-requests/</guid>
      <description>本文详细介绍了 Hyperf 框架中 HTTP 服务处理请求的整个过程。</description>
      <content:encoded><![CDATA[<p>上一篇文章介绍了 <a href="https://her-cat.com/posts/2023/05/15/what-happens-when-hyperf-http-server-starts/">HTTP 服务启动时发生了什么</a>，让我们对于 Hyperf 框架以及 HTTP 服务的启动过程有了更进一步的理解。今天我们继续来了解一下，当我们访问 HTTP 服务时，它是如何处理请求并返回响应内容的。</p>
<p>在 HTTP 服务启动时，Hyperf 会向 Swoole 注册请求事件处理函数。当收到 HTTP 请求时，Swoole 会调用该函数。</p>
<p>在 HTTP 服务启动后，当我们向 HTTP 服务发送请求时，HTTP 请求会被发送到 Swoole 中。Swoole 会将 HTTP 报文解析成 HTTP 请求对象，并构造出一个 HTTP 响应对象。</p>
<p>接着，Swoole 会调用请求事件处理函数，将请求和响应对象作为参数传递给该函数。在该函数中完成对请求的处理，并调用响应对象发送响应内容。</p>
<p><img decoding="async" height="802" loading="lazy" sizes="(max-width: 768px) 100vw, (max-width: 1024px) 768px, 1024px" src="/posts/2023/12/30/how-does-hyperf-http-server-handle-requests/hyperf-http-server-handle-requests.png" srcset="/posts/2023/12/30/how-does-hyperf-http-server-handle-requests/hyperf-http-server-handle-requests_hu_ab3a5f95ef7d76d6.png 384w, /posts/2023/12/30/how-does-hyperf-http-server-handle-requests/hyperf-http-server-handle-requests_hu_708848c23e4f21f2.png 768w" style="max-width: 100%; height: auto; aspect-ratio: 1.0711;" width="859"></p>
<p>在这篇文章中，我们将分三个部分介绍 Hyperf 中 HTTP 服务处理请求的过程，在第一部分将会介绍如何注册 HTTP 服务的请求事件处理函数，第二部分会介绍如何处理 HTTP 请求并发送响应内容。</p>
<h2 id="注册请求事件处理函数">注册请求事件处理函数</h2>
<p>在 HTTP 服务启动时，Hyperf 需要向 Swoole 注册请求事件处理函数，那么在 Hyperf 中这个处理函数是什么呢？</p>
<p>在 HTTP 服务「读取服务配置」阶段，会从 <code>config/autoload/server.php</code> 配置文件中获取配置信息，其中 <code>Event::ON_REQUEST</code> 是请求事件的枚举值，<code>Hyperf\HttpServer\Server::onRequest()</code> 就是请求事件的处理函数。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="k">return</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="o">...</span>
</span></span><span class="line"><span class="cl">    <span class="s1">&#39;servers&#39;</span> <span class="o">=&gt;</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="p">[</span>
</span></span><span class="line"><span class="cl">            <span class="o">...</span>
</span></span><span class="line"><span class="cl">            <span class="s1">&#39;callbacks&#39;</span> <span class="o">=&gt;</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">                <span class="nx">Event</span><span class="o">::</span><span class="na">ON_REQUEST</span> <span class="o">=&gt;</span> <span class="p">[</span><span class="nx">Hyperf\HttpServer\Server</span><span class="o">::</span><span class="na">class</span><span class="p">,</span> <span class="s1">&#39;onRequest&#39;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">            <span class="p">],</span>
</span></span><span class="line"><span class="cl">        <span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="p">]</span>
</span></span></code></pre></div><p>在「初始化 HTTP 服务」阶段，将请求事件处理函数注册到 Swoole 中，这部分工作的主要内容如下所示：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="k">foreach</span> <span class="p">(</span><span class="nv">$events</span> <span class="k">as</span> <span class="nv">$event</span> <span class="o">=&gt;</span> <span class="nv">$callback</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="o">...</span>
</span></span><span class="line"><span class="cl">    <span class="p">[</span><span class="nv">$className</span><span class="p">,</span> <span class="nv">$method</span><span class="p">]</span> <span class="o">=</span> <span class="nv">$callback</span><span class="p">;</span>    
</span></span><span class="line"><span class="cl">    <span class="nv">$class</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-&gt;</span><span class="na">container</span><span class="o">-&gt;</span><span class="na">get</span><span class="p">(</span><span class="nv">$className</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nx">method_exists</span><span class="p">(</span><span class="nv">$class</span><span class="p">,</span> <span class="s1">&#39;setServerName&#39;</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// 设置服务名称
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="nv">$class</span><span class="o">-&gt;</span><span class="na">setServerName</span><span class="p">(</span><span class="nv">$serverName</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>    <span class="c1">// 初始化核心中间件
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">if</span> <span class="p">(</span><span class="nv">$class</span> <span class="nx">instanceof</span> <span class="nx">MiddlewareInitializerInterface</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nv">$class</span><span class="o">-&gt;</span><span class="na">initCoreMiddleware</span><span class="p">(</span><span class="nv">$serverName</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="o">...</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// 注册事件处理函数
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nv">$server</span><span class="o">-&gt;</span><span class="na">on</span><span class="p">(</span><span class="nv">$event</span><span class="p">,</span> <span class="nv">$callback</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>在 <code>initCoreMiddleware()</code> 方法中，初始化了 HTTP 服务的路由信息，并且通过服务名称获取该 HTTP 服务的中间件以及异常处理器。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="k">public</span> <span class="k">function</span> <span class="nf">initCoreMiddleware</span><span class="p">(</span><span class="nx">string</span> <span class="nv">$serverName</span><span class="p">)</span><span class="o">:</span> <span class="nx">void</span>  <span class="p">{</span>    
</span></span><span class="line"><span class="cl">    <span class="nv">$this</span><span class="o">-&gt;</span><span class="na">serverName</span> <span class="o">=</span> <span class="nv">$serverName</span><span class="p">;</span>  
</span></span><span class="line"><span class="cl">    <span class="nv">$this</span><span class="o">-&gt;</span><span class="na">coreMiddleware</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-&gt;</span><span class="na">createCoreMiddleware</span><span class="p">();</span>    
</span></span><span class="line"><span class="cl">    <span class="nv">$config</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-&gt;</span><span class="na">container</span><span class="o">-&gt;</span><span class="na">get</span><span class="p">(</span><span class="nx">ConfigInterface</span><span class="o">::</span><span class="na">class</span><span class="p">);</span>    
</span></span><span class="line"><span class="cl">    <span class="nv">$this</span><span class="o">-&gt;</span><span class="na">middlewares</span> <span class="o">=</span> <span class="nv">$config</span><span class="o">-&gt;</span><span class="na">get</span><span class="p">(</span><span class="s1">&#39;middlewares.&#39;</span> <span class="o">.</span> <span class="nv">$serverName</span><span class="p">,</span> <span class="p">[]);</span>    
</span></span><span class="line"><span class="cl">    <span class="nv">$this</span><span class="o">-&gt;</span><span class="na">exceptionHandlers</span> <span class="o">=</span> <span class="nv">$config</span><span class="o">-&gt;</span><span class="na">get</span><span class="p">(</span><span class="s1">&#39;exceptions.handler.&#39;</span> <span class="o">.</span> <span class="nv">$serverName</span><span class="p">,</span> <span class="nv">$this</span><span class="o">-&gt;</span><span class="na">getDefaultExceptionHandler</span><span class="p">());</span>  
</span></span><span class="line"><span class="cl"><span class="p">}</span>  
</span></span></code></pre></div><h2 id="处理-http-请求">处理 HTTP 请求</h2>
<p>通过上面的内容我们可以知道，处理 HTTP 请求的逻辑是在 <code>Hyperf\HttpServer\Server::onRequest()</code> 方法中完成的，该方法的原型如下：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="k">public</span> <span class="k">function</span> <span class="nf">onRequest</span><span class="p">(</span><span class="nv">$request</span><span class="p">,</span> <span class="nv">$response</span><span class="p">)</span><span class="o">:</span> <span class="nx">void</span>  
</span></span></code></pre></div><p>对于 <code>Hyperf\HttpServer\Server::onRequest()</code> 方法来说，我把它执行的工作分成了以下几个阶段。</p>
<h3 id="初始化请求和响应对象">初始化请求和响应对象</h3>
<p>在这一阶段中，需要初始化 PSR-7 请求和响应对象。这是因为，Hyperf 的标准组件都是基于 PSR 标准实现的，而底层框架可能并没有基于 PSR 标准实现，因此需要先进行兼容性适配。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="p">[</span><span class="nv">$psr7Request</span><span class="p">,</span> <span class="nv">$psr7Response</span><span class="p">]</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-&gt;</span><span class="na">initRequestAndResponse</span><span class="p">(</span><span class="nv">$request</span><span class="p">,</span> <span class="nv">$response</span><span class="p">);</span>  
</span></span></code></pre></div><p>在 <code>initRequestAndResponse()</code> 方法中，先判断对象是否基于 PSR 标准，如果不是，则将其转换成 PSR-7 请求和响应对象。</p>
<h3 id="匹配请求的路由信息">匹配请求的路由信息</h3>
<p>在这一阶段中，调用 <code>Hyperf\HttpServer\CoreMiddleware::dispatch()</code> 方法，使用上面初始化好的 PSR-7 请求对象匹配路由信息。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="nv">$psr7Request</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-&gt;</span><span class="na">coreMiddleware</span><span class="o">-&gt;</span><span class="na">dispatch</span><span class="p">(</span><span class="nv">$psr7Request</span><span class="p">);</span>  
</span></span></code></pre></div><p>在 <code>dispatch()</code> 方法中，使用请求对象的请求方式、请求地址匹配 HTTP 服务中的路由信息，将匹配结果转换成 <code>Hyperf\HttpServer\Router\Dispatched</code> 对象，并保存到新的请求对象的属性中并返回。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="k">public</span> <span class="k">function</span> <span class="nf">dispatch</span><span class="p">(</span><span class="nx">ServerRequestInterface</span> <span class="nv">$request</span><span class="p">)</span><span class="o">:</span> <span class="nx">ServerRequestInterface</span>  
</span></span><span class="line"><span class="cl"><span class="p">{</span>  
</span></span><span class="line"><span class="cl">    <span class="nv">$routes</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-&gt;</span><span class="na">dispatcher</span><span class="o">-&gt;</span><span class="na">dispatch</span><span class="p">(</span><span class="nv">$request</span><span class="o">-&gt;</span><span class="na">getMethod</span><span class="p">(),</span> <span class="nv">$request</span><span class="o">-&gt;</span><span class="na">getUri</span><span class="p">()</span><span class="o">-&gt;</span><span class="na">getPath</span><span class="p">());</span>  
</span></span><span class="line"><span class="cl">  
</span></span><span class="line"><span class="cl">    <span class="nv">$dispatched</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Dispatched</span><span class="p">(</span><span class="nv">$routes</span><span class="p">);</span>  
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nx">Context</span><span class="o">::</span><span class="na">set</span><span class="p">(</span><span class="nx">ServerRequestInterface</span><span class="o">::</span><span class="na">class</span><span class="p">,</span> <span class="nv">$request</span><span class="o">-&gt;</span><span class="na">withAttribute</span><span class="p">(</span><span class="nx">Dispatched</span><span class="o">::</span><span class="na">class</span><span class="p">,</span> <span class="nv">$dispatched</span><span class="p">));</span>  
</span></span><span class="line"><span class="cl"><span class="p">}</span>  
</span></span></code></pre></div><p>看到这里，你可能会有疑问，为什么不将 <code>Dispatched</code> 直接保存到原来的请求对象的属性中？</p>
<p>因为这是 PSR-7 标准规定的。在 PSR-7 标准中，请求被认为是不可变的；必须实现所有可能更改状态的方法，以便它们保留当前请求的内部状态，并返回包含更改状态的实例。</p>
<p>也就是说，如果你想修改请求对象中的信息，那么你必须从当前对象中克隆一个新的对象，然后在新的对象中进行修改并返回新的对象。下面是 <code>withAttribute</code> 方法的实现：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="k">public</span> <span class="k">function</span> <span class="nf">withAttribute</span><span class="p">(</span><span class="nv">$name</span><span class="p">,</span> <span class="nv">$value</span><span class="p">)</span>  
</span></span><span class="line"><span class="cl"><span class="p">{</span>  
</span></span><span class="line"><span class="cl">    <span class="nv">$clone</span> <span class="o">=</span> <span class="k">clone</span> <span class="nv">$this</span><span class="p">;</span>  
</span></span><span class="line"><span class="cl">    <span class="nv">$clone</span><span class="o">-&gt;</span><span class="na">attributes</span><span class="p">[</span><span class="nv">$name</span><span class="p">]</span> <span class="o">=</span> <span class="nv">$value</span><span class="p">;</span>  
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nv">$clone</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>  
</span></span></code></pre></div><p>知道了这一点之后，相信你以后再也不会有「为什么我设置了属性但是却拿不到值」这种疑惑了。</p>
<h3 id="准备全局和路由中间件">准备全局和路由中间件</h3>
<p>在 HTTP 服务中，中间件根据作用范围分为两种：全局中间件和路由中间件。</p>
<p>全局中间件会被应用到所有路由上，而路由中间件仅应用到部分路由上，是否应用需要根据路由匹配结果来决定。如果匹配到了路由，则从中间件管理器中获取该路由的中间件。否则，仅使用全局中间件。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="nv">$dispatched</span> <span class="o">=</span> <span class="nv">$psr7Request</span><span class="o">-&gt;</span><span class="na">getAttribute</span><span class="p">(</span><span class="nx">Dispatched</span><span class="o">::</span><span class="na">class</span><span class="p">);</span>  
</span></span><span class="line"><span class="cl"><span class="c1">// 获取全局中间件  
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nv">$middlewares</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-&gt;</span><span class="na">middlewares</span><span class="p">;</span>  
</span></span><span class="line"><span class="cl"><span class="c1">// 判断是否匹配到了路由  
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">if</span> <span class="p">(</span><span class="nv">$dispatched</span><span class="o">-&gt;</span><span class="na">isFound</span><span class="p">())</span> <span class="p">{</span>  
</span></span><span class="line"><span class="cl">    <span class="c1">// 通过服务名称、请求地址、请求方式获取路由中间件  
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nv">$registeredMiddlewares</span> <span class="o">=</span> <span class="nx">MiddlewareManager</span><span class="o">::</span><span class="na">get</span><span class="p">(</span><span class="nv">$this</span><span class="o">-&gt;</span><span class="na">serverName</span><span class="p">,</span> <span class="nv">$dispatched</span><span class="o">-&gt;</span><span class="na">handler</span><span class="o">-&gt;</span><span class="na">route</span><span class="p">,</span> <span class="nv">$psr7Request</span><span class="o">-&gt;</span><span class="na">getMethod</span><span class="p">());</span>  
</span></span><span class="line"><span class="cl">    <span class="nv">$middlewares</span> <span class="o">=</span> <span class="nx">array_merge</span><span class="p">(</span><span class="nv">$middlewares</span><span class="p">,</span> <span class="nv">$registeredMiddlewares</span><span class="p">);</span>  
</span></span><span class="line"><span class="cl"><span class="p">}</span>  
</span></span></code></pre></div><p>在一切准备就绪后，Hyperf 会调用 <code>Hyperf\Dispatcher\HttpDispatcher::dispatch()</code> 方法，将请求对象依次交给每个中间件进行处理，然后调用核心中间件的 <code>Hyperf\HttpServer\CoreMiddleware::process()</code> 方法进行最终处理。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="nv">$psr7Response</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-&gt;</span><span class="na">dispatcher</span><span class="o">-&gt;</span><span class="na">dispatch</span><span class="p">(</span><span class="nv">$psr7Request</span><span class="p">,</span> <span class="nv">$middlewares</span><span class="p">,</span> <span class="nv">$this</span><span class="o">-&gt;</span><span class="na">coreMiddleware</span><span class="p">);</span>  
</span></span></code></pre></div><h3 id="执行路由处理函数">执行路由处理函数</h3>
<p>在 <code>Hyperf\HttpServer\CoreMiddleware::process()</code> 方法中，主要逻辑是根据 <code>Dispatcher</code> 对象的状态执行相应的动作。</p>
<ul>
<li>如果路由不存在，则抛出 <code>NotFoundHttpException</code> 异常。</li>
<li>如果请求方式不正确，则抛出 <code>MethodNotAllowedHttpException</code> 异常。</li>
<li>如果找到路由，则解析并执行绑定在该路由上的处理函数，然后返回响应对象。</li>
</ul>
<p>以下代码展示了这部分的执行逻辑。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="k">public</span> <span class="k">function</span> <span class="nf">process</span><span class="p">(</span><span class="nx">ServerRequestInterface</span> <span class="nv">$request</span><span class="p">,</span> <span class="nx">RequestHandlerInterface</span> <span class="nv">$handler</span><span class="p">)</span><span class="o">:</span> <span class="nx">ResponseInterface</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="o">...</span>
</span></span><span class="line"><span class="cl">    <span class="sd">/** @var Dispatched $dispatched */</span>
</span></span><span class="line"><span class="cl">    <span class="nv">$dispatched</span> <span class="o">=</span> <span class="nv">$request</span><span class="o">-&gt;</span><span class="na">getAttribute</span><span class="p">(</span><span class="nx">Dispatched</span><span class="o">::</span><span class="na">class</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// 根据 `Dispatcher` 对象的状态执行相应动作
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nv">$response</span> <span class="o">=</span> <span class="nx">match</span> <span class="p">(</span><span class="nv">$dispatched</span><span class="o">-&gt;</span><span class="na">status</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">Dispatcher</span><span class="o">::</span><span class="na">NOT_FOUND</span> <span class="o">=&gt;</span> <span class="nv">$this</span><span class="o">-&gt;</span><span class="na">handleNotFound</span><span class="p">(</span><span class="nv">$request</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="nx">Dispatcher</span><span class="o">::</span><span class="na">METHOD_NOT_ALLOWED</span> <span class="o">=&gt;</span> <span class="nv">$this</span><span class="o">-&gt;</span><span class="na">handleMethodNotAllowed</span><span class="p">(</span><span class="nv">$dispatched</span><span class="o">-&gt;</span><span class="na">params</span><span class="p">,</span> <span class="nv">$request</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="nx">Dispatcher</span><span class="o">::</span><span class="na">FOUND</span> <span class="o">=&gt;</span> <span class="nv">$this</span><span class="o">-&gt;</span><span class="na">handleFound</span><span class="p">(</span><span class="nv">$dispatched</span><span class="p">,</span> <span class="nv">$request</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="k">default</span> <span class="o">=&gt;</span> <span class="k">null</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Hyperf 支持闭包和请求处理器两种方式设置路由的处理函数，以下是这两种方式的示例。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="c1">// 闭包  
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nx">Router</span><span class="o">::</span><span class="na">get</span><span class="p">(</span><span class="s1">&#39;/hello-hyperf&#39;</span><span class="p">,</span> <span class="k">function</span> <span class="p">()</span> <span class="p">{</span>  
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="s1">&#39;Hello Hyperf.&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>  
</span></span><span class="line"><span class="cl">  
</span></span><span class="line"><span class="cl"><span class="c1">// 请求处理器，下面三种方式的任意一种都可以达到同样的效果  
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nx">Router</span><span class="o">::</span><span class="na">get</span><span class="p">(</span><span class="s1">&#39;/hello-hyperf&#39;</span><span class="p">,</span> <span class="s1">&#39;App\Controller\IndexController::hello&#39;</span><span class="p">);</span>  
</span></span><span class="line"><span class="cl"><span class="nx">Router</span><span class="o">::</span><span class="na">get</span><span class="p">(</span><span class="s1">&#39;/hello-hyperf&#39;</span><span class="p">,</span> <span class="s1">&#39;App\Controller\IndexController@hello&#39;</span><span class="p">);</span>  
</span></span><span class="line"><span class="cl"><span class="nx">Router</span><span class="o">::</span><span class="na">get</span><span class="p">(</span><span class="s1">&#39;/hello-hyperf&#39;</span><span class="p">,</span> <span class="p">[</span><span class="nx">App\Controller\IndexController</span><span class="o">::</span><span class="na">class</span><span class="p">,</span> <span class="s1">&#39;hello&#39;</span><span class="p">]);</span>  
</span></span></code></pre></div><p>在 <code>handleFound()</code> 方法中，如果处理函数是闭包，则调用 <code>parseClosureParameters()</code> 方法解析闭包的参数，然后运行闭包。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="k">if</span> <span class="p">(</span><span class="nv">$dispatched</span><span class="o">-&gt;</span><span class="na">handler</span><span class="o">-&gt;</span><span class="na">callback</span> <span class="nx">instanceof</span> <span class="nx">Closure</span><span class="p">)</span> <span class="p">{</span>    
</span></span><span class="line"><span class="cl">    <span class="nv">$parameters</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-&gt;</span><span class="na">parseClosureParameters</span><span class="p">(</span><span class="nv">$dispatched</span><span class="o">-&gt;</span><span class="na">handler</span><span class="o">-&gt;</span><span class="na">callback</span><span class="p">,</span> <span class="nv">$dispatched</span><span class="o">-&gt;</span><span class="na">params</span><span class="p">);</span>    
</span></span><span class="line"><span class="cl">    <span class="nv">$callback</span> <span class="o">=</span> <span class="nv">$dispatched</span><span class="o">-&gt;</span><span class="na">handler</span><span class="o">-&gt;</span><span class="na">callback</span><span class="p">;</span>    
</span></span><span class="line"><span class="cl">    <span class="nv">$response</span> <span class="o">=</span> <span class="nv">$callback</span><span class="p">(</span><span class="o">...</span><span class="nv">$parameters</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>  
</span></span></code></pre></div><p>如果处理函数是请求处理器，则调用 <code>prepareHandler()</code> 方法解析出请求处理器中的控制器（Controller）和操作（Action），通过容器实例化出控制器对象，然后调用 <code>parseMethodParameters()</code> 方法解析操作方法的参数，最后运行该方法。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="p">[</span><span class="nv">$controller</span><span class="p">,</span> <span class="nv">$action</span><span class="p">]</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-&gt;</span><span class="na">prepareHandler</span><span class="p">(</span><span class="nv">$dispatched</span><span class="o">-&gt;</span><span class="na">handler</span><span class="o">-&gt;</span><span class="na">callback</span><span class="p">);</span>  
</span></span><span class="line"><span class="cl"><span class="nv">$controllerInstance</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-&gt;</span><span class="na">container</span><span class="o">-&gt;</span><span class="na">get</span><span class="p">(</span><span class="nv">$controller</span><span class="p">);</span>  
</span></span><span class="line"><span class="cl"><span class="o">...</span>  
</span></span><span class="line"><span class="cl"><span class="nv">$parameters</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-&gt;</span><span class="na">parseMethodParameters</span><span class="p">(</span><span class="nv">$controller</span><span class="p">,</span> <span class="nv">$action</span><span class="p">,</span> <span class="nv">$dispatched</span><span class="o">-&gt;</span><span class="na">params</span><span class="p">);</span>  
</span></span><span class="line"><span class="cl"><span class="nv">$response</span> <span class="o">=</span> <span class="nv">$controllerInstance</span><span class="o">-&gt;</span><span class="p">{</span><span class="nv">$action</span><span class="p">}(</span><span class="o">...</span><span class="nv">$parameters</span><span class="p">);</span>  
</span></span></code></pre></div><p>处理函数执行完成后，如果返回的结果没有实现 <code>ResponseInterface</code> 接口，则调用 <code>transferToResponse()</code> 方法对其进行转换，最后返回响应对象。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="k">if</span> <span class="p">(</span><span class="o">!</span> <span class="nv">$response</span> <span class="nx">instanceof</span> <span class="nx">ResponseInterface</span><span class="p">)</span> <span class="p">{</span>  
</span></span><span class="line"><span class="cl">    <span class="nv">$response</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-&gt;</span><span class="na">transferToResponse</span><span class="p">(</span><span class="nv">$response</span><span class="p">,</span> <span class="nv">$request</span><span class="p">);</span>  
</span></span><span class="line"><span class="cl"><span class="p">}</span>  
</span></span><span class="line"><span class="cl"><span class="k">return</span> <span class="nv">$response</span><span class="o">-&gt;</span><span class="na">withAddedHeader</span><span class="p">(</span><span class="s1">&#39;Server&#39;</span><span class="p">,</span> <span class="s1">&#39;Hyperf&#39;</span><span class="p">);</span>  
</span></span></code></pre></div><h3 id="发送响应内容">发送响应内容</h3>
<p>有了响应对象之后，就需要将响应内容发送给客户端，这部分工作通过调用 <code>Hyperf\HttpServer\ResponseEmitter::emit()</code> 方法完成。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="c1">// Send the Response to client.  
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">if</span> <span class="p">(</span><span class="o">!</span> <span class="nx">isset</span><span class="p">(</span><span class="nv">$psr7Response</span><span class="p">)</span> <span class="o">||</span> <span class="o">!</span> <span class="nv">$psr7Response</span> <span class="nx">instanceof</span> <span class="nx">ResponseInterface</span><span class="p">)</span> <span class="p">{</span>  
</span></span><span class="line"><span class="cl">    <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>  
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="p">(</span><span class="nx">isset</span><span class="p">(</span><span class="nv">$psr7Request</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="nv">$psr7Request</span><span class="o">-&gt;</span><span class="na">getMethod</span><span class="p">()</span> <span class="o">===</span> <span class="s1">&#39;HEAD&#39;</span><span class="p">)</span> <span class="p">{</span>  
</span></span><span class="line"><span class="cl">    <span class="nv">$this</span><span class="o">-&gt;</span><span class="na">responseEmitter</span><span class="o">-&gt;</span><span class="na">emit</span><span class="p">(</span><span class="nv">$psr7Response</span><span class="p">,</span> <span class="nv">$response</span><span class="p">,</span> <span class="k">false</span><span class="p">);</span>  
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="k">else</span> <span class="p">{</span>  
</span></span><span class="line"><span class="cl">    <span class="nv">$this</span><span class="o">-&gt;</span><span class="na">responseEmitter</span><span class="o">-&gt;</span><span class="na">emit</span><span class="p">(</span><span class="nv">$psr7Response</span><span class="p">,</span> <span class="nv">$response</span><span class="p">);</span>  
</span></span><span class="line"><span class="cl"><span class="p">}</span>  
</span></span></code></pre></div><p>在 <code>emit()</code> 方法中，将 PSR-7 响应对象中的响应头、Cookies 以及状态码等信息写入到 Swoole 的响应对象中。然后判断响应体是否为文件对象，如果是则调用 <code>Swoole\Http\Response::sendfile()</code> 方法将文件发送到客户端；否则调用 <code>Swoole\Http\Response::end()</code> 方法发送响应内容。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="k">public</span> <span class="k">function</span> <span class="nf">emit</span><span class="p">(</span><span class="nx">ResponseInterface</span> <span class="nv">$response</span><span class="p">,</span> <span class="nx">mixed</span> <span class="nv">$connection</span><span class="p">,</span> <span class="nx">bool</span> <span class="nv">$withContent</span> <span class="o">=</span> <span class="k">true</span><span class="p">)</span><span class="o">:</span> <span class="nx">void</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">try</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="nx">strtolower</span><span class="p">(</span><span class="nv">$connection</span><span class="o">-&gt;</span><span class="na">header</span><span class="p">[</span><span class="s1">&#39;Upgrade&#39;</span><span class="p">]</span> <span class="o">??</span> <span class="s1">&#39;&#39;</span><span class="p">)</span> <span class="o">===</span> <span class="s1">&#39;websocket&#39;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// 将 PSR-7 响应对象的信息写入到 Swoole 的响应对象中
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="nv">$this</span><span class="o">-&gt;</span><span class="na">buildSwooleResponse</span><span class="p">(</span><span class="nv">$connection</span><span class="p">,</span> <span class="nv">$response</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// 判断响应内容是否为文件
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="nv">$content</span> <span class="o">=</span> <span class="nv">$response</span><span class="o">-&gt;</span><span class="na">getBody</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="nv">$content</span> <span class="nx">instanceof</span> <span class="nx">FileInterface</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="c1">// 发送文件到客户端
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>            <span class="nv">$connection</span><span class="o">-&gt;</span><span class="na">sendfile</span><span class="p">(</span><span class="nv">$content</span><span class="o">-&gt;</span><span class="na">getFilename</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// 发送响应内容
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="k">if</span> <span class="p">(</span><span class="nv">$withContent</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nv">$connection</span><span class="o">-&gt;</span><span class="na">end</span><span class="p">((</span><span class="nx">string</span><span class="p">)</span> <span class="nv">$content</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nv">$connection</span><span class="o">-&gt;</span><span class="na">end</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">Throwable</span> <span class="nv">$exception</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nv">$this</span><span class="o">-&gt;</span><span class="na">logger</span><span class="o">?-&gt;</span><span class="na">critical</span><span class="p">((</span><span class="nx">string</span><span class="p">)</span> <span class="nv">$exception</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>最后，我们就能够在浏览器中看到响应内容了。</p>
<h2 id="总结">总结</h2>
<p>在这篇文章中，我们了解了 Hyperf 处理 HTTP 请求的完整流程。希望这些内容能帮你更好地理解 Hyperf 的工作原理。</p>
]]></content:encoded>
    </item>
    <item>
      <title>深入 Hyperf：HTTP 服务启动时发生了什么？</title>
      <link>https://her-cat.com/posts/2023/05/15/what-happens-when-hyperf-http-server-starts/</link>
      <pubDate>Mon, 15 May 2023 23:34:06 +0800</pubDate>
      <guid>https://her-cat.com/posts/2023/05/15/what-happens-when-hyperf-http-server-starts/</guid>
      <description>在这篇文章中，我会从启动命令开始，给你介绍下 Hyperf 的 HTTP 服务是如何完成初始化并启动的。</description>
      <content:encoded><![CDATA[<p>当我们创建 Hyperf 项目之后，只需要在终端执行 <code>php bin/hyperf.php start</code> 启动命令，等上几秒钟，就可以看到终端输出的 Worker 进程已启动，HTTP 服务监听在 9501 端口的日志信息。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">INFO</span><span class="p">]</span> <span class="nx">Worker</span><span class="c1">#3 started.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">[</span><span class="nx">INFO</span><span class="p">]</span> <span class="nx">Worker</span><span class="c1">#1 started.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">[</span><span class="nx">INFO</span><span class="p">]</span> <span class="nx">Worker</span><span class="c1">#2 started.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">[</span><span class="nx">INFO</span><span class="p">]</span> <span class="nx">Worker</span><span class="c1">#0 started.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">[</span><span class="nx">INFO</span><span class="p">]</span> <span class="nx">HTTP</span> <span class="nx">Server</span> <span class="nx">listening</span> <span class="nx">at</span> <span class="mf">0.0</span><span class="o">.</span><span class="mf">0.0</span><span class="o">:</span><span class="mi">9501</span>
</span></span></code></pre></div><p>打开浏览器访问 <code>http://127.0.0.1:9501</code>，不出意外的话，页面会显示 <code>Hello Hyperf</code>，说明 HTTP 服务已经在工作了。那么这是怎么做到的呢？当我们执行启动命令后，Hyperf 是如何让 HTTP 服务启动的？</p>
<p>所以今天这篇文章我会从启动命令开始，给你介绍下 HTTP 服务是如何完成初始化并启动的。通过阅读这篇文章，你可以了解到以下内容：</p>
<ul>
<li>Hyperf 启动时会做哪些初始化操作？</li>
<li>HTTP 服务启动时会做哪些初始化操作？</li>
<li>HTTP 服务初始化时有哪些关键配置项？</li>
</ul>
<p>接下来，我们就从 Hyperf 的入口文件开始，了解启动 HTTP 服务的实现思路。</p>
<h2 id="binhyperfphp-文件hyperf-的入口">bin/hyperf.php 文件：Hyperf 的入口</h2>
<p>在启动命令中，除了 PHP 可执行文件以外，有两个是我们要关注的重点：</p>
<ul>
<li>bin/hyperf.php：Hyperf 的入口文件</li>
<li>start：启动命令的参数</li>
</ul>
<p>先来看一下 bin/hyperf.php 文件，我将该文件的执行逻辑分成了四个阶段。</p>
<h3 id="初始化项目配置信息">初始化项目配置信息</h3>
<p>在这个阶段中，主要是通过调用一些 PHP 内置函数，完成 PHP 相关的配置初始化，比如运行内存大小限制、错误级别、时区等等。</p>
<p>我们需要注意下在这一阶段定义的两个常量： BASE_PATH 和 SWOOLE_HOOK_FLAGS。</p>
<ul>
<li>BASE_PATH：保存的是 Hyperf 项目所在目录的完整路径，Hyperf 中很多操作都是基于该常量来定位目录和文件的路径。</li>
<li>SWOOLE_HOOK_FLAGS：Swoole 采用 Hook 原生 PHP 函数的方式实现协程客户端，该常量保存的是 Hook 的函数的范围，比如套接字、文件、curl 等等。SWOOLE_HOOK_ALL 表示 Hook 所有函数。</li>
</ul>
<p>我们经常会在 Swoole 相关的资料文档中看到「一键协程化」技术，实际上指的就是在启用协程时传入 SWOOLE_HOOK_ALL 配置项，通过 Hook 所有函数，让项目中会发生 IO 阻塞的代码变成可以协程调度的异步 IO，即一键协程化。</p>
<h3 id="初始化类加载器">初始化类加载器</h3>
<p>在 Hyperf 中，我们可以使用注解减少一些繁琐的配置，还可以基于注解实现很多强大的功能。比如注解注入、AOP 面向切面编程、路由定义、权限控制等等。这些功能能够正常运行，其实都离不开类加载器在初始化过程中的准备工作。</p>
<p>在初始化类加载器过程中，主要会进行以下操作：</p>
<ul>
<li>收集注解使用信息并完成注解收集器的初始化。</li>
<li>生成代理类，为实现 AOP 及 Inject 注解注入功能做准备工作。</li>
<li>生成运行时缓存，提高框架启动速度。</li>
</ul>
<h3 id="初始化依赖注入容器">初始化依赖注入容器</h3>
<p>在这个阶段， Hyperf 会先读取预先定义好的依赖关系的配置信息，包括 config/autoload/dependencies.php 配置文件中用户自定义的依赖关系，以及各组件中通过 ConfigProvider 机制定义的依赖关系。将这些初始的依赖关系保存到依赖注入容器中，完成对容器的初始化。</p>
<h3 id="初始化命令行应用">初始化命令行应用</h3>
<p>我们回过头来看一下启动命令，你会发现，实际上 Hyperf 本身就是一个命令行应用，而启动命令中的 start 不过是命令行应用的参数，也就是要执行的命令的名字。</p>
<p>在 Hyperf 中有很多内置的命令，比如 start、migrate、gen 等等，当然我们也可以根据自己的需求自定义命令。初始化命令行应用的过程，就是将这些 Hyperf 内置的命令、自定义的命令，注册到命令行应用中的过程。</p>
<h2 id="初始化并启动-http-服务">初始化并启动 HTTP 服务</h2>
<p>到了这里，Hyperf 的初始化工作就已经结束了，命令行应用就会开始对启动命令中的参数进行解析，通过参数找到在命令行应用中注册的命令并执行。参数 start 对应的命令类是 StartServer，你可以在 <code>hyperf/server</code> 组件中找到它。</p>
<p>在 StartServer 中，完成了对 HTTP 服务的初始化以及启动操作，包含检查运行环境、读取服务配置文件、初始化 HTTP 服务、启动 HTTP 服务四个步骤，下面我们来了解一下这些步骤中分别做了哪些事情。</p>
<h3 id="检查运行环境">检查运行环境</h3>
<p>我们知道，Hyperf 目前使用 Swoole 作为底层框架，所以在启动的时候，会先检查是否安装了 Swoole 的扩展，然后再检查是否禁用了 Swoole 的函数短名（short function name），如果没有禁用，就会输出提示信息并终止程序的运行。</p>
<h3 id="读取服务配置">读取服务配置</h3>
<p>在 Hyperf 中，我们使用 <code>config/autoload/server.php</code> 文件来配置服务信息，详细的字段说明可以查看 <a href="https://hyperf.wiki/3.0/#/zh-cn/config?id=serverphp-%e9%85%8d%e7%bd%ae%e8%af%b4%e6%98%8e">Hyperf 官方文档</a>。</p>
<p>其中有两个字段需要注意，分别是 <code>server.type</code> 和 <code>server.servers.type</code>，很多人不太清楚这两个配置项的作用和区别，下面我们来了解一下。</p>
<p>Swoole 提供了异步和协程两种风格的服务端，下面是两者的不同之处。</p>
<ul>
<li>协程风格的服务端<strong>可以在运行时动态创建、销毁</strong>，而异步风格的服务端在启动后就不能再对它进行操作了。</li>
<li>协程风格的服务端对连接的处理是在单独的协程中完成，与客户端的交互是顺序性的，而异步风格的服务端无法保证顺序性。</li>
</ul>
<p>Hyperf 作为上层框架，当然要支持这两种风格的服务端，同时还要考虑到扩展性，方便后续接入其它风格的服务端。</p>
<p>所以 Hyperf 在设计之初做了一层抽象，定义了一个 <code>ServerInterface</code> 接口，在接口中定义了三个常量，作为服务类型的枚举值。用于在配置文件中通过  <code>server.servers.type</code> 配置项设置服务的类型。同时，还定义了构造函数、初始化、启动三个方法。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="k">interface</span> <span class="nx">ServerInterface</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>    
</span></span><span class="line"><span class="cl">	<span class="c1">// HTTP 服务  
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">public</span> <span class="k">const</span> <span class="no">SERVER_HTTP</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>    
</span></span><span class="line"><span class="cl">    <span class="c1">// Websocket 服务  
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">public</span> <span class="k">const</span> <span class="no">SERVER_WEBSOCKET</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span>    
</span></span><span class="line"><span class="cl">   <span class="c1">// TCP 服务  
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">public</span> <span class="k">const</span> <span class="no">SERVER_BASE</span> <span class="o">=</span> <span class="mi">3</span><span class="p">;</span>    
</span></span><span class="line"><span class="cl">   <span class="c1">// 构造函数  
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">public</span> <span class="k">function</span> <span class="fm">__construct</span><span class="p">(</span><span class="nx">ContainerInterface</span> <span class="nv">$container</span><span class="p">,</span> <span class="nx">LoggerInterface</span> <span class="nv">$logger</span><span class="p">,</span> <span class="nx">EventDispatcherInterface</span> <span class="nv">$dispatcher</span><span class="p">);</span>    
</span></span><span class="line"><span class="cl">   <span class="c1">// 初始化  
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">public</span> <span class="k">function</span> <span class="nf">init</span><span class="p">(</span><span class="nx">ServerConfig</span> <span class="nv">$config</span><span class="p">)</span><span class="o">:</span> <span class="nx">ServerInterface</span><span class="p">;</span>    
</span></span><span class="line"><span class="cl">   <span class="c1">// 启动  
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">public</span> <span class="k">function</span> <span class="nf">start</span><span class="p">()</span><span class="o">:</span> <span class="nx">void</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>  
</span></span></code></pre></div><p>Hyperf 不仅实现了基于 Swoole 的两种风格的服务端，还实现了基于 Swow 的服务端。</p>
<ul>
<li>Hyperf\Server\Server：异步风格的服务端，由 Swoole 提供底层支持。</li>
<li>Hyperf\Server\CoroutineServer：协程风格的服务端，由 Swoole 提供底层支持。</li>
<li>Hyperf\Server\SwowServer：协程风格的服务端，由 Swow 提供底层支持。</li>
</ul>
<p>我们可以通过 <code>server.type</code> 配置项，来决定使用哪种风格的服务端用于运行各种类型的服务。当然，你也可以通过实现 <code>ServerInterface</code> 接口，自定义其它类型的服务端。</p>
<h3 id="初始化-http-服务">初始化 HTTP 服务</h3>
<p>通过上面的内容你可以知道，在运行 Hyperf 的时候，只能使用一种服务端，但是可以运行多个不同类型的服务，比如 HTTP 服务、Websocket 服务等等。为了便于说明，我会使用异步风格服务端给你介绍初始化 HTTP 服务的过程。</p>
<p>初始化 HTTP 服务的操作，是在 ServerFactory::configure 方法中完成的，主要可以分为两个步骤。</p>
<ul>
<li>第一步，将配置信息解析成 ServerConfig 对象。</li>
</ul>
<p>在这一步骤中，主要是将配置文件中数组形式的配置信息，解析成 ServerConfig 对象。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">ServerConfig</span> <span class="k">implements</span> <span class="nx">Arrayable</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">public</span> <span class="k">function</span> <span class="fm">__construct</span><span class="p">(</span><span class="k">protected</span> <span class="k">array</span> <span class="nv">$config</span> <span class="o">=</span> <span class="p">[])</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">	    <span class="c1">// 将各种类型的服务解析成 Port 对象
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="nv">$servers</span> <span class="o">=</span> <span class="p">[];</span>
</span></span><span class="line"><span class="cl">        <span class="k">foreach</span> <span class="p">(</span><span class="nv">$config</span><span class="p">[</span><span class="s1">&#39;servers&#39;</span><span class="p">]</span> <span class="k">as</span> <span class="nv">$item</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nv">$servers</span><span class="p">[]</span> <span class="o">=</span> <span class="nx">Port</span><span class="o">::</span><span class="na">build</span><span class="p">(</span><span class="nv">$item</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="c1">// 将其它类型的配置都保存到对象中
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="nv">$this</span><span class="o">-&gt;</span><span class="na">setType</span><span class="p">(</span><span class="nv">$config</span><span class="p">[</span><span class="s1">&#39;type&#39;</span><span class="p">]</span> <span class="o">??</span> <span class="nx">Server</span><span class="o">::</span><span class="na">class</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="o">-&gt;</span><span class="na">setMode</span><span class="p">(</span><span class="nv">$config</span><span class="p">[</span><span class="s1">&#39;mode&#39;</span><span class="p">]</span> <span class="o">??</span> <span class="mi">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="o">-&gt;</span><span class="na">setServers</span><span class="p">(</span><span class="nv">$servers</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="o">-&gt;</span><span class="na">setProcesses</span><span class="p">(</span><span class="nv">$config</span><span class="p">[</span><span class="s1">&#39;processes&#39;</span><span class="p">]</span> <span class="o">??</span> <span class="p">[])</span>
</span></span><span class="line"><span class="cl">            <span class="o">-&gt;</span><span class="na">setSettings</span><span class="p">(</span><span class="nv">$config</span><span class="p">[</span><span class="s1">&#39;settings&#39;</span><span class="p">]</span> <span class="o">??</span> <span class="p">[])</span>
</span></span><span class="line"><span class="cl">            <span class="o">-&gt;</span><span class="na">setCallbacks</span><span class="p">(</span><span class="nv">$config</span><span class="p">[</span><span class="s1">&#39;callbacks&#39;</span><span class="p">]</span> <span class="o">??</span> <span class="p">[]);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>当没有设置服务端的类型时，默认使用 Hyperf\Server\Server，即异步类型的服务端。</p>
<ul>
<li>第二步，调用 Hyperf\Server\Server::init 方法完成对 HTTP 服务的初始化。</li>
</ul>
<p>在这一步骤中，会调用 ServerFactory::getServer 方法，根据 ServerConfig 对象中的 type 属性实例化出对应的服务端对象，即 Hyperf\Server\Server 对象。在 Hyperf\Server\Server 对象中，定义了一个 server 属性，用于保存 Swoole 异步风格服务器对象。在 Swoole 异步风格的服务端中，有以下三种类型的服务器：</p>
<ul>
<li>Swoole\Server：TCP 服务器，是所有异步风格服务器的基类。</li>
<li>Swoole\Http\Server：HTTP 服务器。</li>
<li>Swoole\WebSocket\Server：WebSocket 服务器。</li>
</ul>
<p>在 init 方法中，会根据 <code>server.servers.type</code> 配置项的值（即 <code>ServerInterface</code> 接口中的常量），实例化出相应的服务器对象，并保存到 server 属性中。</p>
<p>这里会有一个问题，在 Hyperf\Server\Server 对象中只有一个 server 属性，但是，在 <code>server.servers</code> 配置项中，我们可以配置多个不同类型的服务，那么是如何支持运行多个服务的呢？</p>
<p>这里就跟 Swoole 的服务器实现有关，Swoole 的异步风格服务器可以通过调用 <a href="https://wiki.swoole.com/#/server/methods?id=addlistener">addListener</a> 方法监听多个端口，每个端口都可以设置不同的协议处理方式。这样就实现了一个服务器对象，同时运行多个不同类型的服务。</p>
<p>下面我们来看一下 init 方法的主要逻辑。</p>
<p>首先，在 init 方法中会先调用 ServerFactory::sortServers 方法，对需要启动的服务按照类型 Websocket、HTTP、TCP 的顺序进行排序。</p>
<p>然后，依次遍历这些服务，完成对每个服务的初始化。循环中包括两个分支：</p>
<ul>
<li><strong>第一个分支对应了 server 属性未初始化的情况。</strong> 此时，会调用 makeServer 方法实例化出相应的服务器对象，然后为<strong>服务器对象</strong>注册事件回调函数，最后初始化服务器对象的配置信息。</li>
<li><strong>第二个分支对应了 server 属性已初始化的情况。</strong> 此时，会调用服务器对象的 addListener 方法，增加一个端口并返回子服务器对象，然后为<strong>子服务器对象</strong>注册事件回调函数，最后初始化子服务器对象的配置信息。</li>
</ul>
<p>在 makeServer 方法中，会根据服务类型实例化出相应的服务器对象，下面代码展示了这部分的逻辑，你可以看下。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="k">switch</span> <span class="p">(</span><span class="nv">$type</span><span class="p">)</span> <span class="p">{</span>  
</span></span><span class="line"><span class="cl">    <span class="k">case</span> <span class="nx">ServerInterface</span><span class="o">::</span><span class="na">SERVER_HTTP</span><span class="o">:</span>  
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="k">new</span> <span class="nx">Swoole\Http\Server</span><span class="p">(</span><span class="nv">$host</span><span class="p">,</span> <span class="nv">$port</span><span class="p">,</span> <span class="nv">$mode</span><span class="p">,</span> <span class="nv">$sockType</span><span class="p">);</span>  
</span></span><span class="line"><span class="cl">    <span class="k">case</span> <span class="nx">ServerInterface</span><span class="o">::</span><span class="na">SERVER_WEBSOCKET</span><span class="o">:</span>  
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="k">new</span> <span class="nx">Swoole\WebSocket\Server</span><span class="p">(</span><span class="nv">$host</span><span class="p">,</span> <span class="nv">$port</span><span class="p">,</span> <span class="nv">$mode</span><span class="p">,</span> <span class="nv">$sockType</span><span class="p">);</span>  
</span></span><span class="line"><span class="cl">    <span class="k">case</span> <span class="nx">ServerInterface</span><span class="o">::</span><span class="na">SERVER_BASE</span><span class="o">:</span>  
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="k">new</span> <span class="nx">Swoole\Server</span><span class="p">(</span><span class="nv">$host</span><span class="p">,</span> <span class="nv">$port</span><span class="p">,</span> <span class="nv">$mode</span><span class="p">,</span> <span class="nv">$sockType</span><span class="p">);</span>  
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Swoole 提供了很多事件，比如 workerStart 工作进程启动后的事件、request 收到请求后的事件，这些事件在 Hyperf\Server\Event 中都有相应的常量。</p>
<p>在 Hyperf 中，有三种事件回调函数的配置，分别是全局事件、服务事件、默认事件。</p>
<ul>
<li>全局事件：使用 <code>server.callbacks</code> 配置项设置全局的事件的回调函数。</li>
<li>服务事件：使用 <code>server.servers.callbacks</code> 配置项为每一个服务单独设置事件的回调函数。</li>
<li>默认事件：在 Hyperf\Server\Server 对象的 defaultCallbacks 方法中配置了一些默认的事件的回调函数。</li>
</ul>
<p>这些配置优先级是：服务事件 &gt; 全局事件 &gt; 默认事件。下面的代码展示了注册事件的回调函数的核心逻辑。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="c1">// 按照优先级获取配置的所有事件及其回调函数
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nv">$callbacks</span> <span class="o">=</span> <span class="nx">array_replace</span><span class="p">(</span><span class="nv">$this</span><span class="o">-&gt;</span><span class="na">defaultCallbacks</span><span class="p">(),</span> <span class="nv">$config</span><span class="o">-&gt;</span><span class="na">getCallbacks</span><span class="p">(),</span> <span class="nv">$callbacks</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="k">foreach</span> <span class="p">(</span><span class="nv">$callbacks</span> <span class="k">as</span> <span class="nv">$event</span> <span class="o">=&gt;</span> <span class="nv">$callback</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="c1">// 非 Swoole 事件，直接跳过
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">if</span> <span class="p">(</span><span class="o">!</span> <span class="nx">Event</span><span class="o">::</span><span class="na">isSwooleEvent</span><span class="p">(</span><span class="nv">$event</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">continue</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="o">...</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// 为服务器对象注册该事件的回调函数
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nv">$server</span><span class="o">-&gt;</span><span class="na">on</span><span class="p">(</span><span class="nv">$event</span><span class="p">,</span> <span class="nv">$callback</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h3 id="启动-http-服务">启动 HTTP 服务</h3>
<p>在启动 HTTP 服务之前，会执行以下代码<strong>设置一键协程化 Hook 的函数范围</strong>，swoole_hook_flags 函数的返回值就是 SWOOLE_HOOK_FLAGS 常量的值，即 SWOOLE_HOOK_ALL。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="nx">Coroutine</span><span class="o">::</span><span class="na">set</span><span class="p">([</span><span class="s1">&#39;hook_flags&#39;</span> <span class="o">=&gt;</span> <span class="nx">swoole_hook_flags</span><span class="p">()]);</span>
</span></span></code></pre></div><p>接着会调用 ServerFactory::start 方法启动服务，在该方法中，直接调用 Hyperf\Server\Server 的 start 方法启动 Swoole 服务器。</p>
<p>当 Swoole 服务器启动后，会执行注册在服务器对象的 Event::ON_WORKER_START 事件的回调函数 WorkerStartCallback::onWorkerStart。</p>
<p>在 onWorkerStart 方法中，输出 <code>Worker#{$workerId} started.</code> 日志信息，并通过事件分发器分发 <code>AfterWorkerStart</code> 事件，在该事件的监听器 <code>AfterWorkerStartListener</code> 中，输出 <code>HTTP Server listening at 0.0.0.0:9501</code> 日志信息。</p>
<p>到这里，HTTP 服务就已经启动了。</p>
<h2 id="总结">总结</h2>
<p>在这篇文章中，我们通过 bin/hyperf.php 文件，了解了 Hyperf 在初始化框架时会执行哪些操作。接着，又通过 StartServer 了解了 HTTP 服务在启动过程中的四个步骤。其中，HTTP 服务的初始化是整个启动过程中的关键步骤，你可以配合源码进一步了解 Hyperf 的设计和实现思路。</p>
<p>尽管本文的主题是 HTTP 服务，但实际上，无论是 WebSocket服务、TCP服务还是其他类型的服务，这些服务的启动过程与 HTTP 服务的启动过程大同小异。</p>
<p>因此，掌握 HTTP 服务的启动过程，不仅有助于你了解 HTTP 服务的运行细节，还有助于你了解 Hyperf 以及其它类型服务的运行细节。当你遇到问题时，可以按照启动过程中的步骤逐步检查，从而帮助你更快地解决问题。</p>
]]></content:encoded>
    </item>
    <item>
      <title>一次 Hyperf 注解失效问题分析</title>
      <link>https://her-cat.com/posts/2023/03/02/hyperf-annotation-failure-problem-analysis/</link>
      <pubDate>Thu, 02 Mar 2023 23:58:06 +0800</pubDate>
      <guid>https://her-cat.com/posts/2023/03/02/hyperf-annotation-failure-problem-analysis/</guid>
      <description>本文讲述了我排查「Hyperf 注解失效」问题的过程，整个排查过程看似一气呵成，但实际上要曲折得多，甚至一度觉得这是个玄学问题。</description>
      <content:encoded><![CDATA[<h2 id="问题环境">问题环境</h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">PHP: 8.0.13
</span></span><span class="line"><span class="cl">Swoole: 4.6.2
</span></span><span class="line"><span class="cl">Hyperf: 2.2.33
</span></span><span class="line"><span class="cl">运行环境: Docker Desktop on WSL2  
</span></span></code></pre></div><h2 id="问题背景">问题背景</h2>
<p>有同事说我之前使用注解实现的某个功能有问题，具体表现就是有<strong>部分</strong>使用了注解的类没有被 Hyperf 收集到注解收集器中，导致出现了不符合预期的结果。</p>
<p>由于这个功能已经运行了一段时间，并且我在自己的电脑（Mac）上测试是正常的，找另外一个跟他同样使用 Windows + Docker 开发的同事进行测试也是正常的，所以可以排除业务代码和环境的问题。</p>
<p>简化后的代码如下：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="c1">#[Attribute(Attribute::TARGET_CLASS)]  
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">class</span> <span class="nc">CustomAnnotation</span> <span class="k">extends</span> <span class="nx">AbstractAnnotation</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>  
</span></span><span class="line"><span class="cl">  
</span></span><span class="line"><span class="cl"><span class="c1">#[CustomAnnotation]  
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">class</span> <span class="nc">Foo</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>  
</span></span><span class="line"><span class="cl"><span class="p">}</span>  
</span></span><span class="line"><span class="cl">  
</span></span><span class="line"><span class="cl"><span class="c1">#[CustomAnnotation]  
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">class</span> <span class="nc">Bar</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>  
</span></span><span class="line"><span class="cl"><span class="p">}</span>  
</span></span></code></pre></div><p>在上面的代码中，定义了一个注解类 <code>CustomAnnotation</code>，并且在两个类上使用了这个注解。期望的结果是 <code>Foo</code> 和 <code>Bar</code> 都能够被 Hyperf 收集到注解收集器中，但实际上只有 <code>Foo</code> 被收集到了。</p>
<blockquote>
<p>Foo 和 Bar 分别在不同的文件中，但是都在同一个目录下，该目录下的文件数量有 60+。</p></blockquote>
<p>于是我俩开始在他的电脑上排查是不是 Hyperf 的问题。</p>
<h2 id="源码分析">源码分析</h2>
<p>在 Hyperf 启动时， <code>ClassLoader</code> 类加载器会扫描项目中所有的类文件，并将元数据（注解与类之间的关系）收集到相应的注解收集器中，如果没有自定义注解收集器，则默认统一收集到 <code>Hyperf\Di\Annotation\AnnotationCollector</code> 类中。</p>
<p>下面是完成收集注解的主要逻辑：</p>
<ul>
<li>使用 <a href="https://github.com/symfony/finder">symfony/finder</a> 组件提供的 <code>Finder</code> 类遍历指定目录下所有的 PHP 类文件。</li>
<li>通过反射读取每个文件中的类及其属性、方法上使用的注解。</li>
<li>依次检查这些注解是否实现了 <code>Hyperf\Di\Annotation\AnnotationInterface</code> 接口，该接口定义了三个方法分别用于收集类、方法、属性的元数据。</li>
<li>如果注解实现了该接口，根据注解使用位置调用相应的方法将其收集到注解收集器中。</li>
</ul>
<p>完成收集后，我们就能使用注解收集器提供的静态方法的获取对应的元数据用于实现一些自定义的逻辑和功能。</p>
<p>第一步就是先检查类文件是否被 <code>Finder</code> 类读取到了，这部分的逻辑在 <code>ReflectionManager::getAllClasses()</code> 静态方法中。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="k">public</span> <span class="k">static</span> <span class="k">function</span> <span class="nf">getAllClasses</span><span class="p">(</span><span class="k">array</span> <span class="nv">$paths</span><span class="p">)</span><span class="o">:</span> <span class="k">array</span>  
</span></span><span class="line"><span class="cl"><span class="p">{</span>  
</span></span><span class="line"><span class="cl">    <span class="nv">$finder</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Finder</span><span class="p">();</span>  
</span></span><span class="line"><span class="cl">    <span class="c1">// 设置读取指定目录下的 PHP 文件
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nv">$finder</span><span class="o">-&gt;</span><span class="na">files</span><span class="p">()</span><span class="o">-&gt;</span><span class="na">in</span><span class="p">(</span><span class="nv">$paths</span><span class="p">)</span><span class="o">-&gt;</span><span class="na">name</span><span class="p">(</span><span class="s1">&#39;*.php&#39;</span><span class="p">);</span>  
</span></span><span class="line"><span class="cl">    <span class="nv">$parser</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Ast</span><span class="p">();</span>  
</span></span><span class="line"><span class="cl">  
</span></span><span class="line"><span class="cl">    <span class="nv">$reflectionClasses</span> <span class="o">=</span> <span class="p">[];</span>  
</span></span><span class="line"><span class="cl">    <span class="k">foreach</span> <span class="p">(</span><span class="nv">$finder</span> <span class="k">as</span> <span class="nv">$file</span><span class="p">)</span> <span class="p">{</span>  
</span></span><span class="line"><span class="cl">        <span class="k">try</span> <span class="p">{</span>  
</span></span><span class="line"><span class="cl">	        <span class="c1">// 解析文件内容获取类名称
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>            <span class="nv">$stmts</span> <span class="o">=</span> <span class="nv">$parser</span><span class="o">-&gt;</span><span class="na">parse</span><span class="p">(</span><span class="nv">$file</span><span class="o">-&gt;</span><span class="na">getContents</span><span class="p">());</span>  
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">(</span><span class="o">!</span> <span class="nv">$className</span> <span class="o">=</span> <span class="nv">$parser</span><span class="o">-&gt;</span><span class="na">parseClassByStmts</span><span class="p">(</span><span class="nv">$stmts</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	            <span class="c1">// 没获取到说明没有定义类
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>                <span class="k">continue</span><span class="p">;</span>  
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="nv">$reflectionClasses</span><span class="p">[</span><span class="nv">$className</span><span class="p">]</span> <span class="o">=</span> <span class="k">static</span><span class="o">::</span><span class="na">reflectClass</span><span class="p">(</span><span class="nv">$className</span><span class="p">);</span>  
</span></span><span class="line"><span class="cl">        <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">\Throwable</span><span class="p">)</span> <span class="p">{</span>  
</span></span><span class="line"><span class="cl">        <span class="p">}</span>    
</span></span><span class="line"><span class="cl">    <span class="p">}</span>    
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nv">$reflectionClasses</span><span class="p">;</span>  
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>将获取目录下文件的这段代码提出来单独进行测试。由于 <code>Finder</code> 类实现了 <code>IteratorAggregate</code> 接口，所以在上面的代码中可以直接对 <code>Finder</code> 类进行遍历，也可以使用 <code>iterator_to_array()</code> 函数直接获取迭代器的结果。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="nv">$finder</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Finder</span><span class="p">();</span>  
</span></span><span class="line"><span class="cl"><span class="c1">// 设置读取指定目录下的 PHP 文件
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nv">$finder</span><span class="o">-&gt;</span><span class="na">files</span><span class="p">()</span><span class="o">-&gt;</span><span class="na">in</span><span class="p">(</span><span class="s1">&#39;出现问题的目录路径&#39;</span><span class="p">)</span><span class="o">-&gt;</span><span class="na">name</span><span class="p">(</span><span class="s1">&#39;*.php&#39;</span><span class="p">);</span> 
</span></span><span class="line"><span class="cl"><span class="nx">var_dump</span><span class="p">(</span><span class="nx">iterator_to_array</span><span class="p">(</span><span class="nv">$finder</span><span class="p">));</span>
</span></span></code></pre></div><p>通过观察打印的结果就发现了问题所在：没有读取到 <code>Bar</code> 的类文件。</p>
<p>当时就在想，这么流行的一个组件包总不能出现这么低级的 Bug 吧？抱着怀疑的心态继续分析 <code>Finder</code> 类实现迭代器的代码，最后将问题定位到了 PHP 内置的 <code>RecursiveDirectoryIterator</code> 类上，<code>Finder</code> 类实际上就是对 PHP 的这些类做了一层封装。</p>
<p><a href="https://www.php.net/manual/zh/class.recursivedirectoryiterator.php">RecursiveDirectoryIterator</a> 提供了一个用于递归迭代文件系统目录的功能，用这个类再次进行上面的测试，依然没有读取到 <code>Bar</code> 的类文件。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="nv">$iter</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">RecursiveDirectoryIterator</span><span class="p">(</span><span class="s1">&#39;出现问题的目录路径&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="nx">var_dump</span><span class="p">(</span><span class="nx">iterator_to_array</span><span class="p">(</span><span class="nv">$iter</span><span class="p">));</span>
</span></span></code></pre></div><p>于是，我又一次陷入了怀疑中，难道 PHP 实现的这个类有问题？还得继续看 PHP 的源码？我在犹豫了一会后打开了 Google，抱着肯定有人也遇到过这个问题的想法输入了「RecursiveDirectoryIterator bug」，按下回车，在短暂的页面加载后&hellip;</p>
<p>嘿，还真有人已经遇到过这个问题。</p>
<h2 id="真相大白">真相大白</h2>
<p>在前几条搜索结果中，赫然发现有人在 PHP 官方的 Bug 系统反馈了这个问题：<a href="https://bugs.php.net/bug.php?id=80227">RecursiveDirectoryIterator returns incorrect results for Docker Desktop on WSL2</a>，并贴心的附带了可以复现问题的代码。</p>
<p>下面是精简过后的复现代码。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="nv">$filesPath</span> <span class="o">=</span> <span class="no">__DIR__</span><span class="o">.</span><span class="s1">&#39;/files&#39;</span><span class="p">;</span>  
</span></span><span class="line"><span class="cl">  
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="p">(</span><span class="o">!</span> <span class="nx">mkdir</span><span class="p">(</span><span class="nv">$filesPath</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="o">!</span> <span class="nx">is_dir</span><span class="p">(</span><span class="nv">$filesPath</span><span class="p">))</span> <span class="p">{</span>  
</span></span><span class="line"><span class="cl">    <span class="k">throw</span> <span class="k">new</span> <span class="nx">\RuntimeException</span><span class="p">(</span><span class="nx">sprintf</span><span class="p">(</span><span class="s1">&#39;Directory &#34;%s&#34; was not created&#39;</span><span class="p">,</span> <span class="s1">&#39;files&#39;</span><span class="p">));</span>  
</span></span><span class="line"><span class="cl"><span class="p">}</span>  
</span></span><span class="line"><span class="cl">  
</span></span><span class="line"><span class="cl"><span class="nv">$max</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>  
</span></span><span class="line"><span class="cl"><span class="nv">$stop</span> <span class="o">=</span> <span class="mi">5000</span><span class="p">;</span>  
</span></span><span class="line"><span class="cl">  
</span></span><span class="line"><span class="cl"><span class="c1">// 生成测试文件，模拟目录中文件较多的情况  
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">foreach</span><span class="p">(</span><span class="nx">range</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="nv">$stop</span><span class="p">)</span> <span class="k">as</span> <span class="nv">$index</span><span class="p">)</span> <span class="p">{</span>  
</span></span><span class="line"><span class="cl">    <span class="nv">$message</span> <span class="o">=</span> <span class="nx">sprintf</span><span class="p">(</span><span class="s2">&#34;creating %s</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">,</span> <span class="nv">$index</span><span class="p">);</span>  
</span></span><span class="line"><span class="cl">    <span class="k">echo</span> <span class="nv">$message</span><span class="p">;</span>  
</span></span><span class="line"><span class="cl">    <span class="nx">file_put_contents</span><span class="p">(</span><span class="no">__DIR__</span> <span class="o">.</span> <span class="s1">&#39;/files/file&#39;</span> <span class="o">.</span> <span class="nv">$index</span><span class="p">,</span> <span class="nx">str_repeat</span><span class="p">(</span><span class="s1">&#39;A&#39;</span><span class="p">,</span> <span class="mi">100</span><span class="p">));</span>  
</span></span><span class="line"><span class="cl"><span class="p">}</span>  
</span></span><span class="line"><span class="cl">  
</span></span><span class="line"><span class="cl"><span class="nv">$iter</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">\RecursiveDirectoryIterator</span><span class="p">(</span><span class="nv">$filesPath</span><span class="p">,</span> <span class="nx">FilesystemIterator</span><span class="o">::</span><span class="na">KEY_AS_PATHNAME</span><span class="o">|</span><span class="nx">FilesystemIterator</span><span class="o">::</span><span class="na">CURRENT_AS_FILEINFO</span><span class="o">|</span><span class="nx">FilesystemIterator</span><span class="o">::</span><span class="na">SKIP_DOTS</span><span class="p">);</span>  
</span></span><span class="line"><span class="cl"><span class="nx">var_dump</span><span class="p">(</span><span class="nx">iterator_count</span><span class="p">(</span><span class="nv">$iter</span><span class="p">));</span>
</span></span><span class="line"><span class="cl"><span class="c1">// 打印出来的数字小于 5000 说明复现成功了
</span></span></span></code></pre></div><p>PHP 官方给出了回复：这是 WSL 的 Bug，并提供了相关的 issue：<a href="https://github.com/microsoft/WSL/issues/5074">WSL2: Seek of directory entry by lseek does not work on v9fs</a>。里面的实际输出跟我们发现这个问题时的打印结果几乎一模一样，感兴趣的可以去看看。</p>
<p>有人可能会问，<code>lseek()</code> 函数跟 <code>RecursiveDirectoryIterator</code> 类有什么关系吗 ？</p>
<p>当然有！将上面的代码保存到 test.php 文件，然后执行 <code>strace php test.php</code> 命令查看 PHP 代码的系统调用情况。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">...省略其他部分...
</span></span><span class="line"><span class="cl">openat(AT_FDCWD, &#34;/home/ubuntu/files&#34;, O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY) = 4
</span></span><span class="line"><span class="cl">fstat(4, {st_mode=S_IFDIR|0775, st_size=135168, ...}) = 0
</span></span><span class="line"><span class="cl">brk(0x55d84733f000)                     = 0x55d84733f000
</span></span><span class="line"><span class="cl">getdents(4, /* 1024 entries */, 32768)  = 32752
</span></span><span class="line"><span class="cl">lseek(4, 0, SEEK_SET)                   = 0
</span></span><span class="line"><span class="cl">getdents(4, /* 1024 entries */, 32768)  = 32752
</span></span><span class="line"><span class="cl">getdents(4, /* 1024 entries */, 32768)  = 32768
</span></span><span class="line"><span class="cl">getdents(4, /* 1024 entries */, 32768)  = 32768
</span></span><span class="line"><span class="cl">getdents(4, /* 1024 entries */, 32768)  = 32768
</span></span><span class="line"><span class="cl">getdents(4, /* 906 entries */, 32768)   = 28992
</span></span><span class="line"><span class="cl">getdents(4, /* 0 entries */, 32768)     = 0
</span></span><span class="line"><span class="cl">write(1, &#34;int(5000)\n&#34;, 10int(5000)
</span></span><span class="line"><span class="cl">)             = 10
</span></span><span class="line"><span class="cl">close(3)                                = 0
</span></span><span class="line"><span class="cl">close(4)                                = 0
</span></span><span class="line"><span class="cl">...省略其他部分...
</span></span></code></pre></div><p>可以看到，<code>RecursiveDirectoryIterator</code> 类在底层中调用了 <code>lseek()</code> 函数，它的作用是设置文件偏移量。<code>lseek(4, 0, SEEK_SET) </code> 表示将文件偏移量设置为 0，即文件开头的位置，该函数无法工作会导致下次操作依然使用的是原来的文件偏移量。</p>
<blockquote>
<p>Linux 中万物皆为文件，包括目录。</p></blockquote>
<p>用 PHP 代码来举个例子，这里使用 PHP 的 <code>rewinddir()</code> 函数代替 <code>lseek()</code> 函数，实际上底层调用的还是 <code>lseek()</code> 函数。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="nv">$dh</span> <span class="o">=</span> <span class="nx">opendir</span><span class="p">(</span><span class="no">__DIR__</span> <span class="o">.</span> <span class="s1">&#39;/files&#39;</span><span class="p">);</span>  
</span></span><span class="line"><span class="cl">  
</span></span><span class="line"><span class="cl"><span class="k">echo</span> <span class="s1">&#39;开始读取目录中的所有文件:&#39;</span> <span class="o">.</span> <span class="nx">PHP_EOL</span><span class="p">;</span>  
</span></span><span class="line"><span class="cl"><span class="k">while</span> <span class="p">((</span><span class="nv">$file</span> <span class="o">=</span> <span class="nx">readdir</span><span class="p">(</span><span class="nv">$dh</span><span class="p">))</span> <span class="o">!==</span> <span class="k">false</span><span class="p">)</span> <span class="p">{</span>  
</span></span><span class="line"><span class="cl">    <span class="k">echo</span> <span class="s1">&#39;filename:&#39;</span> <span class="o">.</span> <span class="nv">$file</span> <span class="o">.</span> <span class="nx">PHP_EOL</span><span class="p">;</span>  
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">  
</span></span><span class="line"><span class="cl"><span class="k">echo</span> <span class="s1">&#39;再次读取目录中的所有文件:&#39;</span> <span class="o">.</span> <span class="nx">PHP_EOL</span><span class="p">;</span>  
</span></span><span class="line"><span class="cl"><span class="c1">// 这时文件偏移量已经到达文件的末尾，再次读取目录将不会有任何输出，模拟 lseek() 函数无法工作的情况 
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">while</span> <span class="p">((</span><span class="nv">$file</span> <span class="o">=</span> <span class="nx">readdir</span><span class="p">(</span><span class="nv">$dh</span><span class="p">))</span> <span class="o">!==</span> <span class="k">false</span><span class="p">)</span> <span class="p">{</span>  
</span></span><span class="line"><span class="cl">    <span class="k">echo</span> <span class="s1">&#39;filename:&#39;</span> <span class="o">.</span> <span class="nv">$file</span> <span class="o">.</span> <span class="nx">PHP_EOL</span><span class="p">;</span>  
</span></span><span class="line"><span class="cl"><span class="p">}</span>  
</span></span><span class="line"><span class="cl">  
</span></span><span class="line"><span class="cl"><span class="c1">// 将文件偏移量重置到文件的开头  
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nx">rewinddir</span><span class="p">(</span><span class="nv">$dh</span><span class="p">);</span>  
</span></span><span class="line"><span class="cl">  
</span></span><span class="line"><span class="cl"><span class="k">echo</span> <span class="s1">&#39;重置偏移量后读取目录中的所有文件:&#39;</span> <span class="o">.</span> <span class="nx">PHP_EOL</span><span class="p">;</span>  
</span></span><span class="line"><span class="cl"><span class="c1">// 与第一次读取的结果相同，模拟 lseek() 函数正常工作的情况
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">while</span> <span class="p">((</span><span class="nv">$file</span> <span class="o">=</span> <span class="nx">readdir</span><span class="p">(</span><span class="nv">$dh</span><span class="p">))</span> <span class="o">!==</span> <span class="k">false</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">echo</span> <span class="s1">&#39;filename:&#39;</span> <span class="o">.</span> <span class="nv">$file</span> <span class="o">.</span> <span class="nx">PHP_EOL</span><span class="p">;</span>  
</span></span><span class="line"><span class="cl"><span class="p">}</span>  
</span></span><span class="line"><span class="cl">  
</span></span><span class="line"><span class="cl"><span class="nx">closedir</span><span class="p">(</span><span class="nv">$dh</span><span class="p">);</span>
</span></span></code></pre></div><p>在 WSL2 以外的系统中运行以上代码，可以得到与预期一致的结果。那么在 WSL2 中运行的结果是什么？</p>
<h2 id="解决问题">解决问题</h2>
<p>当然，最好是 WSL 官方能够修复这个问题，但是从有人提出这个问题到现在已经快三年了依然没有被解决的情况来看，不知道得等到猴年马月。</p>
<p>提问的作者也给出了一种解决方案，开启 Hyper V。但是经过测试后发现开启 Hyper V 依然会出现这个问题，所以最后直接从 WSL2 回滚到 WSL1，从另一种「根本上」解决这个问题。</p>
<h2 id="总结">总结</h2>
<p>等等，文章开头不是说已经排除是环境的问题了吗？怎么最后又是环境的问题了？</p>
<p>是的，这是由于我当时并没有问清楚，只是确认了另一个同事是用 Docker 运行的，我怎么也没想到他是本地运行了个虚拟机，然后在虚拟机里面运行 Docker&hellip;</p>
<p>当然，后面的源码分析也不是一点作用都没有，至少将问题的范围从 Hyperf 框架缩小到了 <code>Finder</code> 类，再到 <code>RecursiveDirectoryIterator</code> 类。否则直接 Google 搜索「Hyperf 注解失效」是很难找到正确答案的。</p>
<p>在这篇文章中，讲述了我排查「Hyperf 注解失效」问题的过程，整个排查过程看似一气呵成，但实际上要曲折得多，甚至一度觉得这是个玄学问题。</p>
<p>最后，没有 Bug 的程序是不存在的，不要过度迷信那些看似很可靠的系统。</p>
]]></content:encoded>
    </item>
    <item>
      <title>译｜PHP 7 新的 Hashtable 实现</title>
      <link>https://her-cat.com/posts/2022/10/31/php7-hashtable-implementation/</link>
      <pubDate>Mon, 31 Oct 2022 23:45:20 +0800</pubDate>
      <guid>https://her-cat.com/posts/2022/10/31/php7-hashtable-implementation/</guid>
      <description>&lt;p&gt;大约三年前，我写了一篇分析 PHP 5 中&lt;a href=&#34;https://www.npopov.com/2011/12/12/How-big-are-PHP-arrays-really-Hint-BIG.html&#34;&gt;数组的内存使用情况&lt;/a&gt;的文章。即将到来的 PHP 7 作为我工作的一部分，我重点关注于优化数据结构的大小以及内存分配上，为此重写了 Zend 引擎的大部分的内容。在这篇文章中，我将对新的 HashTable 实现进行概述，并说明为什么它比以前的实现更高效。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>大约三年前，我写了一篇分析 PHP 5 中<a href="https://www.npopov.com/2011/12/12/How-big-are-PHP-arrays-really-Hint-BIG.html">数组的内存使用情况</a>的文章。即将到来的 PHP 7 作为我工作的一部分，我重点关注于优化数据结构的大小以及内存分配上，为此重写了 Zend 引擎的大部分的内容。在这篇文章中，我将对新的 HashTable 实现进行概述，并说明为什么它比以前的实现更高效。</p>
<p>为了测量内存利用率，我将会使用下面的脚本创建一个包含 100000 个不同整数的数组：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="nv">$startMemory</span> <span class="o">=</span> <span class="nx">memory_get_usage</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="nv">$array</span> <span class="o">=</span> <span class="nx">range</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">100000</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="k">echo</span> <span class="nx">memory_get_usage</span><span class="p">()</span> <span class="o">-</span> <span class="nv">$startMemory</span><span class="p">,</span> <span class="s2">&#34; bytes</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">;</span>
</span></span></code></pre></div><p>下面这张表格展示了使用 PHP 5.6 和 PHP 7 在 32 位和 64 位系统上的内存使用情况：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">        |   32 bit |    64 bit
</span></span><span class="line"><span class="cl">------------------------------
</span></span><span class="line"><span class="cl">PHP 5.6 | 7.37 MiB | 13.97 MiB
</span></span><span class="line"><span class="cl">------------------------------
</span></span><span class="line"><span class="cl">PHP 7.0 | 3.00 MiB |  4.00 MiB
</span></span></code></pre></div><p>可以看到，PHP 7 中的数组在 32 位上使用的内存要比 PHP 5.6 少 2.5 倍，在 64 位（LP64）上要少 3.5 倍，这是相当可观的性能提升。</p>
<h2 id="hashtable-介绍">Hashtable 介绍</h2>
<p>从本质上讲，PHP 的数组是有序字典，即一个表示键/值对的有序列表，其中键/值映射是使用 Hashtable 实现的。</p>
<p><a href="https://en.wikipedia.org/wiki/Hash_table">Hashtable</a> 是一种非常常见的数据结构，它本质上解决了计算机只能直接表示连续的整数索引数组的问题，而程序员往往希望使用字符串或其它复杂类型作为键。</p>
<p>Hashtable 背后的概念非常简单：字符串通过哈希函数（hash function）处理后，哈希函数会返回一个整数（哈希值），然后将这个整数作为「普通」数组的索引。这里有一个问题，两个不同的字符串的可以产生相同的哈希值，因为字符串的大小几乎是无限制的，而哈希值则受到整数大小的限制。因此，HashTable 需要实现某种冲突解决机制。</p>
<p>冲突解决机制主要有两种：<a href="https://en.wikipedia.org/wiki/Open_addressing">开放寻址法</a>，如果发生哈希冲突，元素将被存储在不同的索引中；拉链法，所有哈希值相同的元素被存储在一个链表中。PHP 使用的是第二种。</p>
<p>通常情况下，Hashtable 中的元素是没有明确的顺序的。元素在底层数组中的存储顺序取决于哈希函数（hash function），并且非常的随机。但是这种行为与 PHP 数组的语义不一致：如果你遍历一个 PHP 数组，你将会按照元素的插入顺序取回元素。这意味着 PHP 的 Hashtable 实现必须支持一种额外的机制来记录数组元素的顺序。</p>
<h2 id="旧的-hashtable-实现">旧的 Hashtable 实现</h2>
<p>在这里，我只对旧的 Hashtable 实现做一个简短的概述，更全面的解释请看《PHP 内部实现》书籍中的 <a href="http://www.phpinternalsbook.com/hashtables/basic_structure.html">Hashtable 章节</a>。</p>
<p>下面这张图从高层次的视角展示了 PHP 5 的 Hashtable 实现。</p>
<p><img decoding="async" height="414" loading="lazy" sizes="(max-width: 768px) 100vw, (max-width: 1024px) 768px, 1024px" src="/posts/2022/10/31/php7-hashtable-implementation/basic_hashtable.png" srcset="/posts/2022/10/31/php7-hashtable-implementation/basic_hashtable_hu_2a7bcb15630063e2.png 384w, /posts/2022/10/31/php7-hashtable-implementation/basic_hashtable_hu_929c3fa8fcd5c59b.png 768w" style="max-width: 100%; height: auto; aspect-ratio: 1.9396;" width="803"></p>
<p>「冲突解决机制」链表中的元素被称为「bucket」，每一个 bucket 都是单独分配的。上面的图片中省略了这些 bucket 中存储的实际值（只展示了键）。值被存储在单独分配的 zval 结构体中，其大小为 16 字节（32 位）或 24 字节（64 位）。</p>
<p>上面的图片中没有展示的另一点是，冲突解决机制链表实际上是一个双向链表（使元素的删除变得简单）。除了冲突解决机制链表以外，Hashtable 还有另一个双向链表用来存储数组元素的顺序。对于按照顺序包含 &ldquo;a&rdquo;、&ldquo;b&rdquo;、&ldquo;c&rdquo; 的数组，该链表可能如下所示：</p>
<p><img decoding="async" height="312" loading="lazy" sizes="(max-width: 768px) 100vw, (max-width: 1024px) 768px, 1024px" src="/posts/2022/10/31/php7-hashtable-implementation/ordered_hashtable.png" srcset="/posts/2022/10/31/php7-hashtable-implementation/ordered_hashtable_hu_2abe221b1e4ac56e.png 384w" style="max-width: 100%; height: auto; aspect-ratio: 1.5577;" width="486"></p>
<p>那么，为什么旧的 Hashtable 结构体在内存使用和性能方面都如此低效？有以下几个主要因素：</p>
<ul>
<li>bucket 需要单独分配。分配的速度非常慢，并且还需要 8/16 字节的分配开销。单独分配意味着 bucket 在内存中会更加分散，从而会降低缓存的效率。</li>
<li>zval 也需要单独分配。同样，这也是很慢的，而且会产生额外的 header 开销。另外这需要每个 bucket 都保存一个指向 zval 的指针。因为旧的实现过于考虑通用性，所以实际上不只需要一个指针，而是两个指针。</li>
<li>两个双向链表导致每个 bucket 总共需要 4 个指针。仅此就已经占用了 16/32 字节。此外，遍历链表是一种非常缓存不友好（cache-unfriendly）的操作。</li>
</ul>
<p>新的 Hashtable 实现试图解决（或者至少是改善）这些问题。</p>
<h2 id="新的-zval-实现">新的 zval 实现</h2>
<p>在讨论新的 Hashtable 之前，我想快速地介绍一下新的 zval 结构体，并强调它与旧结构体的区别。zval 结构体定义如下：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="k">struct</span> <span class="n">_zval_struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="n">zend_value</span> <span class="n">value</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="k">union</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="nf">ZEND_ENDIAN_LOHI_4</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">				<span class="n">zend_uchar</span> <span class="n">type</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">				<span class="n">zend_uchar</span> <span class="n">type_flags</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">				<span class="n">zend_uchar</span> <span class="n">const_flags</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">				<span class="n">zend_uchar</span> <span class="n">reserved</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">		<span class="p">}</span> <span class="n">v</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">		<span class="kt">uint32_t</span> <span class="n">type_info</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span> <span class="n">u1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="k">union</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="kt">uint32_t</span> <span class="n">var_flags</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">		<span class="kt">uint32_t</span> <span class="n">next</span><span class="p">;</span>       <span class="cm">/* 哈希冲突链表 */</span>
</span></span><span class="line"><span class="cl">		<span class="kt">uint32_t</span> <span class="n">cache_slot</span><span class="p">;</span> <span class="cm">/* 缓存槽 */</span>
</span></span><span class="line"><span class="cl">		<span class="kt">uint32_t</span> <span class="n">lineno</span><span class="p">;</span>     <span class="cm">/* 行号（用于 ast 节点） */</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span> <span class="n">u2</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><p>你可以忽略这个定义中的 ZEND_ENDIAN_LOHI_4 宏，它只是为了确保在具有不同字节序的机器上有一个可预测的内存布局。</p>
<p>zval 结构体有三个部分：第一个成员是 <code>value</code>。<code>zend_value</code> 联合体的大小为 8 字节，它可以存储不同类型的值，包括整数、字符串、数组等等。实际存储的内容取决于 zval 的类型。</p>
<p>第二部分是 4 个字节大小的 <code>type_info</code>，包括实际的类型（如 IS_STRING 或 IS_ARRAY），以及一些提供给该类型信息的附加标志。例如，如果 zval 存储的是一个对象，那么类型标志会说它是一个非常量（non-constant）、引用计数（refcounted）、垃圾回收（garbage-collectible）、不可复制（non-copying）的类型。</p>
<p>zval 结构体的最后 4 个字节通常是未使用的（它实际上只是显式填充，否则编译器会自动引入这种填充，即内存对齐）。然而在特殊情况下，这块空间被用来存储一些额外的信息。例如，AST 节点用它来存储行号，VM 常量用它来存储缓存槽索引，Hashtable 用它来存储冲突解决机制链表中的下一个元素。最后一部分对我们很重要。</p>
<p>如果你将其与之前的 zval 实现相比较，有一个区别特别明显：新的 zval 结构体不再存储 refcount。这背后的原因是，zval 本身不再被单独分配。相反，zval 直接嵌入到存储它的东西中（例如 Hashtable 的 bucket）。</p>
<p>虽然 zval 本身不再存储 refcount，但是字符串、数组、对象和资源这些复杂的数据类型仍然可以使用它。实际上，新的 zval 设计已经将 refcount（以及循环收集器的信息）移动到了数组、对象等复杂类型中。这种做法有很多优点，其中一部分优点如下：</p>
<ul>
<li>zval 存储简单值（例如布尔值、整数或浮点数）不再需要任何分配。因此，通过避免不必要的分配和释放以及提高缓存本地性，这节省了分配头开销并提高了性能。</li>
<li>zval 存储简单值时，不需要存储 refcount 和垃圾回收根缓冲区（GC root buffer）。</li>
<li>我们需要避免双重引用计数。例如，之前的对象使用了 zval 的 refcount，又使用了一个额外的对象 refcount，这对于支持按对象传递的语义来说是必须的。</li>
<li>由于现在所有复杂的值都嵌入了 refcount，它们可以独立于 zval 机制而被共享。特别是现在还可以共享字符串。这对 Hashtable 的实现很重要，因为它不再需要复制 non-interned 字符串的键。</li>
</ul>
<h2 id="新的-hashtable-实现">新的 Hashtable 实现</h2>
<p>在所有前期准备工作结束后，我们终于可以看看 PHP 7 所使用的新的 Hashtable 实现。让我们先来看一下 bucket 的结构体：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="k">typedef</span> <span class="k">struct</span> <span class="n">_Bucket</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="n">zend_ulong</span>        <span class="n">h</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="n">zend_string</span>      <span class="o">*</span><span class="n">key</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="n">zval</span>              <span class="n">val</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="n">Bucket</span><span class="p">;</span>
</span></span></code></pre></div><p>bucket 是 Hashtable 中的一个条目。它包含了你所期望的东西：哈希值 <code>h</code>，字符串键 <code>key</code> 和 zval 值 <code>val</code>。当键为整数时，整数会被存储在 <code>h</code> 中（在这种情况下，键和哈希值是相同的），并且成员 <code>key</code> 将会是 <code>NULL</code>。</p>
<p>如你所见，zval 直接嵌入到了 bucket 结构体中，因此它不需要单独分配，也就不会产生分配带来的开销。</p>
<p>Hashtable 的结构更加有趣：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="k">typedef</span> <span class="k">struct</span> <span class="n">_HashTable</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="kt">uint32_t</span>          <span class="n">nTableSize</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="kt">uint32_t</span>          <span class="n">nTableMask</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="kt">uint32_t</span>          <span class="n">nNumUsed</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="kt">uint32_t</span>          <span class="n">nNumOfElements</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="n">zend_long</span>         <span class="n">nNextFreeElement</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="n">Bucket</span>           <span class="o">*</span><span class="n">arData</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="kt">uint32_t</span>         <span class="o">*</span><span class="n">arHash</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="kt">dtor_func_t</span>       <span class="n">pDestructor</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="kt">uint32_t</span>          <span class="n">nInternalPointer</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="k">union</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="nf">ZEND_ENDIAN_LOHI_3</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">				<span class="n">zend_uchar</span>    <span class="n">flags</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">				<span class="n">zend_uchar</span>    <span class="n">nApplyCount</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">				<span class="kt">uint16_t</span>      <span class="n">reserve</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">		<span class="p">}</span> <span class="n">v</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">		<span class="kt">uint32_t</span> <span class="n">flags</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span> <span class="n">u</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="n">HashTable</span><span class="p">;</span>
</span></span></code></pre></div><p>bucket（即数组元素）存储在 <code>arData</code> 数组中。该数组的以 2 次方幂进行分配，数组大小存储在 <code>nTableSize</code> 中（最小值为 8）。元素的实际数量存储在 <code>nNumOfElements</code> 中。注意，这个数组直接包含 <code>Bucket</code> 结构体。以前我们使用一个指向单独分配的 bucket 的指针数组，这意味着我们需要执行更多次的分配和释放，必须要承担分配带来的开销，还要存维护额外的指针。</p>
<h2 id="元素的顺序">元素的顺序</h2>
<p><code>arData</code> 数组按照插入顺序存储元素。所以第一个数组元素将会被存储在 <code>arData[0]</code> 中，第二个存储在 <code>arData[1]</code> 中等等。任何时候都与所使用的键无关，只取决于插入的顺序。</p>
<p>因此，如果在 Hashtable 中存储 5 个元素，将使用插槽 <code>arData[0]</code> 到 <code>arData[4]</code>，下一个空闲的插槽是 <code>arData[5]</code>。我们使用 <code>nNumUsed</code> 来记录这个数字。你可能会问：为什么我们要单独存储它，它和 <code>nNumOfElements</code> 不是一样的吗？</p>
<p>是的，但是只有在只执行插入操作的情况下，它们会是一样的。如果从 Hashtable 中删除了一个元素，我们显然不想移动 <code>arData</code> 中出现在被删除元素之后的所有元素，以便再次拥有一个连续的数组。相反，我们只是使用 <code>IS_UNDEF</code> zval 类型来标记已删除的值。</p>
<p>举个例子，思考下面的代码：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="nv">$array</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">	<span class="s1">&#39;foo&#39;</span> <span class="o">=&gt;</span> <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">	<span class="s1">&#39;bar&#39;</span> <span class="o">=&gt;</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">	<span class="mi">0</span>     <span class="o">=&gt;</span> <span class="mi">2</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">	<span class="s1">&#39;xyz&#39;</span> <span class="o">=&gt;</span> <span class="mi">3</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">	<span class="mi">2</span>     <span class="o">=&gt;</span> <span class="mi">4</span>
</span></span><span class="line"><span class="cl"><span class="p">];</span>
</span></span><span class="line"><span class="cl"><span class="nx">unset</span><span class="p">(</span><span class="nv">$array</span><span class="p">[</span><span class="mi">0</span><span class="p">]);</span>
</span></span><span class="line"><span class="cl"><span class="nx">unset</span><span class="p">(</span><span class="nv">$array</span><span class="p">[</span><span class="s1">&#39;xyz&#39;</span><span class="p">]);</span>
</span></span></code></pre></div><p>将会产生以下 <code>arData</code> 结构：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">nTableSize     = 8
</span></span><span class="line"><span class="cl">nNumOfElements = 3
</span></span><span class="line"><span class="cl">nNumUsed       = 5
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">[0]: key=&#34;foo&#34;, val=int(0)
</span></span><span class="line"><span class="cl">[1]: key=&#34;bar&#34;, val=int(1)
</span></span><span class="line"><span class="cl">[2]: val=UNDEF
</span></span><span class="line"><span class="cl">[3]: val=UNDEF
</span></span><span class="line"><span class="cl">[4]: h=2, val=int(4)
</span></span><span class="line"><span class="cl">[5]: NOT INITIALIZED
</span></span><span class="line"><span class="cl">[6]: NOT INITIALIZED
</span></span><span class="line"><span class="cl">[7]: NOT INITIALIZED
</span></span></code></pre></div><p>正如你所看到的，前五个 <code>arData</code> 元素已被使用，但是位置 2（键为 <code>0</code>）和位置 3（键为 <code>'xyz'</code>）的元素已被替换成了 <code>IS_UNDEF</code>，这是因为它们被 <code>unset</code> 了。现在这些元素的存在只是在浪费内存。但是，当 <code>nNumUsed</code> 等于 <code>nTableSize</code> 时，PHP 将尝试压缩 <code>arData</code> 数组，删除沿途添加的任何 <code>UNDEF</code> 条目。只有当所有的 bucket 都包含一个值时，<code>arData</code> 数组才会被重新分配到两倍的大小。</p>
<p>与 PHP 5.x 中使用的双向链表相比，维护数组顺序的新方法有几个优点。一个明显的优势是，我们为每个 bucket 节省了两个指针，相当于 8/16 字节，此外，这意味着数组的迭代看起来大致如下：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="kt">uint32_t</span> <span class="n">i</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">ht</span><span class="o">-&gt;</span><span class="n">nNumUsed</span><span class="p">;</span> <span class="o">++</span><span class="n">i</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="n">Bucket</span> <span class="o">*</span><span class="n">b</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">ht</span><span class="o">-&gt;</span><span class="n">arData</span><span class="p">[</span><span class="n">i</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="p">(</span><span class="nf">Z_ISUNDEF</span><span class="p">(</span><span class="n">b</span><span class="o">-&gt;</span><span class="n">val</span><span class="p">))</span> <span class="k">continue</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="c1">// do stuff with bucket
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span>
</span></span></code></pre></div><p>这相当于对内存的线性扫描，这比遍历链表（在相对随机的内存地址之间来回）更高效。</p>
<p>目前的实现有一个问题，就是 <code>arData</code> 永远不会收缩（除非明确告诉它）。因此，如果你创建一个有几百万个元素的数组，之后将它们删除，那么这个数组仍然会占用大量的内存。如果利用率低于一定水平，我们也许应该把 <code>arData</code> 的大小减半。</p>
<h2 id="hashtable-查找">HashTable 查找</h2>
<p>到目前为止，我们只讨论了 PHP 数组如何表示顺序。实际上，Hashtable 的查找使用的是第二个数组 <code>arHash</code>，它由 <code>uint32_t</code> 类型的值组成。<code>arHash</code> 数组的大小与 <code>arData</code> 数组的大小相同（即 <code>nTableSize</code>），两者实际上都被分配在同一段连续的内存中。</p>
<p>从哈希函数（DJBX33A 是一种哈希算法，用于字符串类型的键）返回的哈希值是一个 32 位或 64 位无符号整数，这个整数值太大了，不能直接作为哈希数组的索引。我们首先需要使用模运算将其调整到 <code>nTableSize</code> 的大小范围内。我们使用 <code>hash &amp; (ht-&gt;nTableSize - 1)</code> 代替 <code>hash &amp; ht-&gt;nTableSize</code>，如果大小是 2 的幂也是一样的，不需要昂贵的整数除法运算。<code>ht-&gt;nTableSize - 1</code> 的值存储在 <code>ht-&gt;nTableMask</code> 中。</p>
<p>接下来，我们在哈希数组中查找索引 <code>idx = ht-&gt;arHash[hash &amp; ht-&gt;nTableMask]</code>，这个索引（idx）相当于冲突解决机制链表的头。因此，<code>ht-&gt;arData[idx]</code> 是我们要检查的第一个条目。如果键和我们要找的一样，那么就完成了查找。</p>
<p>否则，我们必须继续检查冲突解决机制链表中的下一个元素。该元素的索引存储在 <code>bucket-&gt;val.u2.next</code> 中，它是 zval 结构体的最后 4 个字节，这些字节在这个上下文中具有特殊的意义。我们继续遍历这个链表（使用索引而不是指针），直到找到正确的 bucket，或者命中 <code>INVALID_IDX</code>，这意味着查找的键对应的元素不存在。</p>
<p>在代码中，查找机制如下所示：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="n">zend_ulong</span> <span class="n">h</span> <span class="o">=</span> <span class="nf">zend_string_hash_val</span><span class="p">(</span><span class="n">key</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kt">uint32_t</span> <span class="n">idx</span> <span class="o">=</span> <span class="n">ht</span><span class="o">-&gt;</span><span class="n">arHash</span><span class="p">[</span><span class="n">h</span> <span class="o">&amp;</span> <span class="n">ht</span><span class="o">-&gt;</span><span class="n">nTableMask</span><span class="p">];</span>
</span></span><span class="line"><span class="cl"><span class="k">while</span> <span class="p">(</span><span class="n">idx</span> <span class="o">!=</span> <span class="n">INVALID_IDX</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="n">Bucket</span> <span class="o">*</span><span class="n">b</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">ht</span><span class="o">-&gt;</span><span class="n">arData</span><span class="p">[</span><span class="n">idx</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="p">(</span><span class="n">b</span><span class="o">-&gt;</span><span class="n">h</span> <span class="o">==</span> <span class="n">h</span> <span class="o">&amp;&amp;</span> <span class="nf">zend_string_equals</span><span class="p">(</span><span class="n">b</span><span class="o">-&gt;</span><span class="n">key</span><span class="p">,</span> <span class="n">key</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">return</span> <span class="n">b</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">	<span class="n">idx</span> <span class="o">=</span> <span class="nf">Z_NEXT</span><span class="p">(</span><span class="n">b</span><span class="o">-&gt;</span><span class="n">val</span><span class="p">);</span> <span class="c1">// b-&gt;val.u2.next
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="k">return</span> <span class="nb">NULL</span><span class="p">;</span>
</span></span></code></pre></div><p>让我们考虑以下这种方法是如何改进以前的实现的：在 PHP 5.x 中，冲突解决机制使用基于指针的双向链表。使用 <code>uint32_t</code> 索引代替指针更好，因为它们的大小只有 64 位系统的一半。此外，在 4 个字节中，意味着我们可以将「next」链接嵌入到未使用的 zval 插槽中，因此我们基本上可以免费使用它。</p>
<p>我们现在使用的是单向链表，不再需要「prev」指针了。「prev」指针主要用于删除元素，因为当你删除某个元素的时候，你需要调整「prev」指针的「next」指针所指向的元素。但是，如果通过键删除元素，通过遍历冲突解决机制链表，你实际上已经知道前一个元素了。</p>
<p>少数情况下，删除发生在其它上下文中（例如，删除当前正在遍历的元素），将不得不遍历冲突解决机制链表找到前一个元素。但由于这是一个相当不重要的场景，我们更倾向于节省内存，而不是增加一个链表用于这种场景下查找前一个元素。</p>
<h2 id="packed-hashtable">Packed Hashtable</h2>
<p>PHP 中所有的数组都使用了 Hashtable。但是，在连续的、以整数为索引的数组（即真正的数组）这种比较常见的情况下，整个哈希过程没有多大意义。这就是为什么 PHP 7 引入了「Packed Hashtable」的概念。</p>
<p>在 Packed Hashtable 中，<code>arHash</code> 数组为 <code>NULL</code>，并且直接使用索引从 <code>arData</code> 中进行查找。如果你要找的键为 <code>5</code>，那么这个元素将位于 <code>arData[5]</code> 中，或者它根本就不存在。没有必要去遍历冲突解决机制链表。</p>
<p>注意，即使对于整数索引数组，PHP 也必须保持顺序。数组 <code>[0 =&gt; 1, 1 =&gt; 2]</code> 和 <code>[1 =&gt; 2, 0 =&gt; 1]</code> 是不一样的。Packed Hashtable 优化只有在键按升序排列时才起作用。它们之间可能有间隙（键不一定是连续的），但是它们要是呈递增趋势的。因此，如果元素以「错误」的顺序（例如反过来）插入到数组中，则不会使用 Packed Hashtable。</p>
<p>另外需要注意，Packed Hashtable 仍然存储大量无用的信息。例如，我们可以根据 bucket 的内存地址确定它的索引，因此 <code>bucket-&gt;h</code> 是多余的。<code>bucket-&gt;key</code> 的值始终为 <code>NULL</code>，因此它也只是在浪费内存。</p>
<p>我们保留这些无用的值以便于 bucket 使用相同的结构体，与是否使用 Packed Hashtable 无关。</p>
<p>这意味着迭代可以使用使用相同的 key。然而，将来我们可能会切换到「fully packed」的结构，如果可能的话，将会使用 zval 数组来实现。</p>
<h2 id="空的-hashtable">空的 Hashtable</h2>
<p>在 PHP 5.x 和 PHP 7 中，对于空的 Hashtable 都做了一些特殊处理。如果你创建一个空数组 <code>[]</code>，很有可能你实际上不会插入任何元素。因此，arrData/arHash 数组只有在第一个元素被插入到 Hashtable 时才会被分配。</p>
<p>为了避免在许多地方检查这种特殊情况，这里使用了一个小技巧：当 <code>nTableSize</code> 被设置为提示的大小或默认值 8 时，<code>nTableMask</code> 被设置为 0（通常为 <code>nTableSize - 1</code>）。这意味着 <code>hash &amp; ht-&gt;nTableMask</code> 的结果总是为 0。</p>
<p>因此，这种情况下的 <code>arHash</code> 数组只需要有一个包含 <code>INVALID_IDX</code> 值的元素（索引为 0），这个特殊数组被称为 <code>uninitialized_bucket</code>，是静态分配的。当执行查找时，我们总是找到 <code>INVALID_IDX</code> 值，这意味着没有找到键（这正是空数组所需要的）。</p>
<h2 id="内存利用率">内存利用率</h2>
<p>这一节将说明 PHP 7 中 Hashtable 实现的最重要的几个方面。首先让我们总结一下为什么新的实现使用较少的内存。在这里，我只使用 64 位系统的数字，并且只关注每个元素的大小，忽略 Hashtable 结构体（从渐进的角度看，它的意义不大）。</p>
<p>在 PHP 5.x 中，每个元素需要高达 144 个字节。在 PHP 7 中，这个值降到了 36 个字节，对于 Packed Hashtable 来说这个值是 32 字节。下面这些就是导致这戏的差异的原因：</p>
<ul>
<li>zval 不是单独分配的，所以我们节省了 16 个字节的分配开销。</li>
<li>bucket 不是单独分配的，因此我们节省了另外 16 个字节的分配开销。</li>
<li>对于简单的值，zval 比原来要小 16 个字节。</li>
<li>不再为了保持元素的顺序而需要 16 个字节维护双向链表，并且这个顺序是隐式的。</li>
<li>冲突解决机制链表限制是单向链表，节省了 8 个字节。此外，它现在是一个索引列表，索引被嵌入到了 zval 中，所以实际上我们又节省了 8 个字节。</li>
<li>由于 zval 被直接嵌入到 bucket 中，我们不再需要存储一个指向它的指针。由于以前实现的细节问题，我们实际上又节省了两个指针，这又是 16 个字节。</li>
<li>bucket 不再存储键的长度，这又节省了 8 个字节。然而，如果键实际上是一个字符串而不是整数，那么长度仍然需要存储在 <code>zend_string</code> 结构体中。在这种情况下，无非准确的估算内存使用情况，因为 <code>zend_string</code> 结构体是共享的，对于以前的 Hashtable 来说，如果字符串是非内部（non-interned）的，则需要拷贝字符串。</li>
<li>数组包含了冲突解决机制链表的头（基于索引的 head），因此每个元素可以节省 4 个字节。对于压缩数组（Packed Hashtable）来说，根本不需要冲突解决机制链表，在这种情况下，我们又节省了 4 个字节。</li>
</ul>
<p>然而，上面的总结只是为了更清楚地说明新的 Hashtable 在哪些方面比旧的实现要好。首先，新的 Hashtable 大量的使用了嵌入式（而不是单独分配的）结构体。这会产生什么负面影响吗？</p>
<p>如果你仔细观察本文开头的实际测量结果，你会发现 PHP 7 在 64 位系统上，一个有 100000 个元素的数组占用了 4.00 MiB 的内存。在这种情况下，我们处理的是一个压缩数组（Packed Hashtable），实际上我们预期占用的内存应该是 32 * 100000 = 3.05 MiB。导致这个差异的原因是，在为数组分配内存时，总是以 2 次方幂进行分配。所以当数组包含 100000 个元素时，<code>nTableSize</code> 的值将会是 2 ^ 17 = 131072，最终会分配的内存是 32 * 131072（也就是 4.00 MiB）。</p>
<p>当然，以前的 Hashtable 实现也是使用 2 次方幂进行分配。但是，它只用这种方式分配了一个 bucket 指针数组（其中每个指针是 8 字节）。其它的都是按需分配的。所以在 PHP 7 中，我们浪费了 32 * 31072（0.95 MiB）未使用的内存，而在 PHP 5.x 中，我们只浪费了 8 * 31072（0.24 MiB）。</p>
<p>另一个需要考虑的问题是，如果数组中存储的所有值都不相同，会发生什么情况。为了简单起见，我们假设数组中所有的值都是相同的。因此，我们使用 <code>array_fill</code> 函数替换原本的 <code>range</code> 函数：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="nv">$startMemory</span> <span class="o">=</span> <span class="nx">memory_get_usage</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="nv">$array</span> <span class="o">=</span> <span class="nx">array_fill</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">100000</span><span class="p">,</span> <span class="mi">42</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="k">echo</span> <span class="nx">memory_get_usage</span><span class="p">()</span> <span class="o">-</span> <span class="nv">$startMemory</span><span class="p">,</span> <span class="s2">&#34; bytes</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">;</span>
</span></span></code></pre></div><p>测试的结果如下：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl">        <span class="o">|</span>   <span class="mi">32</span> <span class="nx">bit</span> <span class="o">|</span>    <span class="mi">64</span> <span class="nx">bit</span>
</span></span><span class="line"><span class="cl"><span class="o">------------------------------</span>
</span></span><span class="line"><span class="cl"><span class="nx">PHP</span> <span class="mf">5.6</span> <span class="o">|</span> <span class="mf">4.70</span> <span class="nx">MiB</span> <span class="o">|</span>  <span class="mf">9.39</span> <span class="nx">MiB</span>
</span></span><span class="line"><span class="cl"><span class="o">------------------------------</span>
</span></span><span class="line"><span class="cl"><span class="nx">PHP</span> <span class="mf">7.0</span> <span class="o">|</span> <span class="mf">3.00</span> <span class="nx">MiB</span> <span class="o">|</span>  <span class="mf">4.00</span> <span class="nx">MiB</span>
</span></span></code></pre></div><p>正如你所看到的，在 PHP 7 中，内存使用情况与 <code>range</code> 的结果一致。由于所有的 zval 都是单独分配的，所以测试结果不可能是不一样的。另一方面，在 PHP 5.x 中，内存使用情况明显降低，这是因为所有的值只使用一个 zval。因此，虽然我们在 PHP 7 上仍然有一些进步，但是现在差距变小了。</p>
<p>一旦我们考虑到字符串键（可能是共享的或非共享的，又或者是非内部的）和复杂值的时候，事情就变得更加复杂了。重点在于 PHP 7 中的数组比 PHP 5.x 占用的内存要少很多，但是在很多情况下，引言中的数字可能过于乐观。</p>
<h2 id="性能">性能</h2>
<p>我们已经讨论了很多关于内存使用情况的内容了，所以让我们转到下一个话题，性能。phpng 项目的最终目的不是为了改善内存的使用情况，而是提高性能。内存利用率的提高只是一种手段，因为更少的内存能够提高的 CPU 缓存利用率，从而带来更好的性能。</p>
<p>当然，还有很多其它原因导致新的实现会这么快：首先，减少了内存分配的次数。不管数组元素的值是否共享，我们都为每个元素节省了两次内存分配。内存分配是相当昂贵的操作，这对于性能的提升非常重要。</p>
<p>尤其是现在的数组遍历对缓存更加友好（ cache-friendly），因为它现在是对内存进行线性遍历，而不是随机访问（random-access）的链表遍历。</p>
<p>关于性能的话题可能还有很多可以说的，但本文主要的重点是内存的使用情况，所以我不会在这里进一步详述。</p>
<h2 id="总结">总结</h2>
<p>就 Hashtable 的实现而言，PHP 7 无疑是向前迈进了一大步，很多无用的开销现在都已经被解决了。</p>
<p>因此，问题是：接下来我们该怎么办？在前面我已经提到的一个想法是，在整数键呈递增的情况下使用「fully packed」结构，这意味着将使用一个普通的 zval 数组实现，这是我们在不开始专门研究统一类型数组的情况下所能做到的最好的事情。</p>
<p>也许还有别的方向可以去尝试一下。例如，将哈希冲突解决机制从拉链法改成开放寻址法（例如使用 Robin Hood probing），在内存使用（没有冲突解决机制链表）和性能（更好的缓存效率，取决于算法的细节）方面都可能会更好。然而，开放寻址法相对来说很难跟保持数组元素顺序的需求结合起来，所以这个想法在实际情况中并不可行。</p>
<p>另一个想法是将 bucket 结构体中的 <code>h</code> 和 <code>key</code> 字段组合起来。整数键只使用 <code>h</code> 字段，字符串键也会在 <code>key</code> 中存储哈希值。然而，这可能会对性能产生不利的影响，因为获取哈希值将需要额外的内存间接寻址。</p>
<p>最后我想说的是，PHP 7 不仅改进了 Hashtable 的内部实现，而且还改进了相关的调用 API。在此之前，我经常不得不查看如何使用 <code>zend_hash_find</code> 这样简单的操作，特别是需要多少个间接级别（提示：3 个）。在 PHP 7 中，只需要编写 <code>zend_hash_find(ht, key)</code>，就可以得到一个 <code>zval *</code>。总的来说，我发现为 PHP 7 编写扩展程序变得更加愉快了。</p>
<p>希望我能够为你提供一些对于 PHP 7 的 Hashtable 内部实现的见解。也许我还会写一篇关于 zval 的后续文章。我已经在这篇文章中提到了一些不同之处，但关于这个话题还有很多可以说的。</p>
<blockquote>
<p>原文：<a href="https://www.npopov.com/2014/12/22/PHPs-new-hashtable-implementation.html">PHP&rsquo;s new hashtable implementation</a></p></blockquote>
]]></content:encoded>
    </item>
    <item>
      <title>Yar 源码阅读笔记：RPC 服务端</title>
      <link>https://her-cat.com/posts/2022/01/18/yar-internals-server/</link>
      <pubDate>Tue, 18 Jan 2022 17:35:06 +0800</pubDate>
      <guid>https://her-cat.com/posts/2022/01/18/yar-internals-server/</guid>
      <description>Yar 服务端处理客户端请求的步骤：解析请求数据、执行被调用方法、返回响应结果。</description>
      <content:encoded><![CDATA[<h2 id="前言">前言</h2>
<p>在前面的文章中介绍了 Yar 客户端以及相关模块的实现，弄清楚了客户端的远程调用是如何发送出去的、发送的内容是什么、以及如何处理响应结果。</p>
<p>今天我们就来看看 Yar 服务端是如何处理客户端请求的。</p>
<h2 id="服务端的实现">服务端的实现</h2>
<p>在开篇的<a href="/posts/2021/10/17/yar-internals-start/#Yar-%E6%9C%8D%E5%8A%A1%E7%AB%AF">服务端示例</a>中可以看到，Yar_Server 有两个方法：构造函数和 handle 方法。</p>
<p>下面是 Yar_Server 的方法和属性的定义。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// source：yar_server.c
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="n">zend_function_entry</span> <span class="n">yar_server_methods</span><span class="p">[]</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nf">PHP_ME</span><span class="p">(</span><span class="n">yar_server</span><span class="p">,</span> <span class="n">__construct</span><span class="p">,</span> <span class="n">arginfo_service___construct</span><span class="p">,</span> <span class="n">ZEND_ACC_PUBLIC</span><span class="o">|</span><span class="n">ZEND_ACC_CTOR</span><span class="o">|</span><span class="n">ZEND_ACC_FINAL</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nf">PHP_ME</span><span class="p">(</span><span class="n">yar_server</span><span class="p">,</span> <span class="n">handle</span><span class="p">,</span> <span class="n">arginfo_service_void</span><span class="p">,</span> <span class="n">ZEND_ACC_PUBLIC</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">PHP_FE_END</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">YAR_STARTUP_FUNCTION</span><span class="p">(</span><span class="n">service</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">zend_class_entry</span> <span class="n">ce</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nf">INIT_CLASS_ENTRY</span><span class="p">(</span><span class="n">ce</span><span class="p">,</span> <span class="s">&#34;Yar_Server&#34;</span><span class="p">,</span> <span class="n">yar_server_methods</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">yar_server_ce</span> <span class="o">=</span> <span class="nf">zend_register_internal_class</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ce</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nf">zend_declare_property_null</span><span class="p">(</span><span class="n">yar_server_ce</span><span class="p">,</span> <span class="nf">ZEND_STRL</span><span class="p">(</span><span class="s">&#34;_executor&#34;</span><span class="p">),</span> <span class="n">ZEND_ACC_PROTECTED</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">SUCCESS</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>下面来看看这两个方法的实现。</p>
<h3 id="构造函数">构造函数</h3>
<p>构造函数的作用是将需要对外提供服务的对象保存到 Yar_Server 的 _executor 中。处理客户端的请求时，就会执行该对象中被调用的方法。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// source：yar_server.c
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="nf">PHP_METHOD</span><span class="p">(</span><span class="n">yar_server</span><span class="p">,</span> <span class="n">__construct</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">zval</span> <span class="o">*</span><span class="n">obj</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nf">zend_parse_parameters_throw</span><span class="p">(</span><span class="nf">ZEND_NUM_ARGS</span><span class="p">(),</span> <span class="s">&#34;o&#34;</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">obj</span><span class="p">)</span> <span class="o">==</span> <span class="n">FAILURE</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// 保存对象到 _executor 中
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nf">zend_update_property</span><span class="p">(</span><span class="n">yar_server_ce</span><span class="p">,</span> <span class="nf">getThis</span><span class="p">(),</span> <span class="s">&#34;_executor&#34;</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="s">&#34;_executor&#34;</span><span class="p">)</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="n">obj</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h3 id="处理请求">处理请求</h3>
<p>在 handle 方法中，通过判断请求方式执行不同的逻辑。</p>
<ul>
<li>非 POST 请求：例如 GET 请求，输出 RPC 服务的 API 信息。</li>
<li>POST 请求：处理客户端的远程调用，执行被调用的方法，返回响应结果。</li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-gdscript3" data-lang="gdscript3"><span class="line"><span class="cl"><span class="o">//</span> <span class="n">source</span><span class="err">：</span><span class="n">yar_server</span><span class="o">.</span><span class="n">c</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">PHP_METHOD</span><span class="p">(</span><span class="n">yar_server</span><span class="p">,</span> <span class="n">handle</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="o">//</span> <span class="o">...</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">const</span> <span class="n">char</span> <span class="o">*</span><span class="n">method</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">zval</span> <span class="o">*</span><span class="n">executor</span><span class="p">,</span> <span class="n">rv</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="o">//</span> <span class="err">获取被调用的对象</span>
</span></span><span class="line"><span class="cl">    <span class="n">executor</span> <span class="o">=</span> <span class="n">zend_read_property</span><span class="p">(</span><span class="n">yar_server_ce</span><span class="p">,</span> <span class="n">getThis</span><span class="p">(),</span> <span class="n">ZEND_STRL</span><span class="p">(</span><span class="s2">&#34;_executor&#34;</span><span class="p">),</span> <span class="mi">0</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">rv</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">IS_OBJECT</span> <span class="o">!=</span> <span class="n">Z_TYPE_P</span><span class="p">(</span><span class="n">executor</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">php_error_docref</span><span class="p">(</span><span class="n">NULL</span><span class="p">,</span> <span class="n">E_WARNING</span><span class="p">,</span> <span class="s2">&#34;executor is not a valid object&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">RETURN_FALSE</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="o">//</span> <span class="err">判断请求方式，非</span> <span class="n">POST</span> <span class="err">请求则输出</span> <span class="n">API</span> <span class="err">信息</span>
</span></span><span class="line"><span class="cl">    <span class="n">method</span> <span class="o">=</span> <span class="n">SG</span><span class="p">(</span><span class="n">request_info</span><span class="p">)</span><span class="o">.</span><span class="n">request_method</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">method</span> <span class="o">||</span> <span class="n">strncasecmp</span><span class="p">(</span><span class="n">method</span><span class="p">,</span> <span class="s2">&#34;POST&#34;</span><span class="p">,</span> <span class="mi">4</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="o">//</span> <span class="err">检查是否开启了</span> <span class="n">expose_info</span> 
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">YAR_G</span><span class="p">(</span><span class="n">expose_info</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">php_yar_server_info</span><span class="p">(</span><span class="n">executor</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="n">RETURN_TRUE</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">zend_throw_exception</span><span class="p">(</span><span class="n">yar_server_exception_ce</span><span class="p">,</span> <span class="s2">&#34;server info is not allowed to access&#34;</span><span class="p">,</span> <span class="n">YAR_ERR_FORBIDDEN</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="o">//</span> <span class="err">处理客户端的远程调用</span>
</span></span><span class="line"><span class="cl">    <span class="n">php_yar_server_handle</span><span class="p">(</span><span class="n">executor</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">RETURN_TRUE</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>php_yar_server_info 函数的作用是输出 API 信息页面的 HTML 及被调用对象中所有 public 方法的信息。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// source：yar_server.c
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="k">static</span> <span class="kt">void</span> <span class="nf">php_yar_server_info</span><span class="p">(</span><span class="n">zval</span> <span class="o">*</span><span class="n">obj</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">char</span> <span class="n">buf</span><span class="p">[</span><span class="mi">1024</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="n">zend_class_entry</span> <span class="o">*</span><span class="n">ce</span> <span class="o">=</span> <span class="nf">Z_OBJCE_P</span><span class="p">(</span><span class="n">obj</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// 输出 header 部分
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nf">snprintf</span><span class="p">(</span><span class="n">buf</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">buf</span><span class="p">),</span> <span class="n">HTML_MARKUP_HEADER</span><span class="p">,</span> <span class="nf">ZSTR_VAL</span><span class="p">(</span><span class="n">ce</span><span class="o">-&gt;</span><span class="n">name</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">    <span class="nf">PHPWRITE</span><span class="p">(</span><span class="n">buf</span><span class="p">,</span> <span class="nf">strlen</span><span class="p">(</span><span class="n">buf</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// 输出 css 和 javascript 部分
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nf">PHPWRITE</span><span class="p">(</span><span class="n">HTML_MARKUP_CSS</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">HTML_MARKUP_CSS</span><span class="p">)</span> <span class="o">-</span> <span class="mi">1</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nf">PHPWRITE</span><span class="p">(</span><span class="n">HTML_MARKUP_SCRIPT</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">HTML_MARKUP_SCRIPT</span><span class="p">)</span> <span class="o">-</span> <span class="mi">1</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// 输出网页标题
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nf">snprintf</span><span class="p">(</span><span class="n">buf</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">buf</span><span class="p">),</span> <span class="n">HTML_MARKUP_TITLE</span><span class="p">,</span> <span class="nf">ZSTR_VAL</span><span class="p">(</span><span class="n">ce</span><span class="o">-&gt;</span><span class="n">name</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">    <span class="nf">PHPWRITE</span><span class="p">(</span><span class="n">buf</span><span class="p">,</span> <span class="nf">strlen</span><span class="p">(</span><span class="n">buf</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// 读取并输出被调用对象的方法列表
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nf">zend_hash_apply_with_argument</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ce</span><span class="o">-&gt;</span><span class="n">function_table</span><span class="p">,</span> <span class="p">(</span><span class="kt">apply_func_arg_t</span><span class="p">)</span><span class="n">php_yar_print_info</span><span class="p">,</span> <span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="p">)(</span><span class="n">ce</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// 输出 footer 部分
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nf">PHPWRITE</span><span class="p">(</span><span class="n">HTML_MARKUP_FOOTER</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">HTML_MARKUP_FOOTER</span><span class="p">)</span> <span class="o">-</span> <span class="mi">1</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">static</span> <span class="kt">int</span> <span class="nf">php_yar_print_info</span><span class="p">(</span><span class="n">zval</span> <span class="o">*</span><span class="n">ptr</span><span class="p">,</span> <span class="kt">void</span> <span class="o">*</span><span class="n">argument</span><span class="p">)</span> <span class="cm">/* </span><span class="p">{{{</span> <span class="err">*/</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">zend_function</span> <span class="o">*</span><span class="n">f</span> <span class="o">=</span> <span class="nf">Z_FUNC_P</span><span class="p">(</span><span class="n">ptr</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// 判断是否为 public 方法
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">if</span> <span class="p">(</span><span class="n">f</span><span class="o">-&gt;</span><span class="n">common</span><span class="p">.</span><span class="n">fn_flags</span> <span class="o">&amp;</span> <span class="n">ZEND_ACC_PUBLIC</span> 
</span></span><span class="line"><span class="cl">        <span class="o">&amp;&amp;</span> <span class="n">f</span><span class="o">-&gt;</span><span class="n">common</span><span class="p">.</span><span class="n">function_name</span> <span class="o">&amp;&amp;</span> <span class="o">*</span><span class="p">(</span><span class="nf">ZSTR_VAL</span><span class="p">(</span><span class="n">f</span><span class="o">-&gt;</span><span class="n">common</span><span class="p">.</span><span class="n">function_name</span><span class="p">))</span> <span class="o">!=</span> <span class="sc">&#39;_&#39;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kt">char</span> <span class="o">*</span><span class="n">prototype</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// 获取函数原型
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="k">if</span> <span class="p">((</span><span class="n">prototype</span> <span class="o">=</span> <span class="nf">php_yar_get_function_declaration</span><span class="p">(</span><span class="n">f</span><span class="p">)))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="kt">char</span> <span class="o">*</span><span class="n">buf</span><span class="p">,</span> <span class="o">*</span><span class="n">doc_comment</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">(</span><span class="n">f</span><span class="o">-&gt;</span><span class="n">type</span> <span class="o">==</span> <span class="n">ZEND_USER_FUNCTION</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="k">if</span> <span class="p">(</span><span class="n">f</span><span class="o">-&gt;</span><span class="n">op_array</span><span class="p">.</span><span class="n">doc_comment</span> <span class="o">!=</span> <span class="nb">NULL</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                    <span class="c1">// 读取函数的 doc 注释
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>                    <span class="n">doc_comment</span> <span class="o">=</span> <span class="p">(</span><span class="kt">char</span> <span class="o">*</span><span class="p">)</span><span class="nf">ZSTR_VAL</span><span class="p">(</span><span class="n">f</span><span class="o">-&gt;</span><span class="n">op_array</span><span class="p">.</span><span class="n">doc_comment</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">                <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="c1">// 输出函数原型及注释
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>            <span class="nf">spprintf</span><span class="p">(</span><span class="o">&amp;</span><span class="n">buf</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">HTML_MARKUP_ENTRY</span><span class="p">,</span> <span class="n">prototype</span><span class="p">,</span> <span class="n">doc_comment</span><span class="o">?</span> <span class="nl">doc_comment</span> <span class="p">:</span> <span class="s">&#34;&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="nf">efree</span><span class="p">(</span><span class="n">prototype</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="nf">PHPWRITE</span><span class="p">(</span><span class="n">buf</span><span class="p">,</span> <span class="nf">strlen</span><span class="p">(</span><span class="n">buf</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">            <span class="nf">efree</span><span class="p">(</span><span class="n">buf</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">ZEND_HASH_APPLY_KEEP</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>下面继续看看 php_yar_server_handle 函数，该函数的主要逻辑如下：</p>
<ul>
<li>读取 HTTP 请求中 body 的内容。</li>
<li>调用 php_yar_protocol_parse 函数解析读取到的内容。</li>
<li>调用 php_yar_packager_unpack 函数对 payload 进行解码。</li>
<li>调用 php_yar_request_unpack 函数解析请求数据。</li>
<li>执行被调用的方法。</li>
<li>返回响应结果。</li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// source：yar_server.c
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="k">static</span> <span class="kt">void</span> <span class="nf">php_yar_server_handle</span><span class="p">(</span><span class="n">zval</span> <span class="o">*</span><span class="n">obj</span><span class="p">)</span> <span class="cm">/* </span><span class="p">{{{</span> <span class="err">*/</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl">    <span class="c1">// 读取 HTTP 请求 body 中的内容
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">while</span> <span class="p">(</span><span class="o">!</span><span class="nf">php_stream_eof</span><span class="p">(</span><span class="n">s</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">len</span> <span class="o">+=</span> <span class="nf">php_stream_read</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="n">buf</span> <span class="o">+</span> <span class="n">len</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">buf</span><span class="p">)</span> <span class="o">-</span> <span class="n">len</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">len</span> <span class="o">==</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">buf</span><span class="p">)</span> <span class="o">||</span> <span class="n">raw_data</span><span class="p">.</span><span class="n">s</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nf">smart_str_appendl</span><span class="p">(</span><span class="o">&amp;</span><span class="n">raw_data</span><span class="p">,</span> <span class="n">buf</span><span class="p">,</span> <span class="n">len</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="n">len</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// 获取 body 中的内容及长度
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">if</span> <span class="p">(</span><span class="n">len</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">payload</span> <span class="o">=</span> <span class="n">buf</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">payload_len</span> <span class="o">=</span> <span class="n">len</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">raw_data</span><span class="p">.</span><span class="n">s</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nf">smart_str_0</span><span class="p">(</span><span class="o">&amp;</span><span class="n">raw_data</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">payload</span> <span class="o">=</span> <span class="nf">ZSTR_VAL</span><span class="p">(</span><span class="n">raw_data</span><span class="p">.</span><span class="n">s</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">payload_len</span> <span class="o">=</span> <span class="nf">ZSTR_LEN</span><span class="p">(</span><span class="n">raw_data</span><span class="p">.</span><span class="n">s</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nf">php_yar_error</span><span class="p">(</span><span class="n">response</span><span class="p">,</span> <span class="n">YAR_ERR_PACKAGER</span><span class="p">,</span> <span class="s">&#34;empty request body&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nf">DEBUG_S</span><span class="p">(</span><span class="s">&#34;0: empty request &#39;%s&#39;&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">goto</span> <span class="n">response_no_output</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// 解析 body 中的内容
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="p">(</span><span class="n">header</span> <span class="o">=</span> <span class="nf">php_yar_protocol_parse</span><span class="p">(</span><span class="n">payload</span><span class="p">)))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nf">php_yar_error</span><span class="p">(</span><span class="n">response</span><span class="p">,</span> <span class="n">YAR_ERR_PACKAGER</span><span class="p">,</span> <span class="s">&#34;malformed request header &#39;%.10s&#39;&#34;</span><span class="p">,</span> <span class="n">payload</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nf">DEBUG_S</span><span class="p">(</span><span class="s">&#34;0: malformed request &#39;%s&#39;&#34;</span><span class="p">,</span> <span class="n">payload</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">goto</span> <span class="n">response_no_output</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// 跳过 Yar 协议的头部信息
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="n">payload</span> <span class="o">+=</span> <span class="k">sizeof</span><span class="p">(</span><span class="kt">yar_header_t</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">payload_len</span> <span class="o">-=</span> <span class="k">sizeof</span><span class="p">(</span><span class="kt">yar_header_t</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// 解码 payload 部分
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="p">(</span><span class="n">post_data</span> <span class="o">=</span> <span class="nf">php_yar_packager_unpack</span><span class="p">(</span><span class="n">payload</span><span class="p">,</span> <span class="n">payload_len</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">err_msg</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">ret</span><span class="p">)))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nf">php_yar_error</span><span class="p">(</span><span class="n">response</span><span class="p">,</span> <span class="n">YAR_ERR_PACKAGER</span><span class="p">,</span> <span class="n">err_msg</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nf">efree</span><span class="p">(</span><span class="n">err_msg</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">goto</span> <span class="n">response_no_output</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">pkg_name</span> <span class="o">=</span> <span class="n">payload</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// 解析请求数据
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="n">request</span> <span class="o">=</span> <span class="nf">php_yar_request_unpack</span><span class="p">(</span><span class="n">post_data</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nf">php_output_start_user</span><span class="p">(</span><span class="nb">NULL</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">PHP_OUTPUT_HANDLER_STDFLAGS</span><span class="p">)</span> <span class="o">==</span> <span class="n">FAILURE</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nf">php_yar_error</span><span class="p">(</span><span class="n">response</span><span class="p">,</span> <span class="n">YAR_ERR_OUTPUT</span><span class="p">,</span> <span class="s">&#34;start output buffer failed&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">goto</span> <span class="n">response_no_output</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// 检查被调用方法是否存在
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nf">zend_hash_exists</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ce</span><span class="o">-&gt;</span><span class="n">function_table</span><span class="p">,</span> <span class="n">method</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nf">zend_string_release</span><span class="p">(</span><span class="n">method</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nf">php_yar_error</span><span class="p">(</span><span class="n">response</span><span class="p">,</span> <span class="n">YAR_ERR_REQUEST</span><span class="p">,</span> <span class="s">&#34;call to undefined api %s::%s()&#34;</span><span class="p">,</span> <span class="n">ce</span><span class="o">-&gt;</span><span class="n">name</span><span class="p">,</span> <span class="nf">ZSTR_VAL</span><span class="p">(</span><span class="n">request</span><span class="o">-&gt;</span><span class="n">method</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">        <span class="k">goto</span> <span class="n">response</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">zend_try</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// 初始化被调用方法的参数
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="n">func_params_ht</span> <span class="o">=</span> <span class="nf">Z_ARRVAL</span><span class="p">(</span><span class="n">request</span><span class="o">-&gt;</span><span class="n">parameters</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">count</span> <span class="o">=</span> <span class="nf">zend_hash_num_elements</span><span class="p">(</span><span class="n">func_params_ht</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">count</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="n">func_params</span> <span class="o">=</span> <span class="nf">safe_emalloc</span><span class="p">(</span><span class="k">sizeof</span><span class="p">(</span><span class="n">zval</span><span class="p">),</span> <span class="n">count</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">            <span class="nf">ZEND_HASH_FOREACH_VAL</span><span class="p">(</span><span class="n">func_params_ht</span><span class="p">,</span> <span class="n">tmp_zval</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="nf">ZVAL_COPY</span><span class="p">(</span><span class="o">&amp;</span><span class="n">func_params</span><span class="p">[</span><span class="n">i</span><span class="o">++</span><span class="p">],</span> <span class="n">tmp_zval</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span> <span class="nf">ZEND_HASH_FOREACH_END</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">func_params</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="nf">ZVAL_STR</span><span class="p">(</span><span class="o">&amp;</span><span class="n">func</span><span class="p">,</span> <span class="n">request</span><span class="o">-&gt;</span><span class="n">method</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// 执行被调用的方法
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="k">if</span> <span class="p">(</span><span class="n">FAILURE</span> <span class="o">==</span> <span class="nf">call_user_function</span><span class="p">(</span><span class="nb">NULL</span><span class="p">,</span> <span class="n">obj</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">func</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">retval</span><span class="p">,</span> <span class="n">count</span><span class="p">,</span> <span class="n">func_params</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">(</span><span class="n">count</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="k">for</span> <span class="p">(;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">count</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                    <span class="nf">zval_ptr_dtor</span><span class="p">(</span><span class="o">&amp;</span><span class="n">func_params</span><span class="p">[</span><span class="n">i</span><span class="p">]);</span>
</span></span><span class="line"><span class="cl">                <span class="p">}</span>
</span></span><span class="line"><span class="cl">                <span class="nf">efree</span><span class="p">(</span><span class="n">func_params</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="nf">php_yar_error</span><span class="p">(</span><span class="n">response</span><span class="p">,</span> <span class="n">YAR_ERR_REQUEST</span><span class="p">,</span> <span class="s">&#34;call to api %s::%s() failed&#34;</span><span class="p">,</span> <span class="n">ce</span><span class="o">-&gt;</span><span class="n">name</span><span class="p">,</span> <span class="n">request</span><span class="o">-&gt;</span><span class="n">method</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="k">goto</span> <span class="n">response</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        
</span></span><span class="line"><span class="cl">        <span class="c1">// 释放参数
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="k">if</span> <span class="p">(</span><span class="n">count</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="k">for</span> <span class="p">(;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">count</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="nf">zval_ptr_dtor</span><span class="p">(</span><span class="o">&amp;</span><span class="n">func_params</span><span class="p">[</span><span class="n">i</span><span class="p">]);</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="nf">efree</span><span class="p">(</span><span class="n">func_params</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span> <span class="n">zend_catch</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">bailout</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span> <span class="nf">zend_end_try</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// 如果发生异常则响应异常信息
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">if</span> <span class="p">(</span><span class="nf">EG</span><span class="p">(</span><span class="n">exception</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">zend_object</span> <span class="o">*</span><span class="n">exception</span> <span class="o">=</span> <span class="nf">EG</span><span class="p">(</span><span class="n">exception</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nf">php_yar_response_set_exception</span><span class="p">(</span><span class="n">response</span><span class="p">,</span> <span class="n">exception</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nf">EG</span><span class="p">(</span><span class="n">exception</span><span class="p">)</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span> <span class="cm">/* exception may have __destruct will be called */</span>
</span></span><span class="line"><span class="cl">        <span class="nf">OBJ_RELEASE</span><span class="p">(</span><span class="n">exception</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nf">zend_clear_exception</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// 正常响应
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nl">response</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">   
</span></span><span class="line"><span class="cl">    <span class="c1">// 获取输出到缓冲区的内容
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">if</span> <span class="p">(</span><span class="nf">php_output_get_contents</span><span class="p">(</span><span class="o">&amp;</span><span class="n">output</span><span class="p">)</span> <span class="o">==</span> <span class="n">FAILURE</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nf">php_output_end</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="nf">php_yar_error</span><span class="p">(</span><span class="n">response</span><span class="p">,</span> <span class="n">YAR_ERR_OUTPUT</span><span class="p">,</span> <span class="s">&#34;unable to get ob content&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">goto</span> <span class="n">response_no_output</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="c1">// 将缓冲区的内容保存到 response 的 out 中
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nf">php_yar_response_alter_body</span><span class="p">(</span><span class="n">response</span><span class="p">,</span> <span class="nf">Z_STR</span><span class="p">(</span><span class="n">output</span><span class="p">),</span> <span class="n">YAR_RESPONSE_REPLACE</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// 无输出响应
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nl">response_no_output</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// 组装并输出响应内容
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nf">php_yar_server_response</span><span class="p">(</span><span class="n">request</span><span class="p">,</span> <span class="n">response</span><span class="p">,</span> <span class="n">pkg_name</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">request</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// 释放请求对象
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="nf">php_yar_request_destroy</span><span class="p">(</span><span class="n">request</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// 释放响应对象
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nf">php_yar_response_destroy</span><span class="p">(</span><span class="n">response</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">bailout</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nf">zend_bailout</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h2 id="总结">总结</h2>
<p>在这篇文章中，介绍了 Yar 服务端是如何处理客户端请求的，主要是三个步骤：解析请求数据、执行被调用方法、返回响应结果。结合之前的文章就可以知道在一次远程调用的过程中，客户端、服务端都分别做了些什么。</p>
]]></content:encoded>
    </item>
    <item>
      <title>Yar 源码阅读笔记：客户端的并行调用</title>
      <link>https://her-cat.com/posts/2022/01/14/yar-internals-client-concurrent-call/</link>
      <pubDate>Fri, 14 Jan 2022 19:50:08 +0800</pubDate>
      <guid>https://her-cat.com/posts/2022/01/14/yar-internals-client-concurrent-call/</guid>
      <description>主要介绍实现并行调用相关的数据结构，并通过 GDB 调试代码的方式展示并行调用运行的过程。</description>
      <content:encoded><![CDATA[<h2 id="前言">前言</h2>
<p>在上一篇文章中，介绍了客户端同步调用的具体实现的，主要还是通过调用传输模块的相关函数，完成发送和接收远程调用的数据。</p>
<p>在调用多个远程方法时，同步调用是以串行的方式执行的，导致运行效率比较低，所以需要使用并行调用来提高调用多个远程方法的运行效率，减少整体运行的时间。</p>
<h2 id="并行传输模块">并行传输模块</h2>
<h3 id="yar_transport_multi_t">yar_transport_multi_t</h3>
<p>在数据传输模块中，我们提到了 yar_transport_multi_t *multi 就是用来实现并行调用的，先来看看该结构体的定义。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// source：yar_transport.h
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="c1">// 并行传输器的工厂
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">typedef</span> <span class="k">struct</span> <span class="n">_yar_transport_multi</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">struct</span> <span class="n">_yar_transport_multi_interface</span> <span class="o">*</span> <span class="p">(</span><span class="o">*</span><span class="n">init</span><span class="p">)();</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="kt">yar_transport_multi_t</span><span class="p">;</span>
</span></span></code></pre></div><p>可以将 yar_transport_multi_t 看作是并行传输器的工厂，使用 init 函数创建并行传输器的实例，也就是为 _yar_transport_multi_interface 结构体分配内存，并初始化相关配置。</p>
<h3 id="yar_transport_multi_interface_t">yar_transport_multi_interface_t</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// source：yar_transport.h
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="c1">// 并行传输器
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">typedef</span> <span class="k">struct</span> <span class="n">_yar_transport_multi_interface</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">void</span> <span class="o">*</span><span class="n">data</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">int</span> <span class="p">(</span><span class="o">*</span><span class="n">add</span><span class="p">)(</span><span class="k">struct</span> <span class="n">_yar_transport_multi_interface</span> <span class="o">*</span><span class="n">self</span><span class="p">,</span>     <span class="kt">yar_transport_interface_t</span> <span class="o">*</span><span class="n">cp</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="kt">int</span> <span class="p">(</span><span class="o">*</span><span class="n">exec</span><span class="p">)(</span><span class="k">struct</span> <span class="n">_yar_transport_multi_interface</span> <span class="o">*</span><span class="n">self</span><span class="p">,</span>     <span class="n">yar_concurrent_client_callback</span> <span class="o">*</span><span class="n">callback</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="kt">void</span> <span class="p">(</span><span class="o">*</span><span class="n">close</span><span class="p">)(</span><span class="k">struct</span> <span class="n">_yar_transport_multi_interface</span> <span class="o">*</span><span class="n">self</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="kt">yar_transport_multi_interface_t</span><span class="p">;</span>
</span></span></code></pre></div><p>其实 yar_transport_multi_interface_t 结构体和 yar_transport_interface_t 结构体类似，都是用来定义在传输时使用的函数，下面是字段说明：</p>
<ul>
<li>data：存储并行传输器的数据指针，指向 yar_curl_multi_data_t 结构体。</li>
<li>add：函数指针，将一个同步传输器（yar_transport_interface_t）实例中的数据存储到并行传输器中。</li>
<li>exec：函数指针，执行请求，将所有的同步传输器实例中的数据发送出去。</li>
<li>close：函数指针，释放相关资源。</li>
</ul>
<p>从上面的说明可以看出来，并行传输器实际上是在管理多个同步传输器实例，先调用 add 函数将同步传输器实例存储到并行传输器中，然后通过 exec 函数将这些同步传输器实例中的数据发送出去，并对响应结果进行处理，最后调用 close 函数清理相关资源。</p>
<h3 id="yar_curl_multi_data_t">yar_curl_multi_data_t</h3>
<p>虽然 yar_curl_multi_data_t 结构体的字段比较少，但是通过这个结构体，可以知道并行传输器是如何存储同步传输器实例的数据的。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// source：transports/curl.c
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="k">typedef</span> <span class="k">struct</span> <span class="n">_yar_curl_multi_data_t</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">CURLM</span> <span class="o">*</span><span class="n">cm</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">yar_transport_interface_t</span> <span class="o">*</span><span class="n">chs</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="kt">yar_curl_multi_data_t</span><span class="p">;</span>
</span></span></code></pre></div><p>cm 字段用来存储 curl 批处理实例的指针，chs 则是同步传输器的指针，同步传输器中 data 字段指向的结构体中有一个 next 字段，用来指向下一个同步传输器：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// source：transports/curl.c
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="k">typedef</span> <span class="k">struct</span> <span class="n">_yar_curl_data_t</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="kt">yar_transport_interface_t</span> <span class="o">*</span><span class="n">next</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="kt">yar_curl_data_t</span><span class="p">;</span>
</span></span></code></pre></div><p>看到这里你应该就明白了，Yar 通过单链表的结构将所有的同步传输器实例串起来， yar_curl_multi_data_t 中的 chs 字段相当于头指针。</p>
<p><img decoding="async" height="386" loading="lazy" sizes="(max-width: 768px) 100vw, (max-width: 1024px) 768px, 1024px" src="/posts/2022/01/14/yar-internals-client-concurrent-call/yar-concurrent-call-data.png" srcset="/posts/2022/01/14/yar-internals-client-concurrent-call/yar-concurrent-call-data_hu_95be10b6b4e6abfa.png 384w, /posts/2022/01/14/yar-internals-client-concurrent-call/yar-concurrent-call-data_hu_347ecb3b272b64de.png 768w, /posts/2022/01/14/yar-internals-client-concurrent-call/yar-concurrent-call-data_hu_be303216934272d.png 1024w" style="max-width: 100%; height: auto; aspect-ratio: 3.5104;" width="1355"></p>
<h2 id="并行客户端">并行客户端</h2>
<p>先来看看并行客户端的方法列表及属性。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// source：yar_client.c
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl">    <span class="c1">// 方法列表
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="n">zend_function_entry</span> <span class="n">yar_concurrent_client_methods</span><span class="p">[]</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nf">PHP_ME</span><span class="p">(</span><span class="n">yar_concurrent_client</span><span class="p">,</span> <span class="n">call</span><span class="p">,</span> <span class="n">arginfo_client_async</span><span class="p">,</span> <span class="n">ZEND_ACC_PUBLIC</span><span class="o">|</span><span class="n">ZEND_ACC_STATIC</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="nf">PHP_ME</span><span class="p">(</span><span class="n">yar_concurrent_client</span><span class="p">,</span> <span class="n">loop</span><span class="p">,</span> <span class="n">arginfo_client_loop</span><span class="p">,</span> <span class="n">ZEND_ACC_PUBLIC</span><span class="o">|</span><span class="n">ZEND_ACC_STATIC</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="nf">PHP_ME</span><span class="p">(</span><span class="n">yar_concurrent_client</span><span class="p">,</span> <span class="n">reset</span><span class="p">,</span><span class="n">arginfo_client_void</span><span class="p">,</span> <span class="n">ZEND_ACC_PUBLIC</span><span class="o">|</span><span class="n">ZEND_ACC_STATIC</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">PHP_FE_END</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nf">INIT_CLASS_ENTRY</span><span class="p">(</span><span class="n">ce</span><span class="p">,</span> <span class="s">&#34;Yar_Concurrent_Client&#34;</span><span class="p">,</span> <span class="n">yar_concurrent_client_methods</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">yar_concurrent_client_ce</span> <span class="o">=</span> <span class="nf">zend_register_internal_class</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ce</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// 声明并行客户端的属性
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nf">zend_declare_property_null</span><span class="p">(</span><span class="n">yar_concurrent_client_ce</span><span class="p">,</span> <span class="nf">ZEND_STRL</span><span class="p">(</span><span class="s">&#34;_callstack&#34;</span><span class="p">),</span> <span class="n">ZEND_ACC_PROTECTED</span><span class="o">|</span><span class="n">ZEND_ACC_STATIC</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nf">zend_declare_property_null</span><span class="p">(</span><span class="n">yar_concurrent_client_ce</span><span class="p">,</span> <span class="nf">ZEND_STRL</span><span class="p">(</span><span class="s">&#34;_callback&#34;</span><span class="p">),</span> <span class="n">ZEND_ACC_PROTECTED</span><span class="o">|</span><span class="n">ZEND_ACC_STATIC</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nf">zend_declare_property_null</span><span class="p">(</span><span class="n">yar_concurrent_client_ce</span><span class="p">,</span> <span class="nf">ZEND_STRL</span><span class="p">(</span><span class="s">&#34;_error_callback&#34;</span><span class="p">),</span> <span class="n">ZEND_ACC_PROTECTED</span><span class="o">|</span><span class="n">ZEND_ACC_STATIC</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nf">zend_declare_property_bool</span><span class="p">(</span><span class="n">yar_concurrent_client_ce</span><span class="p">,</span> <span class="nf">ZEND_STRL</span><span class="p">(</span><span class="s">&#34;_start&#34;</span><span class="p">),</span> <span class="mi">0</span><span class="p">,</span> <span class="n">ZEND_ACC_PROTECTED</span><span class="o">|</span><span class="n">ZEND_ACC_STATIC</span><span class="p">);</span>
</span></span></code></pre></div><p>由此我们可以知道并行客户端大概长这样：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="k">class</span> <span class="err">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">protected</span> <span class="k">static</span> <span class="nv">$_callstack</span> <span class="o">=</span> <span class="k">null</span><span class="p">;</span> <span class="c1">// 存储每个调用的数据
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">protected</span> <span class="k">static</span> <span class="nv">$_callback</span> <span class="o">=</span> <span class="k">null</span><span class="p">;</span> <span class="c1">// 并行客户端的回调函数
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">protected</span> <span class="k">static</span> <span class="nv">$_error_callback</span> <span class="o">=</span> <span class="k">null</span><span class="p">;</span> <span class="c1">// 并行客户端的异常回调函数
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">protected</span> <span class="k">static</span> <span class="nv">$_start</span> <span class="o">=</span> <span class="k">false</span><span class="p">;</span> <span class="c1">// 是否已启动
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    
</span></span><span class="line"><span class="cl">    <span class="c1">// 调用某个方法
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">public</span> <span class="k">static</span> <span class="k">function</span> <span class="nf">call</span><span class="p">(</span><span class="nv">$uri</span><span class="p">,</span> <span class="nv">$method</span><span class="p">,</span> <span class="nv">$parameters</span> <span class="o">=</span> <span class="k">null</span><span class="p">,</span> <span class="nv">$callback</span> <span class="o">=</span> <span class="k">null</span><span class="p">,</span> <span class="nv">$error_callback</span> <span class="o">=</span> <span class="k">null</span><span class="p">,</span> <span class="nv">$options</span> <span class="o">=</span> <span class="k">array</span><span class="p">())</span> 
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="c1">// 执行调用并等待响应
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">public</span> <span class="k">static</span> <span class="k">function</span> <span class="nf">loop</span><span class="p">(</span><span class="nv">$callback</span> <span class="o">=</span> <span class="k">null</span><span class="p">,</span> <span class="nv">$error_callback</span> <span class="o">=</span> <span class="k">null</span><span class="p">)</span> 
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="c1">// 重置并行客户端
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">public</span> <span class="k">static</span> <span class="k">function</span> <span class="nf">reset</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Yar 定义了 yar_call_data_t 结构体将 call 方法的参数存起来，当作远程调用的数据。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// source：yar_transport.h
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="k">typedef</span> <span class="k">struct</span> <span class="n">_yar_call_data</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">zend_long</span> <span class="n">sequence</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">zend_string</span> <span class="o">*</span><span class="n">uri</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">zend_string</span> <span class="o">*</span><span class="n">method</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">zval</span> <span class="n">callback</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">zval</span> <span class="n">ecallback</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">zval</span> <span class="n">parameters</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">zval</span> <span class="n">options</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="kt">yar_call_data_t</span><span class="p">;</span>
</span></span></code></pre></div><p>在 PHP 中使用该结构体前，需要在 Yar 启动时向 PHP 注册该资源类型，同时传入析构函数，用于 PHP 释放该资源的时候调用。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="nf">YAR_STARTUP_FUNCTION</span><span class="p">(</span><span class="n">transport</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// ... 
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl">    <span class="n">le_calldata</span> <span class="o">=</span> <span class="nf">zend_register_list_destructors_ex</span><span class="p">(</span><span class="n">php_yar_calldata_dtor</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="s">&#34;Yar Call Data&#34;</span><span class="p">,</span> <span class="n">module_number</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">SUCCESS</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>zend_register_list_destructors_ex 函数会返回一个整数，表示注册的资源类型，在取回资源时需要用到该整数。</p>
<h2 id="并行调用">并行调用</h2>
<p>这次我们使用 gdb 调试的方式，一步步的展示并行调用的过程，在此之前需要编译好 Yar，编译时记得关闭编译优化，并在 php.ini 中配置好扩展，然后启动开篇中的 <a href="/posts/2021/10/17/yar-internals-start/#Yar-%E6%9C%8D%E5%8A%A1%E7%AB%AF">Yar 服务端示例</a>。</p>
<h3 id="yar_concurrent_clientcall">Yar_Concurrent_Client::call</h3>
<p>在<a href="/posts/2021/10/17/yar-internals-start/#%E5%B9%B6%E8%A1%8C%E8%B0%83%E7%94%A8">客户端的并行调用示例</a>中，先调用了 call 方法，然后调用了 loop 方法，所以我们分别在这两个方法的上打断点。通过上面并行客户端的介绍可以知道，实现 call 方法的函数是 <strong>zim_yar_concurrent_client_call</strong>，我们在该函数上打断点即可。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="err">$</span> <span class="n">gdb</span> <span class="nf">php</span>
</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="n">gdb</span><span class="p">)</span> <span class="n">b</span> <span class="n">zim_yar_concurrent_client_call</span>
</span></span><span class="line"><span class="cl"><span class="n">Function</span> <span class="s">&#34;zim_yar_concurrent_client_call&#34;</span> <span class="n">not</span> <span class="n">defined</span><span class="p">.</span>
</span></span><span class="line"><span class="cl"><span class="n">Make</span> <span class="n">breakpoint</span> <span class="n">pending</span> <span class="n">on</span> <span class="n">future</span> <span class="n">shared</span> <span class="n">library</span> <span class="n">load</span><span class="o">?</span> <span class="p">(</span><span class="n">y</span> <span class="n">or</span> <span class="p">[</span><span class="n">n</span><span class="p">])</span> <span class="n">y</span>
</span></span><span class="line"><span class="cl"><span class="n">Breakpoint</span> <span class="mi">1</span> <span class="p">(</span><span class="n">zim_yar_concurrent_client_call</span><span class="p">)</span> <span class="n">pending</span><span class="p">.</span>
</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="n">gdb</span><span class="p">)</span>
</span></span></code></pre></div><p>开始运行 PHP 并指定运行的 PHP 脚本。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="p">(</span><span class="n">gdb</span><span class="p">)</span> <span class="n">r</span> <span class="n">concurrent_client</span><span class="p">.</span><span class="n">php</span>
</span></span><span class="line"><span class="cl"><span class="n">Starting</span> <span class="nl">program</span><span class="p">:</span> <span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">bin</span><span class="o">/</span><span class="n">php</span> <span class="n">concurrent_client</span><span class="p">.</span><span class="n">php</span>
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="n">Thread</span> <span class="n">debugging</span> <span class="n">using</span> <span class="n">libthread_db</span> <span class="n">enabled</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="n">Using</span> <span class="n">host</span> <span class="n">libthread_db</span> <span class="n">library</span> <span class="s">&#34;/lib/x86_64-linux-gnu/libthread_db.so.1&#34;</span><span class="p">.</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">Breakpoint</span> <span class="mi">1</span><span class="p">,</span> <span class="nf">zim_yar_concurrent_client_call</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">execute_data</span><span class="o">=</span><span class="mh">0x5555557de9ee</span> <span class="o">&lt;</span><span class="n">zend_restore_lexical_state</span><span class="o">+</span><span class="mi">222</span><span class="o">&gt;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">return_value</span><span class="o">=</span><span class="mh">0x555555a2b240</span> <span class="o">&lt;</span><span class="n">language_scanner_globals</span><span class="o">&gt;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">at</span> <span class="o">/</span><span class="n">home</span><span class="o">/</span><span class="n">vagrant</span><span class="o">/</span><span class="n">code</span><span class="o">/</span><span class="n">her</span><span class="o">-</span><span class="n">cat</span><span class="o">/</span><span class="n">yar</span><span class="o">/</span><span class="n">yar_client</span><span class="p">.</span><span class="nl">c</span><span class="p">:</span><span class="mi">645</span>
</span></span><span class="line"><span class="cl"><span class="mi">645</span>    <span class="nf">PHP_METHOD</span><span class="p">(</span><span class="n">yar_concurrent_client</span><span class="p">,</span> <span class="n">call</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="n">gdb</span><span class="p">)</span>
</span></span></code></pre></div><p>zim_yar_concurrent_client_call 函数作用是将 call 方法的参数组装为 yar_call_data_t 结构体，并存储到并行客户端的 _callstack 数组中，然后返回本次调用的序号。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// source：yar_client.c
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="nf">PHP_METHOD</span><span class="p">(</span><span class="n">yar_concurrent_client</span><span class="p">,</span> <span class="n">call</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">zend_string</span> <span class="o">*</span><span class="n">uri</span><span class="p">,</span> <span class="o">*</span><span class="n">method</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">zend_string</span> <span class="o">*</span><span class="n">name</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">zval</span> <span class="o">*</span><span class="n">callstack</span><span class="p">,</span> <span class="n">item</span><span class="p">,</span> <span class="o">*</span><span class="n">status</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">zval</span> <span class="o">*</span><span class="n">error_callback</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">,</span> <span class="o">*</span><span class="n">callback</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">,</span> <span class="o">*</span><span class="n">parameters</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">,</span> <span class="o">*</span><span class="n">options</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">yar_call_data_t</span> <span class="o">*</span><span class="n">entry</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// 解析 call 方法中的参数
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">if</span> <span class="p">(</span><span class="nf">zend_parse_parameters</span><span class="p">(</span><span class="nf">ZEND_NUM_ARGS</span><span class="p">(),</span> <span class="s">&#34;SS|a!z!za&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="o">&amp;</span><span class="n">uri</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">method</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">parameters</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">callback</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">error_callback</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">options</span><span class="p">)</span> <span class="o">==</span> <span class="n">FAILURE</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// 省略一些对参数校验的代码...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="c1">// 校验并行客户端的状态
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="n">status</span> <span class="o">=</span> <span class="nf">zend_read_static_property</span><span class="p">(</span><span class="n">yar_concurrent_client_ce</span><span class="p">,</span> <span class="nf">ZEND_STRL</span><span class="p">(</span><span class="s">&#34;_start&#34;</span><span class="p">),</span> <span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nf">UNEXPECTED</span><span class="p">(</span><span class="nf">Z_TYPE_P</span><span class="p">(</span><span class="n">status</span><span class="p">)</span> <span class="o">==</span> <span class="n">IS_TRUE</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nf">php_error_docref</span><span class="p">(</span><span class="nb">NULL</span><span class="p">,</span> <span class="n">E_WARNING</span><span class="p">,</span> <span class="s">&#34;concurrent client has already started&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">RETURN_FALSE</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// 分配调用数据的内存
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="n">entry</span> <span class="o">=</span> <span class="nf">ecalloc</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="kt">yar_call_data_t</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// 将参数赋值到调用数据上
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="n">entry</span><span class="o">-&gt;</span><span class="n">uri</span> <span class="o">=</span> <span class="nf">zend_string_copy</span><span class="p">(</span><span class="n">uri</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">entry</span><span class="o">-&gt;</span><span class="n">method</span> <span class="o">=</span> <span class="nf">zend_string_copy</span><span class="p">(</span><span class="n">method</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">callback</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="nf">Z_ISNULL_P</span><span class="p">(</span><span class="n">callback</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nf">ZVAL_COPY</span><span class="p">(</span><span class="o">&amp;</span><span class="n">entry</span><span class="o">-&gt;</span><span class="n">callback</span><span class="p">,</span> <span class="n">callback</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">error_callback</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="nf">Z_ISNULL_P</span><span class="p">(</span><span class="n">error_callback</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nf">ZVAL_COPY</span><span class="p">(</span><span class="o">&amp;</span><span class="n">entry</span><span class="o">-&gt;</span><span class="n">ecallback</span><span class="p">,</span> <span class="n">error_callback</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">parameters</span> <span class="o">&amp;&amp;</span> <span class="n">IS_ARRAY</span> <span class="o">==</span> <span class="nf">Z_TYPE_P</span><span class="p">(</span><span class="n">parameters</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nf">ZVAL_COPY</span><span class="p">(</span><span class="o">&amp;</span><span class="n">entry</span><span class="o">-&gt;</span><span class="n">parameters</span><span class="p">,</span> <span class="n">parameters</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">options</span> <span class="o">&amp;&amp;</span> <span class="n">IS_ARRAY</span> <span class="o">==</span> <span class="nf">Z_TYPE_P</span><span class="p">(</span><span class="n">options</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nf">ZVAL_COPY</span><span class="p">(</span><span class="o">&amp;</span><span class="n">entry</span><span class="o">-&gt;</span><span class="n">options</span><span class="p">,</span> <span class="n">options</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// 初始化调用栈数组
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="n">callstack</span> <span class="o">=</span> <span class="nf">zend_read_static_property</span><span class="p">(</span><span class="n">yar_concurrent_client_ce</span><span class="p">,</span> <span class="nf">ZEND_STRL</span><span class="p">(</span><span class="s">&#34;_callstack&#34;</span><span class="p">),</span> <span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nf">Z_ISNULL_P</span><span class="p">(</span><span class="n">callstack</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">zval</span> <span class="n">rv</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="nf">array_init</span><span class="p">(</span><span class="o">&amp;</span><span class="n">rv</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nf">zend_update_static_property</span><span class="p">(</span><span class="n">yar_concurrent_client_ce</span><span class="p">,</span> <span class="nf">ZEND_STRL</span><span class="p">(</span><span class="s">&#34;_callstack&#34;</span><span class="p">),</span> <span class="o">&amp;</span><span class="n">rv</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nf">ZVAL_ARR</span><span class="p">(</span><span class="n">callstack</span><span class="p">,</span> <span class="nf">Z_ARRVAL</span><span class="p">(</span><span class="n">rv</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">        <span class="nf">Z_DELREF</span><span class="p">(</span><span class="n">rv</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// 将调用数据注册到 zend 中，并返回 zend_resouce 类型的数据
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nf">ZVAL_RES</span><span class="p">(</span><span class="o">&amp;</span><span class="n">item</span><span class="p">,</span> <span class="nf">zend_register_resource</span><span class="p">(</span><span class="n">entry</span><span class="p">,</span> <span class="n">le_calldata</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// 设置调用数据的序号
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="n">entry</span><span class="o">-&gt;</span><span class="n">sequence</span> <span class="o">=</span> <span class="nf">zend_hash_num_elements</span><span class="p">(</span><span class="nf">Z_ARRVAL_P</span><span class="p">(</span><span class="n">callstack</span><span class="p">))</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// 将 zend_resource 类型的调用数据存储到 callstack 中
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nf">zend_hash_next_index_insert</span><span class="p">(</span><span class="nf">Z_ARRVAL_P</span><span class="p">(</span><span class="n">callstack</span><span class="p">),</span> <span class="o">&amp;</span><span class="n">item</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nf">RETURN_LONG</span><span class="p">(</span><span class="n">entry</span><span class="o">-&gt;</span><span class="n">sequence</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>让代码执行到最后一行，并打印 entry 中的数据。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">(gdb) u 733
</span></span><span class="line"><span class="cl">zim_yar_concurrent_client_call (execute_data=0x7ffff54130d0,
</span></span><span class="line"><span class="cl">    return_value=0x7fffffffaa80)
</span></span><span class="line"><span class="cl">    at /home/vagrant/code/her-cat/yar/yar_client.c:733
</span></span><span class="line"><span class="cl">733        RETURN_LONG(entry-&gt;sequence);
</span></span><span class="line"><span class="cl">(gdb) p *entry-&gt;uri.val@32
</span></span><span class="line"><span class="cl">$5 = &#34;http://127.0.0.1:3000/server.php&#34;
</span></span><span class="line"><span class="cl">(gdb) p *entry-&gt;method.val@5
</span></span><span class="line"><span class="cl">$6 = &#34;login&#34;
</span></span><span class="line"><span class="cl">(gdb) p entry-&gt;sequence
</span></span><span class="line"><span class="cl">$7 = 1
</span></span></code></pre></div><h3 id="yar_concurrent_clientloop">Yar_Concurrent_Client::loop</h3>
<p>先在 loop 方法上打断点，然后让代码执行到断点处。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">(gdb) b zim_yar_concurrent_client_loop
</span></span><span class="line"><span class="cl">Breakpoint 2 at 0x7ffff56e97f7: file /home/vagrant/code/her-cat/yar/yar_client.c, line 751.
</span></span><span class="line"><span class="cl">(gdb) c
</span></span><span class="line"><span class="cl">Continuing.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Breakpoint 1, zim_yar_concurrent_client_call (execute_data=0x7ffff54130d0, return_value=0x7fffffffaa80)
</span></span><span class="line"><span class="cl">    at /home/vagrant/code/her-cat/yar/yar_client.c:645
</span></span><span class="line"><span class="cl">645    PHP_METHOD(yar_concurrent_client, call) {
</span></span><span class="line"><span class="cl">(gdb)
</span></span></code></pre></div><p>在 zim_yar_concurrent_client_loop 函数中，先校验 loop 方法的参数及并行客户端的运行状态，然后从并行客户端中读取 _callstack 数组，调用 php_yar_concurrent_client_handle 函数。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// source：yar_client.c
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="nf">PHP_METHOD</span><span class="p">(</span><span class="n">yar_concurrent_client</span><span class="p">,</span> <span class="n">loop</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">zend_string</span> <span class="o">*</span><span class="n">name</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">zval</span> <span class="o">*</span><span class="n">callstack</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">zval</span> <span class="o">*</span><span class="n">callback</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">,</span> <span class="o">*</span><span class="n">error_callback</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">zval</span> <span class="o">*</span><span class="n">status</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">unsigned</span> <span class="n">ret</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// 解析参数
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">if</span> <span class="p">(</span><span class="nf">zend_parse_parameters</span><span class="p">(</span><span class="nf">ZEND_NUM_ARGS</span><span class="p">(),</span> <span class="s">&#34;|zz&#34;</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">callback</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">error_callback</span><span class="p">)</span> <span class="o">==</span> <span class="n">FAILURE</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// 判断运行状态
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="n">status</span> <span class="o">=</span> <span class="nf">zend_read_static_property</span><span class="p">(</span><span class="n">yar_concurrent_client_ce</span><span class="p">,</span> <span class="nf">ZEND_STRL</span><span class="p">(</span><span class="s">&#34;_start&#34;</span><span class="p">),</span> <span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nf">UNEXPECTED</span><span class="p">(</span><span class="nf">Z_TYPE_P</span><span class="p">(</span><span class="n">status</span><span class="p">)</span> <span class="o">==</span> <span class="n">IS_TRUE</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nf">php_error_docref</span><span class="p">(</span><span class="nb">NULL</span><span class="p">,</span> <span class="n">E_WARNING</span><span class="p">,</span> <span class="s">&#34;concurrent client has already started&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">RETURN_FALSE</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="c1">// 省略一些对参数校验的代码...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl">    <span class="c1">// 读取所有的调用数据
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="n">callstack</span> <span class="o">=</span> <span class="nf">zend_read_static_property</span><span class="p">(</span><span class="n">yar_concurrent_client_ce</span><span class="p">,</span> <span class="nf">ZEND_STRL</span><span class="p">(</span><span class="s">&#34;_callstack&#34;</span><span class="p">),</span> <span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nf">Z_ISNULL_P</span><span class="p">(</span><span class="n">callstack</span><span class="p">)</span> <span class="o">||</span> <span class="nf">zend_hash_num_elements</span><span class="p">(</span><span class="nf">Z_ARRVAL_P</span><span class="p">(</span><span class="n">callstack</span><span class="p">))</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">RETURN_TRUE</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// 更新为运行中的状态
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nf">ZVAL_BOOL</span><span class="p">(</span><span class="n">status</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">ret</span> <span class="o">=</span> <span class="nf">php_yar_concurrent_client_handle</span><span class="p">(</span><span class="n">callstack</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nf">ZVAL_BOOL</span><span class="p">(</span><span class="n">status</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nf">RETURN_BOOL</span><span class="p">(</span><span class="n">ret</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h3 id="php_yar_concurrent_client_handle">php_yar_concurrent_client_handle</h3>
<p>让代码执行到调用 php_yar_concurrent_client_handle 的前一行，然后进入函数内部。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">(gdb) u 801
</span></span><span class="line"><span class="cl">zim_yar_concurrent_client_loop (execute_data=0x7ffff54130d0, return_value=0x7fffffffaa80)
</span></span><span class="line"><span class="cl">    at /home/vagrant/code/her-cat/yar/yar_client.c:801
</span></span><span class="line"><span class="cl">801        ZVAL_BOOL(status, 1);
</span></span><span class="line"><span class="cl">(gdb) s
</span></span><span class="line"><span class="cl">802        ret = php_yar_concurrent_client_handle(callstack);
</span></span><span class="line"><span class="cl">(gdb) s
</span></span><span class="line"><span class="cl">php_yar_concurrent_client_handle (callstack=0x0) at /home/vagrant/code/her-cat/yar/yar_client.c:456
</span></span><span class="line"><span class="cl">456    int php_yar_concurrent_client_handle(zval *callstack) /* {{{ */ {
</span></span><span class="line"><span class="cl">(gdb)
</span></span></code></pre></div><p>php_yar_concurrent_client_handle 函数的主要逻辑：</p>
<ul>
<li>遍历 callstack 数组
<ul>
<li>为每一个调用数据创建同步传输器。</li>
<li>分别调用同步传输器中的 open、send、calldata 等函数初始化调用请求。</li>
<li>调用 multi-&gt;add 函数将这些同步传输器存储到并行调用器中。</li>
</ul>
</li>
<li>调用 multi-&gt;exec 函数将这些请求发送出去。</li>
<li>收到响应结果后调用 php_yar_concurrent_client_callback 函数进行处理。</li>
<li>最后调用 multi-&gt;close 函数释放相关资源。</li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// source：yar_client.c
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="kt">int</span> <span class="nf">php_yar_concurrent_client_handle</span><span class="p">(</span><span class="n">zval</span> <span class="o">*</span><span class="n">callstack</span><span class="p">)</span> <span class="cm">/* </span><span class="p">{{{</span> <span class="err">*/</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">char</span> <span class="o">*</span><span class="n">msg</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">zval</span> <span class="o">*</span><span class="n">calldata</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">yar_request_t</span> <span class="o">*</span><span class="n">request</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">const</span> <span class="kt">yar_transport_t</span> <span class="o">*</span><span class="n">factory</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">yar_transport_interface_t</span> <span class="o">*</span><span class="n">transport</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">yar_transport_multi_interface_t</span> <span class="o">*</span><span class="n">multi</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// 获取 curl 传输器工厂实例
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="n">factory</span> <span class="o">=</span> <span class="nf">php_yar_transport_get</span><span class="p">(</span><span class="nf">ZEND_STRL</span><span class="p">(</span><span class="s">&#34;curl&#34;</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// 创建并行传输器实例
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="n">multi</span> <span class="o">=</span> <span class="n">factory</span><span class="o">-&gt;</span><span class="n">multi</span><span class="o">-&gt;</span><span class="nf">init</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// 遍历 callstack 数组
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nf">ZEND_HASH_FOREACH_VAL</span><span class="p">(</span><span class="nf">Z_ARRVAL_P</span><span class="p">(</span><span class="n">callstack</span><span class="p">),</span> <span class="n">calldata</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kt">yar_call_data_t</span> <span class="o">*</span><span class="n">entry</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kt">long</span> <span class="n">flags</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">entry</span> <span class="o">=</span> <span class="p">(</span><span class="kt">yar_call_data_t</span> <span class="o">*</span><span class="p">)</span><span class="nf">zend_fetch_resource</span><span class="p">(</span><span class="nf">Z_RES_P</span><span class="p">(</span><span class="n">calldata</span><span class="p">),</span> <span class="s">&#34;Yar Call Data&#34;</span><span class="p">,</span> <span class="n">le_calldata</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">entry</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">continue</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="nf">Z_ISUNDEF</span><span class="p">(</span><span class="n">entry</span><span class="o">-&gt;</span><span class="n">parameters</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nf">array_init</span><span class="p">(</span><span class="o">&amp;</span><span class="n">entry</span><span class="o">-&gt;</span><span class="n">parameters</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span> 
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// 为每一个调用数据创建同步传输器
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="n">transport</span> <span class="o">=</span> <span class="n">factory</span><span class="o">-&gt;</span><span class="nf">init</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nf">Z_ISUNDEF</span><span class="p">(</span><span class="n">entry</span><span class="o">-&gt;</span><span class="n">options</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">zval</span> <span class="o">*</span><span class="n">flag</span> <span class="o">=</span> <span class="nf">php_yar_client_get_opt</span><span class="p">(</span><span class="o">&amp;</span><span class="n">entry</span><span class="o">-&gt;</span><span class="n">options</span><span class="p">,</span> <span class="n">YAR_OPT_PERSISTENT</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">(</span><span class="n">flag</span> <span class="o">&amp;&amp;</span> <span class="p">(</span><span class="nf">Z_TYPE_P</span><span class="p">(</span><span class="n">flag</span><span class="p">)</span> <span class="o">==</span> <span class="n">IS_TRUE</span> <span class="o">||</span> <span class="p">(</span><span class="nf">Z_TYPE_P</span><span class="p">(</span><span class="n">flag</span><span class="p">)</span> <span class="o">==</span> <span class="n">IS_LONG</span> <span class="o">&amp;&amp;</span> <span class="nf">Z_LVAL_P</span><span class="p">(</span><span class="n">flag</span><span class="p">))))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="n">flags</span> <span class="o">|=</span> <span class="n">YAR_PROTOCOL_PERSISTENT</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// 创建调用请求
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="p">(</span><span class="n">request</span> <span class="o">=</span> <span class="nf">php_yar_request_instance</span><span class="p">(</span><span class="n">entry</span><span class="o">-&gt;</span><span class="n">method</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="o">&amp;</span><span class="n">entry</span><span class="o">-&gt;</span><span class="n">parameters</span><span class="p">,</span> <span class="nf">Z_ISUNDEF</span><span class="p">(</span><span class="n">entry</span><span class="o">-&gt;</span><span class="n">options</span><span class="p">)</span><span class="o">?</span> <span class="nb">NULL</span><span class="o">:</span> <span class="o">&amp;</span> <span class="n">entry</span><span class="o">-&gt;</span><span class="n">options</span><span class="p">)))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">transport</span><span class="o">-&gt;</span><span class="nf">close</span><span class="p">(</span><span class="n">transport</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="n">factory</span><span class="o">-&gt;</span><span class="nf">destroy</span><span class="p">(</span><span class="n">transport</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// 创建一个连接
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="n">msg</span> <span class="o">=</span> <span class="p">(</span><span class="kt">char</span><span class="o">*</span><span class="p">)</span><span class="o">&amp;</span><span class="n">entry</span><span class="o">-&gt;</span><span class="n">options</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">transport</span><span class="o">-&gt;</span><span class="nf">open</span><span class="p">(</span><span class="n">transport</span><span class="p">,</span> <span class="n">entry</span><span class="o">-&gt;</span><span class="n">uri</span><span class="p">,</span> <span class="n">flags</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">msg</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nf">php_yar_client_trigger_error</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="n">YAR_ERR_TRANSPORT</span><span class="p">,</span> <span class="n">msg</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="n">transport</span><span class="o">-&gt;</span><span class="nf">close</span><span class="p">(</span><span class="n">transport</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="n">factory</span><span class="o">-&gt;</span><span class="nf">destroy</span><span class="p">(</span><span class="n">transport</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="nf">efree</span><span class="p">(</span><span class="n">msg</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// 对请求数据进行编码、组装等操作，并将处理后的数据保存到连接中
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">transport</span><span class="o">-&gt;</span><span class="nf">send</span><span class="p">(</span><span class="n">transport</span><span class="p">,</span> <span class="n">request</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">msg</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nf">php_yar_client_trigger_error</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="n">YAR_ERR_TRANSPORT</span><span class="p">,</span> <span class="n">msg</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="n">transport</span><span class="o">-&gt;</span><span class="nf">close</span><span class="p">(</span><span class="n">transport</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="n">factory</span><span class="o">-&gt;</span><span class="nf">destroy</span><span class="p">(</span><span class="n">transport</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="nf">efree</span><span class="p">(</span><span class="n">msg</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        
</span></span><span class="line"><span class="cl">        <span class="c1">// 将调用数据保存到同步传输器中
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="n">transport</span><span class="o">-&gt;</span><span class="nf">calldata</span><span class="p">(</span><span class="n">transport</span><span class="p">,</span> <span class="n">entry</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// 将同步传输器存储到并行调用器中。
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="n">multi</span><span class="o">-&gt;</span><span class="nf">add</span><span class="p">(</span><span class="n">multi</span><span class="p">,</span> <span class="n">transport</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// 释放请求
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="nf">php_yar_request_destroy</span><span class="p">(</span><span class="n">request</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span> <span class="nf">ZEND_HASH_FOREACH_END</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// 执行所有请求
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">multi</span><span class="o">-&gt;</span><span class="nf">exec</span><span class="p">(</span><span class="n">multi</span><span class="p">,</span> <span class="n">php_yar_concurrent_client_callback</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">multi</span><span class="o">-&gt;</span><span class="nf">close</span><span class="p">(</span><span class="n">multi</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// 释放资源
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="n">multi</span><span class="o">-&gt;</span><span class="nf">close</span><span class="p">(</span><span class="n">multi</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>我们看下并行传输器中函数指针对应的函数。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">(gdb) p *multi
</span></span><span class="line"><span class="cl">$10 = {data = 0x7ffff5461050, add = 0x7ffff56eea4e &lt;php_yar_curl_multi_add_handle&gt;,
</span></span><span class="line"><span class="cl">  exec = 0x7ffff56ef1e4 &lt;php_yar_curl_multi_exec&gt;, close = 0x7ffff56ef6af &lt;php_yar_curl_multi_close&gt;}
</span></span></code></pre></div><p>可以看到 add、exec、close 分别对应 php_yar_curl_multi_add_handle、php_yar_curl_multi_exec、php_yar_curl_multi_close 等函数，分别在这些函数上打断点，然后继续运行程序。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">(gdb) b php_yar_curl_multi_add_handle
</span></span><span class="line"><span class="cl">Breakpoint 3 at 0x7ffff56eea4e: file /home/vagrant/code/her-cat/yar/transports/curl.c, line 604.
</span></span><span class="line"><span class="cl">(gdb) b php_yar_curl_multi_exec
</span></span><span class="line"><span class="cl">Breakpoint 4 at 0x7ffff56ef1e4: file /home/vagrant/code/her-cat/yar/transports/curl.c, line 748.
</span></span><span class="line"><span class="cl">(gdb) b php_yar_curl_multi_close
</span></span><span class="line"><span class="cl">Breakpoint 5 at 0x7ffff56ef6af: file /home/vagrant/code/her-cat/yar/transports/curl.c, line 951.
</span></span><span class="line"><span class="cl">(gdb) c
</span></span><span class="line"><span class="cl">Continuing.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Breakpoint 3, php_yar_curl_multi_add_handle (self=0x7ffff5402428, handle=0x7ffff548e060)
</span></span><span class="line"><span class="cl">    at /home/vagrant/code/her-cat/yar/transports/curl.c:604
</span></span><span class="line"><span class="cl">warning: Source file is more recent than executable.
</span></span><span class="line"><span class="cl">604    int php_yar_curl_multi_add_handle(yar_transport_multi_interface_t *self, yar_transport_interface_t *handle) /* {{{ */ {
</span></span><span class="line"><span class="cl">(gdb)
</span></span></code></pre></div><h3 id="php_yar_curl_multi_add_handle">php_yar_curl_multi_add_handle</h3>
<p>php_yar_curl_multi_add_handle 函数的作用：将同步传输器实例以头插法保存到 chs 单链表中。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// source：transports/curl.c
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="kt">int</span> <span class="nf">php_yar_curl_multi_add_handle</span><span class="p">(</span><span class="kt">yar_transport_multi_interface_t</span> <span class="o">*</span><span class="n">self</span><span class="p">,</span> <span class="kt">yar_transport_interface_t</span> <span class="o">*</span><span class="n">handle</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">yar_curl_multi_data_t</span> <span class="o">*</span><span class="n">multi</span> <span class="o">=</span> <span class="p">(</span><span class="kt">yar_curl_multi_data_t</span> <span class="o">*</span><span class="p">)</span><span class="n">self</span><span class="o">-&gt;</span><span class="n">data</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">yar_curl_data_t</span> <span class="o">*</span><span class="n">data</span> <span class="o">=</span> <span class="p">(</span><span class="kt">yar_curl_data_t</span> <span class="o">*</span><span class="p">)</span><span class="n">handle</span><span class="o">-&gt;</span><span class="n">data</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// 预处理，将传输数据保存到 curl 中
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nf">php_yar_curl_prepare</span><span class="p">(</span><span class="n">handle</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// 将同步传输器的 curl 实例添加到 curl 批处理实例中
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nf">curl_multi_add_handle</span><span class="p">(</span><span class="n">multi</span><span class="o">-&gt;</span><span class="n">cm</span><span class="p">,</span> <span class="n">data</span><span class="o">-&gt;</span><span class="n">cp</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// 头插法保存
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">if</span> <span class="p">(</span><span class="n">multi</span><span class="o">-&gt;</span><span class="n">chs</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">data</span><span class="o">-&gt;</span><span class="n">next</span> <span class="o">=</span> <span class="n">multi</span><span class="o">-&gt;</span><span class="n">chs</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">multi</span><span class="o">-&gt;</span><span class="n">chs</span> <span class="o">=</span> <span class="n">handle</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">multi</span><span class="o">-&gt;</span><span class="n">chs</span> <span class="o">=</span> <span class="n">handle</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h3 id="php_yar_curl_multi_exec">php_yar_curl_multi_exec</h3>
<p>php_yar_curl_multi_exec 函数的作用：通过 epoll 或 select 多路复用机制监控文件描述符的状态，并配合 curl_multi_* 系列函数对请求进行处理。</p>
<p>以下是主要逻辑：</p>
<ul>
<li>从 curl 批处理实例中取出所有请求的文件描述符。</li>
<li>调用 select 函数监听文件描述符的状态，阻塞当前进程。</li>
<li>进程从休眠状态恢复后，调用 curl_multi_perform 处理响应数据。</li>
<li>调用 php_yar_curl_multi_parse_response 函数解析响应数据。</li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// source：transports/curl.c
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="kt">int</span> <span class="nf">php_yar_curl_multi_exec</span><span class="p">(</span><span class="kt">yar_transport_multi_interface_t</span> <span class="o">*</span><span class="n">self</span><span class="p">,</span> <span class="n">yar_concurrent_client_callback</span> <span class="o">*</span><span class="n">f</span><span class="p">)</span> <span class="cm">/* </span><span class="p">{{{</span> <span class="err">*/</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">int</span> <span class="n">running_count</span><span class="p">,</span> <span class="n">rest_count</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">yar_curl_multi_data_t</span> <span class="o">*</span><span class="n">multi</span> <span class="o">=</span> <span class="p">(</span><span class="kt">yar_curl_multi_data_t</span> <span class="o">*</span><span class="p">)</span><span class="n">self</span><span class="o">-&gt;</span><span class="n">data</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// 尝试让 libcurl 处理数据，并获取当前正在运行中的数量
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">while</span> <span class="p">(</span><span class="n">CURLM_CALL_MULTI_PERFORM</span> <span class="o">==</span> <span class="nf">curl_multi_perform</span><span class="p">(</span><span class="n">multi</span><span class="o">-&gt;</span><span class="n">cm</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">running_count</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// 第一次调用回调函数，让调用方执行自己的逻辑。
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nf">f</span><span class="p">(</span><span class="nb">NULL</span><span class="p">,</span> <span class="n">YAR_ERR_OKEY</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">goto</span> <span class="n">bailout</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">running_count</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">rest_count</span> <span class="o">=</span> <span class="n">running_count</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">do</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="kt">int</span> <span class="n">max_fd</span><span class="p">,</span> <span class="n">return_code</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="k">struct</span> <span class="n">timeval</span> <span class="n">tv</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="n">fd_set</span> <span class="n">readfds</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="n">fd_set</span> <span class="n">writefds</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="n">fd_set</span> <span class="n">exceptfds</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">            <span class="nf">FD_ZERO</span><span class="p">(</span><span class="o">&amp;</span><span class="n">readfds</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="nf">FD_ZERO</span><span class="p">(</span><span class="o">&amp;</span><span class="n">writefds</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="nf">FD_ZERO</span><span class="p">(</span><span class="o">&amp;</span><span class="n">exceptfds</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            
</span></span><span class="line"><span class="cl">            <span class="c1">// 从 curl 批处理实例中取出所有请求的文件描述符
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>            <span class="nf">curl_multi_fdset</span><span class="p">(</span><span class="n">multi</span><span class="o">-&gt;</span><span class="n">cm</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">readfds</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">writefds</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">exceptfds</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">max_fd</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">(</span><span class="n">max_fd</span> <span class="o">==</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="c1">// max_fd 为 -1 说明 libcurl 正在处理一些不能使用套接字监控的事情，
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>                <span class="c1">// 所以需要等待一会再调用 curl_multi_perform
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>                <span class="kt">long</span> <span class="n">timeout</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="nf">curl_multi_timeout</span><span class="p">(</span><span class="n">multi</span><span class="o">-&gt;</span><span class="n">cm</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">timeout</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">                <span class="k">if</span> <span class="p">(</span><span class="n">timeout</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                    <span class="n">timeout</span> <span class="o">=</span> <span class="mi">50</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="p">}</span>
</span></span><span class="line"><span class="cl">                <span class="k">if</span> <span class="p">(</span><span class="n">timeout</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                    <span class="n">tv</span><span class="p">.</span><span class="n">tv_sec</span> <span class="o">=</span> <span class="n">timeout</span> <span class="o">/</span> <span class="mi">1000</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                    <span class="n">tv</span><span class="p">.</span><span class="n">tv_usec</span> <span class="o">=</span> <span class="p">(</span><span class="n">timeout</span> <span class="o">%</span> <span class="mi">1000</span><span class="p">)</span> <span class="o">*</span> <span class="mi">1000</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                    <span class="nf">select</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">readfds</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">writefds</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">exceptfds</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">tv</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">                <span class="p">}</span>
</span></span><span class="line"><span class="cl">                <span class="c1">// 让 libcurl 处理数据
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>                <span class="k">while</span> <span class="p">(</span><span class="n">CURLM_CALL_MULTI_PERFORM</span> <span class="o">==</span> <span class="nf">curl_multi_perform</span><span class="p">(</span><span class="n">multi</span><span class="o">-&gt;</span><span class="n">cm</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">running_count</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">                <span class="k">goto</span> <span class="n">process</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">            <span class="c1">// 计算 select 超时时间
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>            <span class="n">tv</span><span class="p">.</span><span class="n">tv_sec</span> <span class="o">=</span> <span class="p">(</span><span class="n">zend_ulong</span><span class="p">)(</span><span class="nf">YAR_G</span><span class="p">(</span><span class="n">timeout</span><span class="p">)</span> <span class="o">/</span> <span class="mi">1000</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="n">tv</span><span class="p">.</span><span class="n">tv_usec</span> <span class="o">=</span> <span class="p">(</span><span class="n">zend_ulong</span><span class="p">)((</span><span class="nf">YAR_G</span><span class="p">(</span><span class="n">timeout</span><span class="p">)</span> <span class="o">%</span> <span class="mi">1000</span><span class="p">)</span><span class="o">?</span> <span class="p">(</span><span class="nf">YAR_G</span><span class="p">(</span><span class="n">timeout</span><span class="p">)</span> <span class="o">%</span> <span class="mi">1000</span><span class="p">)</span> <span class="o">*</span> <span class="mi">1000</span> <span class="o">:</span> <span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">            <span class="c1">// 等待请求返回数据
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>            <span class="n">return_code</span> <span class="o">=</span> <span class="nf">select</span><span class="p">(</span><span class="n">max_fd</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">readfds</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">writefds</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">exceptfds</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">tv</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">(</span><span class="n">return_code</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="c1">// 说明某些文件描述符可读/可写，让 libcurl 进行处理
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>                <span class="k">while</span> <span class="p">(</span><span class="n">CURLM_CALL_MULTI_PERFORM</span> <span class="o">==</span> <span class="nf">curl_multi_perform</span><span class="p">(</span><span class="n">multi</span><span class="o">-&gt;</span><span class="n">cm</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">running_count</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="o">-</span><span class="mi">1</span> <span class="o">==</span> <span class="n">return_code</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="c1">// 发生异常
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>                <span class="nf">php_error_docref</span><span class="p">(</span><span class="nb">NULL</span><span class="p">,</span> <span class="n">E_WARNING</span><span class="p">,</span> <span class="s">&#34;select error &#39;%s&#39;&#34;</span><span class="p">,</span> <span class="nf">strerror</span><span class="p">(</span><span class="n">errno</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">                <span class="k">goto</span> <span class="n">onerror</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="c1">// 等待超时
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>                <span class="nf">php_error_docref</span><span class="p">(</span><span class="nb">NULL</span><span class="p">,</span> <span class="n">E_WARNING</span><span class="p">,</span> <span class="s">&#34;select timeout %ldms reached&#34;</span><span class="p">,</span> <span class="nf">YAR_G</span><span class="p">(</span><span class="n">timeout</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">                <span class="k">goto</span> <span class="n">onerror</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="nl">process</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">(</span><span class="n">rest_count</span> <span class="o">&gt;</span> <span class="n">running_count</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="c1">// 解析响应的数据
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>                <span class="kt">int</span> <span class="n">ret</span> <span class="o">=</span> <span class="nf">php_yar_curl_multi_parse_response</span><span class="p">(</span><span class="n">multi</span><span class="p">,</span> <span class="n">f</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">                <span class="k">if</span> <span class="p">(</span><span class="n">ret</span> <span class="o">==</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                    <span class="k">goto</span> <span class="n">bailout</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">ret</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                    <span class="k">goto</span> <span class="n">onerror</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="p">}</span>
</span></span><span class="line"><span class="cl">                <span class="n">rest_count</span> <span class="o">=</span> <span class="n">running_count</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span> <span class="k">while</span> <span class="p">(</span><span class="n">running_count</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// 第一次尝试让 libcurl 处理数据后，
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="c1">// 如果运行中的数量为 0 ，说明已经全部处理完了，
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="c1">// 直接进行解析
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="kt">int</span> <span class="n">ret</span> <span class="o">=</span> <span class="nf">php_yar_curl_multi_parse_response</span><span class="p">(</span><span class="n">multi</span><span class="p">,</span> <span class="n">f</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">ret</span> <span class="o">==</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">goto</span> <span class="n">bailout</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">ret</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">goto</span> <span class="n">onerror</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="nl">onerror</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="nl">bailout</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">self</span><span class="o">-&gt;</span><span class="nf">close</span><span class="p">(</span><span class="n">self</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nf">zend_bailout</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h3 id="php_yar_curl_multi_parse_response">php_yar_curl_multi_parse_response</h3>
<p>在 php_yar_curl_multi_parse_response 函数上打断点，然后继续运行程序。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">(gdb) b php_yar_curl_multi_parse_response
</span></span><span class="line"><span class="cl">Breakpoint 6 at 0x7ffff56eeadb: file /home/vagrant/code/her-cat/yar/transports/curl.c, line 622.
</span></span><span class="line"><span class="cl">(gdb) c
</span></span><span class="line"><span class="cl">Continuing.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Breakpoint 6, php_yar_curl_multi_parse_response (multi=0x20b666f03b932500, f=0x16fb)
</span></span><span class="line"><span class="cl">    at /home/vagrant/code/her-cat/yar/transports/curl.c:622
</span></span><span class="line"><span class="cl">622    static int php_yar_curl_multi_parse_response(yar_curl_multi_data_t *multi, yar_concurrent_client_callback *f) /* {{{ */ {
</span></span><span class="line"><span class="cl">(gdb) 
</span></span></code></pre></div><p>php_yar_curl_multi_parse_response 函数的主要逻辑：</p>
<ul>
<li>从 curl 批处理实例中取出所有收到的响应数据。</li>
<li>遍历 multi-&gt;chs 找到数据对应的同步传输器。</li>
<li>检查响应数据是否正确。</li>
<li>解析响应数据，得到 Yar 协议中的头部及 payload 部分的内容。</li>
<li>调用 php_yar_concurrent_client_callback 函数，执行对应的回调函数。</li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// source：transports/curl.c
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="k">static</span> <span class="kt">int</span> <span class="nf">php_yar_curl_multi_parse_response</span><span class="p">(</span><span class="kt">yar_curl_multi_data_t</span> <span class="o">*</span><span class="n">multi</span><span class="p">,</span> <span class="n">yar_concurrent_client_callback</span> <span class="o">*</span><span class="n">f</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">int</span> <span class="n">msg_in_sequence</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">CURLMsg</span> <span class="o">*</span><span class="n">msg</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">do</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// 从 curl 批处理实例中读取一条消息，并返回剩余消息数量（msg_in_sequence）
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="n">msg</span> <span class="o">=</span> <span class="nf">curl_multi_info_read</span><span class="p">(</span><span class="n">multi</span><span class="o">-&gt;</span><span class="n">cm</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">msg_in_sequence</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">msg</span> <span class="o">&amp;&amp;</span> <span class="n">msg</span><span class="o">-&gt;</span><span class="n">msg</span> <span class="o">==</span> <span class="n">CURLMSG_DONE</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="c1">// 标记是否找到了对应的同步传输器
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>            <span class="kt">unsigned</span> <span class="n">found</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="kt">yar_transport_interface_t</span> <span class="o">*</span><span class="n">handle</span> <span class="o">=</span> <span class="n">multi</span><span class="o">-&gt;</span><span class="n">chs</span><span class="p">,</span> <span class="o">*</span><span class="n">q</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">            <span class="c1">// 遍历单链表
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>            <span class="k">while</span> <span class="p">(</span><span class="n">handle</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="c1">// curl 实例的指针地址相等，说明找到了
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>                <span class="k">if</span> <span class="p">(</span><span class="n">msg</span><span class="o">-&gt;</span><span class="n">easy_handle</span> <span class="o">==</span> <span class="p">((</span><span class="kt">yar_curl_data_t</span><span class="o">*</span><span class="p">)</span><span class="n">handle</span><span class="o">-&gt;</span><span class="n">data</span><span class="p">)</span><span class="o">-&gt;</span><span class="n">cp</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                    <span class="c1">// 从单链表中移除
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>                    <span class="k">if</span> <span class="p">(</span><span class="n">q</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                        <span class="p">((</span><span class="kt">yar_curl_data_t</span> <span class="o">*</span><span class="p">)</span><span class="n">q</span><span class="o">-&gt;</span><span class="n">data</span><span class="p">)</span><span class="o">-&gt;</span><span class="n">next</span> <span class="o">=</span> <span class="p">((</span><span class="kt">yar_curl_data_t</span><span class="o">*</span><span class="p">)</span><span class="n">handle</span><span class="o">-&gt;</span><span class="n">data</span><span class="p">)</span><span class="o">-&gt;</span><span class="n">next</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                        <span class="n">multi</span><span class="o">-&gt;</span><span class="n">chs</span> <span class="o">=</span> <span class="p">((</span><span class="kt">yar_curl_data_t</span><span class="o">*</span><span class="p">)</span><span class="n">handle</span><span class="o">-&gt;</span><span class="n">data</span><span class="p">)</span><span class="o">-&gt;</span><span class="n">next</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                    <span class="p">}</span>
</span></span><span class="line"><span class="cl">                    <span class="n">found</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                    <span class="k">break</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="p">}</span>
</span></span><span class="line"><span class="cl">                <span class="n">q</span> <span class="o">=</span> <span class="n">handle</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="n">handle</span> <span class="o">=</span> <span class="p">((</span><span class="kt">yar_curl_data_t</span><span class="o">*</span><span class="p">)</span><span class="n">handle</span><span class="o">-&gt;</span><span class="n">data</span><span class="p">)</span><span class="o">-&gt;</span><span class="n">next</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">(</span><span class="n">found</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="kt">long</span> <span class="n">http_code</span> <span class="o">=</span> <span class="mi">200</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="kt">yar_response_t</span> <span class="o">*</span><span class="n">response</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="kt">yar_curl_data_t</span> <span class="o">*</span><span class="n">data</span> <span class="o">=</span> <span class="p">(</span><span class="kt">yar_curl_data_t</span> <span class="o">*</span><span class="p">)</span><span class="n">handle</span><span class="o">-&gt;</span><span class="n">data</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">                <span class="c1">// 创建响应实例
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>                <span class="n">response</span> <span class="o">=</span> <span class="nf">php_yar_response_instance</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">                <span class="k">if</span> <span class="p">(</span><span class="n">msg</span><span class="o">-&gt;</span><span class="n">data</span><span class="p">.</span><span class="n">result</span> <span class="o">==</span> <span class="n">CURLE_OK</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                    <span class="nf">curl_multi_remove_handle</span><span class="p">(</span><span class="n">multi</span><span class="o">-&gt;</span><span class="n">cm</span><span class="p">,</span> <span class="n">data</span><span class="o">-&gt;</span><span class="n">cp</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">                    
</span></span><span class="line"><span class="cl">                    <span class="c1">// 获取 HTTP 状态码
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>                    <span class="k">if</span><span class="p">(</span><span class="nf">curl_easy_getinfo</span><span class="p">(</span><span class="n">data</span><span class="o">-&gt;</span><span class="n">cp</span><span class="p">,</span> <span class="n">CURLINFO_RESPONSE_CODE</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">http_code</span><span class="p">)</span> <span class="o">==</span> <span class="n">CURLE_OK</span> <span class="o">&amp;&amp;</span> <span class="n">http_code</span> <span class="o">!=</span> <span class="mi">200</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                        <span class="c1">// 非 200 说明出现异常
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>                        <span class="c1">// 返回异常响应
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>                        <span class="k">continue</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                        <span class="c1">// 状态码为 200 
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>                        <span class="k">if</span> <span class="p">(</span><span class="n">data</span><span class="o">-&gt;</span><span class="n">buf</span><span class="p">.</span><span class="n">s</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                            <span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>                            
</span></span><span class="line"><span class="cl">                            <span class="c1">// 从响应数据中解析出 Yar 协议的头部信息
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>                            <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="p">(</span><span class="n">header</span> <span class="o">=</span> <span class="nf">php_yar_protocol_parse</span><span class="p">(</span><span class="n">payload</span><span class="p">)))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                                <span class="nf">php_yar_error</span><span class="p">(</span><span class="n">response</span><span class="p">,</span> <span class="n">YAR_ERR_PROTOCOL</span><span class="p">,</span> <span class="s">&#34;malformed response header &#39;%.32s&#39;&#34;</span><span class="p">,</span> <span class="n">payload</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">                            <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                                <span class="c1">// 跳过 Yar 协议的头部信息后的内容，就是真正的响应结果
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>                                <span class="n">payload</span> <span class="o">+=</span> <span class="k">sizeof</span><span class="p">(</span><span class="kt">yar_header_t</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">                                <span class="n">payload_len</span> <span class="o">-=</span> <span class="k">sizeof</span><span class="p">(</span><span class="kt">yar_header_t</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">                                
</span></span><span class="line"><span class="cl">                                <span class="c1">// 通过 payload 中的编码方式对 payload 进行解码
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>                                <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="p">(</span><span class="n">retval</span> <span class="o">=</span> <span class="nf">php_yar_packager_unpack</span><span class="p">(</span><span class="n">payload</span><span class="p">,</span> <span class="n">payload_len</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">msg</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">ret</span><span class="p">)))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                                    <span class="nf">php_yar_response_set_error</span><span class="p">(</span><span class="n">response</span><span class="p">,</span> <span class="n">YAR_ERR_PACKAGER</span><span class="p">,</span> <span class="n">msg</span><span class="p">,</span> <span class="nf">strlen</span><span class="p">(</span><span class="n">msg</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">                                <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                                    <span class="c1">// 将解码后的数据存储到 response 中
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>                                    <span class="nf">php_yar_response_map_retval</span><span class="p">(</span><span class="n">response</span><span class="p">,</span> <span class="n">retval</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">                                    <span class="nf">DEBUG_C</span><span class="p">(</span><span class="n">ZEND_ULONG_FMT</span><span class="s">&#34;: server response content packaged by &#39;%.*s&#39;, len &#39;%ld&#39;, content &#39;%.32s&#39;&#34;</span><span class="p">,</span> <span class="n">response</span><span class="o">-&gt;</span><span class="n">id</span><span class="p">,</span> <span class="mi">7</span><span class="p">,</span> <span class="n">payload</span><span class="p">,</span> <span class="n">header</span><span class="o">-&gt;</span><span class="n">body_len</span><span class="p">,</span> <span class="n">payload</span> <span class="o">+</span> <span class="mi">8</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">                                    <span class="nf">zval_ptr_dtor</span><span class="p">(</span><span class="n">retval</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">                                <span class="p">}</span>
</span></span><span class="line"><span class="cl">                                <span class="k">if</span> <span class="p">(</span><span class="n">msg</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                                    <span class="nf">efree</span><span class="p">(</span><span class="n">msg</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">                                <span class="p">}</span>
</span></span><span class="line"><span class="cl">                            <span class="p">}</span>
</span></span><span class="line"><span class="cl">                        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                            <span class="c1">// 响应内容为空，设置错误信息
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>                            <span class="nf">php_yar_response_set_error</span><span class="p">(</span><span class="n">response</span><span class="p">,</span> <span class="n">YAR_ERR_EMPTY_RESPONSE</span><span class="p">,</span> <span class="nf">ZEND_STRL</span><span class="p">(</span><span class="s">&#34;empty response&#34;</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">                        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">                        <span class="c1">// 调用 php_yar_concurrent_client_callback 函数
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>                        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nf">f</span><span class="p">(</span><span class="n">data</span><span class="o">-&gt;</span><span class="n">calldata</span><span class="p">,</span> <span class="n">response</span><span class="o">-&gt;</span><span class="n">status</span><span class="p">,</span> <span class="n">response</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                            <span class="n">handle</span><span class="o">-&gt;</span><span class="nf">close</span><span class="p">(</span><span class="n">handle</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">                            <span class="nf">php_yar_response_destroy</span><span class="p">(</span><span class="n">response</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">                            <span class="k">return</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                        <span class="p">}</span>
</span></span><span class="line"><span class="cl">                        <span class="k">if</span> <span class="p">(</span><span class="nf">EG</span><span class="p">(</span><span class="n">exception</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                            <span class="n">handle</span><span class="o">-&gt;</span><span class="nf">close</span><span class="p">(</span><span class="n">handle</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">                            <span class="nf">php_yar_response_destroy</span><span class="p">(</span><span class="n">response</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">                            <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                        <span class="p">}</span>
</span></span><span class="line"><span class="cl">                    <span class="p">}</span>
</span></span><span class="line"><span class="cl">                <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                    <span class="c1">// 执行请求失败，返回失败原因
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>                    <span class="kt">char</span> <span class="o">*</span><span class="n">err</span> <span class="o">=</span> <span class="p">(</span><span class="kt">char</span> <span class="o">*</span><span class="p">)</span><span class="nf">curl_easy_strerror</span><span class="p">(</span><span class="n">msg</span><span class="o">-&gt;</span><span class="n">data</span><span class="p">.</span><span class="n">result</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">                    <span class="nf">php_yar_response_set_error</span><span class="p">(</span><span class="n">response</span><span class="p">,</span> <span class="n">YAR_ERR_TRANSPORT</span><span class="p">,</span> <span class="n">err</span><span class="p">,</span> <span class="nf">strlen</span><span class="p">(</span><span class="n">err</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">                    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nf">f</span><span class="p">(</span><span class="n">data</span><span class="o">-&gt;</span><span class="n">calldata</span><span class="p">,</span> <span class="n">YAR_ERR_TRANSPORT</span><span class="p">,</span> <span class="n">response</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                        <span class="n">handle</span><span class="o">-&gt;</span><span class="nf">close</span><span class="p">(</span><span class="n">handle</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">                        <span class="nf">php_yar_response_destroy</span><span class="p">(</span><span class="n">response</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">                        <span class="k">return</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                    <span class="p">}</span>
</span></span><span class="line"><span class="cl">                    <span class="k">if</span> <span class="p">(</span><span class="nf">EG</span><span class="p">(</span><span class="n">exception</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                        <span class="n">handle</span><span class="o">-&gt;</span><span class="nf">close</span><span class="p">(</span><span class="n">handle</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">                        <span class="nf">php_yar_response_destroy</span><span class="p">(</span><span class="n">response</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">                        <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                    <span class="p">}</span>
</span></span><span class="line"><span class="cl">                <span class="p">}</span>
</span></span><span class="line"><span class="cl">                <span class="n">handle</span><span class="o">-&gt;</span><span class="nf">close</span><span class="p">(</span><span class="n">handle</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">                <span class="nf">php_yar_response_destroy</span><span class="p">(</span><span class="n">response</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="nf">php_error_docref</span><span class="p">(</span><span class="nb">NULL</span><span class="p">,</span> <span class="n">E_WARNING</span><span class="p">,</span> <span class="s">&#34;unexpected transport info missed&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span> <span class="k">while</span> <span class="p">(</span><span class="n">msg_in_sequence</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h3 id="php_yar_concurrent_client_callback">php_yar_concurrent_client_callback</h3>
<p>该函数的作用：通过响应状态判断本次远程调用的结果，执行相应的回调函数。</p>
<p>主要逻辑：</p>
<ul>
<li>通过响应状态获取要执行的 PHP 回调函数。</li>
<li>组装被调用函数的参数。</li>
<li>执行 PHP 回调函数。</li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// source：yar_client.c
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="kt">int</span> <span class="nf">php_yar_concurrent_client_callback</span><span class="p">(</span><span class="kt">yar_call_data_t</span> <span class="o">*</span><span class="n">calldata</span><span class="p">,</span> <span class="kt">int</span> <span class="n">status</span><span class="p">,</span> <span class="kt">yar_response_t</span> <span class="o">*</span><span class="n">response</span><span class="p">)</span> <span class="cm">/* </span><span class="p">{{{</span> <span class="err">*/</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">zval</span> <span class="n">code</span><span class="p">,</span> <span class="n">retval</span><span class="p">,</span> <span class="n">retval_ptr</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">zval</span> <span class="n">callinfo</span><span class="p">,</span> <span class="o">*</span><span class="n">callback</span><span class="p">,</span> <span class="n">func_params</span><span class="p">[</span><span class="mi">3</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="n">zend_bool</span> <span class="n">bailout</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">unsigned</span> <span class="n">params_count</span><span class="p">,</span> <span class="n">i</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">calldata</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// 通过响应状态获取要执行的 PHP 回调函数
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="k">if</span> <span class="p">(</span><span class="n">status</span> <span class="o">==</span> <span class="n">YAR_ERR_OKEY</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nf">Z_ISUNDEF</span><span class="p">(</span><span class="n">calldata</span><span class="o">-&gt;</span><span class="n">callback</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="n">callback</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">calldata</span><span class="o">-&gt;</span><span class="n">callback</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="n">callback</span> <span class="o">=</span> <span class="nf">zend_read_static_property</span><span class="p">(</span><span class="n">yar_concurrent_client_ce</span><span class="p">,</span> <span class="nf">ZEND_STRL</span><span class="p">(</span><span class="s">&#34;_callback&#34;</span><span class="p">),</span> <span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="n">params_count</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nf">Z_ISUNDEF</span><span class="p">(</span><span class="n">calldata</span><span class="o">-&gt;</span><span class="n">ecallback</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="n">callback</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">calldata</span><span class="o">-&gt;</span><span class="n">ecallback</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="n">callback</span> <span class="o">=</span> <span class="nf">zend_read_static_property</span><span class="p">(</span><span class="n">yar_concurrent_client_ce</span><span class="p">,</span> <span class="nf">ZEND_STRL</span><span class="p">(</span><span class="s">&#34;_error_callback&#34;</span><span class="p">),</span> <span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="n">params_count</span> <span class="o">=</span> <span class="mi">3</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// 没获取到回调函数就提示错误信息
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="k">if</span> <span class="p">(</span><span class="nf">Z_ISNULL_P</span><span class="p">(</span><span class="n">callback</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">(</span><span class="n">status</span> <span class="o">!=</span> <span class="n">YAR_ERR_OKEY</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nf">Z_ISUNDEF</span><span class="p">(</span><span class="n">response</span><span class="o">-&gt;</span><span class="n">err</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                    <span class="nf">php_yar_client_handle_error</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="n">response</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">                <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                    <span class="nf">php_error_docref</span><span class="p">(</span><span class="nb">NULL</span><span class="p">,</span> <span class="n">E_WARNING</span><span class="p">,</span> <span class="s">&#34;[%d]:unknown Error&#34;</span><span class="p">,</span> <span class="n">status</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">                <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nf">Z_ISUNDEF</span><span class="p">(</span><span class="n">response</span><span class="o">-&gt;</span><span class="n">retval</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="nf">zend_print_zval</span><span class="p">(</span><span class="o">&amp;</span><span class="n">response</span><span class="o">-&gt;</span><span class="n">retval</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">status</span> <span class="o">==</span> <span class="n">YAR_ERR_OKEY</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="c1">// 响应状态是成功但是返回值为空，提示错误信息。
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>            <span class="k">if</span> <span class="p">(</span><span class="nf">Z_ISUNDEF</span><span class="p">(</span><span class="n">response</span><span class="o">-&gt;</span><span class="n">retval</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="nf">php_yar_client_trigger_error</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="n">YAR_ERR_REQUEST</span><span class="p">,</span> <span class="s">&#34;%s&#34;</span><span class="p">,</span> <span class="s">&#34;server responsed empty response&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">                <span class="k">return</span> <span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="c1">// 复制返回值
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>            <span class="nf">ZVAL_COPY</span><span class="p">(</span><span class="o">&amp;</span><span class="n">retval</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">response</span><span class="o">-&gt;</span><span class="n">retval</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="c1">// 复制状态及错误信息
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>            <span class="nf">ZVAL_LONG</span><span class="p">(</span><span class="o">&amp;</span><span class="n">code</span><span class="p">,</span> <span class="n">status</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="nf">ZVAL_COPY</span><span class="p">(</span><span class="o">&amp;</span><span class="n">retval</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">response</span><span class="o">-&gt;</span><span class="n">err</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// 初始化调用信息
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="nf">array_init</span><span class="p">(</span><span class="o">&amp;</span><span class="n">callinfo</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="nf">add_assoc_long_ex</span><span class="p">(</span><span class="o">&amp;</span><span class="n">callinfo</span><span class="p">,</span> <span class="s">&#34;sequence&#34;</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="s">&#34;sequence&#34;</span><span class="p">)</span> <span class="o">-</span> <span class="mi">1</span><span class="p">,</span> <span class="n">calldata</span><span class="o">-&gt;</span><span class="n">sequence</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nf">add_assoc_str_ex</span><span class="p">(</span><span class="o">&amp;</span><span class="n">callinfo</span><span class="p">,</span> <span class="s">&#34;uri&#34;</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="s">&#34;uri&#34;</span><span class="p">)</span> <span class="o">-</span> <span class="mi">1</span><span class="p">,</span> <span class="nf">zend_string_copy</span><span class="p">(</span><span class="n">calldata</span><span class="o">-&gt;</span><span class="n">uri</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">        <span class="nf">add_assoc_str_ex</span><span class="p">(</span><span class="o">&amp;</span><span class="n">callinfo</span><span class="p">,</span> <span class="s">&#34;method&#34;</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="s">&#34;method&#34;</span><span class="p">)</span> <span class="o">-</span> <span class="mi">1</span><span class="p">,</span> <span class="nf">zend_string_copy</span><span class="p">(</span><span class="n">calldata</span><span class="o">-&gt;</span><span class="n">method</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// 调用数据为空则获取并行客户端的回调函数
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="n">callback</span> <span class="o">=</span> <span class="nf">zend_read_static_property</span><span class="p">(</span><span class="n">yar_concurrent_client_ce</span><span class="p">,</span> <span class="nf">ZEND_STRL</span><span class="p">(</span><span class="s">&#34;_callback&#34;</span><span class="p">),</span> <span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="nf">Z_ISNULL_P</span><span class="p">(</span><span class="n">callback</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="n">params_count</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">calldata</span> <span class="o">&amp;&amp;</span> <span class="p">(</span><span class="n">status</span> <span class="o">!=</span> <span class="n">YAR_ERR_OKEY</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// 调用失败
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="nf">ZVAL_COPY_VALUE</span><span class="p">(</span><span class="o">&amp;</span><span class="n">func_params</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="o">&amp;</span><span class="n">code</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nf">ZVAL_COPY_VALUE</span><span class="p">(</span><span class="o">&amp;</span><span class="n">func_params</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="o">&amp;</span><span class="n">retval</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nf">ZVAL_COPY_VALUE</span><span class="p">(</span><span class="o">&amp;</span><span class="n">func_params</span><span class="p">[</span><span class="mi">2</span><span class="p">],</span> <span class="o">&amp;</span><span class="n">callinfo</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">calldata</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// 调用成功
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="nf">ZVAL_COPY_VALUE</span><span class="p">(</span><span class="o">&amp;</span><span class="n">func_params</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="o">&amp;</span><span class="n">retval</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nf">ZVAL_COPY_VALUE</span><span class="p">(</span><span class="o">&amp;</span><span class="n">func_params</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="o">&amp;</span><span class="n">callinfo</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// 调用数据为空
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="nf">ZVAL_NULL</span><span class="p">(</span><span class="o">&amp;</span><span class="n">func_params</span><span class="p">[</span><span class="mi">0</span><span class="p">]);</span>
</span></span><span class="line"><span class="cl">        <span class="nf">ZVAL_NULL</span><span class="p">(</span><span class="o">&amp;</span><span class="n">func_params</span><span class="p">[</span><span class="mi">1</span><span class="p">]);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">zend_try</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// 执行回调函数
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="k">if</span> <span class="p">(</span><span class="nf">call_user_function</span><span class="p">(</span><span class="nf">EG</span><span class="p">(</span><span class="n">function_table</span><span class="p">),</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">callback</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">retval_ptr</span><span class="p">,</span> <span class="n">params_count</span><span class="p">,</span> <span class="n">func_params</span><span class="p">)</span> <span class="o">!=</span> <span class="n">SUCCESS</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">for</span> <span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">params_count</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="nf">zval_ptr_dtor</span><span class="p">(</span><span class="o">&amp;</span><span class="n">func_params</span><span class="p">[</span><span class="n">i</span><span class="p">]);</span>    
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">(</span><span class="n">calldata</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="nf">php_error_docref</span><span class="p">(</span><span class="nb">NULL</span><span class="p">,</span> <span class="n">E_WARNING</span><span class="p">,</span> <span class="s">&#34;call to callback failed for request: &#39;%s&#39;&#34;</span><span class="p">,</span> <span class="nf">ZSTR_VAL</span><span class="p">(</span><span class="n">calldata</span><span class="o">-&gt;</span><span class="n">method</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="nf">php_error_docref</span><span class="p">(</span><span class="nb">NULL</span><span class="p">,</span> <span class="n">E_WARNING</span><span class="p">,</span> <span class="s">&#34;call to initial callback failed&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span> <span class="n">zend_catch</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">bailout</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span> <span class="nf">zend_end_try</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// 释放返回值及参数
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nf">Z_ISUNDEF</span><span class="p">(</span><span class="n">retval_ptr</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nf">zval_ptr_dtor</span><span class="p">(</span><span class="o">&amp;</span><span class="n">retval_ptr</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">params_count</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nf">zval_ptr_dtor</span><span class="p">(</span><span class="o">&amp;</span><span class="n">func_params</span><span class="p">[</span><span class="n">i</span><span class="p">]);</span>    
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">bailout</span><span class="o">?</span> <span class="mi">0</span> <span class="o">:</span> <span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>打印 retval 可以看到服务端的返回值：success。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">(gdb) p *retval.value.str.val@7
</span></span><span class="line"><span class="cl">$28 = &#34;success&#34;
</span></span></code></pre></div><h2 id="总结">总结</h2>
<p>本文介绍了实现并行调用相关的数据结构，并通过 GDB 调试代码的方式展示了并行调用运行的过程。</p>
]]></content:encoded>
    </item>
    <item>
      <title>Yar 源码阅读笔记：客户端的同步调用</title>
      <link>https://her-cat.com/posts/2022/01/07/yar-internals-client-sync-call/</link>
      <pubDate>Fri, 07 Jan 2022 16:13:08 +0800</pubDate>
      <guid>https://her-cat.com/posts/2022/01/07/yar-internals-client-sync-call/</guid>
      <description>主要介绍 Yar 客户端是如何实现远程调用的，进一步了解各个模块在远程调用的过程中都做了些什么。</description>
      <content:encoded><![CDATA[<h2 id="前言">前言</h2>
<p>今天这篇文章，主要介绍 Yar 客户端是如何实现远程调用的，进一步了解各个模块在远程调用的过程中都做了些什么。</p>
<h2 id="客户端介绍">客户端介绍</h2>
<p>Yar 客户端的远程调用分为同步调用和并行调用，同步调用是指调用多个远程方法时，必须按照调用顺序一个个地执行，上一个调用没有执行完时，后面的调用必须等待前面的执行完毕，这期间啥也不能干，效率比较低。</p>
<p>这时候就有了并行调用，从名字就可以看出来，并行调用支持同时调用多个远程方法。它会先将所有的调用请发送出去，让服务端开始处理这些请求，然后客户开始端监听每个请求的响应结果，当这些请求中有任何一个请求有响应结果时，执行该请求的回调函数处理相应的业务逻辑。</p>
<blockquote>
<p>这篇文章主要介绍同步调用的实现，所以在本文中提到 “远程调用” 都是指同步调用。</p></blockquote>
<p>我们先来看看<a href="/posts/2021/10/17/yar-internals-start/#%E5%90%8C%E6%AD%A5%E8%B0%83%E7%94%A8">开篇</a>中客户端同步调用的例子。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="c1">// 实例化客户端
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nv">$client</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Yar_Client</span><span class="p">(</span><span class="s2">&#34;http://127.0.0.1:3000/server.php&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// 设置超时时间
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nv">$client</span><span class="o">-&gt;</span><span class="na">setOpt</span><span class="p">(</span><span class="nx">YAR_OPT_CONNECT_TIMEOUT</span><span class="p">,</span> <span class="mi">1000</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// 调用 login 方法
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nv">$result</span> <span class="o">=</span> <span class="nv">$client</span><span class="o">-&gt;</span><span class="na">login</span><span class="p">(</span><span class="s2">&#34;her-cat&#34;</span><span class="p">,</span> <span class="s2">&#34;123456&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">var_dump</span><span class="p">(</span><span class="nv">$result</span><span class="p">);</span>
</span></span></code></pre></div><p>通过上面的例子可以知道，客户端中有三个主要方法：</p>
<ul>
<li>构造函数：用于传入服务端的地址以及可选配置。</li>
<li>setOpt：设置配置项，比如连接超时时间等。</li>
<li>远程方法：调用服务端定义好的方法，这里是 login。</li>
</ul>
<p>当然客户端类还有一些的方法以及属性，可以在定义客户端类的地方找到：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// source：yar_client.c
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="c1">// 客户端方法列表
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="n">zend_function_entry</span> <span class="n">yar_client_methods</span><span class="p">[]</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nf">PHP_ME</span><span class="p">(</span><span class="n">yar_client</span><span class="p">,</span> <span class="n">__construct</span><span class="p">,</span> <span class="n">arginfo_client___construct</span><span class="p">,</span> <span class="n">ZEND_ACC_PUBLIC</span><span class="o">|</span><span class="n">ZEND_ACC_CTOR</span><span class="o">|</span><span class="n">ZEND_ACC_FINAL</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nf">PHP_ME</span><span class="p">(</span><span class="n">yar_client</span><span class="p">,</span> <span class="n">call</span><span class="p">,</span> <span class="n">arginfo_client___call</span><span class="p">,</span> <span class="n">ZEND_ACC_PUBLIC</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nf">PHP_ME</span><span class="p">(</span><span class="n">yar_client</span><span class="p">,</span> <span class="n">__call</span><span class="p">,</span> <span class="n">arginfo_client___call</span><span class="p">,</span> <span class="n">ZEND_ACC_PUBLIC</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nf">PHP_ME</span><span class="p">(</span><span class="n">yar_client</span><span class="p">,</span> <span class="n">getOpt</span><span class="p">,</span> <span class="n">arginfo_client_getopt</span><span class="p">,</span> <span class="n">ZEND_ACC_PUBLIC</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nf">PHP_ME</span><span class="p">(</span><span class="n">yar_client</span><span class="p">,</span> <span class="n">setOpt</span><span class="p">,</span> <span class="n">arginfo_client_setopt</span><span class="p">,</span> <span class="n">ZEND_ACC_PUBLIC</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">PHP_FE_END</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">YAR_STARTUP_FUNCTION</span><span class="p">(</span><span class="n">client</span><span class="p">)</span> <span class="cm">/* {{{ */</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">zend_class_entry</span> <span class="n">ce</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nf">INIT_CLASS_ENTRY</span><span class="p">(</span><span class="n">ce</span><span class="p">,</span> <span class="s">&#34;Yar_Client&#34;</span><span class="p">,</span> <span class="n">yar_client_methods</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">yar_client_ce</span> <span class="o">=</span> <span class="nf">zend_register_internal_class</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ce</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// 声明客户端的属性
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nf">zend_declare_property_long</span><span class="p">(</span><span class="n">yar_client_ce</span><span class="p">,</span> <span class="nf">ZEND_STRL</span><span class="p">(</span><span class="s">&#34;_protocol&#34;</span><span class="p">),</span> <span class="n">YAR_CLIENT_PROTOCOL_HTTP</span><span class="p">,</span> <span class="n">ZEND_ACC_PROTECTED</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nf">zend_declare_property_null</span><span class="p">(</span><span class="n">yar_client_ce</span><span class="p">,</span> <span class="nf">ZEND_STRL</span><span class="p">(</span><span class="s">&#34;_uri&#34;</span><span class="p">),</span> <span class="n">ZEND_ACC_PROTECTED</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nf">zend_declare_property_null</span><span class="p">(</span><span class="n">yar_client_ce</span><span class="p">,</span> <span class="nf">ZEND_STRL</span><span class="p">(</span><span class="s">&#34;_options&#34;</span><span class="p">),</span>  <span class="n">ZEND_ACC_PROTECTED</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nf">zend_declare_property_null</span><span class="p">(</span><span class="n">yar_client_ce</span><span class="p">,</span> <span class="nf">ZEND_STRL</span><span class="p">(</span><span class="s">&#34;_running&#34;</span><span class="p">),</span>  <span class="n">ZEND_ACC_PROTECTED</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// 注册协议常量
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nf">REGISTER_LONG_CONSTANT</span><span class="p">(</span><span class="s">&#34;YAR_CLIENT_PROTOCOL_HTTP&#34;</span><span class="p">,</span> <span class="n">YAR_CLIENT_PROTOCOL_HTTP</span><span class="p">,</span> <span class="n">CONST_PERSISTENT</span> <span class="o">|</span> <span class="n">CONST_CS</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nf">REGISTER_LONG_CONSTANT</span><span class="p">(</span><span class="s">&#34;YAR_CLIENT_PROTOCOL_TCP&#34;</span><span class="p">,</span> <span class="n">YAR_CLIENT_PROTOCOL_TCP</span><span class="p">,</span> <span class="n">CONST_PERSISTENT</span> <span class="o">|</span> <span class="n">CONST_CS</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nf">REGISTER_LONG_CONSTANT</span><span class="p">(</span><span class="s">&#34;YAR_CLIENT_PROTOCOL_UNIX&#34;</span><span class="p">,</span> <span class="n">YAR_CLIENT_PROTOCOL_UNIX</span><span class="p">,</span> <span class="n">CONST_PERSISTENT</span> <span class="o">|</span> <span class="n">CONST_CS</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">SUCCESS</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>下面我们重点来看看客户端远程调用的实现，客户端中的其它方法比较简单，这里就不赘述了。</p>
<h2 id="同步调用">同步调用</h2>
<p>熟悉 PHP 的应该都知道，当我们调用对象中未定义或不可访问的方法时，PHP 会尝试调用魔术方法 <a href="https://www.php.net/manual/zh/language.oop5.overloading.php#object.call">__call</a>，当 __call 方法也未定义时，将会抛出调用未定义方法的异常。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">A</span> 
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">public</span> <span class="k">function</span> <span class="fm">__call</span><span class="p">(</span><span class="nv">$name</span><span class="p">,</span> <span class="nv">$arguments</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">echo</span> <span class="s2">&#34;调用 A 的 &#39;</span><span class="si">{</span><span class="nv">$name</span><span class="si">}</span><span class="s2">&#39; 方法，参数是：&#34;</span> <span class="o">.</span> <span class="nx">implode</span><span class="p">(</span><span class="s1">&#39;，&#39;</span><span class="p">,</span> <span class="nv">$arguments</span><span class="p">)</span> <span class="o">.</span> <span class="s2">&#34;</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">$a</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">A</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="nv">$a</span><span class="o">-&gt;</span><span class="na">login</span><span class="p">(</span><span class="s2">&#34;her-cat&#34;</span><span class="p">,</span> <span class="s2">&#34;123456&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// 输出：调用 A 的 login 方法，参数是：her-cat，123456
</span></span></span></code></pre></div><p>Yar 就是通过 __call 实现了远程调用。</p>
<p>虽然在代码中看起来调用的是客户端的方法，但实际上是通过 __call 魔术方法，将调用的请求转发到服务端上，让服务端执行对应的方法，最后将执行的结果返回，完成了整个远程调用。</p>
<p>下面是客户端执行同步调用时的调用栈。</p>
<p><img decoding="async" height="768" loading="lazy" sizes="(max-width: 768px) 100vw, (max-width: 1024px) 768px, 1024px" src="/posts/2022/01/07/yar-internals-client-sync-call/yar-client-remote-call.png" srcset="/posts/2022/01/07/yar-internals-client-sync-call/yar-client-remote-call_hu_86eb7b9edcad1534.png 384w" style="max-width: 100%; height: auto; aspect-ratio: 0.9831;" width="755"></p>
<h3 id="client__call">Client::__call</h3>
<p>在上面的客户端介绍中的客户端方法列表中，可以看到 Yar 实现了 __call 和 call 方法。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// source：yar_client.c
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="cm">/* {{{ proto Yar_Client::call($method, $parameters = NULL) */</span>
</span></span><span class="line"><span class="cl"><span class="nf">PHP_METHOD</span><span class="p">(</span><span class="n">yar_client</span><span class="p">,</span> <span class="n">call</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nf">PHP_MN</span><span class="p">(</span><span class="n">yar_client___call</span><span class="p">)(</span><span class="n">INTERNAL_FUNCTION_PARAM_PASSTHRU</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="cm">/* }}} */</span>
</span></span></code></pre></div><p>call 函数实际上是 __call 函数的一层包装，可以理解为别名方法。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// source：yar_client.c
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="nf">PHP_METHOD</span><span class="p">(</span><span class="n">yar_client</span><span class="p">,</span> <span class="n">__call</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">zval</span> <span class="o">*</span><span class="n">params</span><span class="p">,</span> <span class="o">*</span><span class="n">protocol</span><span class="p">,</span> <span class="n">rv</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">zval</span> <span class="o">*</span><span class="n">this_ptr</span> <span class="o">=</span> <span class="nf">getThis</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// 解析参数，被调用的方法名称和参数数组
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">if</span> <span class="p">(</span><span class="nf">zend_parse_parameters</span><span class="p">(</span><span class="nf">ZEND_NUM_ARGS</span><span class="p">(),</span> <span class="s">&#34;Sa&#34;</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">method</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">params</span><span class="p">)</span> <span class="o">==</span> <span class="n">FAILURE</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// 读取使用的协议
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="n">protocol</span> <span class="o">=</span> <span class="nf">zend_read_property</span><span class="p">(</span><span class="n">yar_client_ce</span><span class="p">,</span> <span class="n">this_ptr</span><span class="p">,</span> <span class="nf">ZEND_STRL</span><span class="p">(</span><span class="s">&#34;_protocol&#34;</span><span class="p">),</span> <span class="mi">0</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">rv</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// 根据协议调用对应的方法
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">switch</span> <span class="p">(</span><span class="nf">Z_LVAL_P</span><span class="p">(</span><span class="n">protocol</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">case</span> <span class="nl">YAR_CLIENT_PROTOCOL_TCP</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">case</span> <span class="nl">YAR_CLIENT_PROTOCOL_UNIX</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">case</span> <span class="nl">YAR_CLIENT_PROTOCOL_HTTP</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">((</span><span class="nf">php_yar_client_handle</span><span class="p">(</span><span class="nf">Z_LVAL_P</span><span class="p">(</span><span class="n">protocol</span><span class="p">),</span> <span class="nf">getThis</span><span class="p">(),</span> <span class="n">method</span><span class="p">,</span> <span class="n">params</span><span class="p">,</span> <span class="n">return_value</span><span class="p">)))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="k">break</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">default</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">            <span class="nf">php_error_docref</span><span class="p">(</span><span class="nb">NULL</span><span class="p">,</span> <span class="n">E_WARNING</span><span class="p">,</span> <span class="s">&#34;unsupported protocol %ld&#34;</span><span class="p">,</span> <span class="nf">Z_LVAL_P</span><span class="p">(</span><span class="n">protocol</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">            <span class="k">break</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">RETURN_FALSE</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>__call 函数的内容比较简单，先解析参数，得到被调用的方法名称和参数数组，然后从客户端对象中读取协议，然后根据协议调用对应的函数，当遇到不支持的协议时，提示错误信息并返回 false。</p>
<h3 id="php_yar_client_handle">php_yar_client_handle</h3>
<p>php_yar_client_handle 函数包含了整个调用的过程，它就是客户端实现远程调用的核心所在。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// source：yar_client.c
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="k">static</span> <span class="kt">int</span> <span class="nf">php_yar_client_handle</span><span class="p">(</span><span class="kt">int</span> <span class="n">protocol</span><span class="p">,</span> <span class="n">zval</span> <span class="o">*</span><span class="n">client</span><span class="p">,</span> <span class="n">zend_string</span> <span class="o">*</span><span class="n">method</span><span class="p">,</span> <span class="n">zval</span> <span class="o">*</span><span class="n">params</span><span class="p">,</span> <span class="n">zval</span> <span class="o">*</span><span class="n">retval</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">char</span> <span class="o">*</span><span class="n">msg</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">zval</span> <span class="o">*</span><span class="n">uri</span><span class="p">,</span> <span class="o">*</span><span class="n">options</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">zval</span> <span class="n">rv</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">const</span> <span class="kt">yar_transport_t</span> <span class="o">*</span><span class="n">factory</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">yar_transport_interface_t</span> <span class="o">*</span><span class="n">transport</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">yar_request_t</span> <span class="o">*</span><span class="n">request</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">yar_response_t</span> <span class="o">*</span><span class="n">response</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">int</span> <span class="n">flags</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">zval</span> <span class="o">*</span><span class="n">zobj</span> <span class="o">=</span> <span class="n">client</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// 读取服务端的地址
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="n">uri</span> <span class="o">=</span> <span class="nf">zend_read_property</span><span class="p">(</span><span class="n">yar_client_ce</span><span class="p">,</span> <span class="n">zobj</span><span class="p">,</span> <span class="nf">ZEND_STRL</span><span class="p">(</span><span class="s">&#34;_uri&#34;</span><span class="p">),</span> <span class="mi">0</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">rv</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// 通过协议获取对应的传输方式
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">if</span> <span class="p">(</span><span class="n">protocol</span> <span class="o">==</span> <span class="n">YAR_CLIENT_PROTOCOL_HTTP</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">factory</span> <span class="o">=</span> <span class="nf">php_yar_transport_get</span><span class="p">(</span><span class="nf">ZEND_STRL</span><span class="p">(</span><span class="s">&#34;curl&#34;</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">protocol</span> <span class="o">==</span> <span class="n">YAR_CLIENT_PROTOCOL_TCP</span> <span class="o">||</span> <span class="n">protocol</span> <span class="o">==</span> <span class="n">YAR_CLIENT_PROTOCOL_UNIX</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">factory</span> <span class="o">=</span> <span class="nf">php_yar_transport_get</span><span class="p">(</span><span class="nf">ZEND_STRL</span><span class="p">(</span><span class="s">&#34;sock&#34;</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// 初始化传输方式
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="n">transport</span> <span class="o">=</span> <span class="n">factory</span><span class="o">-&gt;</span><span class="nf">init</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">options</span> <span class="o">=</span> <span class="nf">zend_read_property</span><span class="p">(</span><span class="n">yar_client_ce</span><span class="p">,</span> <span class="n">zobj</span><span class="p">,</span> <span class="nf">ZEND_STRL</span><span class="p">(</span><span class="s">&#34;_options&#34;</span><span class="p">),</span> <span class="mi">1</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">rv</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">IS_ARRAY</span> <span class="o">!=</span> <span class="nf">Z_TYPE_P</span><span class="p">(</span><span class="n">options</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">options</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="c1">// 根据被调用的方法名称、参数等信息组装请求数据
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">if</span> <span class="p">(</span><span class="nf">UNEXPECTED</span><span class="p">(</span><span class="o">!</span><span class="p">(</span><span class="n">request</span> <span class="o">=</span> <span class="nf">php_yar_request_instance</span><span class="p">(</span><span class="n">method</span><span class="p">,</span> <span class="n">params</span><span class="p">,</span> <span class="n">options</span><span class="p">))))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">transport</span><span class="o">-&gt;</span><span class="nf">close</span><span class="p">(</span><span class="n">transport</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">factory</span><span class="o">-&gt;</span><span class="nf">destroy</span><span class="p">(</span><span class="n">transport</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">options</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">zval</span> <span class="o">*</span><span class="n">flag</span> <span class="o">=</span> <span class="nf">php_yar_client_get_opt</span><span class="p">(</span><span class="n">options</span><span class="p">,</span> <span class="n">YAR_OPT_PERSISTENT</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">flag</span> <span class="o">&amp;&amp;</span> <span class="p">(</span><span class="nf">Z_TYPE_P</span><span class="p">(</span><span class="n">flag</span><span class="p">)</span> <span class="o">==</span> <span class="n">IS_TRUE</span> <span class="o">||</span> <span class="p">(</span><span class="nf">Z_TYPE_P</span><span class="p">(</span><span class="n">flag</span><span class="p">)</span> <span class="o">==</span> <span class="n">IS_LONG</span> <span class="o">&amp;&amp;</span> <span class="nf">Z_LVAL_P</span><span class="p">(</span><span class="n">flag</span><span class="p">))))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">flags</span> <span class="o">|=</span> <span class="n">YAR_PROTOCOL_PERSISTENT</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">msg</span> <span class="o">=</span> <span class="p">(</span><span class="kt">char</span><span class="o">*</span><span class="p">)</span><span class="n">options</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// 打开/创建一个连接
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">if</span> <span class="p">(</span><span class="nf">UNEXPECTED</span><span class="p">(</span><span class="o">!</span><span class="n">transport</span><span class="o">-&gt;</span><span class="nf">open</span><span class="p">(</span><span class="n">transport</span><span class="p">,</span> <span class="nf">Z_STR_P</span><span class="p">(</span><span class="n">uri</span><span class="p">),</span> <span class="n">flags</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">msg</span><span class="p">)))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nf">php_yar_client_trigger_error</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="n">YAR_ERR_TRANSPORT</span><span class="p">,</span> <span class="n">msg</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nf">php_yar_request_destroy</span><span class="p">(</span><span class="n">request</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nf">ZEND_ASSERT</span><span class="p">(</span><span class="n">msg</span> <span class="o">!=</span> <span class="p">(</span><span class="kt">char</span><span class="o">*</span><span class="p">)</span><span class="n">options</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nf">efree</span><span class="p">(</span><span class="n">msg</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">transport</span><span class="o">-&gt;</span><span class="nf">close</span><span class="p">(</span><span class="n">transport</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">factory</span><span class="o">-&gt;</span><span class="nf">destroy</span><span class="p">(</span><span class="n">transport</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// 对请求数据进行编码、组装等操作，并将处理后的数据保存到连接中
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">if</span> <span class="p">(</span><span class="nf">UNEXPECTED</span><span class="p">(</span><span class="o">!</span><span class="n">transport</span><span class="o">-&gt;</span><span class="nf">send</span><span class="p">(</span><span class="n">transport</span><span class="p">,</span> <span class="n">request</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">msg</span><span class="p">)))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nf">php_yar_client_trigger_error</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="n">YAR_ERR_TRANSPORT</span><span class="p">,</span> <span class="n">msg</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nf">php_yar_request_destroy</span><span class="p">(</span><span class="n">request</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nf">efree</span><span class="p">(</span><span class="n">msg</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">transport</span><span class="o">-&gt;</span><span class="nf">close</span><span class="p">(</span><span class="n">transport</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">factory</span><span class="o">-&gt;</span><span class="nf">destroy</span><span class="p">(</span><span class="n">transport</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// 执行请求
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="n">response</span> <span class="o">=</span> <span class="n">transport</span><span class="o">-&gt;</span><span class="nf">exec</span><span class="p">(</span><span class="n">transport</span><span class="p">,</span> <span class="n">request</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nf">UNEXPECTED</span><span class="p">(</span><span class="n">response</span><span class="o">-&gt;</span><span class="n">status</span> <span class="o">!=</span> <span class="n">YAR_ERR_OKEY</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// 调用失败则返回异常响应
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="nf">php_yar_client_handle_error</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="n">response</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nf">php_yar_request_destroy</span><span class="p">(</span><span class="n">request</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nf">php_yar_response_destroy</span><span class="p">(</span><span class="n">response</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">transport</span><span class="o">-&gt;</span><span class="nf">close</span><span class="p">(</span><span class="n">transport</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">factory</span><span class="o">-&gt;</span><span class="nf">destroy</span><span class="p">(</span><span class="n">transport</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">response</span><span class="o">-&gt;</span><span class="n">out</span> <span class="o">&amp;&amp;</span> <span class="nf">ZSTR_LEN</span><span class="p">(</span><span class="n">response</span><span class="o">-&gt;</span><span class="n">out</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nf">PHPWRITE</span><span class="p">(</span><span class="nf">ZSTR_VAL</span><span class="p">(</span><span class="n">response</span><span class="o">-&gt;</span><span class="n">out</span><span class="p">),</span> <span class="nf">ZSTR_LEN</span><span class="p">(</span><span class="n">response</span><span class="o">-&gt;</span><span class="n">out</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// 调用成功则将响应值拷贝到返回值中。
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="nf">ZVAL_COPY</span><span class="p">(</span><span class="n">retval</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">response</span><span class="o">-&gt;</span><span class="n">retval</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nf">php_yar_request_destroy</span><span class="p">(</span><span class="n">request</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nf">php_yar_response_destroy</span><span class="p">(</span><span class="n">response</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">transport</span><span class="o">-&gt;</span><span class="nf">close</span><span class="p">(</span><span class="n">transport</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">factory</span><span class="o">-&gt;</span><span class="nf">destroy</span><span class="p">(</span><span class="n">transport</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>php_yar_client_handle 函数的主要逻辑都写上注释了，transport 调用的那些函数，就是<a href="/posts/2022/01/06/yar-internals-transport/#yar-transport-interface-t-1">上一篇文章</a>中的那些 curl 函数，我们可以使用 gdb 打印 transport 的值进行验证。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="p">(</span><span class="n">gdb</span><span class="p">)</span> <span class="n">p</span> <span class="o">*</span><span class="n">transport</span>
</span></span><span class="line"><span class="cl"><span class="err">$</span><span class="mi">8</span> <span class="o">=</span> <span class="p">{</span><span class="n">data</span> <span class="o">=</span> <span class="mh">0x7ffff5481000</span><span class="p">,</span> <span class="n">open</span> <span class="o">=</span> <span class="mh">0x7ffff56ecd4b</span> <span class="o">&lt;</span><span class="n">php_yar_curl_open</span><span class="o">&gt;</span><span class="p">,</span> <span class="n">send</span> <span class="o">=</span> <span class="mh">0x7ffff56ee293</span> <span class="o">&lt;</span><span class="n">php_yar_curl_send</span><span class="o">&gt;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">exec</span> <span class="o">=</span> <span class="mh">0x7ffff56eddc0</span> <span class="o">&lt;</span><span class="n">php_yar_curl_exec</span><span class="o">&gt;</span><span class="p">,</span> <span class="n">setopt</span> <span class="o">=</span> <span class="mh">0x7ffff56ee78f</span> <span class="o">&lt;</span><span class="n">php_yar_curl_setopt</span><span class="o">&gt;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">calldata</span> <span class="o">=</span> <span class="mh">0x7ffff56ee761</span> <span class="o">&lt;</span><span class="n">php_yar_curl_set_calldata</span><span class="o">&gt;</span><span class="p">,</span> <span class="n">close</span> <span class="o">=</span> <span class="mh">0x7ffff56edaa1</span> <span class="o">&lt;</span><span class="n">php_yar_curl_close</span><span class="o">&gt;</span><span class="p">}</span>
</span></span></code></pre></div><h2 id="总结">总结</h2>
<p>下一篇文章将会介绍客户端并行调用的实现，并行调用也是 Yar 的亮点之一，其实现相对同步调用来说，会稍微复杂那么一丢丢。</p>
]]></content:encoded>
    </item>
    <item>
      <title>Yar 源码阅读笔记：数据传输模块</title>
      <link>https://her-cat.com/posts/2022/01/06/yar-internals-transport/</link>
      <pubDate>Thu, 06 Jan 2022 19:08:06 +0800</pubDate>
      <guid>https://her-cat.com/posts/2022/01/06/yar-internals-transport/</guid>
      <description>主要介绍 Yar 编码模块的结构体定义，以及 HTTP 传输方式的实现。</description>
      <content:encoded><![CDATA[<h2 id="前言">前言</h2>
<p>在前面几篇文章中，更多的是在研究 Yar 传输的内容，比如协议的格式是什么样的、如何对数据进行编码等等。</p>
<p>今天这篇文章，主要介绍 Yar 编码模块的结构体定义，以及 HTTP 传输方式的实现，更深入的了解 Yar 的协议数据是如何被发送出去的。</p>
<h2 id="传输模块结构体">传输模块结构体</h2>
<p>在 <a href="/posts/2021/11/03/yar-internals-protocol/#%E5%89%8D%E8%A8%80">RPC 通信协议</a> 中有提到过，Yar 支持 HTTP 和 TCP 两种数据传输方式，前者使用 curl，后者使用 socket。</p>
<h3 id="yar_transport_t">yar_transport_t</h3>
<p>我们可以将 yar_transport_t 结构体看作是同步传输器的工厂，用于创建和释放同步传输器的实例。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// source：yar_transport.h
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="c1">// 同步传输器的工厂
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">typedef</span> <span class="k">struct</span> <span class="n">_yar_transport</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">name</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">struct</span> <span class="n">_yar_transport_interface</span> <span class="o">*</span> <span class="p">(</span><span class="o">*</span><span class="n">init</span><span class="p">)();</span>
</span></span><span class="line"><span class="cl">    <span class="kt">void</span> <span class="p">(</span><span class="o">*</span><span class="n">destroy</span><span class="p">)(</span><span class="kt">yar_transport_interface_t</span> <span class="o">*</span><span class="n">self</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="kt">yar_transport_multi_t</span> <span class="o">*</span><span class="n">multi</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="kt">yar_transport_t</span><span class="p">;</span>
</span></span></code></pre></div><p>结构体字段说明：</p>
<ul>
<li>name：传输方式的名称，比如 “curl”。</li>
<li>init：函数指针，创建同步传输器实例并初始化相关配置，函数的返回值是结构体 _yar_transport_interface 的指针。</li>
<li>destroy：函数指针，释放某个同步传输器的资源，函数的参数是结构体 _yar_transport_interface 的指针。</li>
<li>multi：用于实现并行调用的结构体指针，这部分会在<a href="https://her-cat.com/posts/2022/01/14/yar-internals-client-concurrent-call/">并行调用</a>进行详解。</li>
</ul>
<h3 id="yar_transport_interface_t">yar_transport_interface_t</h3>
<p>在 yar_transport_t 结构体中，只定义了 init 和 destroy 这两个函数指针，用于对同步传输器的创建和释放。而真正用于传输时使用的相关函数，则放在了 yar_transport_interface_t 结构体中。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// source：yar_transport.h
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="c1">// 同步传输器
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">typedef</span> <span class="k">struct</span> <span class="n">_yar_transport_interface</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">void</span> <span class="o">*</span><span class="n">data</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">int</span>  <span class="p">(</span><span class="o">*</span><span class="n">open</span><span class="p">)(</span><span class="k">struct</span> <span class="n">_yar_transport_interface</span> <span class="o">*</span><span class="n">self</span><span class="p">,</span> <span class="n">zend_string</span> <span class="o">*</span><span class="n">address</span><span class="p">,</span> <span class="kt">long</span> <span class="n">options</span><span class="p">,</span> <span class="kt">char</span> <span class="o">**</span><span class="n">msg</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="kt">int</span>  <span class="p">(</span><span class="o">*</span><span class="n">send</span><span class="p">)(</span><span class="k">struct</span> <span class="n">_yar_transport_interface</span> <span class="o">*</span><span class="n">self</span><span class="p">,</span> <span class="k">struct</span> <span class="n">_yar_request</span> <span class="o">*</span><span class="n">request</span><span class="p">,</span> <span class="kt">char</span> <span class="o">**</span><span class="n">msg</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">struct</span> <span class="n">_yar_response</span> <span class="o">*</span> <span class="p">(</span><span class="o">*</span><span class="n">exec</span><span class="p">)(</span><span class="k">struct</span> <span class="n">_yar_transport_interface</span> <span class="o">*</span><span class="n">self</span><span class="p">,</span> <span class="k">struct</span> <span class="n">_yar_request</span> <span class="o">*</span><span class="n">request</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="kt">int</span>  <span class="p">(</span><span class="o">*</span><span class="n">setopt</span><span class="p">)(</span><span class="k">struct</span> <span class="n">_yar_transport_interface</span> <span class="o">*</span><span class="n">self</span><span class="p">,</span> <span class="kt">long</span> <span class="n">type</span><span class="p">,</span> <span class="kt">void</span> <span class="o">*</span><span class="n">value</span><span class="p">,</span> <span class="kt">void</span> <span class="o">*</span><span class="n">addition</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="kt">int</span>  <span class="p">(</span><span class="o">*</span><span class="n">calldata</span><span class="p">)(</span><span class="k">struct</span> <span class="n">_yar_transport_interface</span> <span class="o">*</span><span class="n">self</span><span class="p">,</span> <span class="kt">yar_call_data_t</span> <span class="o">*</span><span class="n">calldata</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="kt">void</span> <span class="p">(</span><span class="o">*</span><span class="n">close</span><span class="p">)(</span><span class="k">struct</span> <span class="n">_yar_transport_interface</span> <span class="o">*</span><span class="n">self</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="kt">yar_transport_interface_t</span><span class="p">;</span>
</span></span></code></pre></div><p>结构体说明：</p>
<ul>
<li>data：存储传输相关的数据指针，比如 curl 是 yar_curl_data_t 结构体，socket 是 yar_socket_data_t 结构体。</li>
<li>open：函数指针，打开/创建一个连接，并初始化相关默认值。</li>
<li>send：函数指针，对请求数据进行编码、组装等操作，并将处理后的数据保存到连接中。</li>
<li>exec：函数指针，执行请求，将连接中的数据发送出去。</li>
<li>setopt：函数指针，设置连接相关的选项，比如编码方式、超时时间。</li>
<li>calldata：函数指针，设置回调时的数据，在并行调用时才会用到。</li>
<li>close：函数指针：关闭连接并释放相关的资源。</li>
</ul>
<h2 id="http-传输方式">HTTP 传输方式</h2>
<p>Yar 支持的传输方式都放在了 transports 目录下，因为 HTTP 是 Yar 默认的数据传输方式，并且服务端也只实现了 HTTP 这种方式，所以我们这里用 HTTP 进行举例。</p>
<p>用 c 语言实现 HTTP 请求的类库有很多，Yar 使用的是 curl，所以在 transports 目录下的文件命名是 curl.c。</p>
<h3 id="yar_transport_t-1">yar_transport_t</h3>
<p>根据前面传输模块的介绍，首先需要实现 yar_transport_t 结构体。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// source：transports/curl.c
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="k">const</span> <span class="kt">yar_transport_t</span> <span class="n">yar_transport_curl</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="s">&#34;curl&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">php_yar_curl_init</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">php_yar_curl_destroy</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="o">&amp;</span><span class="n">yar_transport_curl_multi</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><p>curl 是传输方式的名称，php_yar_curl_init 和 php_yar_curl_destroy 是 init 和 destroy 这两个函数指针的实现，yar_transport_curl_multi 用来实现并行调用。</p>
<p>先来看看 php_yar_curl_init 函数。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// source：transports/curl.c
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="kt">yar_transport_interface_t</span> <span class="o">*</span><span class="nf">php_yar_curl_init</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">yar_curl_data_t</span> <span class="o">*</span><span class="n">data</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">yar_transport_interface_t</span> <span class="o">*</span><span class="n">self</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// 分配 yar_transport_interface_t 结构体及数据的内存
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="n">self</span> <span class="o">=</span> <span class="nf">ecalloc</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="kt">yar_transport_interface_t</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">    <span class="n">self</span><span class="o">-&gt;</span><span class="n">data</span> <span class="o">=</span> <span class="n">data</span> <span class="o">=</span> <span class="nf">ecalloc</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="kt">yar_curl_data_t</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// 设置 Yar 的 HTTP 头部信息
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="cm">/* snprintf(content_type, sizeof(content_type), &#34;Content-Type: %s&#34;, YAR_G(content_type)); */</span>
</span></span><span class="line"><span class="cl">    <span class="n">data</span><span class="o">-&gt;</span><span class="n">headers</span> <span class="o">=</span> <span class="nf">curl_slist_append</span><span class="p">(</span><span class="n">data</span><span class="o">-&gt;</span><span class="n">headers</span><span class="p">,</span> <span class="s">&#34;User-Agent: PHP Yar RPC-&#34;</span> <span class="n">PHP_YAR_VERSION</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">data</span><span class="o">-&gt;</span><span class="n">headers</span> <span class="o">=</span> <span class="nf">curl_slist_append</span><span class="p">(</span><span class="n">data</span><span class="o">-&gt;</span><span class="n">headers</span><span class="p">,</span> <span class="s">&#34;Expect:&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// 设置各个函数指针对应的函数实现
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="n">self</span><span class="o">-&gt;</span><span class="n">open</span>       <span class="o">=</span> <span class="n">php_yar_curl_open</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">self</span><span class="o">-&gt;</span><span class="n">send</span>       <span class="o">=</span> <span class="n">php_yar_curl_send</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">self</span><span class="o">-&gt;</span><span class="n">exec</span>       <span class="o">=</span> <span class="n">php_yar_curl_exec</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">self</span><span class="o">-&gt;</span><span class="n">setopt</span>     <span class="o">=</span> <span class="n">php_yar_curl_setopt</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">self</span><span class="o">-&gt;</span><span class="n">calldata</span>   <span class="o">=</span> <span class="n">php_yar_curl_set_calldata</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">self</span><span class="o">-&gt;</span><span class="n">close</span>      <span class="o">=</span> <span class="n">php_yar_curl_close</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// 分配响应数据及请求数据的内存，默认为 1M
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nf">smart_str_alloc</span><span class="p">((</span><span class="o">&amp;</span><span class="n">data</span><span class="o">-&gt;</span><span class="n">buf</span><span class="p">),</span> <span class="n">YAR_PACKAGER_BUFFER_SIZE</span> <span class="cm">/* 1M */</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nf">smart_str_alloc</span><span class="p">((</span><span class="o">&amp;</span><span class="n">data</span><span class="o">-&gt;</span><span class="n">postfield</span><span class="p">),</span> <span class="n">YAR_PACKAGER_BUFFER_SIZE</span> <span class="cm">/* 1M */</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// 返回 yar_transport_interface_t 结构体的指针
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">return</span>  <span class="n">self</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>由于没有资源需要在 destroy 的时候进行释放，所以 php_yar_curl_destroy 是一个空函数。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// source：transports/curl.c
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="kt">void</span> <span class="nf">php_yar_curl_destroy</span><span class="p">(</span><span class="kt">yar_transport_interface_t</span> <span class="o">*</span><span class="n">self</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> 
</span></span></code></pre></div><h3 id="yar_transport_interface_t-1">yar_transport_interface_t</h3>
<p>接下来看看 curl 中 yar_transport_interface_t 相关的实现。</p>
<p>首先是 php_yar_curl_open 函数，以下是该函数的主要逻辑：</p>
<ul>
<li>如果开启了连接持久化，则尝试从持久化的连接池中找到未使用的连接。</li>
<li>没有找到符合条件的连接或者未开启连接持久化，则直接创建一个新的 curl 实例。</li>
<li>解析请求地址，并将请求信息及 curl 实例保存到 data 中，方便后续在其它函数中使用。</li>
<li>如果开启了连接持久化，则设置 keep-alive 及 keep-alive 有效时间的头部信息。</li>
<li>设置自定义的头部信息及域名。</li>
<li>设置 curl 收到响应时的回调函数及其它 curl 选项。</li>
<li>设置 curl 请求地址。</li>
</ul>
<p>打开连接之后，调用 php_yar_curl_send 函数对请求数据进行编码、组装等操作。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// source：transports/curl.c
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="kt">int</span> <span class="nf">php_yar_curl_send</span><span class="p">(</span><span class="kt">yar_transport_interface_t</span><span class="o">*</span> <span class="n">self</span><span class="p">,</span> <span class="kt">yar_request_t</span> <span class="o">*</span><span class="n">request</span><span class="p">,</span> <span class="kt">char</span> <span class="o">**</span><span class="n">msg</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">yar_header_t</span> <span class="n">header</span> <span class="o">=</span> <span class="p">{</span><span class="mi">0</span><span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="kt">yar_curl_data_t</span> <span class="o">*</span><span class="n">data</span> <span class="o">=</span> <span class="p">(</span><span class="kt">yar_curl_data_t</span> <span class="o">*</span><span class="p">)</span><span class="n">self</span><span class="o">-&gt;</span><span class="n">data</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">zend_string</span> <span class="o">*</span><span class="n">payload</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// 对请求数据进行编码
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="p">(</span><span class="n">payload</span> <span class="o">=</span> <span class="nf">php_yar_request_pack</span><span class="p">(</span><span class="n">request</span><span class="p">,</span> <span class="n">msg</span><span class="p">)))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// 通过编码后的数据初始化 header 结构体
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nf">php_yar_protocol_render</span><span class="p">(</span><span class="o">&amp;</span><span class="n">header</span><span class="p">,</span> <span class="n">request</span><span class="o">-&gt;</span><span class="n">id</span><span class="p">,</span> <span class="n">data</span><span class="o">-&gt;</span><span class="n">host</span><span class="o">-&gt;</span><span class="n">user</span><span class="p">,</span> <span class="n">data</span><span class="o">-&gt;</span><span class="n">host</span><span class="o">-&gt;</span><span class="n">pass</span><span class="p">,</span> <span class="nf">ZSTR_LEN</span><span class="p">(</span><span class="n">payload</span><span class="p">),</span> <span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// 将 header 及 payload 按照顺序追加到请求数据的 buf 中
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nf">smart_str_appendl</span><span class="p">(</span><span class="o">&amp;</span><span class="n">data</span><span class="o">-&gt;</span><span class="n">postfield</span><span class="p">,</span> <span class="p">(</span><span class="kt">char</span> <span class="o">*</span><span class="p">)</span><span class="o">&amp;</span><span class="n">header</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="kt">yar_header_t</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">    <span class="nf">smart_str_appendl</span><span class="p">(</span><span class="o">&amp;</span><span class="n">data</span><span class="o">-&gt;</span><span class="n">postfield</span><span class="p">,</span> <span class="nf">ZSTR_VAL</span><span class="p">(</span><span class="n">payload</span><span class="p">),</span> <span class="nf">ZSTR_LEN</span><span class="p">(</span><span class="n">payload</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">    <span class="nf">zend_string_release</span><span class="p">(</span><span class="n">payload</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>数据准备就绪后，就可以调用 php_yar_curl_exec 函数执行请求，将数据发送出去并解析响应结果。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// source：transports/curl.c
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="kt">yar_response_t</span> <span class="o">*</span><span class="nf">php_yar_curl_exec</span><span class="p">(</span><span class="kt">yar_transport_interface_t</span><span class="o">*</span> <span class="n">self</span><span class="p">,</span> <span class="kt">yar_request_t</span> <span class="o">*</span><span class="n">request</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl">    <span class="c1">// 设置请求数据及大小
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nf">curl_easy_setopt</span><span class="p">(</span><span class="n">data</span><span class="o">-&gt;</span><span class="n">cp</span><span class="p">,</span> <span class="n">CURLOPT_POSTFIELDS</span><span class="p">,</span> <span class="nf">ZSTR_VAL</span><span class="p">(</span><span class="n">data</span><span class="o">-&gt;</span><span class="n">postfield</span><span class="p">.</span><span class="n">s</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">    <span class="nf">curl_easy_setopt</span><span class="p">(</span><span class="n">data</span><span class="o">-&gt;</span><span class="n">cp</span><span class="p">,</span> <span class="n">CURLOPT_POSTFIELDSIZE</span><span class="p">,</span> <span class="nf">ZSTR_LEN</span><span class="p">(</span><span class="n">data</span><span class="o">-&gt;</span><span class="n">postfield</span><span class="p">.</span><span class="n">s</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// 设置超时时间等选项
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">if</span> <span class="p">(</span><span class="n">IS_ARRAY</span> <span class="o">==</span> <span class="nf">Z_TYPE</span><span class="p">(</span><span class="n">request</span><span class="o">-&gt;</span><span class="n">options</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">zval</span> <span class="o">*</span><span class="n">pzval</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">((</span><span class="n">pzval</span> <span class="o">=</span> <span class="nf">zend_hash_index_find</span><span class="p">(</span><span class="nf">Z_ARRVAL</span><span class="p">(</span><span class="n">request</span><span class="o">-&gt;</span><span class="n">options</span><span class="p">),</span> <span class="n">YAR_OPT_TIMEOUT</span><span class="p">)))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nf">convert_to_long_ex</span><span class="p">(</span><span class="n">pzval</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="n">self</span><span class="o">-&gt;</span><span class="nf">setopt</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">YAR_OPT_TIMEOUT</span><span class="p">,</span> <span class="p">(</span><span class="kt">long</span> <span class="o">*</span><span class="p">)</span><span class="o">&amp;</span><span class="nf">Z_LVAL_P</span><span class="p">(</span><span class="n">pzval</span><span class="p">),</span> <span class="nb">NULL</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">((</span><span class="n">pzval</span> <span class="o">=</span> <span class="nf">zend_hash_index_find</span><span class="p">(</span><span class="nf">Z_ARRVAL</span><span class="p">(</span><span class="n">request</span><span class="o">-&gt;</span><span class="n">options</span><span class="p">),</span> <span class="n">YAR_OPT_CONNECT_TIMEOUT</span><span class="p">)))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nf">convert_to_long_ex</span><span class="p">(</span><span class="n">pzval</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="n">self</span><span class="o">-&gt;</span><span class="nf">setopt</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">YAR_OPT_CONNECT_TIMEOUT</span><span class="p">,</span> <span class="p">(</span><span class="kt">long</span> <span class="o">*</span><span class="p">)</span><span class="o">&amp;</span><span class="nf">Z_LVAL_P</span><span class="p">(</span><span class="n">pzval</span><span class="p">),</span> <span class="nb">NULL</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">((</span><span class="n">pzval</span> <span class="o">=</span> <span class="nf">zend_hash_index_find</span><span class="p">(</span><span class="nf">Z_ARRVAL</span><span class="p">(</span><span class="n">request</span><span class="o">-&gt;</span><span class="n">options</span><span class="p">),</span> <span class="n">YAR_OPT_PROXY</span><span class="p">)))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nf">convert_to_string_ex</span><span class="p">(</span><span class="n">pzval</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="n">self</span><span class="o">-&gt;</span><span class="nf">setopt</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">YAR_OPT_PROXY</span><span class="p">,</span> <span class="p">(</span><span class="kt">char</span> <span class="o">*</span><span class="p">)</span><span class="o">&amp;</span><span class="nf">Z_STRVAL_P</span><span class="p">(</span><span class="n">pzval</span><span class="p">),</span> <span class="nb">NULL</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">response</span> <span class="o">=</span> <span class="nf">php_yar_response_instance</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// 执行请求
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="n">ret</span> <span class="o">=</span> <span class="nf">curl_easy_perform</span><span class="p">(</span><span class="n">data</span><span class="o">-&gt;</span><span class="n">cp</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">ret</span> <span class="o">!=</span> <span class="n">CURLE_OK</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// 请求失败则将失败信息保存到 response 中并返回
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="n">len</span> <span class="o">=</span> <span class="nf">spprintf</span><span class="p">(</span><span class="o">&amp;</span><span class="n">msg</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="s">&#34;curl exec failed &#39;%s&#39;&#34;</span><span class="p">,</span> <span class="nf">curl_easy_strerror</span><span class="p">(</span><span class="n">ret</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">        <span class="nf">php_yar_response_set_error</span><span class="p">(</span><span class="n">response</span><span class="p">,</span> <span class="n">YAR_ERR_TRANSPORT</span><span class="p">,</span> <span class="n">msg</span><span class="p">,</span> <span class="n">len</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nf">efree</span><span class="p">(</span><span class="n">msg</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">response</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kt">long</span> <span class="n">http_code</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// 检查响应状态码是否等于 200
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="k">if</span><span class="p">(</span><span class="nf">curl_easy_getinfo</span><span class="p">(</span><span class="n">data</span><span class="o">-&gt;</span><span class="n">cp</span><span class="p">,</span> <span class="n">CURLINFO_RESPONSE_CODE</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">http_code</span><span class="p">)</span> <span class="o">==</span> <span class="n">CURLE_OK</span> 
</span></span><span class="line"><span class="cl">                <span class="o">&amp;&amp;</span> <span class="n">http_code</span> <span class="o">!=</span> <span class="mi">200</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">len</span> <span class="o">=</span> <span class="nf">spprintf</span><span class="p">(</span><span class="o">&amp;</span><span class="n">msg</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="s">&#34;server responsed non-200 code &#39;%ld&#39;&#34;</span><span class="p">,</span> <span class="n">http_code</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="nf">php_yar_response_set_error</span><span class="p">(</span><span class="n">response</span><span class="p">,</span> <span class="n">YAR_ERR_TRANSPORT</span><span class="p">,</span> <span class="n">msg</span><span class="p">,</span> <span class="n">len</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="nf">efree</span><span class="p">(</span><span class="n">msg</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">response</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">data</span><span class="o">-&gt;</span><span class="n">buf</span><span class="p">.</span><span class="n">s</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl">        <span class="c1">// 从响应内容中解析出 Yar 协议的头部信息
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="p">(</span><span class="n">header</span> <span class="o">=</span> <span class="nf">php_yar_protocol_parse</span><span class="p">(</span><span class="n">payload</span><span class="p">)))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nf">php_yar_error</span><span class="p">(</span><span class="n">response</span><span class="p">,</span> <span class="n">YAR_ERR_PROTOCOL</span><span class="p">,</span> <span class="s">&#34;malformed response header &#39;%.32s&#39;&#34;</span><span class="p">,</span> <span class="n">payload</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">response</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// 跳过 Yar 协议的头部信息后的内容，就是真正的响应结果
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="n">payload</span> <span class="o">+=</span> <span class="k">sizeof</span><span class="p">(</span><span class="kt">yar_header_t</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">payload_len</span> <span class="o">-=</span> <span class="k">sizeof</span><span class="p">(</span><span class="kt">yar_header_t</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// 通过 payload 中的编码方式对 payload 进行解码
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="p">(</span><span class="n">retval</span> <span class="o">=</span> <span class="nf">php_yar_packager_unpack</span><span class="p">(</span><span class="n">payload</span><span class="p">,</span> <span class="n">payload_len</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">msg</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">ret</span><span class="p">)))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nf">php_yar_response_set_error</span><span class="p">(</span><span class="n">response</span><span class="p">,</span> <span class="n">YAR_ERR_PACKAGER</span><span class="p">,</span> <span class="n">msg</span><span class="p">,</span> <span class="nf">strlen</span><span class="p">(</span><span class="n">msg</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">            <span class="nf">efree</span><span class="p">(</span><span class="n">msg</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">response</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// 将解码后的数据存储到 response 中
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="nf">php_yar_response_map_retval</span><span class="p">(</span><span class="n">response</span><span class="p">,</span> <span class="n">retval</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="nf">zval_ptr_dtor</span><span class="p">(</span><span class="n">retval</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// 响应内容为空，设置错误信息
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="nf">php_yar_response_set_error</span><span class="p">(</span><span class="n">response</span><span class="p">,</span> <span class="n">YAR_ERR_EMPTY_RESPONSE</span><span class="p">,</span> <span class="nf">ZEND_STRL</span><span class="p">(</span><span class="s">&#34;empty response&#34;</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>    
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">response</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>得到响应结果后，调用 php_yar_curl_close 关闭连接。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// source：transports/curl.c
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="kt">void</span> <span class="nf">php_yar_curl_close</span><span class="p">(</span><span class="kt">yar_transport_interface_t</span><span class="o">*</span> <span class="n">self</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">yar_curl_data_t</span> <span class="o">*</span><span class="n">data</span> <span class="o">=</span> <span class="p">(</span><span class="kt">yar_curl_data_t</span> <span class="o">*</span><span class="p">)</span><span class="n">self</span><span class="o">-&gt;</span><span class="n">data</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">data</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">data</span><span class="o">-&gt;</span><span class="n">cp</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">data</span><span class="o">-&gt;</span><span class="n">persistent</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="c1">// 没有开启连接持久化时，清理 curl 连接
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>            <span class="nf">curl_easy_cleanup</span><span class="p">(</span><span class="n">data</span><span class="o">-&gt;</span><span class="n">cp</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="c1">// 否则，设置连接为未使用状态
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>            <span class="n">data</span><span class="o">-&gt;</span><span class="n">plink</span><span class="o">-&gt;</span><span class="n">in_use</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// 释放 data 中的相关资源
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">if</span> <span class="p">(</span><span class="n">data</span><span class="o">-&gt;</span><span class="n">host</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nf">php_url_free</span><span class="p">(</span><span class="n">data</span><span class="o">-&gt;</span><span class="n">host</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nf">smart_str_free</span><span class="p">(</span><span class="o">&amp;</span><span class="n">data</span><span class="o">-&gt;</span><span class="n">buf</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nf">smart_str_free</span><span class="p">(</span><span class="o">&amp;</span><span class="n">data</span><span class="o">-&gt;</span><span class="n">postfield</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nf">curl_slist_free_all</span><span class="p">(</span><span class="n">data</span><span class="o">-&gt;</span><span class="n">headers</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nf">efree</span><span class="p">(</span><span class="n">data</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nf">efree</span><span class="p">(</span><span class="n">self</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h2 id="总结">总结</h2>
<p>这次没有像<a href="https://her-cat.com/posts/2021/12/23/yar-internals-packager/">上一篇文章</a>那样，将传输模块的生命周期各个阶段都写出来，这是因为传输模块在 PHP 生命周期中的几个阶段做的事情跟编码模块差不多，所以就省略掉了。</p>
<p>通过本文可以看出来，Yar 中的 HTTP 传输方式，实际上是对 curl 的一层封装，我写了一个 curl 的小示例，供大家参考。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;stdio.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;curl/curl.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;curl/easy.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;stdint.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;stdlib.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;string.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="cl"><span class="cp">#define DEFAULT_BUF_SIZE 1024
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="cl"><span class="k">typedef</span> <span class="k">struct</span> <span class="n">curl_data_s</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">CURL</span> <span class="o">*</span><span class="n">cp</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">char</span> <span class="o">*</span><span class="n">buf</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint32_t</span> <span class="n">len</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint32_t</span> <span class="n">alloc</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="kt">curl_data_t</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kt">size_t</span> <span class="nf">curl_buf_writer</span><span class="p">(</span><span class="kt">char</span> <span class="o">*</span><span class="n">ptr</span><span class="p">,</span> <span class="kt">size_t</span> <span class="n">size</span><span class="p">,</span> <span class="kt">size_t</span> <span class="n">nmemb</span><span class="p">,</span> <span class="kt">void</span> <span class="o">*</span><span class="n">ctx</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">curl_data_t</span> <span class="o">*</span><span class="n">data</span> <span class="o">=</span> <span class="p">(</span><span class="kt">curl_data_t</span> <span class="o">*</span><span class="p">)</span> <span class="n">ctx</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">size_t</span> <span class="n">len</span> <span class="o">=</span> <span class="n">size</span> <span class="o">*</span> <span class="n">nmemb</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">size_t</span> <span class="n">remain</span> <span class="o">=</span> <span class="n">data</span><span class="o">-&gt;</span><span class="n">alloc</span> <span class="o">-</span> <span class="n">data</span><span class="o">-&gt;</span><span class="n">len</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">len</span> <span class="o">&gt;</span> <span class="n">remain</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">data</span><span class="o">-&gt;</span><span class="n">alloc</span> <span class="o">+=</span> <span class="p">(</span><span class="n">len</span> <span class="o">-</span> <span class="n">remain</span><span class="p">)</span> <span class="o">+</span> <span class="n">DEFAULT_BUF_SIZE</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">data</span><span class="o">-&gt;</span><span class="n">buf</span> <span class="o">=</span> <span class="nf">realloc</span><span class="p">(</span><span class="n">data</span><span class="o">-&gt;</span><span class="n">buf</span><span class="p">,</span> <span class="n">data</span><span class="o">-&gt;</span><span class="n">alloc</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nf">printf</span><span class="p">(</span><span class="s">&#34;writer, realloc size: %lu</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">,</span> <span class="p">(</span><span class="n">len</span> <span class="o">-</span> <span class="n">remain</span><span class="p">)</span> <span class="o">+</span> <span class="n">DEFAULT_BUF_SIZE</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nf">printf</span><span class="p">(</span><span class="s">&#34;write, len: %zu</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">,</span> <span class="n">len</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nf">memcpy</span><span class="p">(</span><span class="n">data</span><span class="o">-&gt;</span><span class="n">buf</span> <span class="o">+</span> <span class="n">data</span><span class="o">-&gt;</span><span class="n">len</span><span class="p">,</span> <span class="n">ptr</span><span class="p">,</span> <span class="n">len</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">data</span><span class="o">-&gt;</span><span class="n">len</span> <span class="o">+=</span> <span class="n">len</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">len</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kt">int</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">curl_data_t</span> <span class="n">data</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">char</span> <span class="o">*</span><span class="n">address</span> <span class="o">=</span> <span class="s">&#34;https://her-cat.com&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">CURL</span> <span class="o">*</span><span class="n">cp</span> <span class="o">=</span> <span class="nf">curl_easy_init</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">data</span><span class="p">.</span><span class="n">cp</span> <span class="o">=</span> <span class="n">cp</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">data</span><span class="p">.</span><span class="n">buf</span> <span class="o">=</span> <span class="nf">malloc</span><span class="p">(</span><span class="n">DEFAULT_BUF_SIZE</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">data</span><span class="p">.</span><span class="n">len</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">data</span><span class="p">.</span><span class="n">alloc</span> <span class="o">=</span> <span class="n">DEFAULT_BUF_SIZE</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nf">curl_easy_setopt</span><span class="p">(</span><span class="n">cp</span><span class="p">,</span> <span class="n">CURLOPT_POST</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nf">curl_easy_setopt</span><span class="p">(</span><span class="n">cp</span><span class="p">,</span> <span class="n">CURLOPT_URL</span><span class="p">,</span> <span class="n">address</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nf">curl_easy_setopt</span><span class="p">(</span><span class="n">cp</span><span class="p">,</span> <span class="n">CURLOPT_SSL_VERIFYHOST</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nf">curl_easy_setopt</span><span class="p">(</span><span class="n">cp</span><span class="p">,</span> <span class="n">CURLOPT_SSL_VERIFYPEER</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nf">curl_easy_setopt</span><span class="p">(</span><span class="n">cp</span><span class="p">,</span> <span class="n">CURLOPT_WRITEDATA</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">data</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nf">curl_easy_setopt</span><span class="p">(</span><span class="n">cp</span><span class="p">,</span> <span class="n">CURLOPT_WRITEFUNCTION</span><span class="p">,</span> <span class="n">curl_buf_writer</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">CURLcode</span> <span class="n">ret</span> <span class="o">=</span> <span class="nf">curl_easy_perform</span><span class="p">(</span><span class="n">cp</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">ret</span> <span class="o">!=</span> <span class="n">CURLE_OK</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nf">printf</span><span class="p">(</span><span class="s">&#34;curl perform failed, reason: %s</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">,</span> <span class="nf">curl_easy_strerror</span><span class="p">(</span><span class="n">ret</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kt">long</span> <span class="n">http_code</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nf">curl_easy_getinfo</span><span class="p">(</span><span class="n">cp</span><span class="p">,</span> <span class="n">CURLINFO_RESPONSE_CODE</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">http_code</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nf">curl_easy_cleanup</span><span class="p">(</span><span class="n">cp</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nf">curl_global_cleanup</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nf">printf</span><span class="p">(</span><span class="s">&#34;http code: %ld</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">,</span> <span class="n">http_code</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nf">printf</span><span class="p">(</span><span class="s">&#34;buf len: %u</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">,</span> <span class="n">data</span><span class="p">.</span><span class="n">len</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nf">printf</span><span class="p">(</span><span class="s">&#34;buf alloc: %u</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">,</span> <span class="n">data</span><span class="p">.</span><span class="n">alloc</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nf">printf</span><span class="p">(</span><span class="s">&#34;response: </span><span class="se">\n</span><span class="s">%s</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">,</span> <span class="n">data</span><span class="p">.</span><span class="n">buf</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div>]]></content:encoded>
    </item>
    <item>
      <title>Yar 源码阅读笔记：消息编码模块</title>
      <link>https://her-cat.com/posts/2021/12/23/yar-internals-packager/</link>
      <pubDate>Thu, 23 Dec 2021 16:08:05 +0800</pubDate>
      <guid>https://her-cat.com/posts/2021/12/23/yar-internals-packager/</guid>
      <description>今天我们来了解一下，Yar 中的消息编码模块是怎样实现的。</description>
      <content:encoded><![CDATA[<h2 id="前言">前言</h2>
<p>在 <a href="https://her-cat.com/posts/2021/11/03/yar-internals-protocol/">上一篇文章</a> 中，我们知道了 Yar 通信协议的格式及作用，还提到了在 Yar 客户端发送请求前和收到响应后，需要先对数据进行编码与解码才能继续进一步操作。</p>
<p>今天我们来了解一下，Yar 中的消息编码模块是怎样实现的。</p>
<h2 id="什么是编码解码">什么是编码/解码</h2>
<p>在介绍 Yar 编码模块之前，我们先来了解一下，什么是编码与解码？</p>
<p>编码（encode）和解码（decode）也有人叫作序列化（serialization）和反序列化（deserialization），其实都是一个意思。</p>
<blockquote>
<p>为了统一描述，在后文中都称为编码和解码。</p></blockquote>
<p>编码是指将数据结构或对象转换成可以存储或传输的数据格式（字节流）。</p>
<p>解码是编码的反向操作，将字节流转换为数据数据结构或对象。</p>
<p>举个例子，假如要将 Person 对象写入到文件中，然后再从文件中还原该对象，应该怎么处理？</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Person</span> 
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">public</span> <span class="nv">$name</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="k">public</span> <span class="nv">$age</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">$person</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Person</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="nv">$person</span><span class="o">-&gt;</span><span class="na">name</span> <span class="o">=</span> <span class="s1">&#39;her-cat&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="nv">$person</span><span class="o">-&gt;</span><span class="na">age</span> <span class="o">=</span> <span class="mi">18</span><span class="p">;</span>
</span></span></code></pre></div><p>PHP 提供了两个函数： <a href="https://www.php.net/manual/zh/function.serialize.php">serialize</a>（编码）和 <a href="https://www.php.net/manual/zh/function.unserialize.php">unserialize</a>（解码）。</p>
<p>serialize 函数可以将除了 resource 类型以外的数据编码为字符串（字节流）。</p>
<p>unserialize 函数可以将编码后的字符串转换为 PHP 的值。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="nv">$encoded</span> <span class="o">=</span> <span class="nx">file_get_contents</span><span class="p">(</span><span class="s1">&#39;person.txt&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="nx">var_dump</span><span class="p">(</span><span class="nv">$encoded</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">$decodedPerson</span> <span class="o">=</span> <span class="nx">unserialize</span><span class="p">(</span><span class="nv">$encoded</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">var_dump</span><span class="p">(</span><span class="nv">$decodedPerson</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="nx">var_dump</span><span class="p">(</span><span class="nv">$decodedPerson</span><span class="o">-&gt;</span><span class="na">name</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="nx">var_dump</span><span class="p">(</span><span class="nv">$decodedPerson</span><span class="o">-&gt;</span><span class="na">age</span><span class="p">);</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">string(57) &#34;O:6:&#34;Person&#34;:2:{s:4:&#34;name&#34;;s:7:&#34;her-cat&#34;;s:3:&#34;age&#34;;i:18;}&#34;
</span></span><span class="line"><span class="cl">object(Person)#2 (2) {
</span></span><span class="line"><span class="cl">  [&#34;name&#34;]=&gt;
</span></span><span class="line"><span class="cl">  string(7) &#34;her-cat&#34;
</span></span><span class="line"><span class="cl">  [&#34;age&#34;]=&gt;
</span></span><span class="line"><span class="cl">  int(18)
</span></span><span class="line"><span class="cl">}
</span></span><span class="line"><span class="cl">string(7) &#34;her-cat&#34;
</span></span><span class="line"><span class="cl">int(18)
</span></span></code></pre></div><h2 id="为什么需要编码解码">为什么需要编码/解码</h2>
<p>目的就是为了在不同的编程语言或不同的载体之间交换数据。</p>
<p>上面将对象写入到文件中就是不同载体之间交换数据的例子，这里再举一个不同语言之间的例子。</p>
<p>就拿 PHP 和 JavaScript 来说，我们用 PHP 写了一个 HTTP 接口给 JavaScript 调用，然后 JavaScript 解析 PHP 响应的内容。</p>
<p>PHP 和 JavaScript 是两种不同的编程语言（<del>废话</del>），两者各种数据结构的实现是不一样的，在内存上的组织方式也不一样，所以它们不能直接使用内存中的数据进行数据交换。</p>
<p>就好比两个人在交流，一个人讲蒙古语，另一个人讲粤语，最后两人都不知道对方讲的啥玩意。</p>
<p>所以，我们需要一个中间人帮忙翻译，或者使用两个人都会的一种语言进行交流，比如普通话。</p>
<p>将 “普通话” 代入到上面的例子中，实际上就是引用一种通用的数据编码格式，让 PHP 与 JavaScript 能够进行“交流”。</p>
<p>假设我们使用 JSON，PHP 将数据发送到前端之前，先调用 json_encode 将数据转换成 JSON 字符串（这个过程就是编码），
前端页面收到 PHP 返回的 JSON 字符串后，将其解析成前端的 JavaScript 的数组/对象（这个过程就是解码）。</p>
<h2 id="yar-编码模块简介">Yar 编码模块简介</h2>
<p>Yar 提供了三种编码方式：分别是 PHP、JSON、Msgpack。</p>
<p><a href="https://www.php.net/manual/zh/function.serialize.php">PHP</a> 是 PHP 内置的一种对数据结构/对象编码的方式，实际上就是使用 serialize 和 unserialize 这对函数，可以参考上面的例子。</p>
<p><a href="http://www.json.org/json-zh">JSON</a> 是目前比较通用且流行的一种数据编码格式，由于其简单、易读的特点，所以被广泛地用于 API 接口、JSON-RPC、数据存储等地方。</p>
<p>但是，JSON 也不是一点儿缺点都没有的，JSON 为了保证其可读性，需要多使用一些内存来保存相关信息，所以在数据传输的过程中，占用的内存就会比 Msgpack 这类编码格式要大一些。</p>
<p><a href="https://msgpack.org/">Msgpack</a> 一种高效的二进制编码格式，有点儿类似于 JSON，但是比 JSON 占用内存更小、效率更高。</p>
<p>前两种方式在 Yar 中已经内置支持了（直接调用相关的 PHP 函数），Msgpack 则需要自己手动安装扩展，并在编译时指定。</p>
<h3 id="编码模块结构体">编码模块结构体</h3>
<p>Yar 定义了编码模块结构体：yar_packager_t。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// source：yar_packager.h
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="k">typedef</span> <span class="k">struct</span> <span class="n">_yar_packager</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">name</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">int</span>  <span class="p">(</span><span class="o">*</span><span class="n">pack</span><span class="p">)</span> <span class="p">(</span><span class="k">const</span> <span class="k">struct</span> <span class="n">_yar_packager</span> <span class="o">*</span><span class="n">self</span><span class="p">,</span> <span class="n">zval</span> <span class="o">*</span><span class="n">pzval</span><span class="p">,</span> <span class="n">smart_str</span> <span class="o">*</span><span class="n">buf</span><span class="p">,</span> <span class="kt">char</span> <span class="o">**</span><span class="n">msg</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">zval</span> <span class="o">*</span> <span class="p">(</span><span class="o">*</span><span class="n">unpack</span><span class="p">)</span> <span class="p">(</span><span class="k">const</span> <span class="k">struct</span> <span class="n">_yar_packager</span> <span class="o">*</span><span class="n">self</span><span class="p">,</span> <span class="kt">char</span> <span class="o">*</span><span class="n">content</span><span class="p">,</span> <span class="kt">size_t</span> <span class="n">len</span><span class="p">,</span> <span class="kt">char</span> <span class="o">**</span><span class="n">msg</span><span class="p">,</span> <span class="n">zval</span> <span class="o">*</span><span class="n">ret</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="kt">yar_packager_t</span><span class="p">;</span>
</span></span></code></pre></div><p>结构体中字段说明：</p>
<ul>
<li>name：编码方式的名称，比如 PHP、JSON、Msgpack。</li>
<li>pack：函数指针，对数据进行编码操作。</li>
<li>unpack：函数指针，对数据进行解码操作。</li>
</ul>
<p>yar_packager_t 结构体有点类似于 OOP 中的抽象类，为不同的编码方式提供了统一的接口，从而实现了多态。</p>
<p>如果我们想要扩展一种编码方式，只需要设置编码方式名称并实现 pack 和 unpack 函数即可。</p>
<h3 id="json-编码方式">JSON 编码方式</h3>
<p>Yar 支持的编码方式都在 packagers 目录下，这里我们用 JSON 方式进行举例。</p>
<p>在 packagers/json.c 文件中定义了一个类型为 yar_packager_t 的常量：yar_packager_json。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="k">const</span> <span class="kt">yar_packager_t</span> <span class="n">yar_packager_json</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="s">&#34;JSON&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">php_yar_packager_json_pack</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">php_yar_packager_json_unpack</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><p>首先是编码方式名称 JSON，然后是编码和解码对应的函数，这两个函数其实是对 json_encode 和 json_decode 函数的一层封装。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="kt">int</span> <span class="nf">php_yar_packager_json_pack</span><span class="p">(</span><span class="k">const</span> <span class="kt">yar_packager_t</span> <span class="o">*</span><span class="n">self</span><span class="p">,</span> <span class="n">zval</span> <span class="o">*</span><span class="n">pzval</span><span class="p">,</span> <span class="n">smart_str</span> <span class="o">*</span><span class="n">buf</span><span class="p">,</span> <span class="kt">char</span> <span class="o">**</span><span class="n">msg</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nf">php_json_encode</span><span class="p">(</span><span class="n">buf</span><span class="p">,</span> <span class="n">pzval</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">zval</span> <span class="o">*</span> <span class="nf">php_yar_packager_json_unpack</span><span class="p">(</span><span class="k">const</span> <span class="kt">yar_packager_t</span> <span class="o">*</span><span class="n">self</span><span class="p">,</span> <span class="kt">char</span> <span class="o">*</span><span class="n">content</span><span class="p">,</span> <span class="kt">size_t</span> <span class="n">len</span><span class="p">,</span> <span class="kt">char</span> <span class="o">**</span><span class="n">msg</span><span class="p">,</span> <span class="n">zval</span> <span class="o">*</span><span class="n">ret</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nf">php_json_decode</span><span class="p">(</span><span class="n">ret</span><span class="p">,</span> <span class="n">content</span><span class="p">,</span> <span class="n">len</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">512</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">ret</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>其它两种编码方式的实现跟 JSON 差不多，这里就不赘述了。</p>
<h2 id="编码模块的生命周期">编码模块的生命周期</h2>
<p>Yar 作为 PHP 的扩展，其编码模块的生命周期肯定也是在 PHP 生命周期中的，这里我画了一张图，方便大家能够更直观的了解 Yar 编码模块的生命周期。</p>
<p><img decoding="async" height="803" loading="lazy" sizes="(max-width: 768px) 100vw, (max-width: 1024px) 768px, 1024px" src="/posts/2021/12/23/yar-internals-packager/yar-packager-life-cycle.png" srcset="/posts/2021/12/23/yar-internals-packager/yar-packager-life-cycle_hu_dd714c7b1978b3fe.png 384w, /posts/2021/12/23/yar-internals-packager/yar-packager-life-cycle_hu_93786d644e8dc225.png 768w, /posts/2021/12/23/yar-internals-packager/yar-packager-life-cycle_hu_900fb45961f1a841.png 1024w" style="max-width: 100%; height: auto; aspect-ratio: 1.3076;" width="1050"></p>
<p>接下来看看编码模块在每个阶段中都做了些什么。</p>
<h3 id="启动编码模块">启动编码模块</h3>
<p>在 PHP 模块初始化阶段，Yar 会先注册一些 Yar 相关的 PHP 常量，比如版本号、客户端相关的选项。紧接着开始初始化模块，除了编码模块以外，还会初始化传输、客户端、服务端及异常等模块。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// source：yar.c
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="nf">PHP_MINIT_FUNCTION</span><span class="p">(</span><span class="n">yar</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// 注册 PHP 常量
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nf">REGISTER_INI_ENTRIES</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="nf">REGISTER_STRINGL_CONSTANT</span><span class="p">(</span><span class="s">&#34;YAR_VERSION&#34;</span><span class="p">,</span> <span class="n">PHP_YAR_VERSION</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">PHP_YAR_VERSION</span><span class="p">)</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="n">CONST_CS</span><span class="o">|</span><span class="n">CONST_PERSISTENT</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nf">REGISTER_LONG_CONSTANT</span><span class="p">(</span><span class="s">&#34;YAR_OPT_PACKAGER&#34;</span><span class="p">,</span> <span class="n">YAR_OPT_PACKAGER</span><span class="p">,</span> <span class="n">CONST_CS</span><span class="o">|</span><span class="n">CONST_PERSISTENT</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nf">REGISTER_LONG_CONSTANT</span><span class="p">(</span><span class="s">&#34;YAR_OPT_PERSISTENT&#34;</span><span class="p">,</span> <span class="n">YAR_OPT_PERSISTENT</span><span class="p">,</span> <span class="n">CONST_CS</span><span class="o">|</span><span class="n">CONST_PERSISTENT</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nf">REGISTER_LONG_CONSTANT</span><span class="p">(</span><span class="s">&#34;YAR_OPT_TIMEOUT&#34;</span><span class="p">,</span> <span class="n">YAR_OPT_TIMEOUT</span><span class="p">,</span> <span class="n">CONST_CS</span><span class="o">|</span><span class="n">CONST_PERSISTENT</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nf">REGISTER_LONG_CONSTANT</span><span class="p">(</span><span class="s">&#34;YAR_OPT_CONNECT_TIMEOUT&#34;</span><span class="p">,</span> <span class="n">YAR_OPT_CONNECT_TIMEOUT</span><span class="p">,</span> <span class="n">CONST_CS</span><span class="o">|</span><span class="n">CONST_PERSISTENT</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nf">REGISTER_LONG_CONSTANT</span><span class="p">(</span><span class="s">&#34;YAR_OPT_HEADER&#34;</span><span class="p">,</span> <span class="n">YAR_OPT_HEADER</span><span class="p">,</span> <span class="n">CONST_CS</span><span class="o">|</span><span class="n">CONST_PERSISTENT</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nf">REGISTER_LONG_CONSTANT</span><span class="p">(</span><span class="s">&#34;YAR_OPT_RESOLVE&#34;</span><span class="p">,</span> <span class="n">YAR_OPT_RESOLVE</span><span class="p">,</span> <span class="n">CONST_CS</span><span class="o">|</span><span class="n">CONST_PERSISTENT</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nf">REGISTER_LONG_CONSTANT</span><span class="p">(</span><span class="s">&#34;YAR_OPT_PROXY&#34;</span><span class="p">,</span> <span class="n">YAR_OPT_PROXY</span><span class="p">,</span> <span class="n">CONST_CS</span><span class="o">|</span><span class="n">CONST_PERSISTENT</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// 初始化模块
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nf">YAR_STARTUP</span><span class="p">(</span><span class="n">service</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nf">YAR_STARTUP</span><span class="p">(</span><span class="n">client</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nf">YAR_STARTUP</span><span class="p">(</span><span class="n">packager</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nf">YAR_STARTUP</span><span class="p">(</span><span class="n">transport</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nf">YAR_STARTUP</span><span class="p">(</span><span class="n">exception</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">SUCCESS</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><blockquote>
<p>今天我们主要研究编码模块，其它几个模块放到后面的文章中。</p></blockquote>
<p>YAR_STARTUP 是一个宏，定义如下：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="cp">#define YAR_STARTUP(module)    ZEND_MODULE_STARTUP_N(yar_##module)(INIT_FUNC_ARGS_PASSTHRU)
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="cl"><span class="cm">/* Name macros */</span>
</span></span><span class="line"><span class="cl"><span class="cp">#define ZEND_MODULE_STARTUP_N(module)    zm_startup_##module
</span></span></span></code></pre></div><p>YAR_STARTUP(packager) 展开之后是这样的：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="nf">zm_startup_yar_packager</span><span class="p">(</span><span class="n">type</span><span class="p">,</span> <span class="n">module_number</span><span class="p">)</span>
</span></span></code></pre></div><p>如果用 zm_startup_yar_packager 在 Yar 源码中是搜不到结果的，这是因为宏只有在编译的时候才会展开，那么为什么用 YAR_STARTUP(packager) 也搜不到呢？</p>
<p>因为在定义函数名的时候用的不是 YAR_STARTUP，而是 YAR_STARTUP_FUNCTION，先来看看定义：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="cp">#define YAR_STARTUP_FUNCTION(module)    ZEND_MINIT_FUNCTION(yar_##module)
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="cl"><span class="cp">#define ZEND_MINIT_FUNCTION    ZEND_MODULE_STARTUP_D
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="cl"><span class="cm">/* Declaration macros */</span>
</span></span><span class="line"><span class="cl"><span class="cp">#define ZEND_MODULE_STARTUP_D(module)    zend_result ZEND_MODULE_STARTUP_N(module)(INIT_FUNC_ARGS)
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="cl"><span class="cm">/* Name macros */</span>
</span></span><span class="line"><span class="cl"><span class="cp">#define ZEND_MODULE_STARTUP_N(module)    zm_startup_##module
</span></span></span></code></pre></div><p>可以看到，最终使用的都是 ZEND_MODULE_STARTUP_N，它还有一个相似的宏：ZEND_MODULE_STARTUP_D，不同的是，前者是用于命名的宏，无参数，而后者是用于声明/定义的宏，有参数。</p>
<p>在 YAR_STARTUP_FUNCTION(packager) 函数中，先调用 php_yar_packager_register 函数往 Yar 编码模块中注册编码方式，然后往 PHP 中注册编码模块相关的 PHP 常量。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// source：yar_packager.c
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="nf">YAR_STARTUP_FUNCTION</span><span class="p">(</span><span class="n">packager</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="c1">// 注册编码方式
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="cp">#ifdef ENABLE_MSGPACK
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>  <span class="nf">php_yar_packager_register</span><span class="p">(</span><span class="o">&amp;</span><span class="n">yar_packager_msgpack</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="cp">#endif
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>  <span class="nf">php_yar_packager_register</span><span class="p">(</span><span class="o">&amp;</span><span class="n">yar_packager_php</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="nf">php_yar_packager_register</span><span class="p">(</span><span class="o">&amp;</span><span class="n">yar_packager_json</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// 注册编码模块相关的 PHP 常量
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="nf">REGISTER_STRINGL_CONSTANT</span><span class="p">(</span><span class="s">&#34;YAR_PACKAGER_PHP&#34;</span><span class="p">,</span> <span class="n">YAR_PACKAGER_PHP</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">YAR_PACKAGER_PHP</span><span class="p">)</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="n">CONST_CS</span><span class="o">|</span><span class="n">CONST_PERSISTENT</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="nf">REGISTER_STRINGL_CONSTANT</span><span class="p">(</span><span class="s">&#34;YAR_PACKAGER_JSON&#34;</span><span class="p">,</span> <span class="n">YAR_PACKAGER_JSON</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">YAR_PACKAGER_JSON</span><span class="p">)</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="n">CONST_CS</span><span class="o">|</span><span class="n">CONST_PERSISTENT</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="cp">#ifdef ENABLE_MSGPACK
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>  <span class="nf">REGISTER_STRINGL_CONSTANT</span><span class="p">(</span><span class="s">&#34;YAR_PACKAGER_MSGPACK&#34;</span><span class="p">,</span> <span class="n">YAR_PACKAGER_MSGPACK</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">YAR_PACKAGER_MSGPACK</span><span class="p">)</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="n">CONST_CS</span><span class="o">|</span><span class="n">CONST_PERSISTENT</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="cp">#endif
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="n">SUCCESS</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>接着我们来了解一下注册编码方式的逻辑，Yar 在编码模块中定义了一个结构体 yar_packagers_list，用于存储所有可用的编码方式。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// source：yar_packager.c
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="k">struct</span> <span class="n">_yar_packagers_list</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">size</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">num</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">const</span> <span class="kt">yar_packager_t</span> <span class="o">**</span><span class="n">packagers</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="n">yar_packagers_list</span><span class="p">;</span>
</span></span></code></pre></div><p>结构体字段说明：</p>
<ul>
<li>size：packagers 数组的大小。</li>
<li>num：packagers 数组中编码方式的数量。</li>
<li>packagers：编码方式的指针数组。</li>
</ul>
<p>在 php_yar_packager_register 函数中，对 yar_packagers_list 进行初始化、扩容及注册编码方式等操作。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// source：yar_packager.c
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="n">PHP_YAR_API</span> <span class="kt">int</span> <span class="nf">php_yar_packager_register</span><span class="p">(</span><span class="k">const</span> <span class="kt">yar_packager_t</span> <span class="o">*</span><span class="n">packager</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">yar_packagers_list</span><span class="p">.</span><span class="n">size</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">       <span class="c1">// size 为 0，说明未初始化
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>       <span class="c1">// 设置 size 初始值为 5，并为 packagers 分配相应的内存
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>       <span class="n">yar_packagers_list</span><span class="p">.</span><span class="n">size</span> <span class="o">=</span> <span class="mi">5</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">       <span class="n">yar_packagers_list</span><span class="p">.</span><span class="n">packagers</span> <span class="o">=</span> <span class="p">(</span><span class="k">const</span> <span class="kt">yar_packager_t</span> <span class="o">**</span><span class="p">)</span><span class="nf">malloc</span><span class="p">(</span><span class="k">sizeof</span><span class="p">(</span><span class="kt">yar_packager_t</span> <span class="o">*</span><span class="p">)</span> <span class="o">*</span> <span class="n">yar_packagers_list</span><span class="p">.</span><span class="n">size</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">yar_packagers_list</span><span class="p">.</span><span class="n">num</span> <span class="o">==</span> <span class="n">yar_packagers_list</span><span class="p">.</span><span class="n">size</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">       <span class="c1">// num 等于 size，说明数组已经满了，需要进行扩容
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>       <span class="n">yar_packagers_list</span><span class="p">.</span><span class="n">size</span> <span class="o">+=</span> <span class="mi">5</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">       <span class="n">yar_packagers_list</span><span class="p">.</span><span class="n">packagers</span> <span class="o">=</span> <span class="p">(</span><span class="k">const</span> <span class="kt">yar_packager_t</span> <span class="o">**</span><span class="p">)</span><span class="nf">realloc</span><span class="p">(</span><span class="n">yar_packagers_list</span><span class="p">.</span><span class="n">packagers</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="kt">yar_packager_t</span> <span class="o">*</span><span class="p">)</span> <span class="o">*</span> <span class="n">yar_packagers_list</span><span class="p">.</span><span class="n">size</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="c1">// 将编码方式存储到 packagers 数组下标为 num 处
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="n">yar_packagers_list</span><span class="p">.</span><span class="n">packagers</span><span class="p">[</span><span class="n">yar_packagers_list</span><span class="p">.</span><span class="n">num</span><span class="p">]</span> <span class="o">=</span> <span class="n">packager</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// 返回存储编码方式的索引位置并加一
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">return</span> <span class="n">yar_packagers_list</span><span class="p">.</span><span class="n">num</span><span class="o">++</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h3 id="激活编码模块">激活编码模块</h3>
<p>每次请求进来到达请求初始化阶段时，就会调用 YAR_ACTIVATE_FUNCTION(packager) 函数设置本次请求使用的编码方式。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// source：yar_packager.c
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="nf">YAR_ACTIVATE_FUNCTION</span><span class="p">(</span><span class="n">packager</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// 获取默认的编码方式
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">const</span> <span class="kt">yar_packager_t</span> <span class="o">*</span><span class="n">packager</span> <span class="o">=</span> <span class="nf">php_yar_packager_get</span><span class="p">(</span><span class="nf">YAR_G</span><span class="p">(</span><span class="n">default_packager</span><span class="p">),</span> <span class="nf">strlen</span><span class="p">(</span><span class="nf">YAR_G</span><span class="p">(</span><span class="n">default_packager</span><span class="p">)));</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">packager</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// 获取成功则直接设置为默认编码方式，并返回
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="nf">YAR_G</span><span class="p">(</span><span class="n">packager</span><span class="p">)</span> <span class="o">=</span> <span class="n">packager</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">SUCCESS</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// 如果没有默认编码方式，则使用 PHP 作为默认的编码方式，并输出警告信息
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nf">YAR_G</span><span class="p">(</span><span class="n">packager</span><span class="p">)</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">yar_packager_php</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nf">php_error</span><span class="p">(</span><span class="n">E_WARNING</span><span class="p">,</span> <span class="s">&#34;unable to find package &#39;%s&#39;, use php instead&#34;</span><span class="p">,</span> <span class="nf">YAR_G</span><span class="p">(</span><span class="n">default_packager</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">SUCCESS</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h3 id="调用编码模块">调用编码模块</h3>
<p>前面两个阶段都属于初始化的阶段，只有执行脚本的阶段才会去调用编码、解码函数，那么什么时候会调用呢？</p>
<p>上一篇文章中有提到 Yar 支持两种数据传输方式：HTTP、TCP。无论使用哪种传输方式，都会在<strong>发送请求前</strong>和<strong>收到响应后</strong>调用编码模块。</p>
<p>为此，Yar 提供了两个通用函数：php_yar_packager_pack、php_yar_packager_unpack，用于对数据进行编码或解码操作。</p>
<h4 id="php_yar_packager_pack">php_yar_packager_pack</h4>
<p>php_yar_packager_pack 函数只需要传入编码方式的名称和需要编码的数据，就可以使用对应的编码方式对数据进行编码。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// source：yar_packager.c
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="n">zend_string</span> <span class="o">*</span><span class="nf">php_yar_packager_pack</span><span class="p">(</span><span class="kt">char</span> <span class="o">*</span><span class="n">packager_name</span><span class="p">,</span> <span class="n">zval</span> <span class="o">*</span><span class="n">pzval</span><span class="p">,</span> <span class="kt">char</span> <span class="o">**</span><span class="n">msg</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">char</span> <span class="n">header</span><span class="p">[</span><span class="mi">8</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="n">smart_str</span> <span class="n">buf</span> <span class="o">=</span> <span class="p">{</span><span class="mi">0</span><span class="p">};</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="c1">// 获取编码方式，如果传入的名称为 null，则使用默认的编码方式
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">const</span> <span class="kt">yar_packager_t</span> <span class="o">*</span><span class="n">packager</span> <span class="o">=</span> <span class="n">packager_name</span> <span class="o">?</span>
</span></span><span class="line"><span class="cl">        <span class="nf">php_yar_packager_get</span><span class="p">(</span><span class="n">packager_name</span><span class="p">,</span> <span class="nf">strlen</span><span class="p">(</span><span class="n">packager_name</span><span class="p">))</span> <span class="o">:</span> <span class="nf">YAR_G</span><span class="p">(</span><span class="n">packager</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">packager</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nf">php_error_docref</span><span class="p">(</span><span class="nb">NULL</span><span class="p">,</span> <span class="n">E_ERROR</span><span class="p">,</span> <span class="s">&#34;unsupported packager %s&#34;</span><span class="p">,</span> <span class="n">packager_name</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="c1">// 将编码方式的名称写入前 8 个字节中
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nf">memcpy</span><span class="p">(</span><span class="n">header</span><span class="p">,</span> <span class="n">packager</span><span class="o">-&gt;</span><span class="n">name</span><span class="p">,</span> <span class="mi">8</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nf">smart_str_alloc</span><span class="p">(</span><span class="o">&amp;</span><span class="n">buf</span><span class="p">,</span> <span class="n">YAR_PACKAGER_BUFFER_SIZE</span> <span class="cm">/* 1M */</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nf">smart_str_appendl</span><span class="p">(</span><span class="o">&amp;</span><span class="n">buf</span><span class="p">,</span> <span class="n">header</span><span class="p">,</span> <span class="mi">8</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="c1">// 调用具体的编码函数
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="n">packager</span><span class="o">-&gt;</span><span class="nf">pack</span><span class="p">(</span><span class="n">packager</span><span class="p">,</span> <span class="n">pzval</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">buf</span><span class="p">,</span> <span class="n">msg</span><span class="p">);</span> 
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">buf</span><span class="p">.</span><span class="n">s</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// 如果编码成功，则在末尾加上 \0
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="nf">smart_str_0</span><span class="p">(</span><span class="o">&amp;</span><span class="n">buf</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">buf</span><span class="p">.</span><span class="n">s</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// 编码失败则释放 buf 并返回 null
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nf">smart_str_free</span><span class="p">(</span><span class="o">&amp;</span><span class="n">buf</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nb">NULL</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>如果使用 JSON 编码方式，那么 packager-&gt;pack 实际上调用的是 php_yar_packager_json_pack 函数，将数据编码为 JSON 字符串。</p>
<h4 id="php_yar_packager_unpack">php_yar_packager_unpack</h4>
<p>php_yar_packager_unpack 函数不像 php_yar_packager_pack 函数直接传入编码方式的名称，而是传入需要解码的数据及数据的长度，通过取数据前 8 字节得到编码方式的名称，然后通过编码方式的名称获取编码方式，最后调用真正的解码函数对数据进行解码。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// source：yar_packager.c
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="n">zval</span> <span class="o">*</span> <span class="nf">php_yar_packager_unpack</span><span class="p">(</span><span class="kt">char</span> <span class="o">*</span><span class="n">content</span><span class="p">,</span> <span class="kt">size_t</span> <span class="n">len</span><span class="p">,</span> <span class="kt">char</span> <span class="o">**</span><span class="n">msg</span><span class="p">,</span> <span class="n">zval</span> <span class="o">*</span><span class="n">ret</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">char</span> <span class="o">*</span><span class="n">pack_info</span> <span class="o">=</span> <span class="n">content</span><span class="p">;</span> <span class="cm">/* 4 bytes, last byte is version */</span>
</span></span><span class="line"><span class="cl">    <span class="k">const</span> <span class="kt">yar_packager_t</span> <span class="o">*</span><span class="n">packager</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// 数据往后移动 8 字节，就是之前提到的 payload
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="n">content</span> <span class="o">=</span> <span class="n">content</span> <span class="o">+</span> <span class="mi">8</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">len</span> <span class="o">-=</span> <span class="mi">8</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="c1">// 编码方式的名称虽然占用了 8 字节，实际上只用了 7 字节，第 8 个字节存储 \0
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="o">*</span><span class="p">(</span><span class="n">pack_info</span> <span class="o">+</span> <span class="mi">7</span><span class="p">)</span> <span class="o">=</span> <span class="sc">&#39;\0&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// 获取编码方式
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="n">packager</span> <span class="o">=</span> <span class="nf">php_yar_packager_get</span><span class="p">(</span><span class="n">pack_info</span><span class="p">,</span> <span class="mi">7</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">packager</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nf">spprintf</span><span class="p">(</span><span class="n">msg</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="s">&#34;unsupported packager &#39;%s&#39;&#34;</span><span class="p">,</span> <span class="n">pack_info</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="nb">NULL</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="c1">// 调用具体的解码函数
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">return</span> <span class="n">packager</span><span class="o">-&gt;</span><span class="nf">unpack</span><span class="p">(</span><span class="n">packager</span><span class="p">,</span> <span class="n">content</span><span class="p">,</span> <span class="n">len</span><span class="p">,</span> <span class="n">msg</span><span class="p">,</span> <span class="n">ret</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>如果使用 JSON 编码方式，那么 packager-&gt;unpack 实际上调用的是 php_yar_packager_json_unpack 函数，将数据（JSON 字符串）解码并存储到 ret 中。</p>
<h3 id="关闭编码模块">关闭编码模块</h3>
<p>当执行完 PHP 脚本后，进入请求关闭阶段清理执行脚本过程中使用到的资源。在此之后，如果是 PHP-FPM 模式下，则会进入请求初始化阶段等待下一次请求到来，停止 PHP-FPM 时才会进入模块关闭阶段；如果是 PHP-Cli 模式会直接进入到模块关闭阶段。</p>
<p>关闭编码模块的逻辑很简单，只需要释放 yar_packagers_list 中 packagers 使用的内存就可以了。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// source：yar_packager.c
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="nf">YAR_SHUTDOWN_FUNCTION</span><span class="p">(</span><span class="n">packager</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">yar_packagers_list</span><span class="p">.</span><span class="n">size</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nf">free</span><span class="p">(</span><span class="n">yar_packagers_list</span><span class="p">.</span><span class="n">packagers</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">SUCCESS</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h2 id="总结">总结</h2>
<p>在本文中，基于 PHP 生命周期介绍了各个阶段中编码模块都做了些什么，在下一篇文章中将会介绍 Yar 的传输模块，通过该模块，我们就可以知道编码后的数据是怎样被发送出去的。</p>
<p>这篇文章断断续续花费了一个月的时间，期间经历了被裁员、离职、面试等等，心情也是跌宕起伏。之前换工作都是无缝衔接，中间也没休息过，所以这次就当放了个假，休息休息。这几天在家做做饭、搞搞卫生，感觉也还不错，在家待了几天后，想着抓紧把 Yar 系列写完，好准备下一个系列的文章。</p>
<p>至于工作，这段时间也面试了几家，如果没有合适的话，那就明年再战。</p>
<p>:)</p>
]]></content:encoded>
    </item>
    <item>
      <title>Yar 源码阅读笔记：RPC 通信协议</title>
      <link>https://her-cat.com/posts/2021/11/03/yar-internals-protocol/</link>
      <pubDate>Wed, 03 Nov 2021 16:48:08 +0800</pubDate>
      <guid>https://her-cat.com/posts/2021/11/03/yar-internals-protocol/</guid>
      <description>在上一篇文章中简单的介绍了 Yar 的基本功能，今天我们来了解一下 Yar 的通信协议。</description>
      <content:encoded><![CDATA[<h2 id="前言">前言</h2>
<p>在上一篇文章中简单的介绍了 Yar 的基本功能，今天我们来了解一下 Yar 的通信协议。</p>
<p>通信协议是服务端与客户端之间进行数据交换的一种约定，只有遵循这种约定才能进行通信。</p>
<p>Yar 支持两种数据传输方式：HTTP、TCP。</p>
<ul>
<li>HTTP：使用 curl 传输 HTTP 协议数据。</li>
<li>TCP：使用 socket 传输 TCP 协议数据。</li>
</ul>
<p>HTTP 本身就是一种服务端与客户端通信的协议，Yar 为什么还要自定义通信协议呢？</p>
<h2 id="为什么要自定义通信协议">为什么要自定义通信协议？</h2>
<p>Yar 直接使用 HTTP 协议进行数据传输是没有任何问题的，因为 HTTP 协议是建立在 TCP 协议之上的应用层协议，其协议本身已经清楚的定义了数据的边界，能够保证客户端/服务端正常地收发每一个数据包。</p>
<p>但是，Yar 数据传输方式除了 HTTP 协议以外还支持 TCP 协议，当 Yar 使用 TCP 协议传输数据时，就必须自己定义一个基于 TCP 协议的应用层协议了。</p>
<p>因为 TCP 是一个基于<strong>字节流</strong>的传输层通信协议，重点在于它是一个流式协议，也就是说数据与数据之间没有明确的边界，它只需要保证数据能够高效、可靠地将数据从一端传输到另一端。</p>
<p>可以把 TCP 想像成一根水管，数据是水管中的水，传输就是水在水管中从一端到另一端的过程。从水管一端接水的时候，你只知道桶里已经接了多少水，但是你根本不知道水管中还有多少水，只能一直等着。如果水突然停了，你也分不清数据到底是被分为多次发送，还是发送完了。</p>
<p>所以自定义通信协议主要目的是为了让接水的人知道本次接水的动作什么时候结束，也就是让客户端/服务端能够辨别出每一个数据包的边界。</p>
<p>这也是网友们常常提到的 TCP「粘包」问题，其实跟 TCP 一点关系都没有，这个问题是由于程序员在设计通信协议时，没有定义清楚数据之间的边界导致的。</p>
<h2 id="常见的协议格式">常见的协议格式</h2>
<p>有以下几种常见的协议格式：</p>
<ul>
<li>特殊字符作为边界</li>
<li>显式编码数据长度</li>
<li>固定数据包大小</li>
</ul>
<h3 id="特殊字符作为边界">特殊字符作为边界</h3>
<p>这种方式是在协议中设置特殊字符作为数据的边界，比如 \r、\n、\0 等等。</p>
<p><img decoding="async" height="125" loading="lazy" sizes="(max-width: 768px) 100vw, (max-width: 1024px) 768px, 1024px" src="/posts/2021/11/03/yar-internals-protocol/special-char.png" srcset="/posts/2021/11/03/yar-internals-protocol/special-char_hu_9b9530960808a74.png 384w" style="max-width: 100%; height: auto; aspect-ratio: 3.1760;" width="397"></p>
<p>HTTP 就是一个很好的例子，HTTP 将 \r\n 作为数据的边界。如果是 GET 请求，当读取到两个连续的 \r\n 时，说明一个完整的 HTTP 数据包结束了。</p>
<p>代码实现可以参考我之前写的《<a href="/posts/2021/04/17/what-is-http-protocol/#GET-%E8%AF%B7%E6%B1%82">浅入浅出 HTTP</a>》中 GET 请求的部分。</p>
<h3 id="显式编码数据长度">显式编码数据长度</h3>
<p>显式编码数据长度就是在协议中提前将要发送的数据长度告诉接收方。</p>
<p><img decoding="async" height="111" loading="lazy" sizes="(max-width: 768px) 100vw, (max-width: 1024px) 768px, 1024px" src="/posts/2021/11/03/yar-internals-protocol/declaration-length.png" srcset="/posts/2021/11/03/yar-internals-protocol/declaration-length_hu_432cf5f8d150a7ce.png 384w" style="max-width: 100%; height: auto; aspect-ratio: 4.8108;" width="534"></p>
<p>接收方在读取数据的时候，就可以先读取发送的数据长度，然后开始一直读取数据，当已读取的数据长度等于发送的数据长度时，停止读取数据。</p>
<p>HTTP 协议除了使用特殊字符作为边界，还使用了显式编码数据长度这种方式。当 HTTP 请求为 POST 或其它需要 body 的方式时，就会读取请求头中的 Content-Length 字段，来确定 body 的长度。</p>
<p>代码实现可以参考我之前写的《<a href="/posts/2021/04/17/what-is-http-protocol/#post-%e8%af%b7%e6%b1%82">浅入浅出 HTTP</a>》中 POST 请求的部分，也可以阅读《<a href="/posts/2021/04/01/workerman-to-access-bilibili-barrage-protocol/">使用 Workerman 接入 Bilibili 直播弹幕协议</a>》中对于协议声明及处理。</p>
<h3 id="固定数据包长度">固定数据包长度</h3>
<p>固定数据包长度的方式跟前一种有点像，但是丢弃了数据头部的长度字段，而是将数据包设为固定长度，属于前者的阉割版本。</p>
<p><img decoding="async" height="173" loading="lazy" src="/posts/2021/11/03/yar-internals-protocol/fixed-length.png" style="max-width: 100%; height: auto; aspect-ratio: 2.0867;" width="361"></p>
<p>这样接收方用规定的数据包长度去接收每一个数据包即可。不过这种方式用的较少，一般用来传输某些固定大小的指令时才会用到。</p>
<h2 id="yar-协议格式">Yar 协议格式</h2>
<p>Yar 的通信协议分为 header 和 body 两个部分，其中，body 由 packager_name（编码方式名称）和 payload（数据）组成。</p>
<p><img decoding="async" height="191" loading="lazy" sizes="(max-width: 768px) 100vw, (max-width: 1024px) 768px, 1024px" src="/posts/2021/11/03/yar-internals-protocol/yar-protocol.png" srcset="/posts/2021/11/03/yar-internals-protocol/yar-protocol_hu_305024c6306332bf.png 384w, /posts/2021/11/03/yar-internals-protocol/yar-protocol_hu_cda2fcdb998bd87e.png 768w" style="max-width: 100%; height: auto; aspect-ratio: 5.3455;" width="1021"></p>
<p>下面分别对这几部分进行说明。</p>
<h3 id="header-部分">header 部分</h3>
<p>header 中存储了 Yar 通信协议的基本信息，比如请求 ID、协议版本、调用方等等。</p>
<p>当然，最重要就是存储了 body 部分数据的长度。所以 Yar 使用的是显式编码数据长度方式。</p>
<p>Yar 定义了 _yar_header 结构体，用来存储 header 的信息。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// source: yar_protocol.h
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="k">typedef</span> <span class="k">struct</span> <span class="n">_yar_header</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint32_t</span>       <span class="n">id</span><span class="p">;</span>          <span class="c1">// 4 字节
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="kt">uint16_t</span>       <span class="n">version</span><span class="p">;</span>     <span class="c1">// 2 字节
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="kt">uint32_t</span>       <span class="n">magic_num</span><span class="p">;</span>   <span class="c1">// 4 字节
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="kt">uint32_t</span>       <span class="n">reserved</span><span class="p">;</span>    <span class="c1">// 4 字节
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="kt">unsigned</span> <span class="kt">char</span>  <span class="n">provider</span><span class="p">[</span><span class="mi">32</span><span class="p">];</span><span class="c1">// 32 字节
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="kt">unsigned</span> <span class="kt">char</span>  <span class="n">token</span><span class="p">[</span><span class="mi">32</span><span class="p">];</span>   <span class="c1">// 32 字节
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="kt">uint32_t</span>       <span class="n">body_len</span><span class="p">;</span>    <span class="c1">// 4 字节
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span> <span class="nf">__attribute__</span> <span class="p">((</span><span class="n">packed</span><span class="p">))</span> <span class="kt">yar_header_t</span><span class="p">;</span>
</span></span></code></pre></div><p>结构体中字段说明：</p>
<ul>
<li>id：唯一请求 ID。</li>
<li>version：协议版本，默认为 0。</li>
<li>magic_num：用于验证请求有效性的特殊值，默认为 0x80DFEC60。</li>
<li>reserved：预留参数，可以用来存储一些请求参数。</li>
<li>provider：客户端的名字。</li>
<li>token：预留参数，可以用来做 API key 验证。</li>
<li>body_len: 用于存储 body 的长度。</li>
</ul>
<p>header 的总长度为 82 字节（设置了内存对齐）。</p>
<h3 id="packager_name-部分">packager_name 部分</h3>
<p>package_name 就是编码方式的名称。因为 Yar 支持多种消息编码方式，所以在 header 后面预留了 8 个字节，用于存储编码方式的名称，比如 JSON、MSGPACK、PHP。</p>
<p>服务端在收到消息后，就可以知道 payload 是通过哪种方式进行编码的，然后就可以使用对应的方式进行解码。</p>
<blockquote>
<p>注意：编码方式名称的 8 个字节是固定的。</p></blockquote>
<h3 id="payload-部分">payload 部分</h3>
<p>payload 是 RPC 传输的数据，数据分为两种：请求数据和响应数据。payload 的数据长度是动态变化的，可以通过 header 中的 body_len 得知。</p>
<h4 id="请求数据">请求数据</h4>
<p>Yar 定义了一个 yar_request_t 结构体，用于在 Yar 内部传递请求数据。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// source: yar_request.h
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="k">typedef</span> <span class="k">struct</span> <span class="n">_yar_request</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="n">zend_ulong</span> <span class="n">id</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="n">zend_string</span> <span class="o">*</span><span class="n">method</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="n">zval</span> <span class="n">parameters</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="n">zval</span> <span class="n">options</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="kt">yar_request_t</span><span class="p">;</span>
</span></span></code></pre></div><p>yar_request_t 结构体字段说明：</p>
<ul>
<li>id: 请求 ID。</li>
<li>method：被调用的方法。</li>
<li>parameters：参数。</li>
<li>options：可选项，例如请求超时时间、编码方式名称等，该参数不会被打包传输。</li>
</ul>
<p>在发送请求前会调用 php_yar_request_pack 函数，将 yar_request_t 中的请求 ID、被调用的方法、参数存储一个 hash 字典中。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// source: yar_request.c
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="n">zend_string</span> <span class="o">*</span><span class="nf">php_yar_request_pack</span><span class="p">(</span><span class="kt">yar_request_t</span> <span class="o">*</span><span class="n">request</span><span class="p">,</span> <span class="kt">char</span> <span class="o">**</span><span class="n">msg</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="n">zval</span> <span class="n">rv</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="n">zend_array</span> <span class="n">req</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="n">zend_string</span> <span class="o">*</span><span class="n">payload</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="kt">char</span> <span class="o">*</span><span class="n">packager_name</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="c1">// 尝试从 options 中取出设置的编码方式名称
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="k">if</span> <span class="p">(</span><span class="n">IS_ARRAY</span> <span class="o">==</span> <span class="nf">Z_TYPE</span><span class="p">(</span><span class="n">request</span><span class="o">-&gt;</span><span class="n">options</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="n">zval</span> <span class="o">*</span><span class="n">pzval</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">		<span class="k">if</span> <span class="p">((</span><span class="n">pzval</span> <span class="o">=</span> <span class="nf">zend_hash_index_find</span><span class="p">(</span><span class="nf">Z_ARRVAL</span><span class="p">(</span><span class="n">request</span><span class="o">-&gt;</span><span class="n">options</span><span class="p">),</span> <span class="n">YAR_OPT_PACKAGER</span><span class="p">))</span> <span class="o">&amp;&amp;</span> <span class="n">IS_STRING</span> <span class="o">==</span> <span class="nf">Z_TYPE_P</span><span class="p">(</span><span class="n">pzval</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="n">packager_name</span> <span class="o">=</span> <span class="nf">Z_STRVAL_P</span><span class="p">(</span><span class="n">pzval</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">		<span class="p">}</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="c1">// 初始化一个 hash 字典
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="nf">zend_hash_init</span><span class="p">(</span><span class="o">&amp;</span><span class="n">req</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="c1">// 将请求 ID 赋值给临时变量 rv
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="nf">ZVAL_LONG</span><span class="p">(</span><span class="o">&amp;</span><span class="n">rv</span><span class="p">,</span> <span class="n">request</span><span class="o">-&gt;</span><span class="n">id</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="c1">// 以字符 i 作为 key，将 rv 中的请求 ID 存储到 hash 字典中
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="nf">zend_hash_add</span><span class="p">(</span><span class="o">&amp;</span><span class="n">req</span><span class="p">,</span> <span class="nf">ZSTR_CHAR</span><span class="p">(</span><span class="sc">&#39;i&#39;</span><span class="p">),</span> <span class="o">&amp;</span><span class="n">rv</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="c1">// 将被调用的方法赋值给临时变量 rv
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="nf">ZVAL_STR</span><span class="p">(</span><span class="o">&amp;</span><span class="n">rv</span><span class="p">,</span> <span class="n">request</span><span class="o">-&gt;</span><span class="n">method</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="c1">// 以字符 m 作为 key，将 rv 中的请求 ID 存储到 hash 字典中
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="nf">zend_hash_add</span><span class="p">(</span><span class="o">&amp;</span><span class="n">req</span><span class="p">,</span> <span class="nf">ZSTR_CHAR</span><span class="p">(</span><span class="sc">&#39;m&#39;</span><span class="p">),</span> <span class="o">&amp;</span><span class="n">rv</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	
</span></span><span class="line"><span class="cl">	<span class="c1">// 如果参数的类型为数组，则以字符 p 作为 key，将参数存储到 hash 字典中；
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="c1">// 否则初始化一个空字典存储到 hash 字典中
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="k">if</span> <span class="p">(</span><span class="n">IS_ARRAY</span> <span class="o">==</span> <span class="nf">Z_TYPE</span><span class="p">(</span><span class="n">request</span><span class="o">-&gt;</span><span class="n">parameters</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nf">zend_hash_add</span><span class="p">(</span><span class="o">&amp;</span><span class="n">req</span><span class="p">,</span> <span class="nf">ZSTR_CHAR</span><span class="p">(</span><span class="sc">&#39;p&#39;</span><span class="p">),</span> <span class="o">&amp;</span><span class="n">request</span><span class="o">-&gt;</span><span class="n">parameters</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="n">zend_array</span> <span class="n">empty_arr</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">		<span class="nf">zend_hash_init</span><span class="p">(</span><span class="o">&amp;</span><span class="n">empty_arr</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">		<span class="nf">ZVAL_ARR</span><span class="p">(</span><span class="o">&amp;</span><span class="n">rv</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">empty_arr</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">		<span class="nf">zend_hash_add</span><span class="p">(</span><span class="o">&amp;</span><span class="n">req</span><span class="p">,</span> <span class="nf">ZSTR_CHAR</span><span class="p">(</span><span class="sc">&#39;p&#39;</span><span class="p">),</span> <span class="o">&amp;</span><span class="n">rv</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="c1">// 调用编码器将 hash 字典进行编码处理
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="nf">ZVAL_ARR</span><span class="p">(</span><span class="o">&amp;</span><span class="n">rv</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">req</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="p">(</span><span class="n">payload</span> <span class="o">=</span> <span class="nf">php_yar_packager_pack</span><span class="p">(</span><span class="n">packager_name</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">rv</span><span class="p">,</span> <span class="n">msg</span><span class="p">)))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nf">zend_hash_destroy</span><span class="p">(</span><span class="o">&amp;</span><span class="n">req</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">		<span class="k">return</span> <span class="nb">NULL</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="c1">// 释放 hash 字典
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="nf">zend_hash_destroy</span><span class="p">(</span><span class="o">&amp;</span><span class="n">req</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="c1">// 返回编码后的数据
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="k">return</span> <span class="n">payload</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>上面的 hash 字典是 PHP 数组的底层实现，所以请求数据实际上是一个数组，数组的 key 是字段的首字母：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="nv">$request</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;i&#34;</span> <span class="o">=&gt;</span> <span class="s2">&#34;123&#34;</span> <span class="p">,</span>                  <span class="c1">// 请求 ID
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="s2">&#34;m&#34;</span> <span class="o">=&gt;</span> <span class="s2">&#34;login&#34;</span> <span class="p">,</span>                <span class="c1">// 被调用的方法
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="s2">&#34;p&#34;</span> <span class="o">=&gt;</span> <span class="p">[</span><span class="s2">&#34;her-cat&#34;</span><span class="p">,</span> <span class="s2">&#34;123456&#34;</span><span class="p">],</span>   <span class="c1">// 参数
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">];</span>
</span></span></code></pre></div><p>假设使用 JSON 方式对请求数据进行编码，那么编码过后得到的 JSON 字符串就是 payload 的内容：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span><span class="nt">&#34;i&#34;</span><span class="p">:</span><span class="s2">&#34;123&#34;</span><span class="p">,</span><span class="nt">&#34;m&#34;</span><span class="p">:</span><span class="s2">&#34;login&#34;</span><span class="p">,</span><span class="nt">&#34;p&#34;</span><span class="p">:[</span><span class="s2">&#34;her-cat&#34;</span><span class="p">,</span><span class="s2">&#34;123456&#34;</span><span class="p">]}</span>
</span></span></code></pre></div><p>包含请求数据的 RPC 通信协议格式：</p>
<p><img decoding="async" height="176" loading="lazy" sizes="(max-width: 768px) 100vw, (max-width: 1024px) 768px, 1024px" src="/posts/2021/11/03/yar-internals-protocol/request-protocol.png" srcset="/posts/2021/11/03/yar-internals-protocol/request-protocol_hu_fb41de708579f30b.png 384w" style="max-width: 100%; height: auto; aspect-ratio: 4.3239;" width="761"></p>
<p>服务端收到请求数据后，使用 JSON 方式进行解码就可以得到 $request 这样的数组。</p>
<h4 id="响应数据">响应数据</h4>
<p>同样，对于响应数据，Yar 也定义了一个 yar_response_t 结构体。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="k">typedef</span> <span class="k">struct</span> <span class="n">_yar_response</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="kt">long</span> <span class="n">id</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="kt">int</span>  <span class="n">status</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="n">zend_string</span> <span class="o">*</span><span class="n">out</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="n">zval</span> <span class="n">err</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="n">zval</span> <span class="n">retval</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="kt">yar_response_t</span><span class="p">;</span>
</span></span></code></pre></div><p>yar_response_t 结构体字段说明：</p>
<ul>
<li>id：同请求 ID。</li>
<li>status：状态，用于判断被调用的方法执行是否成功。</li>
<li>out：服务端输出的内容。</li>
<li>err: 错误或异常信息，通过 status 可以知道 err 的内容类型。</li>
<li>retval：被调用方法的返回值。</li>
</ul>
<p>status 字段有以下几种值：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="cp">#define YAR_ERR_OKEY      		0x0     </span><span class="c1">// 执行成功
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="cp">#define YAR_ERR_PACKAGER  		0x1     </span><span class="c1">// 编码相关错误
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="cp">#define YAR_ERR_PROTOCOL  		0x2     </span><span class="c1">// 协议相关错误
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="cp">#define YAR_ERR_REQUEST   		0x4     </span><span class="c1">// 请求相关错误
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="cp">#define YAR_ERR_OUTPUT    		0x8     </span><span class="c1">// 输出相关错误
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="cp">#define YAR_ERR_TRANSPORT 		0x10    </span><span class="c1">// 数据传输相关错误
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="cp">#define YAR_ERR_FORBIDDEN 		0x20    </span><span class="c1">// 不允许访问
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="cp">#define YAR_ERR_EXCEPTION 		0x40    </span><span class="c1">// 发生异常
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="cp">#define YAR_ERR_EMPTY_RESPONSE 	0x80    </span><span class="c1">// 响应内容为空
</span></span></span></code></pre></div><p>在执行被调用方法完成后，会调用 php_yar_server_response 函数将 yar_response_t 中的数据存储到一个 hash 字典中。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="k">static</span> <span class="kt">void</span> <span class="nf">php_yar_server_response</span><span class="p">(</span><span class="kt">yar_request_t</span> <span class="o">*</span><span class="n">request</span><span class="p">,</span> <span class="kt">yar_response_t</span> <span class="o">*</span><span class="n">response</span><span class="p">,</span> <span class="kt">char</span> <span class="o">*</span><span class="n">pkg_name</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="n">zval</span> <span class="n">rv</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="kt">char</span> <span class="o">*</span><span class="n">err_msg</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="n">zend_array</span> <span class="n">ret</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="n">zend_string</span> <span class="o">*</span><span class="n">payload</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="kt">yar_header_t</span> <span class="n">header</span> <span class="o">=</span> <span class="p">{</span><span class="mi">0</span><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="nf">zend_hash_init</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ret</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="nf">ZVAL_LONG</span><span class="p">(</span><span class="o">&amp;</span><span class="n">rv</span><span class="p">,</span> <span class="n">response</span><span class="o">-&gt;</span><span class="n">id</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="nf">zend_hash_add</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ret</span><span class="p">,</span> <span class="nf">ZSTR_CHAR</span><span class="p">(</span><span class="sc">&#39;i&#39;</span><span class="p">),</span> <span class="o">&amp;</span><span class="n">rv</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="nf">ZVAL_LONG</span><span class="p">(</span><span class="o">&amp;</span><span class="n">rv</span><span class="p">,</span> <span class="n">response</span><span class="o">-&gt;</span><span class="n">status</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="nf">zend_hash_add</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ret</span><span class="p">,</span> <span class="nf">ZSTR_CHAR</span><span class="p">(</span><span class="sc">&#39;s&#39;</span><span class="p">),</span> <span class="o">&amp;</span><span class="n">rv</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="p">(</span><span class="n">response</span><span class="o">-&gt;</span><span class="n">out</span> <span class="o">&amp;&amp;</span> <span class="nf">ZSTR_LEN</span><span class="p">(</span><span class="n">response</span><span class="o">-&gt;</span><span class="n">out</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nf">ZVAL_STR</span><span class="p">(</span><span class="o">&amp;</span><span class="n">rv</span><span class="p">,</span> <span class="n">response</span><span class="o">-&gt;</span><span class="n">out</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">		<span class="nf">zend_hash_add</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ret</span><span class="p">,</span> <span class="nf">ZSTR_CHAR</span><span class="p">(</span><span class="sc">&#39;o&#39;</span><span class="p">),</span> <span class="o">&amp;</span><span class="n">rv</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nf">Z_ISUNDEF</span><span class="p">(</span><span class="n">response</span><span class="o">-&gt;</span><span class="n">retval</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nf">zend_hash_add</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ret</span><span class="p">,</span> <span class="nf">ZSTR_CHAR</span><span class="p">(</span><span class="sc">&#39;r&#39;</span><span class="p">),</span> <span class="o">&amp;</span><span class="n">response</span><span class="o">-&gt;</span><span class="n">retval</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nf">Z_ISUNDEF</span><span class="p">(</span><span class="n">response</span><span class="o">-&gt;</span><span class="n">err</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nf">zend_hash_add</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ret</span><span class="p">,</span> <span class="nf">ZSTR_CHAR</span><span class="p">(</span><span class="sc">&#39;e&#39;</span><span class="p">),</span> <span class="o">&amp;</span><span class="n">response</span><span class="o">-&gt;</span><span class="n">err</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	
</span></span><span class="line"><span class="cl">	<span class="c1">// ...省略部分代码
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span>
</span></span></code></pre></div><p>与请求数据的处理逻辑差不多，最终会得到一个存储响应数据的数组，类似于下面这样：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">$response = [
</span></span><span class="line"><span class="cl">    &#34;i&#34; =&gt; &#34;123&#34;,       // 请求 ID
</span></span><span class="line"><span class="cl">    &#34;s&#34; =&gt; &#34;0&#34; ,        // 状态
</span></span><span class="line"><span class="cl">    &#34;r&#34; =&gt; &#34;success&#34;,   // 返回值
</span></span><span class="line"><span class="cl">    &#34;o&#34; =&gt; &#34;&#34;,          // 输出
</span></span><span class="line"><span class="cl">    &#34;e&#34; =&gt; &#34;&#34;,          // 错误或异常
</span></span><span class="line"><span class="cl">];
</span></span></code></pre></div><p>使用 JSON 方式进行编码后得到 payload：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span><span class="nt">&#34;i&#34;</span><span class="p">:</span><span class="s2">&#34;123&#34;</span><span class="p">,</span><span class="nt">&#34;s&#34;</span><span class="p">:</span><span class="s2">&#34;0&#34;</span><span class="p">,</span><span class="nt">&#34;r&#34;</span><span class="p">:</span><span class="s2">&#34;success&#34;</span><span class="p">,</span><span class="nt">&#34;o&#34;</span><span class="p">:</span><span class="s2">&#34;&#34;</span><span class="p">,</span><span class="nt">&#34;e&#34;</span><span class="p">:</span><span class="s2">&#34;&#34;</span><span class="p">}</span>
</span></span></code></pre></div><p>包含响应数据的 RPC 通信协议格式：</p>
<p><img decoding="async" height="176" loading="lazy" sizes="(max-width: 768px) 100vw, (max-width: 1024px) 768px, 1024px" src="/posts/2021/11/03/yar-internals-protocol/response-protocol.png" srcset="/posts/2021/11/03/yar-internals-protocol/response-protocol_hu_421a1980690532f3.png 384w" style="max-width: 100%; height: auto; aspect-ratio: 4.3239;" width="761"></p>
<p>客户端收到响应数据后，使用 JSON 方式解码得到的也是这样一个数组。</p>
<p>通过状态判断执行结果，如果执行失败，获取错误或异常信息并抛出；执行成功则将返回值返回给调用方。</p>
<h2 id="总结">总结</h2>
<p>通过本文，让我们对于 Yar 的通信协议有了一定的了解。先解释了为什么 Yar 需要自定义通信协议，并列举了常见的协议格式，然后详细地介绍了 Yar 通信协议中的内容及作用。</p>
]]></content:encoded>
    </item>
    <item>
      <title>Yar 源码阅读笔记：开篇</title>
      <link>https://her-cat.com/posts/2021/10/17/yar-internals-start/</link>
      <pubDate>Sun, 17 Oct 2021 23:38:04 +0800</pubDate>
      <guid>https://her-cat.com/posts/2021/10/17/yar-internals-start/</guid>
      <description>Yar（yet another RPC framework,）是鸟哥在 2012 年开发的一个轻量级的并行 RPC 框架，支持多种编码方式（JSON、msgpack、PHP）及 HTTP、TCP 两种数据传输方式</description>
      <content:encoded><![CDATA[<h2 id="前言">前言</h2>
<p>本文是 Yar 源码系列的第一篇文章，主要介绍 Yar 以及服务端、客户端的基本使用，详细的源码分析会放在后续的文章中。</p>
<h2 id="为什么要研究-yar">为什么要研究 Yar？</h2>
<p>我从 8 月初开始阅读 《<a href="https://weread.qq.com/web/reader/88932cf05e4e8b88992748akc81322c012c81e728d9d180">PHP 7底层设计与源码实现</a>》这本书，直到前一阵子才看完，算是通读了一遍。看完之后总想着动手实操一番，将书中的理论知识赋予实践，做到理论实践相结合。</p>
<p>在挑选研究项目的时候，有以下几个可选项：</p>
<ul>
<li><a href="https://github.com/swoole/swoole-src">Swoole</a></li>
<li><a href="https://github.com/swow/swow">Swow</a></li>
<li><a href="https://github.com/laruence/yar">Yar</a></li>
</ul>
<p>Swoole/Swow 是 PHP 高性能协程的网络通信引擎，从语言上来讲，前者由 C++ 编写，后者主要由 C 语言编写，一部分逻辑用 PHP 实现。从编码风格、代码实现来说，我更喜欢后者，虽然公司主要用的 Swoole 框架&hellip;</p>
<p>没有选 Swoole/Swow 的原因是，这两个项目包含了网络编程、并发编程、多进程/线程、进程间通信等多项技术，它们支持的功能特性也非常多，讲清楚这些功能的实现也需要花费不少的功夫，不符合我们小而美的目标。</p>
<p>所以在权衡了一阵之后，最终选择了 Yar。</p>
<h2 id="yar-介绍">Yar 介绍</h2>
<p>Yar（yet another RPC framework,）是鸟哥（laruence）在 2012 年开发的一个轻量级的并行 RPC 框架，支持多种编码方式（JSON、msgpack、PHP）及 HTTP、TCP 两种数据传输方式，最重要的是支持并行调用，可以让多个数据源并行处理，从而提高系统的性能。</p>
<p><img decoding="async" height="171" loading="lazy" sizes="(max-width: 768px) 100vw, (max-width: 1024px) 768px, 1024px" src="/posts/2021/10/17/yar-internals-start/yar.png" srcset="/posts/2021/10/17/yar-internals-start/yar_hu_303b508d6f6ed19b.png 384w, /posts/2021/10/17/yar-internals-start/yar_hu_2f58ff7d6afaaf58.png 768w" style="max-width: 100%; height: auto; aspect-ratio: 4.5673;" width="781"></p>
<h2 id="yar-服务端">Yar 服务端</h2>
<p>下面是一个 Yar 服务端的例子：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">User</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="sd">/**
</span></span></span><span class="line"><span class="cl"><span class="sd">     * 用户登录
</span></span></span><span class="line"><span class="cl"><span class="sd">     * @param string $username 用户名
</span></span></span><span class="line"><span class="cl"><span class="sd">     * @param string $password 密码
</span></span></span><span class="line"><span class="cl"><span class="sd">     * @return string
</span></span></span><span class="line"><span class="cl"><span class="sd">     */</span>
</span></span><span class="line"><span class="cl">    <span class="k">public</span> <span class="k">function</span> <span class="nf">login</span><span class="p">(</span><span class="nx">string</span> <span class="nv">$username</span><span class="p">,</span> <span class="nx">string</span> <span class="nv">$password</span><span class="p">)</span><span class="o">:</span> <span class="nx">string</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="s1">&#39;success&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">$server</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Yar_Server</span><span class="p">(</span><span class="k">new</span> <span class="nx">User</span><span class="p">());</span>
</span></span><span class="line"><span class="cl"><span class="nv">$server</span><span class="o">-&gt;</span><span class="na">handle</span><span class="p">();</span>
</span></span></code></pre></div><p>在上面的例子中，定义了一个 User 类，并简单地实现了一个 login 方法，然后实例化 User 类并传入到 Yar_Server 的构造函数中，随后调用 Server 中的 handle 方法，处理即将到来的请求。</p>
<p>在 handle 方法中，通过判断请求方式执行不同的操作。</p>
<p>当请求方式为 POST 时，将会执行 RPC 远程服务 的逻辑：对请求数据进行解析、校验，拿到被调用的方法名称及参数后，执行该方法并响应结果。</p>
<p>当请求方式为非 POST 方式时，输出 RPC 服务的 API 信息。如果在 php.ini 中设置了 yar.expose_info = 0，表示不公开信息，将会抛出 “不允许访问服务信息” 的异常。</p>
<p>将上述的例子保存为 server.php，然后执行 php -S 127.0.0.1:3000 启动 PHP 自带的 Web 服务。</p>
<p>在浏览器中输入 http://127.0.0.1:3000 ，打开服务端 API 信息页面：</p>
<p><img decoding="async" height="624" loading="lazy" sizes="(max-width: 768px) 100vw, (max-width: 1024px) 768px, 1024px" src="/posts/2021/10/17/yar-internals-start/yar-server.png" srcset="/posts/2021/10/17/yar-internals-start/yar-server_hu_6e0901798edd2147.png 384w, /posts/2021/10/17/yar-internals-start/yar-server_hu_f51fa2833acd1fd5.png 768w, /posts/2021/10/17/yar-internals-start/yar-server_hu_5b58a03fb0efd3ff.png 1024w" style="max-width: 100%; height: auto; aspect-ratio: 1.9006;" width="1186"></p>
<p>从页面中我们可以看到这个 RPC 服务支持的方法列表及方法名称、参数、注释信息。</p>
<h2 id="yar-客户端">Yar 客户端</h2>
<p>Yar 客户端支持同步调用、并行调用两种方式。</p>
<ul>
<li>同步调用也就是串行调用，必须前一个请求执行完才能执行下一个请求，不能充分的利用系统资源。</li>
<li>并行调用顾名思义就是能够同时执行多个请求，不需要等待某个请求结束再执行，请求完成后会运行指定的回调函数。</li>
</ul>
<p>当然，客户端还支持一些其它的特性，例如：请求持久化、自定义 DNS、HTTP 代理等，这些特性的使用方法可以在官方文档中找到，这里就不赘述了。</p>
<h3 id="同步调用">同步调用</h3>
<p>Yar 的同步调用非常简单，就像调用本地方法一样：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="c1">// 实例化客户端
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nv">$client</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Yar_Client</span><span class="p">(</span><span class="s2">&#34;http://127.0.0.1:3000/server.php&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// 设置超时时间
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nv">$client</span><span class="o">-&gt;</span><span class="na">SetOpt</span><span class="p">(</span><span class="nx">YAR_OPT_CONNECT_TIMEOUT</span><span class="p">,</span> <span class="mi">1000</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// 调用 login 方法
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nv">$result</span> <span class="o">=</span> <span class="nv">$client</span><span class="o">-&gt;</span><span class="na">login</span><span class="p">(</span><span class="s2">&#34;her-cat&#34;</span><span class="p">,</span> <span class="s2">&#34;123456&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">var_dump</span><span class="p">(</span><span class="nv">$result</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// 输出：string(7) &#34;success&#34;
</span></span></span></code></pre></div><h3 id="并行调用">并行调用</h3>
<p>并行调用的使用方式跟同步调用有一点点区别，调用远程方法时需要通过并行客户端中的 call 方法，并传入相应的回调函数，当请求完成时就会调用该回调函数。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="k">function</span> <span class="nf">callback</span><span class="p">(</span><span class="nv">$ret</span><span class="p">,</span> <span class="nv">$callInfo</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">var_dump</span><span class="p">(</span><span class="nv">$ret</span><span class="p">,</span> <span class="nv">$callInfo</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">function</span> <span class="nf">error_callback</span><span class="p">(</span><span class="nv">$type</span><span class="p">,</span> <span class="nv">$error</span><span class="p">,</span> <span class="nv">$callInfo</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">error_log</span><span class="p">(</span><span class="nv">$error</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">$url</span> <span class="o">=</span> <span class="s1">&#39;http://127.0.0.1:3000/server.php&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">Yar_Concurrent_Client</span><span class="o">::</span><span class="na">call</span><span class="p">(</span><span class="nv">$url</span><span class="p">,</span> <span class="s1">&#39;login&#39;</span><span class="p">,</span> <span class="p">[</span><span class="s1">&#39;her-cat&#39;</span><span class="p">,</span> <span class="s1">&#39;123456&#39;</span><span class="p">],</span> <span class="s1">&#39;callback&#39;</span><span class="p">,</span> <span class="s1">&#39;error_callback&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="nx">Yar_Concurrent_Client</span><span class="o">::</span><span class="na">call</span><span class="p">(</span><span class="nv">$url</span><span class="p">,</span> <span class="s1">&#39;login&#39;</span><span class="p">,</span> <span class="p">[</span><span class="s1">&#39;her-cat&#39;</span><span class="p">,</span> <span class="s1">&#39;123456&#39;</span><span class="p">],</span> <span class="s1">&#39;callback&#39;</span><span class="p">,</span> <span class="s1">&#39;error_callback&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="nx">Yar_Concurrent_Client</span><span class="o">::</span><span class="na">call</span><span class="p">(</span><span class="nv">$url</span><span class="p">,</span> <span class="s1">&#39;login&#39;</span><span class="p">,</span> <span class="p">[</span><span class="s1">&#39;her-cat&#39;</span><span class="p">,</span> <span class="s1">&#39;123456&#39;</span><span class="p">],</span> <span class="s1">&#39;callback&#39;</span><span class="p">,</span> <span class="s1">&#39;error_callback&#39;</span><span class="p">,</span> <span class="p">[</span><span class="nx">YAR_OPT_TIMEOUT</span> <span class="o">=&gt;</span> <span class="mi">1</span><span class="p">]);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">Yar_Concurrent_Client</span><span class="o">::</span><span class="na">loop</span><span class="p">();</span>
</span></span></code></pre></div><p>可以看到传入的参数除了 callback 以外，还传入了 error_callback 回调函数，当服务端执行方法的过程中发生了错误，就会运行 error_callback 回调函数。</p>
<h2 id="总结">总结</h2>
<p>到这里本文就结束了，后续就会开始从源码的角度去分析这些功能是如何实现的。</p>
<p>Goodbye~</p>
]]></content:encoded>
    </item>
    <item>
      <title>PHP 源码阅读笔记：编译与调试 PHP</title>
      <link>https://her-cat.com/posts/2021/08/06/php-internals-compile-and-debugging/</link>
      <pubDate>Fri, 06 Aug 2021 18:52:15 +0800</pubDate>
      <guid>https://her-cat.com/posts/2021/08/06/php-internals-compile-and-debugging/</guid>
      <description>工欲善其事必先利其器。</description>
      <content:encoded><![CDATA[<h2 id="基本信息">基本信息</h2>
<ul>
<li>PHP 版本：7.1.0</li>
<li>调试环境：Ubuntu（WSL）</li>
<li>调试工具：GDB、Clion</li>
</ul>
<h2 id="编译-php">编译 PHP</h2>
<p>下载并安装 PHP：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ wget http://cn2.php.net/distributions/php-7.1.0.tar.gz
</span></span><span class="line"><span class="cl">$ tar -xzvf php-7.1.0.tar.gz
</span></span><span class="line"><span class="cl">$ <span class="nb">cd</span> php-7.1.0
</span></span><span class="line"><span class="cl">$ ./configure --prefix<span class="o">=</span><span class="nv">$HOME</span>/php-7.1.0/build --enable-fpm
</span></span></code></pre></div><p>注意：<code>$HOME/php-7.1.0/build</code> 是 PHP 执行文件和库文件安装的目录，可以自定义。<code>--enable-fpm</code> 表示同时安装 php-fpm。</p>
<p>接下来修改编译优化等级，方便我们在调试的过程中查看变量的内容。</p>
<p>首先在 <code>php-7.1.0/Makefile</code> 文件中搜索 <code>CFLAGS_CLEAN</code>，然后将 <code>-02</code> 改为 <code>-o0</code>，表示不需要优化。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">CFLAGS_CLEAN = -I/usr/include -g -O0 -fvisibility=hidden -DZEND_SIGNALS $(PROF_FLAGS)
</span></span></code></pre></div><p>执行 make 命令：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ make <span class="o">&amp;&amp;</span> make install
</span></span></code></pre></div><p>执行完成后，就可以在 <code>php-7.1.0/build/bin</code> 目录下看到 PHP 相关的可执行文件了。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pear  peardev  pecl  phar  phar.phar  php  php-cgi  php-config  phpdbg  phpize
</span></span></code></pre></div><h2 id="调试-php">调试 PHP</h2>
<h3 id="使用-clion-进行调试">使用 Clion 进行调试</h3>
<p>首先用 Clion 打开 <code>php-7.1.0</code> 目录，Clion 会在根目录下自动创建 CMakeLists.txt 文件，复制下面的内容覆盖 CMakeLists.txt 中的内容：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">cmake_minimum_required(VERSION 3.10)
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">project(php_7_1_0)
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">set(CMAKE_CXX_STANDARD 14)
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">set(PHP_SOURCE .)
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">file(GLOB_RECURSE HEAD_FILES FOLLOW_SYMLINKS ${PHP_SOURCE})
</span></span><span class="line"><span class="cl">file(GLOB_RECURSE HEAD_FILES FOLLOW_SYMLINKS ${PHP_SOURCE}/ext)
</span></span><span class="line"><span class="cl">file(GLOB_RECURSE HEAD_FILES FOLLOW_SYMLINKS ${PHP_SOURCE}/Zend)
</span></span><span class="line"><span class="cl">file(GLOB_RECURSE HEAD_FILES FOLLOW_SYMLINKS ${PHP_SOURCE}/sapi)
</span></span><span class="line"><span class="cl">file(GLOB_RECURSE HEAD_FILES FOLLOW_SYMLINKS ${PHP_SOURCE}/pear)
</span></span><span class="line"><span class="cl">file(GLOB_RECURSE HEAD_FILES FOLLOW_SYMLINKS ${PHP_SOURCE}/TSRM)
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">file(GLOB_RECURSE SRC_LIST FOLLOW_SYMLINKS
</span></span><span class="line"><span class="cl">        ${PHP_SOURCE}/*.c
</span></span><span class="line"><span class="cl">        ${PHP_SOURCE}/ext/*.c
</span></span><span class="line"><span class="cl">        ${PHP_SOURCE}/main/*.c
</span></span><span class="line"><span class="cl">        ${PHP_SOURCE}/Zend/*.c
</span></span><span class="line"><span class="cl">        ${PHP_SOURCE}/sapi/*.c
</span></span><span class="line"><span class="cl">        ${PHP_SOURCE}/pear/*.c
</span></span><span class="line"><span class="cl">        ${PHP_SOURCE}/TSRM/*.c
</span></span><span class="line"><span class="cl">)
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">include_directories(${PHP_SOURCE})
</span></span><span class="line"><span class="cl">include_directories(${PHP_SOURCE}/ext)
</span></span><span class="line"><span class="cl">include_directories(${PHP_SOURCE}/main)
</span></span><span class="line"><span class="cl">include_directories(${PHP_SOURCE}/Zend)
</span></span><span class="line"><span class="cl">include_directories(${PHP_SOURCE}/sapi)
</span></span><span class="line"><span class="cl">include_directories(${PHP_SOURCE}/pear)
</span></span><span class="line"><span class="cl">include_directories(${PHP_SOURCE}/TSRM)
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">add_executable(${PROJECT_NAME} ${SRC_LIST})
</span></span><span class="line"><span class="cl">add_custom_target(makefile COMMAND make &amp;&amp; make install WORKING_DIRECTORY ${PROJECT_SOURCE_DIR})
</span></span></code></pre></div><p>注意：<code>/home/ubuntu/php-7.1.0</code> 要替换成你自己的 PHP 所在的路径。</p>
<p>点击右上角绿色锤子旁边的下拉框 =&gt; Edit Configurations =&gt; makefile，按照下面的步骤进行操作：</p>
<ul>
<li><strong>Target：makefile</strong>（一般不用动）</li>
<li><strong>Executable</strong>：点击右边的 <code>...</code> 按钮，选择 php-7.1.0/bin/php 文件</li>
<li><strong>Program arguments</strong>：输入你要调试的 PHP 脚本的文件名，比如 index.php</li>
<li><strong>Working directory</strong>：选择上面 PHP 脚本所在的目录</li>
<li>删除 <strong>Before launch</strong> 中的 <strong>Build</strong>，避免每次调试都重新构建 PHP</li>
</ul>
<p>点击右上角绿色的小虫子或者使用快捷键 <code>Shift + F9</code> 开始调试</p>
<h3 id="使用-gdb-进行调试">使用 GDB 进行调试</h3>
<p>用来测试的 index.php ：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="o">&lt;?</span><span class="nx">php</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">$a</span> <span class="o">=</span> <span class="s1">&#39;hello&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">var_dump</span><span class="p">(</span><span class="nv">$a</span><span class="p">);</span>
</span></span></code></pre></div><p>开始 GDB 调试：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">gdb /php-7.1.0/build/bin/php
</span></span></code></pre></div><p>使用 b 命令在 var_dump 处打断点：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="o">(</span>gdb<span class="o">)</span> b zif_var_dump
</span></span><span class="line"><span class="cl">Breakpoint <span class="m">1</span> at 0x48f3bc: file /home/ubuntu/php-7.1.0/ext/standard/var.c, line 200.
</span></span></code></pre></div><p>为啥这里是 zif_var_dump？</p>
<p>因为 zif_var_dump 是 var_dump 函数的底层实现， 是由 PHP_FUNCITON(var_dump) 宏展开后的名称，所以直接搜 zif_var_dump 是搜不到的。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="cp">#define ZEND_FN(name)   zif_##name
</span></span></span><span class="line"><span class="cl"><span class="cp">#define ZEND_NAMED_FUNCTION(name)   void name(INTERNAL_FUNCTION_PARAMETERS)
</span></span></span><span class="line"><span class="cl"><span class="cp">#define ZEND_FUNCTION(name) ZEND_NAMED_FUNCTION(ZEND_FN(name))
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="cl"><span class="cm">/* PHP-named Zend macro wrappers */</span>
</span></span><span class="line"><span class="cl"><span class="cp">#define PHP_FUNCTION			ZEND_FUNCTION
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="cl"><span class="nf">PHP_FUNCTION</span><span class="p">(</span><span class="n">var_dump</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="n">zval</span> <span class="o">*</span><span class="n">args</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="kt">int</span> <span class="n">argc</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="kt">int</span>	<span class="n">i</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="p">(</span><span class="nf">zend_parse_parameters</span><span class="p">(</span><span class="nf">ZEND_NUM_ARGS</span><span class="p">(),</span> <span class="s">&#34;+&#34;</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">args</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">argc</span><span class="p">)</span> <span class="o">==</span> <span class="n">FAILURE</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="k">for</span> <span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">argc</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nf">php_var_dump</span><span class="p">(</span><span class="o">&amp;</span><span class="n">args</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="mi">1</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>开始运行 PHP 文件：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="o">(</span>gdb<span class="o">)</span> r index.php
</span></span><span class="line"><span class="cl">Starting program: /home/ubuntu/php-7.1.0/build/bin/php index.php
</span></span><span class="line"><span class="cl"><span class="o">[</span>Thread debugging using libthread_db enabled<span class="o">]</span>
</span></span><span class="line"><span class="cl">Using host libthread_db library <span class="s2">&#34;/lib/x86_64-linux-gnu/libthread_db.so.1&#34;</span>.
</span></span><span class="line"><span class="cl">Breakpoint 1, zif_var_dump <span class="o">(</span><span class="nv">execute_data</span><span class="o">=</span>0x7ffffb6130d0, <span class="nv">return_value</span><span class="o">=</span>0x7ffffffea400<span class="o">)</span>
</span></span><span class="line"><span class="cl">    at /home/ubuntu/php-7.1.0/ext/standard/var.c:200
</span></span><span class="line"><span class="cl"><span class="m">200</span>     <span class="o">{</span>
</span></span></code></pre></div><p>代码执行到 zif_var_dump 就停止了，说明命中了断点。我们可以使用 l 命令查看附近的源码：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="o">(</span>gdb<span class="o">)</span> l
</span></span><span class="line"><span class="cl"><span class="m">195</span>     /* <span class="o">}}}</span> */
</span></span><span class="line"><span class="cl"><span class="m">196</span>
</span></span><span class="line"><span class="cl"><span class="m">197</span>     /* <span class="o">{{{</span> proto void var_dump<span class="o">(</span>mixed var<span class="o">)</span>
</span></span><span class="line"><span class="cl"><span class="m">198</span>        Dumps a string representation of variable to output */
</span></span><span class="line"><span class="cl"><span class="m">199</span>     PHP_FUNCTION<span class="o">(</span>var_dump<span class="o">)</span>
</span></span><span class="line"><span class="cl"><span class="m">200</span>     <span class="o">{</span>
</span></span><span class="line"><span class="cl"><span class="m">201</span>             zval *args<span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="m">202</span>             int argc<span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="m">203</span>             int     i<span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="m">204</span>
</span></span></code></pre></div><p>使用 bt 命令查看调用栈：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="o">(</span>gdb<span class="o">)</span> bt
</span></span><span class="line"><span class="cl"><span class="c1">#0  zif_var_dump (execute_data=0x7ffffb6130d0, return_value=0x7ffffffea400)</span>
</span></span><span class="line"><span class="cl">    at /home/ubuntu/php-7.1.0/ext/standard/var.c:200
</span></span><span class="line"><span class="cl"><span class="c1">#1  0x0000000008642151 in ZEND_DO_ICALL_SPEC_RETVAL_UNUSED_HANDLER ()</span>
</span></span><span class="line"><span class="cl">    at /home/ubuntu/php-7.1.0/Zend/zend_vm_execute.h:628
</span></span><span class="line"><span class="cl"><span class="c1">#2  0x000000000864038e in execute_ex (ex=0x7ffffb613030) at /home/ubuntu/php-7.1.0/Zend/zend_vm_execute.h:429</span>
</span></span><span class="line"><span class="cl"><span class="c1">#3  0x0000000008640d05 in zend_execute (op_array=0x7ffffb67e000, return_value=0x0)</span>
</span></span><span class="line"><span class="cl">    at /home/ubuntu/php-7.1.0/Zend/zend_vm_execute.h:474
</span></span><span class="line"><span class="cl"><span class="c1">#4  0x00000000085a2ca6 in zend_execute_scripts (type=8, retval=0x0, file_count=3)</span>
</span></span><span class="line"><span class="cl">    at /home/ubuntu/php-7.1.0/Zend/zend.c:1474
</span></span><span class="line"><span class="cl"><span class="c1">#5  0x00000000084eb1ce in php_execute_script (primary_file=0x7ffffffecd00) at /home/ubuntu/php-7.1.0/main/main.c:2533</span>
</span></span><span class="line"><span class="cl"><span class="c1">#6  0x0000000008743462 in do_cli (argc=2, argv=0x90c1ef0) at /home/ubuntu/php-7.1.0/sapi/cli/php_cli.c:990</span>
</span></span><span class="line"><span class="cl"><span class="c1">#7  0x000000000874483e in main (argc=2, argv=0x90c1ef0) at /home/ubuntu/php-7.1.0/sapi/cli/php_cli.c:1378</span>
</span></span></code></pre></div><p>使用 u 命令让代码运行到指定的行：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="o">(</span>gdb<span class="o">)</span> u <span class="m">209</span>
</span></span><span class="line"><span class="cl">zif_var_dump <span class="o">(</span><span class="nv">execute_data</span><span class="o">=</span>0x7ffffb6130d0, <span class="nv">return_value</span><span class="o">=</span>0x7ffffffea400<span class="o">)</span>
</span></span><span class="line"><span class="cl">    at /home/ubuntu/php-7.1.0/ext/standard/var.c:209
</span></span><span class="line"><span class="cl"><span class="m">209</span>             <span class="k">for</span> <span class="o">(</span><span class="nv">i</span> <span class="o">=</span> 0<span class="p">;</span> i &lt; argc<span class="p">;</span> i++<span class="o">)</span> <span class="o">{</span>
</span></span></code></pre></div><p>使用 p 命令打印变量 $a 的值：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="o">(</span>gdb<span class="o">)</span> p args<span class="o">[</span>0<span class="o">]</span>.value.str.val
</span></span><span class="line"><span class="cl"><span class="nv">$1</span> <span class="o">=</span> <span class="s2">&#34;h&#34;</span>
</span></span></code></pre></div><p>使用 @ 指定输出长度：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="o">(</span>gdb<span class="o">)</span> p args<span class="o">[</span>0<span class="o">]</span>.value.str.val@5
</span></span><span class="line"><span class="cl"><span class="nv">$2</span> <span class="o">=</span> <span class="o">{</span><span class="s2">&#34;h&#34;</span>, <span class="s2">&#34;e&#34;</span>, <span class="s2">&#34;l&#34;</span>, <span class="s2">&#34;l&#34;</span>, <span class="s2">&#34;o&#34;</span><span class="o">}</span>
</span></span></code></pre></div>]]></content:encoded>
    </item>
    <item>
      <title>Workerman 源码分析：文件上传</title>
      <link>https://her-cat.com/posts/2021/05/08/workerman-file-upload/</link>
      <pubDate>Sat, 08 May 2021 17:42:00 +0800</pubDate>
      <guid>https://her-cat.com/posts/2021/05/08/workerman-file-upload/</guid>
      <description>Workerman 将解析协议这一步进行后置，当程序需要用到 HTTP 协议携带的信息时才会解析相应的数据，并将解析结果</description>
      <content:encoded><![CDATA[<h2 id="前言">前言</h2>
<p>在 Nginx 中 HTTP 数据是一边接收一边进行解析的，如果解析过程中发现收到的数据有问题就会停止解析，并且停止接收数据。</p>
<p>而 Workerman 将解析协议这一步进行后置，当程序需要用到 HTTP 协议携带的信息时才会解析相应的数据，并把解析结果缓存起来，下次获取信息时就直接从缓存中读取即可，避免多次解析。</p>
<p>两种方式各有自己的优点，前者的优点就是可以及时的检查数据是否有问题，后者的优点是在接收数据的逻辑相对要简单一点。</p>
<p>解析上传文件的逻辑在 <code>Request::parseUploadFiles</code> 方法中，下面是它的调用栈。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-gdscript3" data-lang="gdscript3"><span class="line"><span class="cl"><span class="o">//</span> <span class="n">source</span><span class="p">:</span> <span class="n">Protocols</span>\<span class="n">Http</span>\<span class="n">Request</span><span class="o">.</span><span class="n">php</span>
</span></span><span class="line"><span class="cl"><span class="n">post</span><span class="p">()</span><span class="o">/</span><span class="n">file</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="o">-&gt;</span> <span class="n">parsePost</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="o">-&gt;</span> <span class="n">parseUploadFiles</span><span class="p">()</span>
</span></span></code></pre></div><h2 id="解析-post-数据">解析 POST 数据</h2>
<p>当调用 post 或 file 方法获取 POST 参数或上传的文件时，程序会检查是否已经解析过了，如果已经解析过了则直接返回对应的结果，否则就会调用 parsePost 方法解析数据。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-gdscript3" data-lang="gdscript3"><span class="line"><span class="cl"><span class="n">protected</span> <span class="n">function</span> <span class="n">parsePost</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="o">//</span> <span class="n">rawBody</span><span class="p">()</span> <span class="err">返回的就是请求体的内容</span>
</span></span><span class="line"><span class="cl">    <span class="o">$</span><span class="n">body_buffer</span> <span class="o">=</span> <span class="o">$</span><span class="n">this</span><span class="o">-&gt;</span><span class="n">rawBody</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="o">//</span> <span class="err">初始化用于保存解析数据的缓存</span>
</span></span><span class="line"><span class="cl">    <span class="o">$</span><span class="n">this</span><span class="o">-&gt;</span><span class="n">_data</span><span class="p">[</span><span class="s1">&#39;post&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="o">$</span><span class="n">this</span><span class="o">-&gt;</span><span class="n">_data</span><span class="p">[</span><span class="s1">&#39;files&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="n">array</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="o">$</span><span class="n">body_buffer</span> <span class="o">===</span> <span class="s1">&#39;&#39;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="o">//</span> <span class="err">尝试从缓存中读取</span>
</span></span><span class="line"><span class="cl">    <span class="o">$</span><span class="n">cacheable</span> <span class="o">=</span> <span class="k">static</span><span class="p">::</span><span class="o">$</span><span class="n">_enableCache</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">isset</span><span class="p">(</span><span class="o">$</span><span class="n">body_buffer</span><span class="p">[</span><span class="mi">1024</span><span class="p">]);</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="o">$</span><span class="n">cacheable</span> <span class="o">&amp;&amp;</span> <span class="n">isset</span><span class="p">(</span><span class="k">static</span><span class="p">::</span><span class="o">$</span><span class="n">_postCache</span><span class="p">[</span><span class="o">$</span><span class="n">body_buffer</span><span class="p">]))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="o">$</span><span class="n">this</span><span class="o">-&gt;</span><span class="n">_data</span><span class="p">[</span><span class="s1">&#39;post&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="k">static</span><span class="p">::</span><span class="o">$</span><span class="n">_postCache</span><span class="p">[</span><span class="o">$</span><span class="n">body_buffer</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="o">//</span> <span class="err">读取请求头中的</span> <span class="n">content</span><span class="o">-</span><span class="n">type</span> <span class="err">字段，如果请求头没有被解析过，那么也会进行解析并缓存</span>
</span></span><span class="line"><span class="cl">    <span class="o">$</span><span class="n">content_type</span> <span class="o">=</span> <span class="o">$</span><span class="n">this</span><span class="o">-&gt;</span><span class="n">header</span><span class="p">(</span><span class="s1">&#39;content-type&#39;</span><span class="p">,</span> <span class="s1">&#39;&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="o">//</span> <span class="err">尝试解析</span> <span class="n">boundary</span> <span class="err">字段，如果获取到了说明是</span> <span class="n">multipart</span><span class="o">/</span><span class="n">form</span><span class="o">-</span><span class="n">data</span> <span class="err">类型的，可能会上传文件</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span>\<span class="n">preg_match</span><span class="p">(</span><span class="s1">&#39;/boundary=&#34;?(\S+)&#34;?/&#39;</span><span class="p">,</span> <span class="o">$</span><span class="n">content_type</span><span class="p">,</span> <span class="o">$</span><span class="n">match</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="o">//</span> <span class="o">$</span><span class="n">match</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="err">中就是我们上面说的边界值</span>
</span></span><span class="line"><span class="cl">        <span class="o">//</span> <span class="err">加上</span> <span class="o">--</span> <span class="err">得到请求体中的边界值</span>
</span></span><span class="line"><span class="cl">        <span class="o">$</span><span class="n">http_post_boundary</span> <span class="o">=</span> <span class="s1">&#39;--&#39;</span> <span class="o">.</span> <span class="o">$</span><span class="n">match</span><span class="p">[</span><span class="mi">1</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">        <span class="o">$</span><span class="n">this</span><span class="o">-&gt;</span><span class="n">parseUploadFiles</span><span class="p">(</span><span class="o">$</span><span class="n">http_post_boundary</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="o">//</span> <span class="err">如果从</span> <span class="n">content</span><span class="o">-</span><span class="n">type</span> <span class="err">中匹配到了</span> <span class="n">json</span><span class="err">，比如</span> <span class="n">application</span><span class="o">/</span><span class="n">json</span>
</span></span><span class="line"><span class="cl">    <span class="o">//</span> <span class="err">说明请求体的数据是</span> <span class="n">JSON</span> <span class="err">格式的</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span>\<span class="n">preg_match</span><span class="p">(</span><span class="s1">&#39;/</span><span class="se">\b</span><span class="s1">json</span><span class="se">\b</span><span class="s1">/i&#39;</span><span class="p">,</span> <span class="o">$</span><span class="n">content_type</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="o">$</span><span class="n">this</span><span class="o">-&gt;</span><span class="n">_data</span><span class="p">[</span><span class="s1">&#39;post&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="p">(</span><span class="n">array</span><span class="p">)</span> <span class="n">json_decode</span><span class="p">(</span><span class="o">$</span><span class="n">body_buffer</span><span class="p">,</span> <span class="bp">true</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="o">//</span> <span class="err">否则就是</span> <span class="n">application</span><span class="o">/</span><span class="n">x</span><span class="o">-</span><span class="n">www</span><span class="o">-</span><span class="n">form</span><span class="o">-</span><span class="n">urlencoded</span>
</span></span><span class="line"><span class="cl">            \<span class="n">parse_str</span><span class="p">(</span><span class="o">$</span><span class="n">body_buffer</span><span class="p">,</span> <span class="o">$</span><span class="n">this</span><span class="o">-&gt;</span><span class="n">_data</span><span class="p">[</span><span class="s1">&#39;post&#39;</span><span class="p">]);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="o">//</span> <span class="err">如果开启了缓存，就把解析结果缓存起来</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="o">$</span><span class="n">cacheable</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">static</span><span class="p">::</span><span class="o">$</span><span class="n">_postCache</span><span class="p">[</span><span class="o">$</span><span class="n">body_buffer</span><span class="p">]</span> <span class="o">=</span> <span class="o">$</span><span class="n">this</span><span class="o">-&gt;</span><span class="n">_data</span><span class="p">[</span><span class="s1">&#39;post&#39;</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span>\<span class="n">count</span><span class="p">(</span><span class="k">static</span><span class="p">::</span><span class="o">$</span><span class="n">_postCache</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">256</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">unset</span><span class="p">(</span><span class="k">static</span><span class="p">::</span><span class="o">$</span><span class="n">_postCache</span><span class="p">[</span><span class="n">key</span><span class="p">(</span><span class="k">static</span><span class="p">::</span><span class="o">$</span><span class="n">_postCache</span><span class="p">)]);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h2 id="解析请求体的内容">解析请求体的内容</h2>
<p>接下来就是 parseUploadFiles 方法的内容了。</p>
<p>首先处理请求体，删掉末尾的结束边界值，然后通过边界值得到数据块数组。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">// 先获取请求体的内容
</span></span><span class="line"><span class="cl">$http_body = $this-&gt;rawBody();
</span></span><span class="line"><span class="cl">//删除末尾的结束边界值
</span></span><span class="line"><span class="cl">$http_body = \substr($http_body, 0, \strlen($http_body) - (\strlen($http_post_boundary) + 4))
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">// 通过边界值 + \r\n 分割请求体，得到数据块数组
</span></span><span class="line"><span class="cl">$boundary_data_array = \explode($http_post_boundary . &#34;\r\n&#34;, $http_body);
</span></span><span class="line"><span class="cl">if ($boundary_data_array[0] === &#39;&#39;) {
</span></span><span class="line"><span class="cl">    unset($boundary_data_array[0]);
</span></span><span class="line"><span class="cl">};
</span></span></code></pre></div><p>为什么计算 substr 结束位置最后要 +4 呢？</p>
<p>因为 <code>结束边界值</code> = <code>边界值</code> + <code>--</code> + <code>\r\n</code>。</p>
<p>接下来用两个 foreach 和一个 switch case 来解析请求体的内容。</p>
<h3 id="遍历所有的数据块">遍历所有的数据块</h3>
<p>通过 <code>\r\n\r\n</code> 分割数据块，得到<strong>数据块的头部信息</strong>和<strong>数据块的值</strong>，然后去除数据块的值末尾的 <code>\r\n</code>。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">foreach ($boundary_data_array as $boundary_data_buffer) {
</span></span><span class="line"><span class="cl">    list($boundary_header_buffer, $boundary_value) = \explode(&#34;\r\n\r\n&#34;, $boundary_data_buffer, 2);
</span></span><span class="line"><span class="cl">    // 去除 $boundary_value 末尾的 \r\n
</span></span><span class="line"><span class="cl">    $boundary_value = \substr($boundary_value, 0, -2);
</span></span><span class="line"><span class="cl">    $key++;
</span></span><span class="line"><span class="cl">}
</span></span></code></pre></div><h3 id="解析数据块的头部信息">解析数据块的头部信息</h3>
<p>数据块的头部信息可能存在多行，所以需要通过 <code>\r\n</code> 分割头部信息字符串得到头部信息的数组。</p>
<p>然后遍历该数组，在循环中通过 <code>: </code> 分割每行的头部信息，得到字段名 $header_key 和字段值 $header_value 。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-gdscript3" data-lang="gdscript3"><span class="line"><span class="cl"><span class="n">foreach</span> <span class="p">(</span>\<span class="n">explode</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\r\n</span><span class="s2">&#34;</span><span class="p">,</span> <span class="o">$</span><span class="n">boundary_header_buffer</span><span class="p">)</span> <span class="n">as</span> <span class="o">$</span><span class="n">item</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">list</span><span class="p">(</span><span class="o">$</span><span class="n">header_key</span><span class="p">,</span> <span class="o">$</span><span class="n">header_value</span><span class="p">)</span> <span class="o">=</span> \<span class="n">explode</span><span class="p">(</span><span class="s2">&#34;: &#34;</span><span class="p">,</span> <span class="o">$</span><span class="n">item</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="o">$</span><span class="n">header_key</span> <span class="o">=</span> \<span class="n">strtolower</span><span class="p">(</span><span class="o">$</span><span class="n">header_key</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">switch</span> <span class="p">(</span><span class="o">$</span><span class="n">header_key</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">case</span> <span class="s2">&#34;content-disposition&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="o">//</span> <span class="err">匹配到了</span> <span class="n">filename</span> <span class="err">说明是文件数据</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">(</span>\<span class="n">preg_match</span><span class="p">(</span><span class="s1">&#39;/name=&#34;(.*?)&#34;; filename=&#34;(.*?)&#34;/i&#39;</span><span class="p">,</span> <span class="o">$</span><span class="n">header_value</span><span class="p">,</span> <span class="o">$</span><span class="n">match</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="o">$</span><span class="n">error</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="o">$</span><span class="n">tmp_file</span> <span class="o">=</span> <span class="s1">&#39;&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="o">//</span> <span class="err">获取文件大小</span>
</span></span><span class="line"><span class="cl">                <span class="o">$</span><span class="n">size</span> <span class="o">=</span> \<span class="n">strlen</span><span class="p">(</span><span class="o">$</span><span class="n">boundary_value</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">                <span class="o">//</span> <span class="err">获取上传临时目录</span>
</span></span><span class="line"><span class="cl">                <span class="o">$</span><span class="n">tmp_upload_dir</span> <span class="o">=</span> <span class="n">HTTP</span><span class="p">::</span><span class="n">uploadTmpDir</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">                <span class="k">if</span> <span class="p">(</span><span class="o">!$</span><span class="n">tmp_upload_dir</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                    <span class="o">$</span><span class="n">error</span> <span class="o">=</span> <span class="n">UPLOAD_ERR_NO_TMP_DIR</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                    <span class="o">//</span> <span class="err">使用</span> <span class="n">tempnam</span> <span class="err">函数在临时目录下创建一个唯一文件名的临时文件</span>
</span></span><span class="line"><span class="cl">                    <span class="o">$</span><span class="n">tmp_file</span> <span class="o">=</span> \<span class="n">tempnam</span><span class="p">(</span><span class="o">$</span><span class="n">tmp_upload_dir</span><span class="p">,</span> <span class="s1">&#39;workerman.upload.&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">                    <span class="o">//</span> <span class="err">文件创建成功后，将数据块的值写入到文件中</span>
</span></span><span class="line"><span class="cl">                    <span class="k">if</span> <span class="p">(</span><span class="o">$</span><span class="n">tmp_file</span> <span class="o">===</span> <span class="bp">false</span> <span class="o">||</span> <span class="bp">false</span> <span class="o">==</span> \<span class="n">file_put_contents</span><span class="p">(</span><span class="o">$</span><span class="n">tmp_file</span><span class="p">,</span> <span class="o">$</span><span class="n">boundary_value</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                        <span class="o">$</span><span class="n">error</span> <span class="o">=</span> <span class="n">UPLOAD_ERR_CANT_WRITE</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                    <span class="p">}</span>
</span></span><span class="line"><span class="cl">                <span class="p">}</span>
</span></span><span class="line"><span class="cl">                <span class="o">//</span> <span class="err">格式化上传的文件信息</span>
</span></span><span class="line"><span class="cl">                <span class="o">$</span><span class="n">files</span><span class="p">[</span><span class="o">$</span><span class="n">key</span><span class="p">]</span> <span class="o">=</span> <span class="n">array</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                    <span class="s1">&#39;key&#39;</span> <span class="o">=&gt;</span> <span class="o">$</span><span class="n">match</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="o">//</span> <span class="err">表单中的字段名</span>
</span></span><span class="line"><span class="cl">                    <span class="s1">&#39;name&#39;</span> <span class="o">=&gt;</span> <span class="o">$</span><span class="n">match</span><span class="p">[</span><span class="mi">2</span><span class="p">],</span> <span class="o">//</span> <span class="err">文件名称</span>
</span></span><span class="line"><span class="cl">                    <span class="s1">&#39;tmp_name&#39;</span> <span class="o">=&gt;</span> <span class="o">$</span><span class="n">tmp_file</span><span class="p">,</span> <span class="o">//</span> <span class="err">临时文件的完整路径</span>
</span></span><span class="line"><span class="cl">                    <span class="s1">&#39;size&#39;</span> <span class="o">=&gt;</span> <span class="o">$</span><span class="n">size</span><span class="p">,</span> <span class="o">//</span> <span class="err">文件大小</span>
</span></span><span class="line"><span class="cl">                    <span class="s1">&#39;error&#39;</span> <span class="o">=&gt;</span> <span class="o">$</span><span class="n">error</span> <span class="o">//</span> <span class="err">错误</span>
</span></span><span class="line"><span class="cl">                <span class="p">);</span>
</span></span><span class="line"><span class="cl">                <span class="k">break</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="o">//</span> <span class="err">未匹配到</span> <span class="n">filename</span> <span class="err">说明是</span> <span class="n">POST</span> <span class="err">字段，需要解析</span> <span class="o">$</span><span class="n">_POST</span><span class="o">.</span>
</span></span><span class="line"><span class="cl">                <span class="k">if</span> <span class="p">(</span>\<span class="n">preg_match</span><span class="p">(</span><span class="s1">&#39;/name=&#34;(.*?)&#34;$/&#39;</span><span class="p">,</span> <span class="o">$</span><span class="n">header_value</span><span class="p">,</span> <span class="o">$</span><span class="n">match</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                    <span class="o">$</span><span class="n">this</span><span class="o">-&gt;</span><span class="n">_data</span><span class="p">[</span><span class="s1">&#39;post&#39;</span><span class="p">][</span><span class="o">$</span><span class="n">match</span><span class="p">[</span><span class="mi">1</span><span class="p">]]</span> <span class="o">=</span> <span class="o">$</span><span class="n">boundary_value</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="k">break</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">case</span> <span class="s2">&#34;content-type&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="o">//</span> <span class="err">添加文件类型</span>
</span></span><span class="line"><span class="cl">            <span class="o">$</span><span class="n">files</span><span class="p">[</span><span class="o">$</span><span class="n">key</span><span class="p">][</span><span class="s1">&#39;type&#39;</span><span class="p">]</span> <span class="o">=</span> \<span class="n">trim</span><span class="p">(</span><span class="o">$</span><span class="n">header_value</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="k">break</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>switch 中的逻辑就是判断 $header_key 的值，然后执行相应的操作。</p>
<ul>
<li>content-disposition: 通过正则判断是否为文件数据，如果是文件就将数据块的值写入临时文件中，否则将字段和值保存到存放 POST 数据的数组中。</li>
<li>content-type: 记录文件的类型。</li>
</ul>
]]></content:encoded>
    </item><follow_challenge>
      <feedId>58021783493571598</feedId>
      <userId>56882619875632128</userId>
    </follow_challenge>
  </channel>
</rss>
