<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>网络编程 on codedump notes</title>
    <link>https://www.codedump.info/zh/tags/%E7%BD%91%E7%BB%9C%E7%BC%96%E7%A8%8B/</link>
    <description>Recent content in 网络编程 on codedump notes</description>
    <generator>Hugo</generator>
    <language>zh</language>
    <lastBuildDate>Thu, 05 Nov 2020 22:08:40 +0800</lastBuildDate>
    <atom:link href="https://www.codedump.info/zh/tags/%E7%BD%91%E7%BB%9C%E7%BC%96%E7%A8%8B/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>KCP 1.4源码分析</title>
      <link>https://www.codedump.info/zh/post/20201105-kcp/</link>
      <pubDate>Thu, 05 Nov 2020 22:08:40 +0800</pubDate>
      <guid>https://www.codedump.info/zh/post/20201105-kcp/</guid>
      <description>&lt;h1 class=&#34;heading&#34; id=&#34;概述&#34;&gt;&#xA;  概述&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e6%a6%82%e8%bf%b0&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://github.com/skywind3000/kcp&#34;&gt;KCP&lt;/a&gt;是基于UDP协议之上的ARQ协议实现。TCP虽然使用的更广泛，但是在某些实时性要求更高的领域（如实时音视频、即时在线游戏等），会更倾向于使用基于UDP的可靠传输协议。&lt;/p&gt;&#xA;&lt;p&gt;在项目的官网上，对KCP是这么介绍的：&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;KCP是一个快速可靠协议，能以比 TCP 浪费 10%-20% 的带宽的代价，换取平均延迟降低 30%-40%，且最大延迟降低三倍的传输效果。纯算法实现，并不负责底层协议（如UDP）的收发，需要使用者自己定义下层数据包的发送方式，以 callback的方式提供给 KCP。 连时钟都需要外部传递进来，内部不会有任何一次系统调用。&lt;/p&gt;&lt;/blockquote&gt;&#xA;&lt;p&gt;UDP并不是一个可靠的传输协议，如果数据没有发送成功并不会自动重传，KCP基于UDP协议之上实现了自己的ARQ协议，所以在继续介绍KCP协议之前，先大体了解一下ARQ协议。&lt;/p&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;arq的两种模式&#34;&gt;&#xA;  ARQ的两种模式&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#arq%e7%9a%84%e4%b8%a4%e7%a7%8d%e6%a8%a1%e5%bc%8f&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;KCP在UDP之上，自己实现了可靠性的算法，即给UDP加上了诸如超时重传、流量控制等机制，这些都是为了保证ARQ协议的运作。&lt;/p&gt;&#xA;&lt;p&gt;ARQ协议(Automatic Repeat-reQuest)，即自动重传请求，是传输层的错误纠正协议之一，它通过使用确认和超时两个机制，在不可靠的网络上实现可靠的信息传输。&lt;/p&gt;&#xA;&lt;p&gt;ARQ的实现通常有如下两种模式。&lt;/p&gt;&#xA;&lt;h3 class=&#34;heading&#34; id=&#34;停等arq协议stop-and-wait&#34;&gt;&#xA;  停等ARQ协议（stop-and-wait）&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e5%81%9c%e7%ad%89arq%e5%8d%8f%e8%ae%aestop-and-wait&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;停等ARQ协议，意味着每个数据在发送出去之后，在没有收到对端的接收回复之前，将一直等待下去，而不会继续发送新的数据包。如果超时还未收到应答，就会自动重传数据包，以保证数据的可靠性。&lt;/p&gt;&#xA;&lt;p&gt;下图是两种情况下停等协议的示意图：&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;stop-and-wait&#34; src=&#34;https://www.codedump.info/media/imgs/20201105-kcp/stop-and-wait.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; stop-and-wait &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;上图：正常不出错情况下运行的停等协议，消息2必须在发送方收到了消息1的对端确认回复之后才能发送出去。&lt;/li&gt;&#xA;&lt;li&gt;下图：出错情况下运行的停等协议，发送方发现消息1超时还未收到应答，就触发了针对消息1的重传机制。在这之前消息2都不会被发出去。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;协议栈如何确认这个“超时时间”呢？答案是根据数据往返时间动态估算出来的RTO（Retransmission TimeOut，重传超时时间）时间来确认的。&lt;/p&gt;&#xA;&lt;h3 class=&#34;heading&#34; id=&#34;连续arq协议continuous-arq&#34;&gt;&#xA;  连续ARQ协议（Continuous ARQ）&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e8%bf%9e%e7%bb%adarq%e5%8d%8f%e8%ae%aecontinuous-arq&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;可以看到，停等协议的机制是“一应一答”式的，对带宽的利用率不高，传输效率不高。&lt;/p&gt;&#xA;&lt;p&gt;连续ARQ协议，可以一次性发送多个数据，而不必像停等协议那样需要等待上一个数据包的确认回复才能继续发送数据。&lt;/p&gt;&#xA;&lt;p&gt;在使用连续ARQ协议的时候，接收方也并不会针对每一个收到的数据包进行确认应答，而只需应答确认最大的那个数据包，这时就认为在此之前的数据包都收到了。&lt;/p&gt;&#xA;&lt;p&gt;这种模式称为“UNA（unacknowledge，即第一个未应答数据包的序列号，小于该序列号的数据包都已经确认被接收到）”模式，与之对应的是，停等协议是ACK模式。&lt;/p&gt;&#xA;&lt;p&gt;然而，即便是可以一次发送多个数据包，也不意味着可以不受控制的发送数据，因为还要受到几种流量窗口的限制，这部分被称为“流量控制”。&lt;/p&gt;&#xA;&lt;h3 class=&#34;heading&#34; id=&#34;拥塞窗口&#34;&gt;&#xA;  拥塞窗口&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e6%8b%a5%e5%a1%9e%e7%aa%97%e5%8f%a3&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;拥塞窗口更多是对网络上经过的网络设备总体网络情况的一个预估。因为在真正发送数据时，并不清楚这时候的网络情况，因此启动时拥塞窗口会有一个初始值，然后根据以下几种算法进行动态的调整：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;慢启动：在启动时让拥塞窗口缓慢扩张。&lt;/li&gt;&#xA;&lt;li&gt;退半避让：在发生网络拥堵时让拥塞窗口大小减半。&lt;/li&gt;&#xA;&lt;li&gt;快重传：在网络恢复时尽快的将数据发送出去。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 class=&#34;heading&#34; id=&#34;滑动窗口&#34;&gt;&#xA;  滑动窗口&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e6%bb%91%e5%8a%a8%e7%aa%97%e5%8f%a3&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;拥塞窗口是对外部网络情况的一种动态的检测，而滑动窗口则是进程本身接收缓冲区的控制，滑动窗口就是接收方用来通知发送方本方接收缓冲区大小的。由于一个网络进程分为协议层和应用层，如果协议层接收数据很快，但是应用层消费数据很慢，这个滑动窗口就会缩小，通过这种方式来通知对端放缓数据的发送，因为接收方已经忙不过来了。&lt;/p&gt;&#xA;&lt;p&gt;KCP作为一个ARQ协议，内部就是要实现对以上这些机制的支持。&lt;/p&gt;&#xA;&lt;p&gt;如果对TCP协议的实现有一些了解，可以看到上述的对端确认回复、超时重传、拥塞窗口、滑动窗口等概念，在TCP中就有，KCP自己实现的ARQ机制，与TCP对比起来有如下的不同点：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;在TCP中，超时之后的RTO时间直接翻倍（即RTO&lt;em&gt;2），而在KCP启用了快速模式之后，RTO的超时时间是&lt;/em&gt;1.5，避免RTO时间的快速增长。&lt;/li&gt;&#xA;&lt;li&gt;TCP协议在丢包时会直接重传丢的那个包之后的所有数据包，KCP只会选择性的重传真正丢失的数据包。&lt;/li&gt;&#xA;&lt;li&gt;TCP为了充分利用带宽，会延迟发送ACK应答对端，这样会导致计算出来的RTT时间过大，KCP的ACK是否延迟发送则可以调节。&lt;/li&gt;&#xA;&lt;li&gt;KCP 正常模式同 TCP 一样使用公平退让法则，即发送窗口大小由：发送缓存大小、接收端剩余接收缓存大小、丢包退让及慢启动这四要素决定。但传送及时性要求很高的小数据时，可选择通过配置跳过后两步，仅用前两项来控制发送频率。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;本文基于KCP 1.4版本对其实现做分析。&lt;/p&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;术语概念&#34;&gt;&#xA;  术语概念&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e6%9c%af%e8%af%ad%e6%a6%82%e5%bf%b5&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;在展开讨论之前，首先介绍相关的术语概念。&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;ARQ：Automatic Repeat-reQuest，自动重传请求协议。KCP就是其中一种ARQ协议的实现。&lt;/li&gt;&#xA;&lt;li&gt;MTU：Maximum Transmission Unit，最大传输单元，链路层规定的每一帧最大长度，通常为1500字节。&lt;/li&gt;&#xA;&lt;li&gt;MSS：Maximum Segment Size，最大分段大小。通常为MTU-协议头大小。&lt;/li&gt;&#xA;&lt;li&gt;RTT：Round-Trip Time，数据往返时间，即发出消息到接收到对端消息应答之间的时间差。&lt;/li&gt;&#xA;&lt;li&gt;RTO：Retransmission TimeOut，重传超时时间，根据收集到的RTT时间估算。&lt;/li&gt;&#xA;&lt;li&gt;rwnd：Receive Window，接收窗口大小，接收端通过该数据通知发送端本方接收窗口大小。&lt;/li&gt;&#xA;&lt;li&gt;cwnd：Congestion Window，拥塞窗口大小，影响发送方发送数据大小。&lt;/li&gt;&#xA;&lt;li&gt;ack：acknowledge，接收端接收到一个数据包之后，通过应答该数据包序列号来通知发送端接收成功。&lt;/li&gt;&#xA;&lt;li&gt;una：unacknowledge，即第一个未应答数据包的序列号，小于该序列号的数据包都已经确认被接收到。&lt;/li&gt;&#xA;&lt;li&gt;ssthresh：Slow Start threshold，慢启动阈值，用于在发生拥塞的情况下控制窗口的增长速度。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;数据结构&#34;&gt;&#xA;  数据结构&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e6%95%b0%e6%8d%ae%e7%bb%93%e6%9e%84&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;报文定义&#34;&gt;&#xA;  报文定义&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e6%8a%a5%e6%96%87%e5%ae%9a%e4%b9%89&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;每个KCP数据报文，其定义如下，注释中描述了每个字段的含义：&lt;/p&gt;</description>
    </item>
    <item>
      <title>glog C&#43;&#43;版本代码分析</title>
      <link>https://www.codedump.info/zh/post/20190729-glog/</link>
      <pubDate>Mon, 29 Jul 2019 11:43:56 +0800</pubDate>
      <guid>https://www.codedump.info/zh/post/20190729-glog/</guid>
      <description>&lt;p&gt;本文基于glog CPP版本的0.4.0版本，对glog的实现机制做一些简单的分析记录。&lt;/p&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;概述&#34;&gt;&#xA;  概述&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e6%a6%82%e8%bf%b0&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;要记录一条日志，分为两个阶段：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;首先生成日志。&lt;/li&gt;&#xA;&lt;li&gt;然后将日志内容输出到相应的设备，如标准输出、文件等。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;以下就分两部分展开对glog的分析。&lt;/p&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;日志的生成&#34;&gt;&#xA;  日志的生成&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e6%97%a5%e5%bf%97%e7%9a%84%e7%94%9f%e6%88%90&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;一般有两种生成日志数据的方式：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;类printf的方式，将需要输入的数据格式化。&lt;/li&gt;&#xA;&lt;li&gt;类C++ stream流的方式，提供出来operator &amp;laquo;操作符供输入数据。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;前者的好处在于可以对输入的数据格式进行严格检查，不匹配的情况下编译器会进行告警。缺点则是不够灵活。&#xA;后者的好处是灵活，除了用了进行一般的日志输入，还可以写出类似&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;CHECK_IF(某条件不成立) &amp;lt;&amp;lt; 输出日志&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;的操作。&lt;/p&gt;&#xA;&lt;p&gt;glog中选择了第二种方式。&lt;/p&gt;&#xA;&lt;p&gt;首先来看glog对外暴露的用于日志输入的接口。其对应的宏是：&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;#define LOG(severity) COMPACT_GOOGLE_LOG_ ## severity.stream()&#xA;&#xA;#define COMPACT_GOOGLE_LOG_INFO google::LogMessage( \&#xA;    __FILE__, __LINE__)&#xA;#define COMPACT_GOOGLE_LOG_WARNING google::LogMessage( \&#xA;    __FILE__, __LINE__, google::GLOG_WARNING)&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;从中可以看到glog中每一条日志，都对应一个LogMessage的类，然后将返回其中的stream()对象输入日志数据。&lt;/p&gt;&#xA;&lt;p&gt;每个LogMessage内部有一个名为LogMessageData的成员，用于保存这些数据，其中比较重要的成员有以下几个：&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;char message_text_[LogMessage::kMaxLogMessageLen+1]; // 用户存储日志的固定长度数组，大小为30KB。&#xA;LogStream stream_; // 用于接收用户日志的C++stream，构造时传入上面的message_text_来构造，所以实际写数据会到message_text_中。&#xA;void (LogMessage::*send_method_)(); // 用户最终发送日志数据的函数指针。&#xA;timestamp_、tm_time_：保存日志时间相关的成员。&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;实际根据日志创建出一个LogMessage对象时，会根据不同类型的日志，传入不同的send_method函数指针，而每个不同的LogMessage构造时都会去调用其内部的Init函数完成LogMessageData的构造，Init函数主要做的事情有：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;存储send_method函数指针。&lt;/li&gt;&#xA;&lt;li&gt;获取当前的系统时间，存放到相应的成员中。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;而LOG之类的宏，实际返回的就是LogMessageData的stream指针，待到一切的输入完毕，这一条日志对应的LogMessage就会被析构，其析构函数内又会调用成员函数Flush，这个函数最终完成将日志输出的操作：&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;void LogMessage::Flush() {&#xA;  // ...&#xA;  {&#xA;    MutexLock l(&amp;amp;log_mutex);&#xA;    (this-&amp;gt;*(data_-&amp;gt;send_method_))();&#xA;    ++num_messages_[static_cast&amp;lt;int&amp;gt;(data_-&amp;gt;severity_)];&#xA;  }&#xA;  // ...&#xA;}&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;有了以上的准备，实际回头来看一个日志的输入&lt;/p&gt;</description>
    </item>
    <item>
      <title>线上存储服务崩溃问题分析记录</title>
      <link>https://www.codedump.info/zh/post/20190413-problem-fix/</link>
      <pubDate>Sat, 13 Apr 2019 12:04:59 +0800</pubDate>
      <guid>https://www.codedump.info/zh/post/20190413-problem-fix/</guid>
      <description>&lt;p&gt;注：本文为重新发布2017-06-17所写博客，以下为正文部分。&lt;/p&gt;&#xA;&lt;p&gt;上周我们的存储服务在某个线上项目频繁出现崩溃，花了几天的时间来查找解决该问题。在这里，将这个过程做一下记录。&lt;/p&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;加入调试信息&#34;&gt;&#xA;  加入调试信息&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e5%8a%a0%e5%85%a5%e8%b0%83%e8%af%95%e4%bf%a1%e6%81%af&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;由于问题在线上发生，较难重现，首先想到的是能不能加上更多的信息，在问题出现时提供更多的解决思路。&lt;/p&gt;&#xA;&lt;p&gt;首先，我们的代码里，在捕获到进程退出的信号比如SIGABRT、SIGSEGV、SIGILL等信号时，会打印出主线程的堆栈，用于帮助我们发现问题。&lt;/p&gt;&#xA;&lt;p&gt;但是在崩溃的几次情况中，打印出来的信息并不足以帮助我们解决问题，因为打印的崩溃堆栈只有主线程，猜测是不是在辅助线程中发生的异常，于是采取了两个策略：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;ulimit命令打开线上一台服务器的coredump，当再次有崩溃发生时有core文件产生，能够帮助发现问题。&lt;/li&gt;&#xA;&lt;li&gt;加入了一些代码，用于在崩溃的时候同时也打印出所有辅助线程的堆栈信息。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;在做这两部分工作之后，再次发生崩溃的情况下，辅助线程的堆栈并无异常，core文件由于数据错乱也看不出来啥有用的信息来。&lt;/p&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;复现问题&#34;&gt;&#xA;  复现问题&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e5%a4%8d%e7%8e%b0%e9%97%ae%e9%a2%98&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;由于第一步工作受挫，接下来我的思路就在考虑怎么能在开发环境下复现这个问题。&lt;/p&gt;&#xA;&lt;p&gt;我们的存储服务在其他项目上已经上线了有一段时间了，但是并没有出现类似的问题。那么，出现问题的项目，与其他已经上线的服务有啥不同，这里也许是一个突破口。&lt;/p&gt;&#xA;&lt;p&gt;经过咨询业务方，该业务的特点是：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;单条数据大：有的数据可能有几KB，而之前的项目都只有几百字节。&lt;/li&gt;&#xA;&lt;li&gt;读请求并发大，而其他业务是写请求远大于读请求。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;由于我们的存储服务兼容memcached协议，出现问题时也是以memcached协议进行访问的，所以此时我的考虑是找一个memcached压测工具，模拟前面的数据和请求特点来做模拟压测。&lt;/p&gt;&#xA;&lt;p&gt;最后选择的是twitter出品的工具twemperf，其特点是可以指定写入缓存的数据范围，同时还可以指定请求的频率。&lt;/p&gt;&#xA;&lt;p&gt;有了这个工具，首先尝试了往存储中写入大量数据量分布在4KB~10KB的数据，此时没有发现服务有core的情况出现。&#xA;然后，尝试构造大量的读请求，果然出现了core情况，重试了几次，都能稳定的重现问题了。&lt;/p&gt;&#xA;&lt;p&gt;有了能稳定重现问题的办法，总算给问题的解决打开了一个口子。&lt;/p&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;首次尝试&#34;&gt;&#xA;  首次尝试&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e9%a6%96%e6%ac%a1%e5%b0%9d%e8%af%95&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;此时，可以正式的在代码中查找问题的原因了。&lt;/p&gt;&#xA;&lt;p&gt;来大概说明一下该存储服务的架构：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;主线程负责接收客户端请求，并且进行解析。&lt;/li&gt;&#xA;&lt;li&gt;如果是读请求，将分派给读请求处理线程，由这个线程与存储引擎库进行交互，查询数据。此时该线程数量配置为2。&lt;/li&gt;&#xA;&lt;li&gt;存储引擎库负责存储落地到磁盘的数据，类似leveldb，只不过这部分是我们自己写的存储引擎。&lt;/li&gt;&#xA;&lt;li&gt;在读线程从存储引擎中查询数据返回后，将把数据返回给主线程，由主线程负责应答客户端。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;server&#34; src=&#34;https://www.codedump.info/media/imgs/20190413-problem-fix/server.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; server &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;在这几步中，第1和第4步是在主线程中进行的，第2和第3步是在读存储引擎线程中进行的。在这个过程中，如果同一个客户端有多个读请求，那么只有按照这四步在处理完毕一个读请求之后，才会继续从该客户端中取出下一个请求进行处理。&lt;/p&gt;&#xA;&lt;p&gt;在几次重现问题的过程中，发现出错的都是在第2步和第4步中，该请求客户端的数据结构某些成员出现了错乱，即要访问的指针地址已经无效了，导致的错误。&lt;/p&gt;&#xA;&lt;p&gt;指针无效，一般来说有两种可能性：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;被无效地址覆盖了这个指针。&lt;/li&gt;&#xA;&lt;li&gt;指针已经被释放的情况下继续使用。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;当时尝试把一些错误的指针地址打印出来，发现有几次都是是字符串“pcm*”的16进制表示，当时在想这个特殊的字符串到底是什么，百思不得其解的时候，一位曾经使用过mcperf工具的同事，想起来mcperf做压测时的key就是&amp;quot;mcp&amp;quot;开头的，而因为是小端方式，所以如果使用这个类型的字符串，去覆盖指针，那么就变成了&amp;quot;pcm&amp;quot;。我们很快验证了这个说法，mcperf确实是以这个为前缀来写入数据的。&lt;/p&gt;&#xA;&lt;p&gt;此时，猜测问题的原因在于：当读存储引擎线程去访问存储引擎时，某些错误导致从存储引擎读出来的数据，将客户端请求数据写乱，从而导致了崩溃。&lt;/p&gt;&#xA;&lt;p&gt;由于同时有两个读存储引擎的线程，猜测这里是不是因为多线程访问出了问题，导致的错误呢？&lt;/p&gt;&#xA;&lt;p&gt;为了验证这个问题，最简单的办法就是将线程数量改成1，重新用mcperf试了几次，确实没有再次出现问题。此时已经是周五，我们缓了一口气，打算以此修改暂时上测试环境利用周末的时间观察一下情况。&lt;/p&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;柳暗花明&#34;&gt;&#xA;  柳暗花明&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e6%9f%b3%e6%9a%97%e8%8a%b1%e6%98%8e&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;前面提到过，猜测问题出现的原因，是多线程访问存储引擎时将某个数据写错乱了，导致其中的指针无效。&lt;/p&gt;&#xA;&lt;p&gt;clang和gcc 4.8有对应的编译参数，可以用来检测内存错误的写操作，即Address Sanitizer工具。为了兼容线上比较老的系统，之前我们的服务都是在gcc 4.1的环境下进行编译的，为了使用这个工具，首先需要折腾到满足gcc版本号大于4.8的系统上进行编译。&lt;/p&gt;&#xA;&lt;p&gt;然而，在折腾编译并且运行后，同样使用mcperf的情况下，并不能看到有内存错误覆盖写的提示，我尝试了多次都没有看到。难道是工具没有起作用？&lt;/p&gt;&#xA;&lt;p&gt;为了验证该工具的作用，我简单在出错代码的前面加入了一段肯定有问题的代码，比如：&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;char a[100] = {&amp;#39;0&amp;#39;};&#xA;a[100] = &amp;#39;1&amp;#39;;&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;而在加入这段有问题的代码之后再次运行，就能看到编译器对这段代码的提示。可见，Address Sanitizer工具是起作用的。那么，前面的过程中没有看到问题，只能说明一个问题：并没有内存错误写的情况发生。&lt;/p&gt;&#xA;&lt;p&gt;此时想到另一个可能，就是有没有可能是多线程在没有保护的情况下访问了某段数据导致的问题？&lt;/p&gt;&#xA;&lt;p&gt;gcc同样也有类似的工具来检查这类错误，即Thread Sanitizer工具。然而，在给项目Makefile加入该编译参数后，程序一运行就退出了，根本看不出什么有用的信息来。&lt;/p&gt;&#xA;&lt;p&gt;此时想到的另一个工具是valgrind。大多数时候，valgrind只是用来做内存泄露检测的，其实它也可以用来做线程数据竞争的检查，使用参数 &amp;ndash;tool=helgrind 即可。使用valgrind之后，打印出来疑似有问题的代码如下：&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;valgrind&#34; src=&#34;https://www.codedump.info/media/imgs/20190413-problem-fix/valgrind.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; valgrind &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;到了这里，猜测问题的原因就是由于多线程访问数据导致的错误。&lt;/p&gt;&#xA;&lt;p&gt;因为有多个处理读请求数据的线程，首先猜测的是不是某些错误的处理，导致了可以在同一时间多个线程都操作该请求客户端的数据。但是通过review代码，发现这部分处理是没有问题的，另外在访问存储引擎查询数据时，入口处也确实进行了加锁的操作。&lt;/p&gt;</description>
    </item>
    <item>
      <title>TCP协议笔记</title>
      <link>https://www.codedump.info/zh/post/20190227-tcp/</link>
      <pubDate>Wed, 27 Feb 2019 21:01:43 +0800</pubDate>
      <guid>https://www.codedump.info/zh/post/20190227-tcp/</guid>
      <description>&lt;h1 class=&#34;heading&#34; id=&#34;概述&#34;&gt;&#xA;  概述&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e6%a6%82%e8%bf%b0&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;tcpip&#34; src=&#34;https://www.codedump.info/media/imgs/20190227-tcp/tcpip.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; tcpip &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;应用层：通常也称为“七层”，这是大部分服务器工作的层次，如HTTP 服务器等，位于应用层上的信息分组成为报文（message）。识别不同应用层的信息是通过端口号，即不同的端口号提供不同的服务。&lt;/li&gt;&#xA;&lt;li&gt;传输层：通常也称为“四层”，TCP、UDP协议工作在这一层，位于这一层的分组称为报文段（segment）。&lt;/li&gt;&#xA;&lt;li&gt;网络层：通常也称为“三层”，负责将数据包（datagram）从一台主机移动到另一台主机。&lt;/li&gt;&#xA;&lt;li&gt;接口层：通常也称为“二层”，链路层分组称为帧（frame）。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;tcp协议格式&#34;&gt;&#xA;  TCP协议格式&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#tcp%e5%8d%8f%e8%ae%ae%e6%a0%bc%e5%bc%8f&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;tcp-header&#34; src=&#34;https://www.codedump.info/media/imgs/20190227-tcp/tcp-header.jpg&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; tcp-header &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;端口号：tcp使用端口号来标记目标和源端口，tcp头中并没有ip地址信息，根据前面的tcp/ip模型，ip地址这是三层做的事情。&lt;/li&gt;&#xA;&lt;li&gt;序号（Sequence Number）：用于对tcp字节流进行编号，以解决网络包乱序问题。&lt;/li&gt;&#xA;&lt;li&gt;确认号（Acknowledgement Number）：用于确认接收到的报文段序号，用来解决丢包问题。&lt;/li&gt;&#xA;&lt;li&gt;窗口：用于通知对端接收窗口大小，用于解决流控问题。&lt;/li&gt;&#xA;&lt;li&gt;TCP标志位，用于控制TCP协议状态机的，包括以下几个：&#xA;&lt;ul&gt;&#xA;&lt;li&gt;ACK：只有这个标志位置位时，前面的确认号字段才有效。&lt;/li&gt;&#xA;&lt;li&gt;SYN：在连接建立时用来同步序号。当 SYN=1，ACK=0 时表示这是一个连接请求报文段。若对方同意建立连接，则响应报文中 SYN=1，ACK=1。&lt;/li&gt;&#xA;&lt;li&gt;FIN：用来释放一个连接，当 FIN=1 时，表示此报文段的发送方的数据已发送完毕，并要求释放连接。&lt;/li&gt;&#xA;&lt;li&gt;RST：重置连接，比如向一个不存在监听服务的端口发请求时，就会收到RST包。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;TCP选项：这部分可选，不属于TCP头部必然存在的部分。&#xA;&lt;ul&gt;&#xA;&lt;li&gt;MSS（Maximum Segment Size，最大报文长度）：MSS选项用于在TCP连接建立时，收发双方协商通信时每一个报文段所能承载的最大数据长度。为了达到最佳的传输效能，TCP协议在建立连接的时候通常要协商双方的MSS值，这个值TCP协议在实现的时候往往用MTU值代替（需要减去IP数据包包头的大小20Bytes和TCP数据段的包头20Bytes）所以一般MSS值1460。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;tcp连接的建立和终止&#34;&gt;&#xA;  TCP连接的建立和终止&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#tcp%e8%bf%9e%e6%8e%a5%e7%9a%84%e5%bb%ba%e7%ab%8b%e5%92%8c%e7%bb%88%e6%ad%a2&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;连接建立&#34;&gt;&#xA;  连接建立&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e8%bf%9e%e6%8e%a5%e5%bb%ba%e7%ab%8b&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;tcp-connect&#34; src=&#34;https://www.codedump.info/media/imgs/20190227-tcp/tcp-connect.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; tcp-connect &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;以上图说明建立TCP连接的过程，其中左边的A为客户端，右边的B为服务器：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;B调用listen系统命令，进入监听状态，等待客户端的连接。&lt;/li&gt;&#xA;&lt;li&gt;A向B发送连接请求报文，其中TCP标志位里SYN=1，ACK=0，选择一个初始的序号x。&lt;/li&gt;&#xA;&lt;li&gt;B收到请求报文，向 A 发送连接确认报文，SYN=1，ACK=1，确认号为 x+1，同时也选择一个初始的序号 y。&lt;/li&gt;&#xA;&lt;li&gt;A 收到 B 的连接确认报文后，还要向 B 发出确认，确认号为 y+1，序号为 x+1。&lt;/li&gt;&#xA;&lt;li&gt;B 收到 A 的确认后，连接建立。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;以上就是TCP建立连接的三次握手过程，以上流程还需要补充的是：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;对于建链接的3次握手，主要是要初始化Sequence Number 的初始值。通信的双方要互相通知对方自己的初始化的Sequence Number（缩写为ISN：Inital Sequence Number）——所以叫SYN，全称Synchronize Sequence Numbers。也就上图中的 x 和 y。这个号要作为以后的数据通信的序号，以保证应用层接收到的数据不会因为网络上的传输的问题而乱序（TCP会用这个序号来拼接数据）。&lt;/li&gt;&#xA;&lt;li&gt;第三次握手是为了防止失效的连接请求到达服务器，让服务器错误打开连接。客户端发送的连接请求如果在网络中滞留，那么就会隔很长一段时间才能收到服务器端发回的连接确认。客户端等待一个超时重传时间之后，就会重新请求连接。但是这个滞留的连接请求最后还是会到达服务器，如果不进行三次握手，那么服务器就会打开两个连接。如果有第三次握手，客户端会忽略服务器之后发送的对滞留连接请求的连接确认，不进行第三次握手，因此就不会再次打开连接。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 class=&#34;heading&#34; id=&#34;backlog参数与syn-flood攻击&#34;&gt;&#xA;  backlog参数与SYN Flood攻击&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#backlog%e5%8f%82%e6%95%b0%e4%b8%8esyn-flood%e6%94%bb%e5%87%bb&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;listen系统调用中，会传入一个backlog参数，man文档对其的解释是：&lt;/p&gt;</description>
    </item>
    <item>
      <title>zeromq所谓的“无锁消息队列”</title>
      <link>https://www.codedump.info/zh/post/20190209-zeromq-lockfree-queue/</link>
      <pubDate>Sat, 09 Feb 2019 20:10:13 +0800</pubDate>
      <guid>https://www.codedump.info/zh/post/20190209-zeromq-lockfree-queue/</guid>
      <description>&lt;p&gt;本文基于zeromq 4.3.0版本，分析其无锁消息队列的实现。&lt;/p&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;概述&#34;&gt;&#xA;  概述&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e6%a6%82%e8%bf%b0&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;zeromq这个网络库，有以下几个亮点：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;从以往的面向TCP流的网络开发，变成了面向消息的开发。应用层关注的是什么类型的消息，库本身解决网络收发、断线重连等问题。&lt;/li&gt;&#xA;&lt;li&gt;将这些消息的传输模式封装成几个模式，应用开发者只需要关注自己的业务符合什么模式，采用搭积木的方式就能构建起应用服务。&lt;/li&gt;&#xA;&lt;li&gt;内部实现无锁消息队列用于对象间通信，类似actor模式。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;基本架构&#34;&gt;&#xA;  基本架构&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e5%9f%ba%e6%9c%ac%e6%9e%b6%e6%9e%84&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;zeromq内部运行着多个io线程，每个io线程内部有以下两个核心组件：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;poller：即针对epoll、select等事件轮询器的封装。&lt;/li&gt;&#xA;&lt;li&gt;mailbox：负责接收消息的消息邮箱。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;可以简单理解IO线程做的事情是：内部通过一个poller，监听着各种事件，其中包括针对IO线程的mailbox的消息，以及绑定在该IO线程上的IO对象的消息。&lt;/p&gt;&#xA;&lt;p&gt;即这是一个per-thread-per-loop的线程设计，线程之间的通信通过消息邮箱来进行。&lt;/p&gt;&#xA;&lt;p&gt;除了io线程之外，io对象也有mailbox，即如果想与某个IO对象通信也是通过该mailbox进行。由于消息邮箱是zeromq中的重要组成部分，下面将专门分析zeromq是如何实现的。&lt;/p&gt;&#xA;&lt;p&gt;所有需要收发消息的对象都继承自object_t：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-C&#34; data-lang=&#34;C&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;class &lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;object_t&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;public:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;object_t&lt;/span&gt; (zmq::&lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;ctx_t&lt;/span&gt; *ctx_, &lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;uint32_t&lt;/span&gt; tid_);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;process_command&lt;/span&gt; (zmq::&lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;command_t&lt;/span&gt; &amp;amp;cmd_);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;private:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  zmq::&lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;ctx_t&lt;/span&gt; *ctx;&lt;span style=&#34;color:#888;font-style:italic&#34;&gt;//  Context provides access to the global state.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888;font-style:italic&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;uint32_t&lt;/span&gt; tid;&lt;span style=&#34;color:#888;font-style:italic&#34;&gt;//  Thread ID of the thread the object belongs to.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888;font-style:italic&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;send_command&lt;/span&gt; (&lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;command_t&lt;/span&gt; &amp;amp;cmd_);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;而IO对象之间的命令通过command_t结构体来定义：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-C&#34; data-lang=&#34;C&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;struct&lt;/span&gt; &lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;command_t&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#888;font-style:italic&#34;&gt;//  Object to process the command.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888;font-style:italic&#34;&gt;&lt;/span&gt;  zmq::&lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;object_t&lt;/span&gt; *destination;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;enum&lt;/span&gt; &lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;type_t&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ...&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  } type;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;union&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ...&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  } args;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;可以看到，zeromq实现对象间相互通信依赖于mailbox，本文重点在分析其无锁队列的实现上。&lt;/p&gt;</description>
    </item>
    <item>
      <title>Libuv代码简单分析</title>
      <link>https://www.codedump.info/zh/post/20190123-libuv/</link>
      <pubDate>Wed, 23 Jan 2019 08:43:49 +0800</pubDate>
      <guid>https://www.codedump.info/zh/post/20190123-libuv/</guid>
      <description>&lt;p&gt;本文基于libuv 1.x版本进行简单的分析。&lt;/p&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;数据结构&#34;&gt;&#xA;  数据结构&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e6%95%b0%e6%8d%ae%e7%bb%93%e6%9e%84&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;uv__io_t&#34;&gt;&#xA;  uv__io_t&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#uv__io_t&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;uv__io_t用来表示一个IO事件。&lt;/p&gt;&#xA;&lt;p&gt;其成员包括：&lt;/p&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;成员&lt;/th&gt;&#xA;          &lt;th&gt;说明&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;uv__io_cb cb&lt;/td&gt;&#xA;          &lt;td&gt;IO事件被触发的回调函数&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;void* pending_queue[2]&lt;/td&gt;&#xA;          &lt;td&gt;pending队列&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;void* watcher_queue[2]&lt;/td&gt;&#xA;          &lt;td&gt;watcher队列&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;unsigned int pevents&lt;/td&gt;&#xA;          &lt;td&gt;pending的事件mask，等待下一次被添加到事件中&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;unsigned int events&lt;/td&gt;&#xA;          &lt;td&gt;当前的事件mask&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;int fd&lt;/td&gt;&#xA;          &lt;td&gt;事件fd&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;queue&#34;&gt;&#xA;  queue&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#queue&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;libuv的queue实现比较奇葩，一个queue里面的元素会有两个指针，一个指向队列前一个成员，一个指向队列下一个成员，在这里不做阐述，看到类似：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-C&#34; data-lang=&#34;C&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;void&lt;/span&gt;* watcher_queue[2]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这样子定义了有两个void*指针的数组知道这是一个队列就好了。&lt;/p&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;uv_timer_t&#34;&gt;&#xA;  uv_timer_t&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#uv_timer_t&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;定义定时器的结构体，libuv中使用最小堆来维护定时器。&lt;/p&gt;&#xA;&lt;p&gt;一般而言，都是首先从这个最小堆数据结构中获得距离当前最近的定时器，然后拿到它的超时时间，以该超时时间做为下一次loop事件循环的时间，某些情况下会无视这个值，比如存在idle handler的情况下，此时会以0做为超时时间。&lt;/p&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;uv_handle_t及其子类&#34;&gt;&#xA;  uv_handle_t及其子类&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#uv_handle_t%e5%8f%8a%e5%85%b6%e5%ad%90%e7%b1%bb&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;uv_handle_t是libuv中所有handler的基类，libuv中实现继承的手段也比较奇葩：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;类成员定义放在宏里。&lt;/li&gt;&#xA;&lt;li&gt;继承自某个基类的子类按照继承顺序依次放它前面基类的宏。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;比如uv_tcp_t继承自uv_stream_t，而后者又继承自uv_handle_t，三者的定义如下：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-C&#34; data-lang=&#34;C&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;struct&lt;/span&gt; uv_handle_s {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  UV_HANDLE_FIELDS&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;struct&lt;/span&gt; uv_stream_s {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  UV_HANDLE_FIELDS&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  UV_STREAM_FIELDS&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;struct&lt;/span&gt; uv_tcp_s {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  UV_HANDLE_FIELDS&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  UV_STREAM_FIELDS&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  UV_TCP_PRIVATE_FIELDS&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;代码中可以看到：&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
