<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>dh-Personal Technology Blog</title>
    <description>Yunong&apos;s blog,use dh and github pages.</description>
    <link>https://seakingOne.github.io/</link>
    <atom:link href="https://seakingOne.github.io/feed.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Thu, 09 Jul 2026 07:05:01 +0000</pubDate>
    <lastBuildDate>Thu, 09 Jul 2026 07:05:01 +0000</lastBuildDate>
    <generator>Jekyll v3.10.0</generator>
    
      <item>
        <title>神农本草经（中）</title>
        <description>&lt;p&gt;《神农本草经》是中国现存最早的中药学专著，相传成书于汉代，是研究中药起源与功效的重要典籍。以下为《神农本草经·中卷》原文 PDF 在线阅读。&lt;/p&gt;

&lt;div class=&quot;pdf-viewer&quot; id=&quot;pdf-viewer-神农本草经-中&quot; data-src=&quot;/static/assets/img/blog/zhouyi/zhongyi/神农本草经-中.pdf&quot; data-title=&quot;神农本草经（中）&quot;&gt;
  &lt;div class=&quot;pdf-viewer-header&quot;&gt;
    &lt;h4 class=&quot;pdf-viewer-title&quot;&gt;神农本草经（中）&lt;/h4&gt;
    &lt;div class=&quot;pdf-viewer-actions&quot;&gt;
      &lt;span class=&quot;pdf-viewer-status&quot;&gt;正在加载…&lt;/span&gt;
      &lt;a class=&quot;pdf-viewer-download btn btn-white btn-xs&quot; href=&quot;/static/assets/img/blog/zhouyi/zhongyi/神农本草经-中.pdf&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;
        &lt;i class=&quot;fa fa-download&quot;&gt;&lt;/i&gt; 下载原文
      &lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;pdf-viewer-pages&quot;&gt;&lt;/div&gt;
  &lt;div class=&quot;pdf-viewer-fallback&quot;&gt;
    &lt;p&gt;当前浏览器无法渲染 PDF，请 &lt;a href=&quot;/static/assets/img/blog/zhouyi/zhongyi/神农本草经-中.pdf&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;点击此处&lt;/a&gt; 查看原文。&lt;/p&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;script src=&quot;https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js&quot;&gt;&lt;/script&gt;

&lt;script src=&quot;/static/js/pdf-viewer.js&quot;&gt;&lt;/script&gt;

</description>
        <pubDate>Thu, 09 Jul 2026 00:00:00 +0000</pubDate>
        <link>https://seakingOne.github.io/chinese_culture/2026/07/09/%E7%A5%9E%E5%86%9C%E6%9C%AC%E8%8D%89%E7%BB%8F%E4%B8%AD.html</link>
        <guid isPermaLink="true">https://seakingOne.github.io/chinese_culture/2026/07/09/%E7%A5%9E%E5%86%9C%E6%9C%AC%E8%8D%89%E7%BB%8F%E4%B8%AD.html</guid>
        
        <category>中医</category>
        
        <category>本草</category>
        
        <category>神农本草经</category>
        
        <category>古籍</category>
        
        
        <category>Chinese_culture</category>
        
      </item>
    
      <item>
        <title>神农本草经（上）</title>
        <description>&lt;p&gt;《神农本草经》是中国现存最早的中药学专著，相传成书于汉代，是研究中药起源与功效的重要典籍。以下为《神农本草经·上卷》原文 PDF 在线阅读。&lt;/p&gt;

&lt;div class=&quot;pdf-viewer&quot; id=&quot;pdf-viewer-神农本草经-上&quot; data-src=&quot;/static/assets/img/blog/zhouyi/zhongyi/神农本草经-上.pdf&quot; data-title=&quot;神农本草经（上）&quot;&gt;
  &lt;div class=&quot;pdf-viewer-header&quot;&gt;
    &lt;h4 class=&quot;pdf-viewer-title&quot;&gt;神农本草经（上）&lt;/h4&gt;
    &lt;div class=&quot;pdf-viewer-actions&quot;&gt;
      &lt;span class=&quot;pdf-viewer-status&quot;&gt;正在加载…&lt;/span&gt;
      &lt;a class=&quot;pdf-viewer-download btn btn-white btn-xs&quot; href=&quot;/static/assets/img/blog/zhouyi/zhongyi/神农本草经-上.pdf&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;
        &lt;i class=&quot;fa fa-download&quot;&gt;&lt;/i&gt; 下载原文
      &lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;pdf-viewer-pages&quot;&gt;&lt;/div&gt;
  &lt;div class=&quot;pdf-viewer-fallback&quot;&gt;
    &lt;p&gt;当前浏览器无法渲染 PDF，请 &lt;a href=&quot;/static/assets/img/blog/zhouyi/zhongyi/神农本草经-上.pdf&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;点击此处&lt;/a&gt; 查看原文。&lt;/p&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;script src=&quot;https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js&quot;&gt;&lt;/script&gt;

&lt;script src=&quot;/static/js/pdf-viewer.js&quot;&gt;&lt;/script&gt;

</description>
        <pubDate>Sun, 05 Jul 2026 00:00:00 +0000</pubDate>
        <link>https://seakingOne.github.io/chinese_culture/2026/07/05/%E7%A5%9E%E5%86%9C%E6%9C%AC%E8%8D%89%E7%BB%8F%E4%B8%8A.html</link>
        <guid isPermaLink="true">https://seakingOne.github.io/chinese_culture/2026/07/05/%E7%A5%9E%E5%86%9C%E6%9C%AC%E8%8D%89%E7%BB%8F%E4%B8%8A.html</guid>
        
        <category>中医</category>
        
        <category>本草</category>
        
        <category>神农本草经</category>
        
        <category>古籍</category>
        
        
        <category>Chinese_culture</category>
        
      </item>
    
      <item>
        <title>玄空风水之古籍:沈氏玄空学阴宅案例1</title>
        <description>&lt;h1 id=&quot;hi大家好最近又在看古书案例阴宅部分拿出来部分案例做一下解析&quot;&gt;hi，大家好，最近又在看古书案例，阴宅部分拿出来部分案例做一下解析&lt;/h1&gt;

&lt;h2 id=&quot;案例为常州张姓祖墓癸山丁向一运&quot;&gt;案例为常州张姓祖墓，癸山丁向，一运&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/static/assets/img/blog/zhouyi/fengshui/yinzhai-anli-1.png&quot; width=&quot;35%&quot; style=&quot;cursor: zoom-in;&quot; onclick=&quot;let o=document.createElement(&apos;div&apos;);o.style=&apos;position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.85);z-index:9999;display:flex;align-items:center;justify-content:center;cursor:zoom-out;&apos;;let i=document.createElement(&apos;img&apos;);i.src=this.src;i.style=&apos;max-width:95%;max-height:95%;&apos;;o.appendChild(i);document.body.appendChild(o);o.onclick=()=&amp;gt;o.remove();&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h3 id=&quot;先看沈的说明&quot;&gt;先看沈的说明&lt;/h3&gt;

&lt;p&gt;此一六八俱到向上，又见水光，真合五星之妙，长房发秀而财不旺者，盖六为乾，乾属长，六又为官星，故发秀，又为金生向上坎水，谓之生出，故财不旺。次房丁秀大盛而财亦旺者，盖双一到向，坎为中男，故二房更发也。&lt;/p&gt;

&lt;h3 id=&quot;我的看法&quot;&gt;我的看法&lt;/h3&gt;

&lt;p&gt;古人给出的解释爻辩证去思考，首先分房这个说法我只是部分认同，毕竟人家本身的家族情况不一定是长子次子，比如说如果是九运的话，岂不是要看中女吗？所以这个说法我并不是很认同。对于财运这块是双星到向，一运的话正神是1，零神是9，由于双星到山，前面又有河流和水池，所以财运也是&lt;/p&gt;

&lt;p&gt;很不错的，加上河流从艮卦流出，又是未来2的财星，也是很不错的。&lt;/p&gt;

&lt;h2 id=&quot;此处还有一个案例大部分的人都认为后要有靠山前有水其实未必&quot;&gt;此处还有一个案例，大部分的人都认为后要有靠山，前有水，其实未必&lt;/h2&gt;

&lt;h4 id=&quot;无锡石塘湾孙姓祖墓子山午向二运&quot;&gt;无锡石塘湾孙姓祖墓，子山午向，二运&lt;/h4&gt;

&lt;p&gt;&lt;img src=&quot;/static/assets/img/blog/zhouyi/fengshui/yinzhai-anli-2.png&quot; width=&quot;35%&quot; style=&quot;cursor: zoom-in;&quot; onclick=&quot;let o=document.createElement(&apos;div&apos;);o.style=&apos;position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.85);z-index:9999;display:flex;align-items:center;justify-content:center;cursor:zoom-out;&apos;;let i=document.createElement(&apos;img&apos;);i.src=this.src;i.style=&apos;max-width:95%;max-height:95%;&apos;;o.appendChild(i);document.body.appendChild(o);o.onclick=()=&amp;gt;o.remove();&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h3 id=&quot;还是先看沈的说明&quot;&gt;还是先看沈的说明&lt;/h3&gt;

&lt;p&gt;葬后大发财丁者，因两盘旺星到后，坎方有水特大，所谓“冲气乐宫无价宝”是也。然坎方水虽大，而当背冲来终究不美，故一交六运即大败也。&lt;/p&gt;

&lt;h3 id=&quot;我的看法-1&quot;&gt;我的看法&lt;/h3&gt;
&lt;p&gt;其实这个案例就已经说明了并非所有的风水格局都是前水后山就是最好的格局，反而需要看星的旺衰来决定是否为好的格局，双星2到山，旺人旺财，但是由于背面为水，所以财&amp;gt;人丁，3运的话加上又有来方河流，所以继续&lt;/p&gt;

&lt;p&gt;财旺，四运震方又有水，五运巽方也有水，只是说财运不如二运来的好，六运财运自然也就衰败了。&lt;/p&gt;

</description>
        <pubDate>Sun, 14 Jun 2026 00:00:00 +0000</pubDate>
        <link>https://seakingOne.github.io/chinese_culture/2026/06/14/%E7%8E%84%E7%A9%BA%E9%A3%8E%E6%B0%B4%E4%B9%8B%E5%8F%A4%E7%B1%8D-%E6%B2%88%E6%B0%8F%E7%8E%84%E7%A9%BA%E5%AD%A6%E9%98%B4%E5%AE%85%E6%A1%88%E4%BE%8B1.html</link>
        <guid isPermaLink="true">https://seakingOne.github.io/chinese_culture/2026/06/14/%E7%8E%84%E7%A9%BA%E9%A3%8E%E6%B0%B4%E4%B9%8B%E5%8F%A4%E7%B1%8D-%E6%B2%88%E6%B0%8F%E7%8E%84%E7%A9%BA%E5%AD%A6%E9%98%B4%E5%AE%85%E6%A1%88%E4%BE%8B1.html</guid>
        
        <category>周易</category>
        
        <category>玄学</category>
        
        <category>人生感悟</category>
        
        
        <category>Chinese_culture</category>
        
      </item>
    
      <item>
        <title>六爻求测找工作</title>
        <description>&lt;p&gt;hi，大家好，楼主去年开始不是外企裁员了嘛，所以最近在找工作，行业的确不好，面试的机会很好，好不容易有一个公司给了面试机会，所以求测了一卦 &lt;br /&gt;
盘如下： &lt;br /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/assets/img/blog/zhouyi/liuyao-job-1.png&quot; width=&quot;35%&quot; style=&quot;cursor: zoom-in;&quot; onclick=&quot;let o=document.createElement(&apos;div&apos;);o.style=&apos;position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.85);z-index:9999;display:flex;align-items:center;justify-content:center;cursor:zoom-out;&apos;;let i=document.createElement(&apos;img&apos;);i.src=this.src;i.style=&apos;max-width:95%;max-height:95%;&apos;;o.appendChild(i);document.body.appendChild(o);o.onclick=()=&amp;gt;o.remove();&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;hr /&gt;
&lt;h2 id=&quot;解析&quot;&gt;解析&lt;/h2&gt;

&lt;p&gt;我觉得首先看一个卦，不要着急代入用神，这也是我的老师告诉我的，人先入为主解卦会容易错误，尤其是现代社会职业，方向很多，古人所拥有的符号
记录不一定就能代表现在我们发生的事；&lt;/p&gt;

&lt;p&gt;好的，我们先看逻辑，日为卯，月为辰，还是在春天，木气还是挺旺的，卦中唯一动爻是辰-&amp;gt;巳，回头生，而且一个关键点是寅卯辰三会局，春天木气就更强了
，所以关键点就已经出来了，木很旺，火是否能抗住木的气是一个问题（也是过旺不受生），卦中的唯一动爻也是一个关键，火土都是想泄木的气，但是可能有点
弱了&lt;/p&gt;

&lt;p&gt;那么逻辑上我们已经分析完了，现在看用神，上面说了，关键点已经在木了，正好寅又是应爻，代表对方，公司，又是父母爻，你可以说对方是一个大公司，也
可以说父母爻过旺，公司是小作坊，我的取象估计是小公司，业务压力也比较大，毕竟父母爻克子孙；我这边是世爻，正好又是火，所以问题已经很明确了，我
不能得到对方的力量，因为对方的木气太强。&lt;/p&gt;

&lt;p&gt;所以断不能面试上，结果果然如此！&lt;/p&gt;

&lt;hr /&gt;

</description>
        <pubDate>Thu, 16 Apr 2026 00:00:00 +0000</pubDate>
        <link>https://seakingOne.github.io/chinese_culture/2026/04/16/%E5%85%AD%E7%88%BB%E6%B1%82%E6%B5%8B%E6%89%BE%E5%B7%A5%E4%BD%9C.html</link>
        <guid isPermaLink="true">https://seakingOne.github.io/chinese_culture/2026/04/16/%E5%85%AD%E7%88%BB%E6%B1%82%E6%B5%8B%E6%89%BE%E5%B7%A5%E4%BD%9C.html</guid>
        
        <category>周易</category>
        
        <category>玄学</category>
        
        <category>人生感悟</category>
        
        
        <category>Chinese_culture</category>
        
      </item>
    
      <item>
        <title>玄空风水案例-1</title>
        <description>&lt;p&gt;hi，大家好，前段时间帮一个朋友看了他老家的风水，房子的朝向为：乾山巽向兼辰戌 &lt;br /&gt;
玄空盘如下： &lt;br /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/assets/img/blog/zhouyi/fengshui-anli-1.png&quot; width=&quot;35%&quot; style=&quot;cursor: zoom-in;&quot; onclick=&quot;let o=document.createElement(&apos;div&apos;);o.style=&apos;position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.85);z-index:9999;display:flex;align-items:center;justify-content:center;cursor:zoom-out;&apos;;let i=document.createElement(&apos;img&apos;);i.src=this.src;i.style=&apos;max-width:95%;max-height:95%;&apos;;o.appendChild(i);document.body.appendChild(o);o.onclick=()=&amp;gt;o.remove();&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;户型图如下： &lt;br /&gt;
&lt;img src=&quot;/static/assets/img/blog/zhouyi/fengshui-anli-1-fangzi.png&quot; width=&quot;35%&quot; style=&quot;cursor: zoom-in;&quot; onclick=&quot;let o=document.createElement(&apos;div&apos;);o.style=&apos;position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.85);z-index:9999;display:flex;align-items:center;justify-content:center;cursor:zoom-out;&apos;;let i=document.createElement(&apos;img&apos;);i.src=this.src;i.style=&apos;max-width:95%;max-height:95%;&apos;;o.appendChild(i);document.body.appendChild(o);o.onclick=()=&amp;gt;o.remove();&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;hr /&gt;
&lt;h2 id=&quot;解析&quot;&gt;解析&lt;/h2&gt;

&lt;p&gt;首先我们可以看到，是八运的房子，目前房子来说，正门的气收到的是离，星为3,5，从这里就知道了，正门纳气是不好的，而且外来路也是离气，本身房子比较狭长，离气收的又比较足，所以更不好，目前2026年，离卦为飞星5，所以我在2025说：需要注意，卯月、巳月、酉月还有子月，因为这些月份流月飞星为2/9/5/2，家里容易出事，结果在卯月的下半个月，正好又是卯日，家里的老人头晕目眩，被送去医院了。2又是坤、为老母，正好又是奶奶的身体出问题！&lt;/p&gt;

&lt;p&gt;这个案例其实很好的解释了目前不好的气对于人的影响，所以风水学并不是玄学，而是自然界和天上的星对于人的磁场影响。&lt;/p&gt;

&lt;p&gt;后面从屋主这边还有反馈，说是在20年年底的时候也出现了头晕目眩的情况，其实那个时候流年飞星正好也是2，流月的飞星也是2，子月&lt;/p&gt;

&lt;hr /&gt;

</description>
        <pubDate>Tue, 31 Mar 2026 00:00:00 +0000</pubDate>
        <link>https://seakingOne.github.io/chinese_culture/2026/03/31/%E7%8E%84%E7%A9%BA%E9%A3%8E%E6%B0%B4%E6%A1%88%E4%BE%8B-1.html</link>
        <guid isPermaLink="true">https://seakingOne.github.io/chinese_culture/2026/03/31/%E7%8E%84%E7%A9%BA%E9%A3%8E%E6%B0%B4%E6%A1%88%E4%BE%8B-1.html</guid>
        
        <category>周易</category>
        
        <category>玄学</category>
        
        <category>人生感悟</category>
        
        
        <category>Chinese_culture</category>
        
      </item>
    
      <item>
        <title>（滴天髓）甲木怎么取用神</title>
        <description>&lt;p&gt;hi，大家好，甲木的用神其實未必是金，今天我們一樣用袁樹珊的案例，來講解甲木的用神該怎麼取用。&lt;/p&gt;

&lt;hr /&gt;
&lt;h2 id=&quot;前提&quot;&gt;前提&lt;/h2&gt;
&lt;p&gt;《滴天髓》的天干論裡面講到「甲木參天，脫胎要火，春不容金，秋不容土。火熾乘龍，水蕩騎虎，地潤天和，植立千古。」意思是甲木是參天的大樹，需要太陽（火）行光合作用，春天的甲木才剛冒出來，秋天的甲木已經衰敗了，都是說木的氣勢比較弱的時候，所以不能用金、土，在日主比較弱的情況下最好不要去剋洩，否則會比較辛苦、多勞少得。而火很旺的時候最好有個濕土可以吸收火的亢陽之氣，而不是用水，所以火熾乘龍的龍不見得是辰，丑也是可以的；水蕩騎虎，水勢旺盛的情況下，日主甲木至少要坐一個強根，才不會水多木漂。
所以總結就是：
第一、不要受剋太重。
第二、不能太冷也不能太熱。
—&lt;/p&gt;

&lt;hr /&gt;
&lt;p&gt;##
袁樹珊在《命理探原》中，講到甲木的第一個案例是趙厚安先生。很多人認為甲木應該要用金去剋才能成為棟樑，但《碧淵賦》裡面說的意思是，一定要木很旺才能用金，而趙厚安先生的木生在霜降之後，所以木很弱，不要以為水旺木也會跟著旺，水很旺反而會水多木漂，而且甲木也不能只用水，也要用火去照耀，所以水火都要有，而且要均衡。&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;好的，那我們進入正題，先看一下這位先生的八字
&lt;img src=&quot;/static/assets/img/blog/zhouyi/anli-1.png&quot; width=&quot;75%&quot; style=&quot;cursor: zoom-in;&quot; onclick=&quot;let o=document.createElement(&apos;div&apos;);o.style=&apos;position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.85);z-index:9999;display:flex;align-items:center;justify-content:center;cursor:zoom-out;&apos;;let i=document.createElement(&apos;img&apos;);i.src=this.src;i.style=&apos;max-width:95%;max-height:95%;&apos;;o.appendChild(i);document.body.appendChild(o);o.onclick=()=&amp;gt;o.remove();&quot; /&gt;&lt;/p&gt;

&lt;hr /&gt;
&lt;h2 id=&quot;解析&quot;&gt;解析&lt;/h2&gt;
&lt;p&gt;趙厚安八字中的水非常旺，也就是說印很旺，代表他雖然心地善良，頭腦也很聰明，但可能有好高騖遠、懶散的現象，不會想要腳踏實地的去努力，也就是所謂的水多木漂。這時候應該要給他壓力，像是食傷生財或是用財官去壓迫，逼迫他必須要為自己努力的環境下去賺錢或是去負起他該負的責任，脫離過旺的水的保護，脫離父母保護或是資源依賴，才會成長。&lt;/p&gt;

&lt;p&gt;從格局上來看，財跟印的出現，會產生財印對戰的狀況，若是不看大運流年，那這個八字應該就會是一個比較懶散、被動的人，會想要賺快錢，也就會很容易走上歪路。八字中財印對戰是我認為最差的一種格局，如何從八字中看出一個人是不是壞人，財就是一個很重要的因素，想要資源卻不想要苦幹實幹，只想用快速的方式得到，就容易走上歪路，比如笑傲江湖裡的岳不群，跟財的表現有絕對的關係。&lt;/p&gt;

&lt;p&gt;以財壞印的格局裡面最怕出現官，所以不能以金作用神，這個八字最大的病在於水過旺，需要用土來剋水，土是財，但只有求財慾望不行，所以必要有食神傷官去生財，用技術、動手能力、親力親為蹲下去好好做事，來制過旺的印，才能成為有用的人。&lt;/p&gt;

&lt;p&gt;所以必須把用神、格局搞清楚，才有辦法用另一個角度來審視古人的書，才能參透古人在書中所寫的精髓喔！&lt;/p&gt;

&lt;hr /&gt;

</description>
        <pubDate>Fri, 13 Mar 2026 00:00:00 +0000</pubDate>
        <link>https://seakingOne.github.io/chinese_culture/2026/03/13/%E6%BB%B4%E5%A4%A9%E9%AB%93-%E7%94%B2%E6%9C%A8%E6%80%8E%E4%B9%88%E5%8F%96%E7%94%A8%E7%A5%9E.html</link>
        <guid isPermaLink="true">https://seakingOne.github.io/chinese_culture/2026/03/13/%E6%BB%B4%E5%A4%A9%E9%AB%93-%E7%94%B2%E6%9C%A8%E6%80%8E%E4%B9%88%E5%8F%96%E7%94%A8%E7%A5%9E.html</guid>
        
        <category>周易</category>
        
        <category>玄学</category>
        
        <category>人生感悟</category>
        
        
        <category>Chinese_culture</category>
        
      </item>
    
      <item>
        <title>周易学习的感悟</title>
        <description>&lt;p&gt;博主24年到现在一直在研究和学习中国的传统文化，认为里面的知识体系非常的强大，本来在网络上发布所谓的国学可能会被人认为是骗子，但转念一想，国学为中国优秀的传统文化，如果可以被更多的国人所发现，其实是很好的事，所以还是分享一下个人的感悟，咱们不说具体的知识体系，就只是单纯聊聊一些有趣的事情，哈哈。&lt;/p&gt;

&lt;p&gt;1、周易是个什么东西?&lt;a href=&quot;https://zh.wikipedia.org/wiki/%E6%98%93%E7%BB%8F&quot;&gt;这是Wikipedia的解说&lt;/a&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;我们其实在读书的时候就已经接触过了很多的周易中的思想，比如&quot;天行健，君子以自强不息&quot;
但是也不单纯是这方面的文化体系，甚至对于现在社会中的AI、计算机、二进制的发展的思想都来源于周易，我们都知道，在周易的分类中产生了很多的行业，比如八字算命、卜卦、中医等等优秀的产业，首先，我个人是相信这些的，因为我也研究过不少的案例，的的确确人生是按照剧本去发展的，这些行业的扩展也可以指导我们的人生嘛，毕竟谋事在人，成事在天，所以我更觉得周易是一个完整的生态体系，用于指导人生活中的方方面面
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;2、说完了周易一些基本思想，那么我们就可以开始进入核心了，就是2个字：阴、阳&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;对啊，核心就是很简单，阴阳问题，天道、人道没有那么复杂，而现在多数都是人心复杂导致环境复杂，阳的含义：比较容易想到的象就是 男人、刚烈、太阳等等对吧，阴则反之，不管是地球还是浩瀚的宇宙不都是这几种分类吗？
古人有一句话，&quot;祸兮福所倚 福兮祸所伏&quot;，其实已经阐明了阴阳之间的关系，阳中有阴，阴中有阳，2者相互制衡，又相互依存，少了谁都不行。
如果已经明白了上面所说的思路，那么我们来看现在社会中的一些现象，比如生病的问题，现在很多人得癌症，西方医学的思路就是开刀然后吃药，但是他们忽略了一个问题，癌症是怎么来的？为什么会的？如果不改变一些生活习惯，是否还是会复发，这也就是为什么会有癌症转移的问题的核心，好，那咱们点到为止，如果我们知道了阴阳，从宏观来看，那么其实就知道了，来自于阴阳失衡，所以出现了疾病或者运势出现问题。
从这个角度来看，所以大部分人的一生都不是一成不变的，起起伏伏才是常态，也就是阴阳之间的博弈，不过也正是因为如此，人才能去学习、感悟、尽量让人生平缓一点，让阴阳平衡一些，我相信也是大部分人的一个想法。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;持续补充中….&lt;/p&gt;
</description>
        <pubDate>Sun, 10 Aug 2025 00:00:00 +0000</pubDate>
        <link>https://seakingOne.github.io/chinese_culture/2025/08/10/%E5%91%A8%E6%98%93%E5%AD%A6%E4%B9%A0%E7%9A%84%E6%84%9F%E6%82%9F.html</link>
        <guid isPermaLink="true">https://seakingOne.github.io/chinese_culture/2025/08/10/%E5%91%A8%E6%98%93%E5%AD%A6%E4%B9%A0%E7%9A%84%E6%84%9F%E6%82%9F.html</guid>
        
        <category>周易</category>
        
        <category>玄学</category>
        
        <category>人生感悟</category>
        
        
        <category>Chinese_culture</category>
        
      </item>
    
      <item>
        <title>机器学习初探二</title>
        <description>&lt;p&gt;下面我们实际来处理一个案例&amp;lt;p&amp;gt;心脏病预测&amp;lt;/p&amp;gt;&lt;br /&gt;&lt;/p&gt;
&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;#年龄：连续值&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#性别：0-女，1-男&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#胸痛类型：1-典型心绞痛，2-非典型心绞痛，3-非心绞痛，4-无症状&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#静息血压：连续值，单位mmHg&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#胆固醇：连续值，单位mg/dl&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#空腹血糖：1-大于120mg/dl，2-小于等于120mg/dl&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#静息心电图结果：0-正常，1-ST-T异常，2-可能左心室肥大&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#最大心率：连续值&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#运动性心绞痛：1-有，2-无&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#运动后的ST下降：连续值&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#峰值ST段的斜率：1-向上，2-水平，3-向下&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#主血管数量：0到3&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#地中海贫血：一种先天性贫血，3-正常，6-固定缺陷，7-可逆缺陷&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#是否患有心脏病：标签，0-否，1-是&lt;/span&gt;

import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.compose import ColumnTransformer
from sklearn.neighbors import KNeighborsClassifier
import joblib

heart_disease &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; pd.read_csv&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;heart_disease.csv&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;

heart_disease.dropna&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
heart_disease.info&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
heart_disease.head&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;#划分数据集&lt;/span&gt;
x &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; heart_disease.drop&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;是否患有心脏病&quot;&lt;/span&gt;, &lt;span class=&quot;nv&quot;&gt;axis&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#提取标签&lt;/span&gt;
y &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; heart_disease[&lt;span class=&quot;s2&quot;&gt;&quot;是否患有心脏病&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# 将数据集按7:3划分为训练数据与测试数据&lt;/span&gt;
x_train, x_test, y_train, y_test &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; train_test_split&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;x, y, &lt;span class=&quot;nv&quot;&gt;test_size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0.3, &lt;span class=&quot;nv&quot;&gt;random_state&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;100&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# 数值型特征&lt;/span&gt;
numerical_features &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;年龄&quot;&lt;/span&gt;, &lt;span class=&quot;s2&quot;&gt;&quot;静息血压&quot;&lt;/span&gt;, &lt;span class=&quot;s2&quot;&gt;&quot;胆固醇&quot;&lt;/span&gt;, &lt;span class=&quot;s2&quot;&gt;&quot;最大心率&quot;&lt;/span&gt;, &lt;span class=&quot;s2&quot;&gt;&quot;运动后的ST下降&quot;&lt;/span&gt;, &lt;span class=&quot;s2&quot;&gt;&quot;主血管数量&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# 类别型特征&lt;/span&gt;
categorical_features &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;胸痛类型&quot;&lt;/span&gt;, &lt;span class=&quot;s2&quot;&gt;&quot;静息心电图结果&quot;&lt;/span&gt;, &lt;span class=&quot;s2&quot;&gt;&quot;峰值ST段的斜率&quot;&lt;/span&gt;, &lt;span class=&quot;s2&quot;&gt;&quot;地中海贫血&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# 二元特征&lt;/span&gt;
binary_features &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;性别&quot;&lt;/span&gt;, &lt;span class=&quot;s2&quot;&gt;&quot;空腹血糖&quot;&lt;/span&gt;, &lt;span class=&quot;s2&quot;&gt;&quot;运动性心绞痛&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;

preprocessor &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; ColumnTransformer&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;transformers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=[&lt;/span&gt;
         &lt;span class=&quot;c&quot;&gt;# 对数值型特征进行标准化&lt;/span&gt;
         &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;num&quot;&lt;/span&gt;, StandardScaler&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;, numerical_features&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;,
         &lt;span class=&quot;c&quot;&gt;# 对类别型特征进行独热编码，使用drop=&quot;first&quot;避免多重共线性&lt;/span&gt;
         &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;cat&quot;&lt;/span&gt;, OneHotEncoder&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;drop&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;first&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;, categorical_features&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;,
         &lt;span class=&quot;c&quot;&gt;# 二元特征不进行处理&lt;/span&gt;
         &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;binary&quot;&lt;/span&gt;, &lt;span class=&quot;s2&quot;&gt;&quot;passthrough&quot;&lt;/span&gt;, binary_features&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;,]
&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# 执行特征转换&lt;/span&gt;
x_train &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; preprocessor.fit_transform&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;x_train&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# 计算训练集的统计信息并进行转换&lt;/span&gt;
x_test &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; preprocessor.transform&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;x_test&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# 使用训练集计算的信息对测试集进行转换&lt;/span&gt;


&lt;span class=&quot;c&quot;&gt;# 使用K近邻分类模型，K=3&lt;/span&gt;
knn &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; KNeighborsClassifier&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;n_neighbors&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;3&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# 模型训练&lt;/span&gt;
knn.fit&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;x_train, y_train&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# 模型评估，计算准确率&lt;/span&gt;
score &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; knn.score&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;x_test, y_test&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
print&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;score&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;

joblib.dump&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;knn, &lt;span class=&quot;s2&quot;&gt;&quot;knn_heart_disease&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# 加载模型&lt;/span&gt;
knn_loaded &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; joblib.load&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;knn_heart_disease&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# 预测&lt;/span&gt;
y_pred &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; knn_loaded.predict&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;x_test[10:11]&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# 打印真实值与预测值&lt;/span&gt;
print&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;y_test.iloc[10], y_pred&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

</description>
        <pubDate>Sun, 06 Apr 2025 00:00:00 +0000</pubDate>
        <link>https://seakingOne.github.io/java/2025/04/06/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%88%9D%E6%8E%A2%E4%BA%8C.html</link>
        <guid isPermaLink="true">https://seakingOne.github.io/java/2025/04/06/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%88%9D%E6%8E%A2%E4%BA%8C.html</guid>
        
        <category>Java</category>
        
        
        <category>Java</category>
        
      </item>
    
      <item>
        <title>机器学习初探</title>
        <description>&lt;p&gt;机器学习建模流程&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;数据驱动：监督学习;非监督学习&lt;br /&gt;
1、首先做数据处理，清洗；2、数据的特征工程，选择算法；3、模型训练，模型评估；4、模型优化&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;特征工程重点&quot;&gt;特征工程(重点)&lt;/h2&gt;
&lt;blockquote&gt;
  &lt;p&gt;特征工程（Feature Engineering）是机器学习过程中非常重要的一步，指的是通过对原始数据的处理、转换和构造，生成新的特征或选择有效的特征，从而提高模型的性能。简单来说，特征工程是将原始数据转换为可以更好地表示问题的特征形式，帮助模型更好地理解和学习数据中的规律。优秀的特征工程可以显著提高模型的表现；反之，忽视特征工程可能导致模型性能欠佳&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;常用的方法：&lt;/p&gt;

&lt;p&gt;1）低方差过滤法&lt;/p&gt;

&lt;p&gt;对于特征的选择，可以直接基于方差来判断，这是最简单的。低方差的特征意味着该特征的所有样本值几乎相同，对预测影响极小，可以将其去掉&lt;/p&gt;
&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;from sklearn.feature_selection import VarianceThreshold

&lt;span class=&quot;c&quot;&gt;# 低方差过滤：删除方差低于 0.01 的特征&lt;/span&gt;
var_thresh &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; VarianceThreshold&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;threshold&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0.01&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
X_filtered &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; var_thresh.fit_transform&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;X&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;2）相关系数法&lt;/p&gt;

&lt;p&gt;通过计算特征与目标变量或特征之间的相关性，筛选出高相关性特征（与目标相关）或剔除冗余特征（特征间高度相关）。&lt;/p&gt;
&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;#例如，现有一数据集包括不同渠道广告投放金额与销售额。&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#使用pandas.DataFrame.corrwith(method=&quot;pearson&quot;)计算各个特征与标签间的皮尔逊相关系数。&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#正相关：值接近1，说明特征随目标变量增加而增加。&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#负相关：值接近-1，说明特征随目标变量增加而减少。&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#无关：值接近0，说明特征和目标变量无明显关系。&lt;/span&gt;
import pandas as pd

advertising &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; pd.read_csv&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;data/advertising.csv&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
advertising.drop&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;advertising.columns[0], &lt;span class=&quot;nv&quot;&gt;axis&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1, &lt;span class=&quot;nv&quot;&gt;inplace&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;True&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
advertising.dropna&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;inplace&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;True&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
X &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; advertising.drop&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Sales&quot;&lt;/span&gt;, &lt;span class=&quot;nv&quot;&gt;axis&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
y &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; advertising[&lt;span class=&quot;s2&quot;&gt;&quot;Sales&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# 计算皮尔逊相关系数&lt;/span&gt;
print&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;X.corrwith&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;y, &lt;span class=&quot;nv&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;pearson&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# TV   0.782224&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# Radio 0.576223&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# Newspaper 0.228299&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# dtype: float64&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;斯皮尔曼相关系数&lt;/p&gt;

&lt;p&gt;斯皮尔曼相关系数（Spearman’s Rank Correlation Coefficient）的定义是等级变量之间的皮尔逊相关系数。用于衡量两个变量之间的单调关系，即当一个变量增加时，另一个变量是否总是增加或减少（不要求是线性关系）。适用于非线性关系或数据不符合正态分布的情况&lt;/p&gt;
&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;#斯皮尔曼相关系数的取值范围为：&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#：完全正相关（一个变量增加，另一个变量也总是增加）。&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#：完全负相关（一个变量增加，另一个变量总是减少）。&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#：无相关性。&lt;/span&gt;

import pandas as pd

&lt;span class=&quot;c&quot;&gt;#例如，现有一组每周学习时长与数学考试成绩的数据&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# 每周学习时长&lt;/span&gt;
X &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[[&lt;/span&gt;5], &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;8], &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;10], &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;12], &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;15], &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;3], &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;7], &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;9], &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;14], &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;6]]
&lt;span class=&quot;c&quot;&gt;# 数学考试成绩&lt;/span&gt;
y &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;55, 65, 70, 75, 85, 50, 60, 72, 80, 58]
&lt;span class=&quot;c&quot;&gt;# 计算斯皮尔曼相关系数&lt;/span&gt;
X &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; pd.DataFrame&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;X&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
y &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; pd.Series&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;y&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
print&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;X.corrwith&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;y, &lt;span class=&quot;nv&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;spearman&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# 0.987879&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;3）主成分分析（PCA）&lt;/p&gt;

&lt;p&gt;主成分分析（Principal Component Analysis，PCA）是一种常用的降维技术，通过线性变换将高维数据投影到低维空间，同时保留数据的主要变化模式。&lt;/p&gt;
&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;import numpy as np
import matplotlib.pyplot as plt
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler

n_samples &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 1000
&lt;span class=&quot;c&quot;&gt;# 第1个主成分方向&lt;/span&gt;
component1 &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; np.random.normal&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;0, 1, n_samples&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# 第2个主成分方向&lt;/span&gt;
component2 &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; np.random.normal&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;0, 0.2, n_samples&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# 第3个方向（噪声，方差较小）&lt;/span&gt;
noise &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; np.random.normal&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;0, 0.1, n_samples&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# 构造3维数据&lt;/span&gt;
X &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; np.vstack&lt;span class=&quot;o&quot;&gt;([&lt;/span&gt;component1 - component2, component1 + component2, component2 + noise]&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;.T

&lt;span class=&quot;c&quot;&gt;# 标准化&lt;/span&gt;
scaler &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; StandardScaler&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
X_standardized &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; scaler.fit_transform&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;X&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# 应用PCA，将3维数据降维到2维&lt;/span&gt;
pca &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; PCA&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;n_components&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;2&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
X_pca &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; pca.fit_transform&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;X_standardized&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# 可视化&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# 转换前的3维数据可视化&lt;/span&gt;
fig &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; plt.figure&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;figsize&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=(&lt;/span&gt;12, 4&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;
ax1 &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; fig.add_subplot&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;121, &lt;span class=&quot;nv&quot;&gt;projection&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;3d&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
ax1.scatter&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;X[:, 0], X[:, 1], X[:, 2], &lt;span class=&quot;nv&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;g&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
ax1.set_title&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Before PCA (3D)&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
ax1.set_xlabel&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Feature 1&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
ax1.set_ylabel&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Feature 2&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
ax1.set_zlabel&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Feature 3&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# 转换后的2维数据可视化&lt;/span&gt;
ax2 &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; fig.add_subplot&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;122&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
ax2.scatter&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;X_pca[:, 0], X_pca[:, 1], &lt;span class=&quot;nv&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;g&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
ax2.set_title&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;After PCA (2D)&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
ax2.set_xlabel&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Principal Component 1&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
ax2.set_ylabel&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Principal Component 2&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
plt.show&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;模型评估和模型选择重点&quot;&gt;模型评估和模型选择(重点)&lt;/h2&gt;

&lt;p&gt;1）损失函数（值越小效果越好）&lt;/p&gt;

&lt;p&gt;对于模型一次预测结果的好坏，需要有一个度量标准。对于监督学习而言，给定一个输入X，选取的模型就相当于一个“决策函数”f，它可以输出一个预测结果f(X)，而真实的结果（标签）记为Y。f(X) 和Y之间可能会有偏差，我们就用一个损失函数（loss function）来度量预测偏差的程度，记作 L(Y,f(X))。&lt;/p&gt;

&lt;p&gt;0-1损失函数&lt;/p&gt;

&lt;p&gt;平方损失函数&lt;/p&gt;

&lt;p&gt;绝对损失函数&lt;/p&gt;

&lt;p&gt;对数似然损失函数&lt;/p&gt;

&lt;p&gt;2）经验误差与泛化误差&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;根据选取的损失函数，就可以计算出模型f(X)在训练集上的平均误差，称为训练误差，也被称作 经验误差（empirical error） 或 经验风险（empirical risk）&lt;br /&gt;
类似地，在测试数据集上平均误差，被称为测试误差或者 泛化误差（generalization error）。&lt;br /&gt;
一般情况下对模型评估的策略，就是考察经验误差；当经验风险最小时，就认为取到了最优的模型。这种策略被称为 经验风险最小化（empirical risk minimization，ERM）&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;3)欠拟合与过拟合&lt;/p&gt;

&lt;p&gt;拟合（Fitting）是指机器学习模型在训练数据上学习到规律并生成预测结果的过程。理想情况下，模型能够准确地捕捉训练数据的模式，并且在未见过的新数据（测试数据）上也有良好的表现；即模型具有良好的 泛化能力。&lt;/p&gt;

&lt;p&gt;欠拟合（Underfitting）：是指模型在训练数据上表现不佳，无法很好地捕捉数据中的规律。这样的模型不仅在训练集上表现不好，在测试集上也同样表现差。&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;产生原因：
模型复杂度不足：模型过于简单，无法捕捉数据中的复杂关系。 特征不足：输入特征不充分，或者特征选择不恰当，导致模型无法充分学习数据的模式。 训练不充分：训练过程中迭代次数太少，模型没有足够的时间学习数据的规律。
过强的正则化：正则化项设置过大，强制模型过于简单，导致模型无法充分拟合数据。&lt;br /&gt;
解决办法：
增加模型复杂度：选择更复杂的模型。
增加特征或改进特征工程：添加更多的特征或通过特征工程来创造更有信息量的特征。
增加训练时间：增加训练的迭代次数，让模型有更多机会去学习。
减少正则化强度：如果使用了正则化，尝试减小正则化的权重，以让模型更灵活。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;过拟合（Overfitting）：是指模型在训练数据上表现得很好，但在测试数据或新数据上表现较差的情况。过拟合的模型对训练数据中的噪声或细节过度敏感，把训练样本自身的一些特点当作了所有潜在样本都会具有的一般性质，从而失去了泛化能力。&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;产生原因：
模型复杂度过高：模型过于复杂，参数太多。
训练数据不足：数据集太小，模型能记住训练数据的细节，但无法泛化到新数据。
特征过多：特征太多，模型可能会“记住”数据中的噪声，而不是学到真正的规律。
训练过长：训练时间过长，导致模型学习到训练数据中的噪声，而非数据的真正规律。&lt;br /&gt;
解决办法：
减少模型复杂度：降低模型的参数数量、使用简化的模型或降维来减小模型复杂度。
增加训练数据：收集更多数据，或通过数据增强来增加训练数据的多样性。
使用正则化：引入L1、L2正则化，避免过度拟合训练数据。
交叉验证：使用交叉验证技术评估模型在不同数据集上的表现，以减少过拟合的风险。
早停：训练时，当模型的验证损失不再下降时，提前停止训练，避免过度拟合训练集。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;br /&gt;下面是代码模拟&lt;br /&gt;&lt;/p&gt;
&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;#生成随机数,在-3，3中模拟 拟合sin(x)&lt;/span&gt;
import numpy as np
import matplotlib.pyplot as plt &lt;span class=&quot;c&quot;&gt;#绘图&lt;/span&gt;
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error

plt.rcParams[&lt;span class=&quot;s2&quot;&gt;&quot;font.sans-serif&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;KaiTi&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;
plt.rcParams[&lt;span class=&quot;s2&quot;&gt;&quot;axes.unicode_minus&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; False

def polynomial&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;x, degree&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;:
    &lt;span class=&quot;s2&quot;&gt;&quot;&quot;&quot;构成多项式，返回 [x^1,x^2,x^3,...,x^n]&quot;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return &lt;/span&gt;np.hstack&lt;span class=&quot;o&quot;&gt;([&lt;/span&gt;x&lt;span class=&quot;k&quot;&gt;**&lt;/span&gt;i &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;i &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;range&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;1, degree + 1&lt;span class=&quot;o&quot;&gt;)])&lt;/span&gt;

X &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; np.linspace&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-3&lt;/span&gt;, 3, 300&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;.reshape&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt;, 1&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
y &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; np.sin&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;X&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; + np.random.uniform&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-0&lt;/span&gt;.5, 0.5, 300&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;.reshape&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt;, 1&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
fig, ax &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; plt.subplots&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;1, 3, &lt;span class=&quot;nv&quot;&gt;figsize&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=(&lt;/span&gt;15, 4&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;
ax[0].plot&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;X, y, &lt;span class=&quot;s2&quot;&gt;&quot;yo&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
ax[1].plot&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;X, y, &lt;span class=&quot;s2&quot;&gt;&quot;yo&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
ax[2].plot&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;X, y, &lt;span class=&quot;s2&quot;&gt;&quot;yo&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# 划分训练集和测试集&lt;/span&gt;
x_train, x_test, y_train, y_test &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; train_test_split&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;X, y, &lt;span class=&quot;nv&quot;&gt;test_size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0.2, &lt;span class=&quot;nv&quot;&gt;random_state&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;42&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# 创建线性回归模型&lt;/span&gt;
model &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; LinearRegression&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# 欠拟合&lt;/span&gt;
x_train1 &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; x_train
x_test1 &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; x_test
model.fit&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;x_train1, y_train&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# 模型训练&lt;/span&gt;
y_pred1 &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; model.predict&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;x_test1&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# 预测&lt;/span&gt;
ax[0].plot&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;np.array&lt;span class=&quot;o&quot;&gt;([[&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-3&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;, &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;3]]&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;, model.predict&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;np.array&lt;span class=&quot;o&quot;&gt;([[&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-3&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;, &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;3]]&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;, &lt;span class=&quot;s2&quot;&gt;&quot;c&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# 绘制曲线&lt;/span&gt;
ax[0].text&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-3&lt;/span&gt;, 1, f&lt;span class=&quot;s2&quot;&gt;&quot;测试集均方误差：{mean_squared_error(y_test, y_pred1):.4f}&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
ax[0].text&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-3&lt;/span&gt;, 1.3, f&lt;span class=&quot;s2&quot;&gt;&quot;训练集均方误差：{mean_squared_error(y_train, model.predict(x_train1)):.4f}&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# 恰好拟合&lt;/span&gt;
x_train2 &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; polynomial&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;x_train, 5&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
x_test2 &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; polynomial&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;x_test, 5&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
model.fit&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;x_train2, y_train&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# 模型训练&lt;/span&gt;
y_pred2 &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; model.predict&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;x_test2&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# 预测&lt;/span&gt;
ax[1].plot&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;X, model.predict&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;polynomial&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;X, 5&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;, &lt;span class=&quot;s2&quot;&gt;&quot;k&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# 绘制曲线&lt;/span&gt;
ax[1].text&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-3&lt;/span&gt;, 1, f&lt;span class=&quot;s2&quot;&gt;&quot;测试集均方误差：{mean_squared_error(y_test, y_pred2):.4f}&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
ax[1].text&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-3&lt;/span&gt;, 1.3, f&lt;span class=&quot;s2&quot;&gt;&quot;训练集均方误差：{mean_squared_error(y_train, model.predict(x_train2)):.4f}&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# 过拟合&lt;/span&gt;
x_train3 &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; polynomial&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;x_train, 20&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
x_test3 &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; polynomial&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;x_test, 20&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;

model.fit&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;x_train3, y_train&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# 模型训练&lt;/span&gt;
y_pred3 &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; model.predict&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;x_test3&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# 预测&lt;/span&gt;
ax[2].plot&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;X, model.predict&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;polynomial&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;X, 20&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;, &lt;span class=&quot;s2&quot;&gt;&quot;r&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# 绘制曲线&lt;/span&gt;
ax[2].text&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-3&lt;/span&gt;, 1, f&lt;span class=&quot;s2&quot;&gt;&quot;测试集均方误差：{mean_squared_error(y_test, y_pred3):.4f}&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
ax[2].text&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-3&lt;/span&gt;, 1.3, f&lt;span class=&quot;s2&quot;&gt;&quot;训练集均方误差：{mean_squared_error(y_train, model.predict(x_train3)):.4f}&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
plt.show&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;5） 正则化&lt;/p&gt;

&lt;p&gt;正则化（Regularization）是一种在训练机器学习模型时，在损失函数中添加额外项，来惩罚过大的参数，进而限制模型复杂度、避免过拟合，提高模型泛化能力的技术
&lt;br /&gt;下面是代码模拟&lt;br /&gt;&lt;/p&gt;
&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression, Lasso, Ridge
from sklearn.metrics import mean_squared_error

plt.rcParams[&lt;span class=&quot;s2&quot;&gt;&quot;font.sans-serif&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;KaiTi&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;
plt.rcParams[&lt;span class=&quot;s2&quot;&gt;&quot;axes.unicode_minus&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; False

def polynomial&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;x, degree&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;:
     &lt;span class=&quot;s2&quot;&gt;&quot;&quot;&quot;构成多项式，返回 [x^1,x^2,x^3,...,x^n]&quot;&quot;&quot;&lt;/span&gt;
     &lt;span class=&quot;k&quot;&gt;return &lt;/span&gt;np.hstack&lt;span class=&quot;o&quot;&gt;([&lt;/span&gt;x&lt;span class=&quot;k&quot;&gt;**&lt;/span&gt;i &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;i &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;range&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;1, degree + 1&lt;span class=&quot;o&quot;&gt;)])&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# 生成随机数据&lt;/span&gt;
X &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; np.linspace&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-3&lt;/span&gt;, 3, 300&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;.reshape&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt;, 1&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
y &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; np.sin&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;X&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; + np.random.uniform&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-0&lt;/span&gt;.5, 0.5, X.size&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;.reshape&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt;, 1&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
fig, ax &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; plt.subplots&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;2, 3, &lt;span class=&quot;nv&quot;&gt;figsize&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=(&lt;/span&gt;15, 8&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;
ax[0, 0].plot&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;X, y, &lt;span class=&quot;s2&quot;&gt;&quot;yo&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
ax[0, 1].plot&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;X, y, &lt;span class=&quot;s2&quot;&gt;&quot;yo&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
ax[0, 2].plot&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;X, y, &lt;span class=&quot;s2&quot;&gt;&quot;yo&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# 划分训练集和测试集&lt;/span&gt;
x_train, x_test, y_train, y_test &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; train_test_split&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;X, y, &lt;span class=&quot;nv&quot;&gt;test_size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0.2, &lt;span class=&quot;nv&quot;&gt;random_state&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;42&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
x_train1 &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; polynomial&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;x_train, 20&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
x_test1 &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; polynomial&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;x_test, 20&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# 拟合&lt;/span&gt;
model &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; LinearRegression&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
model.fit&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;x_train1, y_train&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# 模型训练&lt;/span&gt;
y_pred3 &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; model.predict&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;x_test1&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# 预测&lt;/span&gt;
ax[0, 0].plot&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;X, model.predict&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;polynomial&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;X, 20&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;, &lt;span class=&quot;s2&quot;&gt;&quot;r&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# 绘制曲线&lt;/span&gt;
ax[0, 0].text&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-3&lt;/span&gt;, 1, f&lt;span class=&quot;s2&quot;&gt;&quot;测试集均方误差：{mean_squared_error(y_test, y_pred3):.4f}&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
ax[1, 0].bar&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;np.arange&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;20&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;, model.coef_.reshape&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# 绘制所有系数&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# L1正则化-Lasso回归&lt;/span&gt;
lasso &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; Lasso&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;alpha&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0.01&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
lasso.fit&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;x_train1, y_train&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# 模型训练&lt;/span&gt;
y_pred3 &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; lasso.predict&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;x_test1&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# 预测&lt;/span&gt;
ax[0, 1].plot&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;X, lasso.predict&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;polynomial&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;X, 20&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;, &lt;span class=&quot;s2&quot;&gt;&quot;r&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# 绘制曲线&lt;/span&gt;
ax[0, 1].text&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-3&lt;/span&gt;, 1, f&lt;span class=&quot;s2&quot;&gt;&quot;测试集均方误差：{mean_squared_error(y_test, y_pred3):.4f}&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
ax[0, 1].text&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-3&lt;/span&gt;, 1.2, &lt;span class=&quot;s2&quot;&gt;&quot;Lasso回归&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
ax[1, 1].bar&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;np.arange&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;20&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;, lasso.coef_&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# 绘制所有系数&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# L2正则化-岭回归&lt;/span&gt;
ridge &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; Ridge&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;alpha&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
ridge.fit&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;x_train1, y_train&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# 模型训练&lt;/span&gt;
y_pred3 &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; ridge.predict&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;x_test1&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# 预测&lt;/span&gt;
ax[0, 2].plot&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;X, ridge.predict&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;polynomial&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;X, 20&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;, &lt;span class=&quot;s2&quot;&gt;&quot;r&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# 绘制曲线&lt;/span&gt;
ax[0, 2].text&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-3&lt;/span&gt;, 1, f&lt;span class=&quot;s2&quot;&gt;&quot;测试集均方误差：{mean_squared_error(y_test, y_pred3):.4f}&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
ax[0, 2].text&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-3&lt;/span&gt;, 1.2, &lt;span class=&quot;s2&quot;&gt;&quot;岭回归&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
ax[1, 2].bar&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;np.arange&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;20&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;, ridge.coef_&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# 绘制所有系数&lt;/span&gt;
plt.show&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;KNN算法&lt;/p&gt;

&lt;p&gt;K近邻算法（K-Nearest Neighbors，KNN）是一种基本的分类与回归方法，属于监督学习算法。其核心思想是通过计算给定样本与数据集中所有样本的距离，找到距离最近的K个样本，然后根据这K个样本的类别或值来预测当前样本的类别或值。&lt;br /&gt;
计算距离：计算待分类样本与训练集中每个样本的距离。距离度量方法：选择合适的距离度量方法，常见的有欧氏距离、曼哈顿距离、切比雪夫距离、闵可夫斯基距离等。&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;from sklearn.neighbors import KNeighborsClassifier

knn &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; KNeighborsClassifier&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;n_neighbors&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;2&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# KNN分类模型，K值为2&lt;/span&gt;
X &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[[&lt;/span&gt;2, 1], &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;3, 1], &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;1, 4], &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;2, 6]] &lt;span class=&quot;c&quot;&gt;# 特征&lt;/span&gt;
y &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;0, 0, 1, 1] &lt;span class=&quot;c&quot;&gt;# 标签&lt;/span&gt;
knn.fit&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;X, y&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# 模型训练&lt;/span&gt;
knn.predict&lt;span class=&quot;o&quot;&gt;([[&lt;/span&gt;4, 9]]&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# 预测&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</description>
        <pubDate>Sat, 05 Apr 2025 00:00:00 +0000</pubDate>
        <link>https://seakingOne.github.io/java/2025/04/05/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%88%9D%E6%8E%A2.html</link>
        <guid isPermaLink="true">https://seakingOne.github.io/java/2025/04/05/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%88%9D%E6%8E%A2.html</guid>
        
        <category>Java</category>
        
        
        <category>Java</category>
        
      </item>
    
      <item>
        <title>JVM之反射实现原理</title>
        <description>&lt;h2 id=&quot;反射调用的实现&quot;&gt;反射调用的实现&lt;/h2&gt;

&lt;blockquote&gt;
  &lt;p&gt;反射调用，也就是 Method.invoke&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;具体demo如下：&lt;/p&gt;
&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
public final class Method extends Executable &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  ...
  public Object invoke&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;Object obj, Object... args&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; throws ... &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    ... // 权限检查
    MethodAccessor ma &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; methodAccessor&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;ma &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; null&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      ma &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; acquireMethodAccessor&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return &lt;/span&gt;ma.invoke&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;obj, args&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;Method.invoke 的源代码，它实际上委派给 MethodAccessor 来处理。MethodAccessor 是一个接口，它有两个已有的具体实现：一个通过本地方法来实现反射调用，另一个则使用了委派模式。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;每个 Method 实例的第一次反射调用都会生成一个委派实现，它所委派的具体实现便是一个本地实现。本地实现非常容易理解。当进入了 Java 虚拟机内部之后，我们便拥有了 Method 实例所指向方法的具体地址。这时候，反射调用无非就是将传入的参数准备好，然后调用进入目标方法。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// v0版本
import java.lang.reflect.Method&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

public class Test &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  public static void target&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;int i&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    new Exception&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;#&quot;&lt;/span&gt; + i&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;.printStackTrace&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

  public static void main&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;String[] args&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; throws Exception &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    Class&amp;lt;?&amp;gt; klass &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; Class.forName&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Test&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    Method method &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; klass.getMethod&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;target&quot;&lt;/span&gt;, int.class&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    method.invoke&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;null, 0&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# 不同版本的输出略有不同，这里我使用了Java 10。&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;java Test
java.lang.Exception: &lt;span class=&quot;c&quot;&gt;#0&lt;/span&gt;
        at Test.target&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;Test.java:5&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl .invoke0&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;Native Method&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
 a      t java.base/jdk.internal.reflect.NativeMethodAccessorImpl. .invoke&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;NativeMethodAccessorImpl.java:62&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
 t       java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.i .invoke&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;DelegatingMethodAccessorImpl.java:43&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
        java.base/java.lang.reflect.Method.invoke&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;Method.java:564&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
  t        Test.main&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;Test.java:131

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;blockquote&gt;
  &lt;p&gt;可以看到，反射调用先是调用了 Method.invoke，然后进入委派实现（DelegatingMethodAccessorImpl），再然后进入本地实现（NativeMethodAccessorImpl），最后到达目标方法。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;为什么反射调用还要采取委派实现作为中间层
Java 的反射调用机制还设立了另一种动态生成字节码的实现（下称动态实现），直接使用 invoke 指令来调用目标方法。之所以采用委派实现，便是为了能够在本地实现以及动态实现中切换。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// 动态实现的伪代码，这里只列举了关键的调用逻辑，其实它还包括调用者检测、参数检测的字节码。
package jdk.internal.reflect&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

public class GeneratedMethodAccessor1 extends ... &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  @Overrides    
  public Object invoke&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;Object obj, Object[] args&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; throws ... &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    Test.target&lt;span class=&quot;o&quot;&gt;((&lt;/span&gt;int&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; args[0]&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return &lt;/span&gt;null&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;动态实现和本地实现相比，其运行效率要快上20倍。这是因为动态实现无需经过 Java 到 C++ 再到 Java 的切换，但由于生成字节码十分耗时，仅调用一次的话，反而是本地实现要快上3到4倍。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;考虑到许多反射调用仅会执行一次，Java 虚拟机设置了一个阈值 15（可以通过 -Dsun.reflect.inflationThreshold= 来调整），当某个反射调用的调用次数在 15 之下时，采用本地实现；当达到 15 时，便开始动态生成字节码，并将委派实现的委派对象切换至动态实现，&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;hibenate中放出来的接口为（可以打印原生SQL）：
public class MyStatementInspector implements StatementInspector &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    @Override
    public String inspect&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;String sql&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return &lt;/span&gt;sql&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
yaml文件中配置：spring.jpa.properties.hibernate.session_factory.statement_inspector&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;com.microfocus.sam.dao.repository.MyStatementInspector
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</description>
        <pubDate>Wed, 18 Oct 2023 00:00:00 +0000</pubDate>
        <link>https://seakingOne.github.io/article/2023/10/18/JVM%E4%B9%8B%E5%8F%8D%E5%B0%84%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86.html</link>
        <guid isPermaLink="true">https://seakingOne.github.io/article/2023/10/18/JVM%E4%B9%8B%E5%8F%8D%E5%B0%84%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86.html</guid>
        
        <category>Java</category>
        
        <category>spring</category>
        
        
        <category>Article</category>
        
      </item>
    
      <item>
        <title>Spring源码解析之类型转换</title>
        <description>&lt;h2 id=&quot;java-提供的转换器-propertyeditor接口propertyeditorsupport可以继承的类&quot;&gt;Java 提供的转换器 PropertyEditor（接口）、PropertyEditorSupport（可以继承的类）&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;#setAsText传入需要转换的string，重写PropertyEditorSupport方法改变类型转换&lt;/li&gt;
  &lt;li&gt;#getValue获取转换后的数据&lt;/li&gt;
  &lt;li&gt;局限性：只支持从string向其他类型做转换。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;具体demo如下：&lt;/p&gt;
&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
public class StringToIntPropertiesEditor extends PropertyEditorSupport &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    @Override
    public void setAsText&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;String text&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; throws IllegalArgumentException &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        this.setValue&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;Integer.valueOf&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;text&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

	 public static void main&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;String[] args&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        StringToIntPropertiesEditor stringToIntPropertiesEditor &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; new StringToIntPropertiesEditor&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        stringToIntPropertiesEditor.setAsText&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;123&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        Object value &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; stringToIntPropertiesEditor.getValue&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        System.out.println&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;value.getClass&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;.getTypeName&lt;span class=&quot;o&quot;&gt;())&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;spring提供的convert&quot;&gt;Spring提供的Convert&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;Converter&amp;lt;S, T&amp;gt;最基本的接口，这个接口的实现是线程安全的，可以共享。实现了所有的基本类型转换，常见的比如date/string/int/long …&lt;/li&gt;
  &lt;li&gt;额外的接口ConditionalConverter，允许Converter、GenericConverter或ConverterFactory基于源和目标类型描述符的属性有条件地执行。&lt;/li&gt;
  &lt;li&gt;GenericConverterGenericConverter，用于在两个或多个类型之间进行转换的通用转换器接口。这是转换器中最灵活的SPI接口，但也是最复杂的。它是灵活的，因为GenericConverter可以支持多个源/目标类型对之间的转换(参见getConvertibleTypes())。此外，GenericConverter实现可以在类型转换过程中访问源/目标字段上下文。这允许解析源和目标字段元数据，如注释和泛型信息，这些元数据可用于影响转换逻辑。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;写一个demo如下：把properties转换为string&lt;/p&gt;
&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;@Component&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;conversionService&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
public class PropertiesToStringConvert implements ConditionalGenericConverter &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    @Override
    public boolean matches&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;TypeDescriptor sourceType, TypeDescriptor targetType&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return &lt;/span&gt;Properties.class.equals&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;sourceType.getType&lt;span class=&quot;o&quot;&gt;())&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; String.class.equals&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;targetType.getType&lt;span class=&quot;o&quot;&gt;())&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    @Override
    public Set&amp;lt;ConvertiblePair&amp;gt; getConvertibleTypes&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return &lt;/span&gt;Collections.singleton&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;new ConvertiblePair&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;Properties.class, String.class&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    @Override
    public Object convert&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;Object &lt;span class=&quot;nb&quot;&gt;source&lt;/span&gt;, TypeDescriptor sourceType, TypeDescriptor targetType&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        Properties properties &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;Properties&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return &lt;/span&gt;properties.toString&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; &amp;lt;util:properties &lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;context&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;
        &amp;lt;prop &lt;span class=&quot;nv&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;1&amp;lt;/prop&amp;gt;
        &amp;lt;prop &lt;span class=&quot;nv&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;mercyblitz&amp;lt;/prop&amp;gt;
 &amp;lt;/util:properties&amp;gt;
 &amp;lt;bean &lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;user&quot;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;xxx.UserInfo&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;
        &amp;lt;property &lt;span class=&quot;nv&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;id&quot;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;1&quot;&lt;/span&gt;/&amp;gt;
        &amp;lt;property &lt;span class=&quot;nv&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;name&quot;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;test&quot;&lt;/span&gt;/&amp;gt;
        &amp;lt;property &lt;span class=&quot;nv&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;context&quot;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;ref&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;context&quot;&lt;/span&gt;/&amp;gt;
    &amp;lt;/bean&amp;gt;

    &amp;lt;bean &lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;convert&quot;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;com.ynhuang.thinking.databinder.convert.PropertiesToStringConvert&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;

    &amp;lt;/bean&amp;gt;

    &amp;lt;bean &lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;conversionService&quot;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;org.springframework.context.support.ConversionServiceFactoryBean&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;
        &amp;lt;property &lt;span class=&quot;nv&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;converters&quot;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;ref&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;convert&quot;&lt;/span&gt; /&amp;gt;
    &amp;lt;/bean&amp;gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;当然在spring中配置convertService除了ConversionServiceFactoryBean，还可以使用ConversionService，ConversionService在beanfactory refresh阶段就已经开始加载了，如果不指定，spring只会初始化默认的转换器，则新加的无效。&lt;/p&gt;

&lt;p&gt;最后，只有转换器处理完成之后，才会完成整个的数据绑定，也就是在spring bean实例化完成阶段。&lt;/p&gt;
</description>
        <pubDate>Thu, 08 Jun 2023 00:00:00 +0000</pubDate>
        <link>https://seakingOne.github.io/article/2023/06/08/Spring%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90%E4%B9%8B%E7%B1%BB%E5%9E%8B%E8%BD%AC%E6%8D%A2.html</link>
        <guid isPermaLink="true">https://seakingOne.github.io/article/2023/06/08/Spring%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90%E4%B9%8B%E7%B1%BB%E5%9E%8B%E8%BD%AC%E6%8D%A2.html</guid>
        
        <category>Java</category>
        
        <category>spring</category>
        
        
        <category>Article</category>
        
      </item>
    
      <item>
        <title>Spring源码解析之validate，valid</title>
        <description>&lt;h2 id=&quot;validator&quot;&gt;Validator&lt;/h2&gt;

&lt;blockquote&gt;
  &lt;p&gt;Spring 具有一个Validator可用于验证对象的接口。该 Validator接口通过使用一个Errors对象来工作，以便在验证时，验证器可以向该Errors对象报告验证失败。
但是由于spring的Validator原生接口与校验密切整合，所以不太合适面向bean类型的校验&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;比如定义基本类如下：&lt;/p&gt;
&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
public class Person &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

	private String name&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
	private int age&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

	// the usual getters and setters...
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;实现接口：&lt;/p&gt;
&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;public class PersonValidator implements Validator &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

	/&lt;span class=&quot;k&quot;&gt;**&lt;/span&gt;
	 &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; This Validator validates only Person instances
	 &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;/
	public boolean supports&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;Class clazz&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;return &lt;/span&gt;Person.class.equals&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;clazz&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

	public void validate&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;Object obj, Errors e&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
		ValidationUtils.rejectIfEmpty&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;e, &lt;span class=&quot;s2&quot;&gt;&quot;name&quot;&lt;/span&gt;, &lt;span class=&quot;s2&quot;&gt;&quot;name.empty&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
		Person p &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;Person&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; obj&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;p.getAge&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &amp;lt; 0&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
			e.rejectValue&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;age&quot;&lt;/span&gt;, &lt;span class=&quot;s2&quot;&gt;&quot;negativevalue&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;p.getAge&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; 110&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
			e.rejectValue&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;age&quot;&lt;/span&gt;, &lt;span class=&quot;s2&quot;&gt;&quot;too.darn.old&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;验证错误会报告给Errors传递给验证器的对象。问题很明显，其实不太利用扩展，validate逻辑和bean本身的逻辑已经绑定在一起了。&lt;/p&gt;

&lt;h2 id=&quot;spring校验的救赎&quot;&gt;spring校验的救赎&lt;/h2&gt;
&lt;blockquote&gt;
  &lt;p&gt;Spring Framework 提供对 Java Bean Validation API 的支持，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@Valid&lt;/code&gt;用于标记入参；
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LocalValidatorFactoryBean&lt;/code&gt;:符合jsr303标准处理，可以定义国际化文案；
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MethodValidationPostProcessor&lt;/code&gt;:整合到spring中，通过aop的方式，默认切入&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@Validate&lt;/code&gt;注解做aop，完整方法参数的校验。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;@Bean
public static LocalValidatorFactoryBean defaultValidator&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    LocalValidatorFactoryBean factoryBean &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; new LocalValidatorFactoryBean&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    MessageInterpolatorFactory interpolatorFactory &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; new MessageInterpolatorFactory&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    factoryBean.setMessageInterpolator&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;interpolatorFactory.getObject&lt;span class=&quot;o&quot;&gt;())&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return &lt;/span&gt;factoryBean&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

@Bean
public static MethodValidationPostProcessor methodValidationPostProcessor&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;Environment environment,
                                                                            @Lazy Validator validator, ObjectProvider&amp;lt;MethodValidationExcludeFilter&amp;gt; excludeFilters&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    FilteredMethodValidationPostProcessor processor &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; new FilteredMethodValidationPostProcessor&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;excludeFilters.orderedStream&lt;span class=&quot;o&quot;&gt;())&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    processor.setValidator&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;validator&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return &lt;/span&gt;processor&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;当然，在spring的整合，可以直接使用DataBinder和校验绑定，完成对于参数的处理。（比如springmvc实现Field校验）&lt;/p&gt;
</description>
        <pubDate>Mon, 05 Jun 2023 00:00:00 +0000</pubDate>
        <link>https://seakingOne.github.io/article/2023/06/05/Spring%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90%E4%B9%8Bvalidate-valid.html</link>
        <guid isPermaLink="true">https://seakingOne.github.io/article/2023/06/05/Spring%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90%E4%B9%8Bvalidate-valid.html</guid>
        
        <category>Java</category>
        
        <category>spring</category>
        
        
        <category>Article</category>
        
      </item>
    
      <item>
        <title>linux socket编程二之epoll</title>
        <description>&lt;h2 id=&quot;epoll&quot;&gt;epoll&lt;/h2&gt;

&lt;blockquote&gt;
  &lt;p&gt;epoll是对于传统socket编程的一种优化，由内核维护一套结构，存储所有的client fd, 如果有数据到达，用户态订阅事件，一旦有事件到达，内核监听后返回对应有事件的fd，用户态做相应的处理。
对于epoll的内核结构，epoll_create会创建一个efd, 作为内核红黑树的根节点，然后上树lfd，cfd，当有事件触发的时候，返回有事件的fd（链表结构）&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;基本demo如下：&lt;/p&gt;
&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;#include &amp;lt;sys/epoll.h&amp;gt;&lt;/span&gt;
Example &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;suggested usage
While the usage of epoll when employed as a level-triggered interface does have  the  same  semantics  as  poll&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;2&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;,  the  edge-triggered  usage
requires  more  clarification  to  avoid stalls &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;the application event loop.  In this example, listener is a nonblocking socket on which lis‐
ten&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;2&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; has been called.  The &lt;span class=&quot;k&quot;&gt;function &lt;/span&gt;do_use_fd&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; uses the new ready file descriptor &lt;span class=&quot;k&quot;&gt;until &lt;/span&gt;EAGAIN is returned by either  &lt;span class=&quot;nb&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;2&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;  or  write&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;2&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;
An  event-driven  state  machine  application  should,  after  having  received  EAGAIN,  record  its current state so that at the next call to
do_use_fd&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; it will &lt;span class=&quot;k&quot;&gt;continue &lt;/span&gt;to &lt;span class=&quot;nb&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;2&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; or write&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;2&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; from where it stopped before.

&lt;span class=&quot;c&quot;&gt;#define MAX_EVENTS 10&lt;/span&gt;
struct epoll_event ev, events[MAX_EVENTS]&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
int listen_sock, conn_sock, nfds, epollfd&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

/&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; Code to &lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;up listening socket, &lt;span class=&quot;s1&quot;&gt;&apos;listen_sock&apos;&lt;/span&gt;,
    &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;socket&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;, &lt;span class=&quot;nb&quot;&gt;bind&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;, listen&lt;span class=&quot;o&quot;&gt;())&lt;/span&gt; omitted &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;/

epollfd &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; epoll_create1&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;0&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;epollfd &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    perror&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;epoll_create1&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;exit&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;EXIT_FAILURE&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

ev.events &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; EPOLLIN&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
ev.data.fd &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; listen_sock&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;epoll_ctl&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;epollfd, EPOLL_CTL_ADD, listen_sock, &amp;amp;ev&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    perror&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;epoll_ctl: listen_sock&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;exit&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;EXIT_FAILURE&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    nfds &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; epoll_wait&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;epollfd, events, MAX_EVENTS, &lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;nfds &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        perror&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;epoll_wait&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;nb&quot;&gt;exit&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;EXIT_FAILURE&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;n &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 0&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; n &amp;lt; nfds&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; ++n&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;events[n].data.fd &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; listen_sock&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            conn_sock &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; accept&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;listen_sock,
                                &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;struct sockaddr &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &amp;amp;addr, &amp;amp;addrlen&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;conn_sock &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
                perror&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;accept&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;nb&quot;&gt;exit&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;EXIT_FAILURE&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
            setnonblocking&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;conn_sock&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            ev.events &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; EPOLLIN | EPOLLET&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            ev.data.fd &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; conn_sock&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;epoll_ctl&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;epollfd, EPOLL_CTL_ADD, conn_sock,
                        &amp;amp;ev&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
                perror&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;epoll_ctl: conn_sock&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;nb&quot;&gt;exit&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;EXIT_FAILURE&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            do_use_fd&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;events[n].data.fd&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

When used as an edge-triggered interface, &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;performance reasons, it is possible to  add  the  file  descriptor  inside  the  epoll  interface
&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;EPOLL_CTL_ADD&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;  once  by  specifying &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;EPOLLIN|EPOLLOUT&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;  This allows you to avoid continuously switching between EPOLLIN and EPOLLOUT calling
epoll_ctl&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;2&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; with EPOLL_CTL_MOD.

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;通过上面的demo我们可以分析一下基本的API:&lt;/p&gt;
&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;c&quot;&gt;#epoll_create()  creates  a  new  epoll(7)  instance.  Since Linux 2.6.8, the size argument is ignored, but must be greater &lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# than  zero; see NOTES below.&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# 创建文件文件句柄，类似socket的创建lfd&lt;/span&gt;
int epoll_create&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;int size&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; 

&lt;span class=&quot;c&quot;&gt;#创建lfd之后，目的是为了接受来自客户端的请求，所以上树，事件为read事件&lt;/span&gt;
int epoll_ctl&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;int epfd, int op, int fd, struct epoll_event &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;event&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
epfd:上个api创建的fd
op:EPOLL_CTL_ADD表示当前添加新的节点
fd:第一次这个添加的为lfd，一般来说cfd设置为非阻塞，来更快的接受请求
event:
typedef union epoll_data &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    void        &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;ptr&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;  
    int          fd&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;  /&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; 当前的fd，lfd或者cfd &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;/
    uint32_t     u32&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    uint64_t     u64&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; epoll_data_t&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

struct epoll_event &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    uint32_t     events&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;      /&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; Epoll events &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;/
    epoll_data_t data&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;        /&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; User data variable &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;/
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;


&lt;span class=&quot;c&quot;&gt;#等待有处理的事件返回&lt;/span&gt;
int epoll_wait&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;int epfd, struct epoll_event &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;events,
                      int maxevents, int &lt;span class=&quot;nb&quot;&gt;timeout&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
events为传出参数，通过他来判断是读还是写事件

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;附上完整的epoll_server代码：&lt;/p&gt;
&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;c&quot;&gt;#include &amp;lt;stdio.h&amp;gt;&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#include &amp;lt;sys/types.h&amp;gt;          /* See NOTES */&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#include &amp;lt;sys/socket.h&amp;gt;&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#include &amp;lt;arpa/inet.h&amp;gt;&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#include &amp;lt;sys/epoll.h&amp;gt;&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#include &amp;lt;unistd.h&amp;gt;&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#include &amp;lt;fcntl.h&amp;gt;&lt;/span&gt;

int main&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

        &lt;span class=&quot;c&quot;&gt;#创建监听句柄&lt;/span&gt;
        int lfd &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; socket&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;AF_INET, SOCK_STREAM, 0&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;c&quot;&gt;#绑定端口&lt;/span&gt;
        struct sockaddr_in server&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        server.sin_family &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; AF_INET&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        server.sin_port &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; htons&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;8888&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        char host[16]&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        inet_pton&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;AF_INET, &lt;span class=&quot;s2&quot;&gt;&quot;127.0.0.1&quot;&lt;/span&gt;, &amp;amp;server.sin_addr.s_addr&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;nb&quot;&gt;bind&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;lfd, &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;struct sockadd_in&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &amp;amp;server, sizeof&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;server&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;c&quot;&gt;#监听&lt;/span&gt;
        listen&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;lfd, 128&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;c&quot;&gt;#create epoll fd&lt;/span&gt;
        int epoll_fd &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;  epoll_create&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;128&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;c&quot;&gt;#lfd上树&lt;/span&gt;
        struct epoll_event event&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        event.data.fd &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; lfd&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        event.events &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; EPOLLIN | EPOLLET&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;#读事件与水平触发&lt;/span&gt;
        epoll_ctl&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;epoll_fd, EPOLL_CTL_ADD, lfd, &amp;amp;event&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;c&quot;&gt;#等待epoll的event返回&lt;/span&gt;
        struct epoll_event events[128]&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;1&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

                &lt;span class=&quot;c&quot;&gt;#返回有事件的数量&lt;/span&gt;
                int count &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; epoll_wait&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;epoll_fd, &amp;amp;events, 128, &lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

                &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;count &amp;lt; 0&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
                        perror&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

                &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;int i &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 0&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; i &amp;lt; count&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; i++&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
                        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;events[i].data.fd &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; lfd&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

                                &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;events[i].events &amp;amp; EPOLLIN&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
                                        &lt;span class=&quot;c&quot;&gt;#接收到新的client&lt;/span&gt;
                                        struct sockaddr_in client&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
                                        socklen_t size &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; sizeof&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;client&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
                                        int cfd &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; accept&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;lfd, &amp;amp;client, &amp;amp;size&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
                                        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;cfd &amp;lt; 0&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
                                                perror&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
                                        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

                                        char ip[32]&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
                                        &lt;span class=&quot;nb&quot;&gt;printf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;new client, ip = %s, port = %d &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;, inet_ntop&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;AF_INET, &amp;amp;client.sin_addr.s_addr, ip, sizeof&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;ip&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;, ntohs&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;client.sin_port&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

                                        &lt;span class=&quot;c&quot;&gt;#将客户端设置非非阻塞&lt;/span&gt;
                                        int flag &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; fcntl&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;cfd, F_GETFL&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
                                        fcntl&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;cfd, F_SETFL, flag|O_NONBLOCK&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

                                        &lt;span class=&quot;c&quot;&gt;#cfd 上树&lt;/span&gt;
                                        &lt;span class=&quot;nb&quot;&gt;printf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;cfd开始上树&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
                                        event.data.fd &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; cfd&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
                                        event.events &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; EPOLLIN | EPOLLET&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
                                        int ret &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; epoll_ctl&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;epoll_fd, EPOLL_CTL_ADD, cfd, &amp;amp;event&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
                                        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;ret &amp;lt; 0&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
                                                perror&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
                                        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
                                &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
                        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
                                &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;events[i].events &amp;amp; EPOLLIN&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

                                        &lt;span class=&quot;c&quot;&gt;# 读取客户端的数据&lt;/span&gt;
                                        &lt;span class=&quot;nb&quot;&gt;printf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;cfd读事件 &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
                                        int cfd &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; events[i].data.fd&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
                                        char buf[128]&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
                                        int read_data &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;cfd, buf, sizeof&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;buf&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
                                        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;read_data &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; 0&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
                                                &lt;span class=&quot;nb&quot;&gt;printf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;client断开链接&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
                                                &lt;span class=&quot;c&quot;&gt;#下树&lt;/span&gt;
                                                epoll_ctl&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;epoll_fd, EPOLL_CTL_DEL, cfd, &amp;amp;event&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
                                        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;read_data &amp;lt; 0&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
                                                perror&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
                                                epoll_ctl&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;epoll_fd, EPOLL_CTL_DEL, cfd, &amp;amp;event&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
                                        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;  &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
                                                &lt;span class=&quot;nb&quot;&gt;printf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;read data = %s &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;, buf&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
                                                write&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;cfd, buf, read_data&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
                                        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

                                &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
                        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;epoll反应堆&quot;&gt;epoll反应堆&lt;/h2&gt;

&lt;blockquote&gt;
  &lt;p&gt;epoll反应堆模型其实就是Libevent库核心思想
基本设计思想：将文件描述符、事件、回调函数使用自定义结构体封装在一起，当某个文件描述符的事件被触发，会自动调用回调函数既可以实现对应的IO操作。最终目的实现不同的文件描述对应不同的事件处理函数&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;c&quot;&gt;#include &amp;lt;stdio.h&amp;gt;&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#include &amp;lt;sys/types.h&amp;gt;&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#include &amp;lt;sys/socket.h&amp;gt;&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#include &amp;lt;arpa/inet.h&amp;gt;&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#include &amp;lt;stdlib.h&amp;gt;&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#include &amp;lt;unistd.h&amp;gt;&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#include &amp;lt;sys/epoll.h&amp;gt;&lt;/span&gt;

struct my_epoll_event &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

        int epoll_fd&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        int current_fd&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        void &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;call_back&lt;span class=&quot;o&quot;&gt;)(&lt;/span&gt;struct my_epoll_event &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;arg&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        int events&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

int read_data&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;struct my_epoll_event&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; mev&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
int write_data&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;struct my_epoll_event&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; mev&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

int write_data&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;struct my_epoll_event&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; mev&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

        int cfd &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; mev-&amp;gt;current_fd&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        write&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;cfd, &lt;span class=&quot;s2&quot;&gt;&quot;OK&quot;&lt;/span&gt;, 2&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

        mev -&amp;gt; call_back &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; read_data&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        event.data.ptr &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; mev&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        event.events &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; EPOLLIN&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        epoll_ctl&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;epoll_fd, EPOLL_CTL_MOD, &amp;amp;event&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

int read_data&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;struct my_epoll_event&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; mev&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

        int epoll_fd &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; mev-&amp;gt;epoll_fd&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        int cfd &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; mev-&amp;gt;current_fd&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        struct epoll_event event&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        event.events &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; EPOLLIN&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

        char buf[128]&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        int data &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;cfd, &amp;amp;buf, sizeof&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;buf&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;data &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; 0 &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; data &amp;lt; 0&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

                &lt;span class=&quot;c&quot;&gt;# client exit&lt;/span&gt;
                &lt;span class=&quot;nb&quot;&gt;printf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;client exit... &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
                epoll_ctl&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;epoll_fd, EPOLL_CTL_DEL, cfd, &amp;amp;event&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;nb&quot;&gt;printf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;read client data = %s &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;, buf&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;c&quot;&gt;# add write&lt;/span&gt;
                mev -&amp;gt; call_back &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; write_data&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
                event.data.ptr &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; mev&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
                event.events &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; EPOLLOUT&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
                epoll_ctl&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;epoll_fd, EPOLL_CTL_MOD, &amp;amp;event&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

int accpet&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;struct my_epoll_event&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; mev&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

        struct epoll_event event&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        struct sockaddr_in client&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        scokelen_t size &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; sizeof&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;client&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        int lfd &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; mev-&amp;gt;current_fd&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        int epoll_fd &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; mev-&amp;gt;epoll_fd&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        int cfd &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; accept&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;lfd, &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;struct aockadd_in &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&amp;amp;client, &amp;amp;size&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

        // add cfd to epoll tree
        mev -&amp;gt; current_fd &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; cfd&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        mev -&amp;gt; call_back &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; read_data&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        event.data.ptr &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; mev&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        event.events &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; EPOLLIN&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        epoll_ctl&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;epoll_fd, EPOLL_CTL_ADD, cfd, &amp;amp;event&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

int main&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

        &lt;span class=&quot;c&quot;&gt;# create listen&lt;/span&gt;
        int lfd &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; socket&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;AF_INET, SOCK_STREAM, 0&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

        int optval &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 1&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        int ret &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; setsockopt&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;lfd, SOL_SOCKET, SO_REUSEADDR, &amp;amp;optval, sizeof&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;optval&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

        struct sockaddr_in server&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        server.sin_family &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; AF_INET&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        server.sin_port &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; htons&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;8888&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        inet_pton&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;AF_INET, &lt;span class=&quot;s2&quot;&gt;&quot;127.0.0.1&quot;&lt;/span&gt;, &amp;amp;server.sin_addr.s_addr&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;nb&quot;&gt;bind&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;lfd, &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;struct sockadd_in &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&amp;amp;server, sizeof&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;server&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

        listen&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;lfd, 128&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;c&quot;&gt;# create epoll&lt;/span&gt;
        int epoll_fd &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; epoll_create&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;1&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        struct epoll_event event&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        event.events &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; EPOLLIN&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        struct my_epoll_event&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; mev &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;struct my_epoll_event&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;malloc&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;sizeof&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;struct my_epoll_event&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        mev-&amp;gt;epoll_fd &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; epoll_fd&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        mev-&amp;gt;current_fd &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; lfd&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        mev-&amp;gt;call_back &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; accpet&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        event.data.ptr &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; mev&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

        struct epoll_event events[128]&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;1&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
                int count &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; epoll_wait&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;epoll_fd, &amp;amp;events, 128, &lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;int i &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 0&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; i &amp;lt; count&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; i++&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
                        &lt;span class=&quot;o&quot;&gt;((&lt;/span&gt;struct my_epoll_event&lt;span class=&quot;o&quot;&gt;)(&lt;/span&gt;events[i].data.ptr&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt; -&amp;gt; callback&lt;span class=&quot;o&quot;&gt;((&lt;/span&gt;struct my_epoll_event&lt;span class=&quot;o&quot;&gt;)(&lt;/span&gt;events[i].data.ptr&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;c&quot;&gt;#free memory&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;mev &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; null&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
                free&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;mev&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;      
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; 

&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</description>
        <pubDate>Wed, 29 Mar 2023 00:00:00 +0000</pubDate>
        <link>https://seakingOne.github.io/linux/2023/03/29/linux-socket%E7%BC%96%E7%A8%8B%E4%BA%8C%E4%B9%8Bepoll.html</link>
        <guid isPermaLink="true">https://seakingOne.github.io/linux/2023/03/29/linux-socket%E7%BC%96%E7%A8%8B%E4%BA%8C%E4%B9%8Bepoll.html</guid>
        
        <category>Linux</category>
        
        
        <category>Linux</category>
        
      </item>
    
      <item>
        <title>Linux socket编程一</title>
        <description>&lt;p&gt;1、了解通讯还需先了解内存, 内存中的多字节数据相对于内存地址有大端和小端之分，磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分。&lt;/p&gt;

&lt;p&gt;网络数据流同样有大端小端之分，那么如何定义网络数据流的地址呢？发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出，
接收主机把从网络上接到的字节依次保存在接收缓冲区中，也是按内存地址从低到高的顺序保存，
因此，网络数据流的地址应这样规定：先发出的数据是低地址，后发出的数据是高地址。但是TCP/IP协议规定，网络数据流应采用大端字节序，
即低地址高字节;为使网络程序具有可移植性，使同样的C代码在大端和小端计算机上编译后都能正常运行，可以调用以下库函数做网络字节序和主机字节序的转换&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &lt;span class=&quot;c&quot;&gt;#include &amp;lt;arpa/inet.h&amp;gt;&lt;/span&gt;
    /&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;主机字节顺序 &lt;span class=&quot;nt&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; 网络字节顺序&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;/
    uint32_t htonl&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;uint32_t hostlong&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;/&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; 端口&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;/
    uint16_t htons&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;uint16_t hostshort&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;/&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; IP&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;/
    /&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;网络字节顺序 &lt;span class=&quot;nt&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; 主机字节顺序&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;/
    uint32_t ntohl&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;uint32_t netlong&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;/&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; 端口&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;/
    uint16_t ntohs&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;uint16_t netshort&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;/&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; IP&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;/
    &lt;span class=&quot;c&quot;&gt;#include &amp;lt;arpa/inet.h&amp;gt; /*IP地址转换函数*/&lt;/span&gt;
    /&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;指定IP 即 字符串&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;点分十进制:xxx.xxx.xx.xxx&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;/
     /&lt;span class=&quot;k&quot;&gt;**&lt;/span&gt; 
       &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; 本地IP转网络字节序 字符串 &lt;span class=&quot;nt&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; int&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;大端方式存储&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
       &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; @param af 地址族协议对应的有AF_INET, AF_INET6等
       &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; @param src 要转换的指定的IP
       &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; @param dest  转换出来的值,  其实是整型值
       &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; 
       &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; @return &lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt; 失败, 0成功, 并对应有errno
       &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;/
    int inet_pton&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;int af, const char &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;src, void &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;dst&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    /&lt;span class=&quot;k&quot;&gt;**&lt;/span&gt;
       &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;  网络字节序转本地IP int -&amp;gt; 字符串
       &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;  @param af    - 地址族协议对应的有AF_INET, AF_INET6等
       &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;  @param src  - 网路字节序格式的int类型的iP
       &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;  @param dst  - 存储字符串ip的数组的地址
       &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;  @param size - dst缓冲区大小
       &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;  
       &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;  @return 返回的即是 dst
      &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;/
    const char &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;inet_ntop&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;int af, const void &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;src, char &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;dst, socklen_t size&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;还要了解sockaddr数据结构, const struct sockaddr *指针, 指向要绑定给sockfd的协议地址。这个地址结构根据地址创建socket时的地址协议族的不同使用不同结构体
原来的结构体即左边第一个, 这种使用不方便所以出现针对不同类型协议的结构体, 可以直接访问其对应的值, 主要是IP和port(端口)&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    ipv4对应的是：
    struct sockaddr_in &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        sa_family_t    sin_family&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; /&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; address family: AF_INET &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;/
        in_port_t      sin_port&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;   /&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; port &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;network byte order &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;/
        struct in_addr sin_addr&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;   /&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; internet address &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;/
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    /&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; Internet address. &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;/
    struct in_addr &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        uint32_t       s_addr&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;     /&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; address &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;network byte order &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;/
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    
    ipv6对应的是：
    struct sockaddr_in6 &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt; 
        sa_family_t     sin6_family&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;   /&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; AF_INET6 &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;/ 
        in_port_t       sin6_port&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;     /&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; port number &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;/ 
        uint32_t        sin6_flowinfo&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; /&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; IPv6 flow information &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;/ 
        struct in6_addr sin6_addr&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;     /&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; IPv6 address &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;/ 
        uint32_t        sin6_scope_id&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; /&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; Scope ID &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;new &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;2.4&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;/ 
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    
    struct in6_addr &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt; 
        unsigned char   s6_addr[16]&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;   /&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; IPv6 address &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;/ 
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    
    &lt;span class=&quot;c&quot;&gt;#define UNIX_PATH_MAX    108&lt;/span&gt;
    struct sockaddr_un &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt; 
        sa_family_t sun_family&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;               /&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; AF_UNIX &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;/ 
        char        sun_path[UNIX_PATH_MAX]&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;  /&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; pathname &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;/ 
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    
网络套接字API函数:

    //创建套接字
     int socket&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;int domain, int &lt;span class=&quot;nb&quot;&gt;type&lt;/span&gt;, int protocol&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
     参数:
     domain:
     &lt;span class=&quot;nb&quot;&gt;type&lt;/span&gt;: tcp - 流式协议  udp - 报式协议
     protocol - 协议类型, 会根据type默认为TCP或UDP
     返回值:文件描述符&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;套接字&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
    //将本地的IP和端口与创建出的套接字绑定
     int &lt;span class=&quot;nb&quot;&gt;bind&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;int sockfd, const struct sockaddr &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;addr, socklen_t addrlen&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
     参数:
     sockfd - 创建出的文件描述符 
     addr - - 端口和IP
     addrlen - addr结构体的长度
    //设置同时连接到服务器的客户端的个数
     int listen&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;int sockfd, int backlog&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
     参数:
     socket函数创建出来的文件描述符
     backlog - -最大值 128
    //阻塞等待客户端连接请求, 并接受连接
     int accept&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;int sockfd, struct sockaddr &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;addr, socklen_t &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;addrlen&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
     参数:
     sockfd:文件描述符, 使用socket创建出的文件描述符
     addr: 存储客户端的端口和IP, 传出参数
     addrlen: 传入传出参数
     返回值: 返回的是一个套接字, 对应客户端: 服务器端与客户端进程通信使用accept的返回值对应的套接字
    
    //客户端与服务器端建立连接的函数
     int connect&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;int sockfd, const struct sockaddr &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;addr, socklen_t addrlen&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
     参数:
     sockfd: 套接字
     addr: 服务器端的IP和端口
     addrlen: 第二个参数的长度
     
     
server:
    
    // server 
    &lt;span class=&quot;c&quot;&gt;#include &amp;lt;stdio.h&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;#include &amp;lt;ctype.h&amp;gt;//小写转大写&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;#include &amp;lt;unistd.h&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;#include &amp;lt;stdlib.h&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;#include &amp;lt;sys/types.h&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;#include &amp;lt;sys/stat.h&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;#include &amp;lt;string.h&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;#include &amp;lt;arpa/inet.h&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;#include &amp;lt;sys/socket.h&amp;gt;&lt;/span&gt;
    
    int main&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;int argc, const char&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; argv[]&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        // 创建监听的套接字
        int lfd &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; socket&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;AF_INET, SOCK_STREAM, 0&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    
        // 绑定
        struct sockaddr_in serv_addr&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        memset&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&amp;amp;serv_addr, 0, sizeof&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;serv_addr&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        serv_addr.sin_family &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; AF_INET&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        serv_addr.sin_port &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; htons&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;8080&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; //没有使用的端口
        serv_addr.sin_addr.s_addr &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; htonl&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;INADDR_ANY&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; //本地所有的IP
        // 另一种写法, 假如是127.0.0.1
        // inet_pton&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;AF_INET, &lt;span class=&quot;s2&quot;&gt;&quot;127.0.0.1&quot;&lt;/span&gt;, &amp;amp;serv_addr.sin_addr.s_addr&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;nb&quot;&gt;bind&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;lfd, &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;struct sockaddr&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&amp;amp;serv_addr, sizeof&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;serv_addr&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    
        // 监听
        listen&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;lfd, 64&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    
        // 阻塞等待连接请求，　并接受连接请求
        struct sockaddr_in clien_addr&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        socklen_t clien_len &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; sizeof&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;clien_addr&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        int cfd &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; accept&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;lfd, &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;struct sockaddr&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&amp;amp;clien_addr, &amp;amp;clien_len&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    
        char ipbuf[128]&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;nb&quot;&gt;printf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;client iP: %s, port: %d&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;, inet_ntop&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;AF_INET, &amp;amp;clien_addr.sin_addr.s_addr, ipbuf, sizeof&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;ipbuf&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;,
               ntohs&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;clien_addr.sin_port&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    
        char buf[1024] &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;0&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;1&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            // &lt;span class=&quot;nb&quot;&gt;read &lt;/span&gt;data, 阻塞读取
            int len &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;cfd, buf, sizeof&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;buf&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;nb&quot;&gt;printf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;read buf = %s&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;, buf&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            // 小写转大写
            &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;int &lt;span class=&quot;nv&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; i&amp;lt;len&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; ++i&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
                buf[i] &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; toupper&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;buf[i]&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;nb&quot;&gt;printf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;after buf = %s&quot;&lt;/span&gt;, buf&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    
            // 大写串发给客户端
            write&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;cfd, buf, strlen&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;buf&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;+1&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    
        close&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;cfd&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        close&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;lfd&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    
        &lt;span class=&quot;k&quot;&gt;return &lt;/span&gt;0&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;         

client:

    client 端相对简单, 另外可以使用nc命令连接-&amp;gt;nc ip prot
    &lt;span class=&quot;c&quot;&gt;#include &amp;lt;stdio.h&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;#include &amp;lt;stdlib.h&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;#include &amp;lt;unistd.h&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;#include &amp;lt;arpa/inet.h&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;#include &amp;lt;sys/socket.h&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;#include &amp;lt;sys/types.h&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;#include &amp;lt;string.h&amp;gt;&lt;/span&gt;
    int main&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;int argc, const char &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;argv[]&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        
        // create
        int fd &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; socket&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;AF_INET, SOCK_STREAM, 0&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;fd &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            perror&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;socket error&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;nb&quot;&gt;exit&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
        
        //connect
        struct sockaddr_in c_addr&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        bzero&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&amp;amp;c_addr, sizeof&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;c_addr&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        c_addr.sin_family &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; AF_INET&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        c_addr.sin_port &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; htons&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;8888&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        inet_pton&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;AF_INET, &lt;span class=&quot;s2&quot;&gt;&quot;127.0.0.1&quot;&lt;/span&gt;, &amp;amp;c_addr.sin_addr.s_addr&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        
        int ret &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; connect&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;fd, &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;struct sockaddr&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&amp;amp;c_addr, sizeof&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;c_addr&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;ret &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            perror&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;connect error&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;nb&quot;&gt;exit&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
        
        &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;1&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            //
            char buf[1024] &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;0&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            fgets&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;buf, sizeof&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;buf&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;, stdin&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            write&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;fd, buf, strlen&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;buf&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            //接收, 阻塞等待
            int len &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;fd, buf, sizeof&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;buf&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;len &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
                 perror&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;read error&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
                 &lt;span class=&quot;nb&quot;&gt;exit&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;nb&quot;&gt;printf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;client recv %s&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;, buf&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
        
        close&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;fd&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;k&quot;&gt;return &lt;/span&gt;0&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;     
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;上面当然都是简单的一对一连接通信，如果需要多个客户端同时处理，需要怎么处理呢？&lt;br /&gt;
&lt;img src=&quot;/static/assets/img/blog/linux/socket/more_client.jpg&quot; width=&quot;65%&quot; /&gt; &lt;br /&gt;
从上面可以看出，我们应该维护一个数组结构，用于存储所有接受的client，然后依次去处理，这是最快的处理方式，那么其实还有其他的处理方式，比如select、poll、epoll&lt;/p&gt;
</description>
        <pubDate>Wed, 29 Mar 2023 00:00:00 +0000</pubDate>
        <link>https://seakingOne.github.io/linux/2023/03/29/Linux-socket%E7%BC%96%E7%A8%8B%E4%B8%80.html</link>
        <guid isPermaLink="true">https://seakingOne.github.io/linux/2023/03/29/Linux-socket%E7%BC%96%E7%A8%8B%E4%B8%80.html</guid>
        
        <category>Linux</category>
        
        
        <category>Linux</category>
        
      </item>
    
      <item>
        <title>C语言基础知识点</title>
        <description>&lt;h2 id=&quot;gun&quot;&gt;GUN&lt;/h2&gt;

&lt;blockquote&gt;
  &lt;p&gt;gcc（GNU Compiler Collection，GNU 编译器套件），是由 GNU 开发的编程语言编译器。gcc原本作为GNU操作系统的官方编译器，现已被大多数类Unix操作系统（如Linux、BSD、Mac OS X等）采纳为标准的编译器，gcc同样适用于微软的Windows。
gcc最初用于编译C语言，随着项目的发展gcc已经成为了能够编译C、C++、Java、Ada、fortran、Object C、Object C++、Go语言的编译器大家族。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;编译命令格式：&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;gcc &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-option1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; ... &amp;lt;filename&amp;gt;
g++ &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-option1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; ... &amp;lt;filename&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;ul&gt;
  &lt;li&gt;命令、选项和源文件之间使用空格分隔&lt;/li&gt;
  &lt;li&gt;一行命令中可以有零个、一个或多个选项&lt;/li&gt;
  &lt;li&gt;文件名可以包含文件的绝对路径，也可以使用相对路径&lt;/li&gt;
  &lt;li&gt;如果命令中不包含输出可执行文件的文件名，可执行文件的文件名会自动生成一个默认名，Linux平台为a.out，Windows平台为a.exe&lt;/li&gt;
&lt;/ul&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;选项&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;含义&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;-o file&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;指定输出文件名为file&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;-E&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;只进行预处理&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;-S(大写)&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;只进行预处理与编译&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;-c(小写)&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;只进行预处理，编译，汇编&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;既然说到了这里，那么不得不提到C语言的编译过程
C代码编译成可执行程序经过4步：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;预处理：宏定义展开、头文件展开、条件编译等，同时将代码中的注释删除，这里并不会检查语法&lt;/li&gt;
  &lt;li&gt;编译：检查语法，将预处理后文件编译生成汇编文件&lt;/li&gt;
  &lt;li&gt;汇编：将汇编文件生成目标文件(二进制文件)&lt;/li&gt;
  &lt;li&gt;链接：C语言写的程序是需要依赖各种库的，所以编译之后还需要把库链接到最终的可执行程序中去&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;比如：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;预处理：gcc -E hello.c -o hello.i&lt;/li&gt;
  &lt;li&gt;编  译：gcc -S hello.i -o hello.s&lt;/li&gt;
  &lt;li&gt;汇  编：gcc -c hello.s -o hello.o&lt;/li&gt;
  &lt;li&gt;链  接：gcc    hello.o -o hello&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;那么还有更简单的一步编译：&lt;/p&gt;
&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;gcc hello.c &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; demo 经过：预处理、编译、汇编、链接的过程
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;##数据类型&lt;/p&gt;
</description>
        <pubDate>Sun, 12 Feb 2023 00:00:00 +0000</pubDate>
        <link>https://seakingOne.github.io/c_or_c++/2023/02/12/C%E8%AF%AD%E8%A8%80%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E7%82%B9.html</link>
        <guid isPermaLink="true">https://seakingOne.github.io/c_or_c++/2023/02/12/C%E8%AF%AD%E8%A8%80%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E7%82%B9.html</guid>
        
        <category>C/C++</category>
        
        
        <category>C_or_c++</category>
        
      </item>
    
      <item>
        <title>Linux编程之Makefile</title>
        <description>&lt;p&gt;什么是makefile？或许很多Windows的程序员都不知道这个东西，因为那些Windows的集成开发环境（integrated development environment，IDE）都为你做了这个工作，
但我觉得要作一个好的和专业的程序员，makefile还是要懂。&lt;/p&gt;

&lt;p&gt;makefile带来的好处就是——“自动化编译”，一旦写好，只需要一个make命令，整个工程完全自动编译，极大的提高了软件开发的效率。 
make是一个命令工具，是一个解释makefile中指令的命令工具，一般来说，大多数的IDE都有这个命令，比如：Delphi的make，Visual C++的nmake，Linux下GNU的make。
可见，makefile都成为了一种在工程方面的编译方法。&lt;/p&gt;

&lt;p&gt;一、基本介绍&lt;br /&gt;
1、makefile的规则：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;target ... : prerequisites ...
    command
    ...
    ...
    
target可以是一个object file（目标文件），也可以是一个执行文件，还可以是一个标签（label）。
prerequisites生成该target所依赖的文件和/或target
command该target要执行的命令（任意的shell命令）
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;一个demo:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;edit : main.o kbd.o command.o display.o \
        insert.o search.o files.o utils.o
    gcc -o edit main.o kbd.o command.o display.o \
        insert.o search.o files.o utils.o

main.o : main.c defs.h
    gcc -c main.c
kbd.o : kbd.c defs.h command.h
    gcc -c kbd.c
command.o : command.c defs.h command.h
    gcc -c command.c
display.o : display.c defs.h buffer.h
    gcc -c display.c
insert.o : insert.c defs.h buffer.h
    gcc -c insert.c
search.o : search.c defs.h buffer.h
    gcc -c search.c
files.o : files.c defs.h buffer.h command.h
    gcc -c files.c
utils.o : utils.c defs.h
    cc -c utils.c
clean :
    rm edit main.o kbd.o command.o display.o \
        insert.o search.o files.o utils.o

在这个makefile中，目标文件（target）包含：执行文件edit和中间目标文件（ *.o ），依赖文件（prerequisites）就是冒号后面的那些 .c 文件和 .h 文件。
每一个 .o 文件都有一组依赖文件，而这些 .o 文件又是执行文件 edit 的依赖文件。依赖关系的实质就是说明了目标文件是由哪些文件生成的，换言之，目标文件是哪些文件更新的。      
于是在我们编程中，如果这个工程已被编译过了，当我们修改了其中一个源文件，比如 file.c ，那么根据我们的依赖性，我们的目标 file.o 会被重编译（也就是在这个依性关系后面所定义的命令），于是 file.o 的文件也是最新的啦，于是 file.o 的文件修改时间要比 edit 要新，所以 edit 也会被重新链接了（详见 edit 目标文件后定义的命令）。
而如果我们改变了 command.h ，那么， kdb.o 、 command.o 和 files.o 都会被重编译，并且， edit 会被重链接。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;2、makefile使用变量&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;edit : main.o kbd.o command.o display.o \
        insert.o search.o files.o utils.o
    cc -o edit main.o kbd.o command.o display.o \
        insert.o search.o files.o utils.o
        
我们可以看到 .o 文件的字符串被重复了两次，如果我们的工程需要加入一个新的 .o 文件，那么我们需要在两个地方加（应该是三个地方，还有一个地方在clean中）。
当然，我们的makefile并不复杂，所以在两个地方加也不累，但如果makefile变得复杂，那么我们就有可能会忘掉一个需要加入的地方，而导致编译失败。所以，为了makefile的易维护，在makefile中我们可以使用变量。makefile的变量也就是一个字符串，理解成C语言中的宏可能会更好。          

objects = main.o kbd.o command.o display.o \
     insert.o search.o files.o utils.o
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;于是，我们就可以很方便地在我们的makefile中以 $(objects) 的方式来使用这个变量了，于是我们的改良版makefile就变成下面这个样子：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;objects = main.o kbd.o command.o display.o \
    insert.o search.o files.o utils.o

edit : $(objects)
    cc -o edit $(objects)
main.o : main.c defs.h
    cc -c main.c
kbd.o : kbd.c defs.h command.h
    cc -c kbd.c
command.o : command.c defs.h command.h
    cc -c command.c
display.o : display.c defs.h buffer.h
    cc -c display.c
insert.o : insert.c defs.h buffer.h
    cc -c insert.c
search.o : search.c defs.h buffer.h
    cc -c search.c
files.o : files.c defs.h buffer.h command.h
    cc -c files.c
utils.o : utils.c defs.h
    cc -c utils.c
clean :
    rm edit $(objects)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;3、makefile文件名&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;默认的情况下，make命令会在当前目录下按顺序找寻文件名为“GNUmakefile”、“makefile”、“Makefile”的文件，找到了解释这个文件。在这三个文件名中，最好使用“Makefile”这个文件名，因为，这个文件名第一个字符为大写，这样有一种显目的感觉。最好不要用“GNUmakefile”，这个文件是GNU的make识别的。
有另外一些make只对全小写的“makefile”文件名敏感，但是基本上来说，大多数的make都支持“makefile”和“Makefile”这两种默认文件名。
当然，你可以使用别的文件名来书写Makefile，比如：“Make.Linux”，“Make.Solaris”，“Make.AIX”等，如果要指定特定的Makefile，你可以使用make的 -f 和 --file 参数，如： make -f Make.Linux 或 make --file Make.AIX 。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;4、引用其他的makefile&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;在Makefile使用 include 关键字可以把别的Makefile包含进来，这很像C语言的 #include ，被包含的文件会原模原样的放在当前文件的包含位置。 include 的语法是：
include &amp;lt;filename&amp;gt;            
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;二、参考链接&lt;br /&gt;
&lt;a href=&quot;https://seisman.github.io/how-to-write-makefile/conditionals.html&quot;&gt;更多makefile学习链接&lt;/a&gt;&lt;/p&gt;

</description>
        <pubDate>Wed, 30 Nov 2022 00:00:00 +0000</pubDate>
        <link>https://seakingOne.github.io/linux/2022/11/30/Linux%E7%BC%96%E7%A8%8B%E4%B9%8BMakefile.html</link>
        <guid isPermaLink="true">https://seakingOne.github.io/linux/2022/11/30/Linux%E7%BC%96%E7%A8%8B%E4%B9%8BMakefile.html</guid>
        
        <category>C/C++</category>
        
        
        <category>Linux</category>
        
      </item>
    
      <item>
        <title>Linux编程之动态库，静态库</title>
        <description>&lt;p&gt;我们都知道，库是写好的，现有的，成熟的，可以复用的代码。本质上来说，库是一种可执行代码的二进制形式，可以被操作系统载入内存执行。库有两种：静态库（.a、.lib）和动态库（.so、.dll）。所谓静态、动态是指链接。库文件是事先编译好的方法的合集。静态库的扩展名一般为”.a”或”.lib”；动态库的扩展名一般为”.so”或”.dll”。&lt;/p&gt;

&lt;p&gt;我们在Linux上制作静态库：&lt;br /&gt;
假如我们有一个test.c的主程序，主程序中调用一个外部的方法，比如add累加的方法，定义为add.c/add.h
首先我们需要编译add.c文件，生成二进制文件&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;gcc -c add.c -o add.o
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;然后把二进制文件打包，以.a作为后缀名，在Linux下一般命名为libxxx.a；&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ar rcs libtest.a add.o
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;最后编译程序，加载外部包&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;gcc -o test test.c -L./ -I./ -ltest  
./test即可正常执行程序 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;动态链接库：&lt;br /&gt;
首先编译文件的时候，需要加入fpic，加入该命令是为了创建与地址无关的编译程序，以作为共享(使用了绝对地址)&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;gcc -fpic -c add.c -o add.o
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;生成共享库:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;gcc -shared add.o -o libtest.so
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;编译:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;gcc test.c -o test -L./ -I./ -itest
但是执行./test会有问题，因为找不到链接器
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;所以我们需要导入环境变量：&lt;br /&gt;  &lt;br /&gt;
&lt;img src=&quot;/static/assets/img/blog/c++/shared_lib/shared_lib.jpg&quot; width=&quot;65%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;当然，还有其他方式处理：&lt;br /&gt;
&lt;img src=&quot;/static/assets/img/blog/c++/shared_lib/shared_lib_1.jpg&quot; width=&quot;65%&quot; /&gt;&lt;/p&gt;

</description>
        <pubDate>Sat, 05 Nov 2022 00:00:00 +0000</pubDate>
        <link>https://seakingOne.github.io/linux/2022/11/05/Linux%E7%BC%96%E7%A8%8B%E4%B9%8B%E5%8A%A8%E6%80%81%E5%BA%93-%E9%9D%99%E6%80%81%E5%BA%93.html</link>
        <guid isPermaLink="true">https://seakingOne.github.io/linux/2022/11/05/Linux%E7%BC%96%E7%A8%8B%E4%B9%8B%E5%8A%A8%E6%80%81%E5%BA%93-%E9%9D%99%E6%80%81%E5%BA%93.html</guid>
        
        <category>C/C++</category>
        
        
        <category>Linux</category>
        
      </item>
    
      <item>
        <title>C++之堆、栈、RAII</title>
        <description>&lt;p&gt;堆，英文是 heap，在内存管理的语境下，指的是动态分配内存的区域。这个堆跟数据结构里的堆不是一回事。这里的内存，被分配之后需要手工释放，否则，就会造成内存泄漏。&lt;br /&gt;
C++ 标准里一个相关概念是自由存储区，英文是 free store，特指使用new和delete来分配和释放内存的区域。一般而言，这是堆的一个子集：new和delete操作都是free store区域；malloc和free操作的区域都是heap；但是new和delete操作都是基于malloc与free实现。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;比如：
vector&amp;lt;int&amp;gt;* ptr_vector = new vector&amp;lt;int&amp;gt;();
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;栈，英文是 stack，在内存管理的语境下，指的是函数调用过程中产生的本地变量和调用数据的区域。这个栈和数据结构里的栈高度相似，都满足“后进先出”（last-in-first-out 或 LIFO）。&lt;/p&gt;

&lt;p&gt;RAII，完整的英文是 Resource Acquisition Is Initialization，是 C++ 所特有的资源管理方式。有少量其他语言，如 D、Ada 和 Rust 也采纳了 RAII，但主流的编程语言中， C++ 是唯一一个依赖 RAII 来做资源管理的。&lt;br /&gt;
RAII（Resource Acquisition Is Initialization）机制是Bjarne Stroustrup首先提出的，是一种利用对象生命周期来控制程序资源（如内存、文件句柄、网络连接、互斥量等等）的简单技术。&lt;br /&gt;
比如我们在C++中经常使用new申请了内存空间，但是却也经常忘记delete回收申请的空间，容易造成内存溢出，于是RAII技术就诞生了，来解决这样的问题。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;void Func() 
{ 
  FILE *fp; 
  char* filename = &quot;test.txt&quot;; 
  if((fp=fopen(filename,&quot;r&quot;))==NULL) 
  { 
      printf(&quot;not open&quot;); 
      exit(0); 
  } 
  ... // 如果 在使用fp指针时产生异常 并退出 
       // 那么 fp文件就没有正常关闭 
       
  fclose(fp); 
} 

此时，就可以让RAII惯用法大显身手了。
RAII的实现原理很简单，利用stack上的临时对象生命期是程序自动管理的这一特点，将我们的资源释放操作封装在一个临时对象中。
具体示例代码如下：
class Resource{}; 
class RAII{ 
public: 
    RAII(Resource* aResource):r_(aResource){} //获取资源 
    ~RAII() {delete r_;} //释放资源 
    Resource* get()    {return r_ ;} //访问资源 
private: 
    Resource* r_; 
}; 

void UseResources()
{
    // 获取资源1
    // ...
    // 获取资源n
   
    // 使用这些资源
   
    // 释放资源n
    // ...
    // 释放资源1
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</description>
        <pubDate>Mon, 08 Aug 2022 00:00:00 +0000</pubDate>
        <link>https://seakingOne.github.io/c_or_c++/2022/08/08/C++%E4%B9%8B%E5%A0%86-%E6%A0%88-RAII.html</link>
        <guid isPermaLink="true">https://seakingOne.github.io/c_or_c++/2022/08/08/C++%E4%B9%8B%E5%A0%86-%E6%A0%88-RAII.html</guid>
        
        <category>C++</category>
        
        <category>堆</category>
        
        <category>栈</category>
        
        <category>RAII</category>
        
        
        <category>C_or_c++</category>
        
      </item>
    
      <item>
        <title>并发编程之并发设计模式线程本地存储</title>
        <description>&lt;p&gt;其实大家都清楚，这个模式在Java中的体现就是ThreadLocal.&lt;br /&gt;
那Java中的ThreadLocal是怎么实现的？
Thread -&amp;gt; ThreadLocalMap -&amp;gt; (map中的存储的key, value), key 为 ThreadLocal, value 为存储的值&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;class Thread {
  //内部持有ThreadLocalMap
  ThreadLocal.ThreadLocalMap 
    threadLocals;
}
class ThreadLocal&amp;lt;T&amp;gt;{
  public T get() {
    //首先获取线程持有的
    //ThreadLocalMap
    ThreadLocalMap map =
      Thread.currentThread()
        .threadLocals;
    //在ThreadLocalMap中
    //查找变量
    Entry e = 
      map.getEntry(this);
    return e.value;  
  }
  static class ThreadLocalMap{
    //内部是数组而不是Map
    Entry[] table;
    //根据ThreadLocal查找Entry
    Entry getEntry(ThreadLocal key){
      //省略查找逻辑
    }
    //Entry定义
    static class Entry extends
    WeakReference&amp;lt;ThreadLocal&amp;gt;{
      Object value;
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;在 Java 的实现方案里面，ThreadLocal 仅仅是一个代理工具类，内部并不持有任何与线程相关的数据，所有和线程相关的数据都存储在 Thread 里面，这样的设计容易理解。而从数据的亲缘性上来讲，ThreadLocalMap 属于 Thread 也更加合理。当然还有一个更加深层次的原因，那就是不容易产生内存泄露。Java 的实现中 Thread 持有 ThreadLocalMap，而且 ThreadLocalMap 里对 ThreadLocal 的引用还是弱引用（WeakReference），所以只要 Thread 对象可以被回收，那么 ThreadLocalMap 就能被回收。&lt;/p&gt;

&lt;p&gt;实际工作中，有很多平台型的技术方案都是采用 ThreadLocal 来传递一些上下文信息，例如 Spring 使用 ThreadLocal 来传递事务信息.&lt;/p&gt;

</description>
        <pubDate>Tue, 26 Jul 2022 00:00:00 +0000</pubDate>
        <link>https://seakingOne.github.io/article/2022/07/26/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B9%8B%E5%B9%B6%E5%8F%91%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%BA%BF%E7%A8%8B%E6%9C%AC%E5%9C%B0%E5%AD%98%E5%82%A8.html</link>
        <guid isPermaLink="true">https://seakingOne.github.io/article/2022/07/26/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B9%8B%E5%B9%B6%E5%8F%91%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%BA%BF%E7%A8%8B%E6%9C%AC%E5%9C%B0%E5%AD%98%E5%82%A8.html</guid>
        
        <category>Java</category>
        
        <category>并发</category>
        
        <category>并发设计模式</category>
        
        <category>ThreadLocal</category>
        
        
        <category>Article</category>
        
      </item>
    
      <item>
        <title>并发编程之并发设计模式Immutability&amp;Copy-on-Write</title>
        <description>&lt;p&gt;利用 Immutability 模式解决并发问题，也许你觉得有点陌生，其实你天天都在享受它的战果。Java 语言里面的 String 和 Long、Integer、Double 等基础类型的包装类都具备不可变性，这些对象的线程安全性都是靠不可变性来保证的。Immutability 模式是最简单的解决并发问题的方法，建议当你试图解决一个并发问题时，可以首先尝试一下 Immutability 模式，看是否能够快速解决。
关键字也就是final的修饰符&lt;/p&gt;

&lt;p&gt;具备不变性的对象，只有一种状态，这个状态由对象内部所有的不变属性共同决定。其实还有一种更简单的不变性对象，那就是无状态。无状态对象内部没有属性，只有方法。除了无状态的对象，你可能还听说过无状态的服务、无状态的协议等等。无状态有很多好处，最核心的一点就是性能。在多线程领域，无状态对象没有线程安全问题，无需同步处理，自然性能很好；在分布式领域，无状态意味着可以无限地水平扩展，所以分布式领域里面性能的瓶颈一定不是出在无状态的服务节点上。&lt;/p&gt;

&lt;p&gt;CopyOnWriteArrayList 和 CopyOnWriteArraySet 这两个 Copy-on-Write 容器，它们背后的设计思想就是 Copy-on-Write
类 Unix 的操作系统中创建进程的 API 是 fork()，传统的 fork() 函数会创建父进程的一个完整副本，例如父进程的地址空间现在用到了 1G 的内存，那么 fork() 子进程的时候要复制父进程整个进程的地址空间（占有 1G 内存）给子进程，这个过程是很耗时的。而 Linux 中的 fork() 函数就聪明得多了，fork() 子进程的时候，并不复制整个进程的地址空间，而是让父子进程共享同一个地址空间；只用在父进程或者子进程需要写入的时候才会复制地址空间，从而使父子进程拥有各自的地址空间。&lt;/p&gt;

</description>
        <pubDate>Fri, 22 Jul 2022 00:00:00 +0000</pubDate>
        <link>https://seakingOne.github.io/article/2022/07/22/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B9%8B%E5%B9%B6%E5%8F%91%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FImmutability&Copy-on-Write.html</link>
        <guid isPermaLink="true">https://seakingOne.github.io/article/2022/07/22/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B9%8B%E5%B9%B6%E5%8F%91%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FImmutability&Copy-on-Write.html</guid>
        
        <category>Java</category>
        
        <category>并发</category>
        
        <category>并发设计模式</category>
        
        <category>Immutability</category>
        
        <category>Copy-on-Write</category>
        
        
        <category>Article</category>
        
      </item>
    
      <item>
        <title>并发编程之线程同步工具类</title>
        <description>&lt;p&gt;线程同步常见的操作方式：sleep，join，wait/notify，但是这种线程同步最好是在2个线程中执行，更多的线程其实是不太合适的，那我们今天就谈谈jdk中最新的同步工具类CountDownLatch，CountDownLatch中维护一个计数器&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// 创建2个线程的线程池
Executor executor = 
  Executors.newFixedThreadPool(2);
while(存在未对账订单){
  // 计数器初始化为2
  CountDownLatch latch = 
    new CountDownLatch(2);
  // 查询未对账订单
  executor.execute(()-&amp;gt; {
    pos = getPOrders();
    latch.countDown();
  });
  // 查询派送单
  executor.execute(()-&amp;gt; {
    dos = getDOrders();
    latch.countDown();
  });
  
  // 等待两个查询操作结束
  latch.await();
  
  // 执行对账操作
  diff = check(pos, dos);
  // 差异写入差异库
  save(diff);
}  
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;CyclicBarrier 的计数器有自动重置的功能，当减到 0 的时候，会自动重置你设置的初始值。这个功能用起来实在是太方便了&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// 订单队列
Vector&amp;lt;P&amp;gt; pos;
// 派送单队列
Vector&amp;lt;D&amp;gt; dos;
// 执行回调的线程池 
Executor executor = 
  Executors.newFixedThreadPool(1);
final CyclicBarrier barrier =
  new CyclicBarrier(2, ()-&amp;gt;{
    executor.execute(()-&amp;gt;check());
  });
  
void check(){
  P p = pos.remove(0);
  D d = dos.remove(0);
  // 执行对账操作
  diff = check(p, d);
  // 差异写入差异库
  save(diff);
}
  
void checkAll(){
  // 循环查询订单库
  Thread T1 = new Thread(()-&amp;gt;{
    while(存在未对账订单){
      // 查询订单库
      pos.add(getPOrders());
      // 等待
      barrier.await();
    }
  });
  T1.start();  
  // 循环查询运单库
  Thread T2 = new Thread(()-&amp;gt;{
    while(存在未对账订单){
      // 查询运单库
      dos.add(getDOrders());
      // 等待
      barrier.await();
    }
  });
  T2.start();
} 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;CountDownLatch 和 CyclicBarrier 是 Java 并发包提供的两个非常易用的线程同步工具类，这两个工具类用法的区别在这里还是有必要再强调一下：CountDownLatch 主要用来解决一个线程等待多个线程的场景，可以类比旅游团团长要等待所有的游客到齐才能去下一个景点；而 CyclicBarrier 是一组线程之间互相等待，更像是几个驴友之间不离不弃。除此之外 CountDownLatch 的计数器是不能循环利用的，也就是说一旦计数器减到 0，再有线程调用 await()，该线程会直接通过。但 CyclicBarrier 的计数器是可以循环利用的，而且具备自动重置的功能，一旦计数器减到 0 会自动重置到你设置的初始值。除此之外，CyclicBarrier 还可以设置回调函数，可以说是功能丰富。&lt;/p&gt;
</description>
        <pubDate>Tue, 19 Jul 2022 00:00:00 +0000</pubDate>
        <link>https://seakingOne.github.io/article/2022/07/19/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B9%8B%E7%BA%BF%E7%A8%8B%E5%90%8C%E6%AD%A5%E5%B7%A5%E5%85%B7%E7%B1%BB.html</link>
        <guid isPermaLink="true">https://seakingOne.github.io/article/2022/07/19/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B9%8B%E7%BA%BF%E7%A8%8B%E5%90%8C%E6%AD%A5%E5%B7%A5%E5%85%B7%E7%B1%BB.html</guid>
        
        <category>Java</category>
        
        <category>并发</category>
        
        <category>线程同步</category>
        
        
        <category>Article</category>
        
      </item>
    
      <item>
        <title>并发编程之异步编程/批量执行异步任务</title>
        <description>&lt;p&gt;我们都知道在异步中，不管是单个线程也好，线程池也好，可以通过实现自Callable或submit()提交一个任务执行后返回结果，返回的结果由Future在接受，获取到最终结果。对于传统的Future就不介绍了。&lt;/p&gt;

&lt;p&gt;CompletableFuture，无论是语义还是线程的维护都交由框架在做，开发人员只需要按照语法来进行操作，从而控制整个线程的操作并且得到执行结果&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;比如：

//任务1：洗水壶-&amp;gt;烧开水
CompletableFuture&amp;lt;Void&amp;gt; f1 = 
  CompletableFuture.runAsync(()-&amp;gt;{
  System.out.println(&quot;T1:洗水壶...&quot;);
  sleep(1, TimeUnit.SECONDS);

  System.out.println(&quot;T1:烧开水...&quot;);
  sleep(15, TimeUnit.SECONDS);
});
//任务2：洗茶壶-&amp;gt;洗茶杯-&amp;gt;拿茶叶
CompletableFuture&amp;lt;String&amp;gt; f2 = 
  CompletableFuture.supplyAsync(()-&amp;gt;{
  System.out.println(&quot;T2:洗茶壶...&quot;);
  sleep(1, TimeUnit.SECONDS);

  System.out.println(&quot;T2:洗茶杯...&quot;);
  sleep(2, TimeUnit.SECONDS);

  System.out.println(&quot;T2:拿茶叶...&quot;);
  sleep(1, TimeUnit.SECONDS);
  return &quot;龙井&quot;;
});
//任务3：任务1和任务2完成后执行：泡茶
CompletableFuture&amp;lt;String&amp;gt; f3 = 
  f1.thenCombine(f2, (__, tf)-&amp;gt;{
    System.out.println(&quot;T1:拿到茶叶:&quot; + tf);
    System.out.println(&quot;T1:泡茶...&quot;);
    return &quot;上茶:&quot; + tf;
  });
//等待任务3执行结果
System.out.println(f3.join());

void sleep(int t, TimeUnit u) {
  try {
    u.sleep(t);
  }catch(InterruptedException e){}
}
// 一次执行结果：
T1:洗水壶...
T2:洗茶壶...
T1:烧开水...
T2:洗茶杯...
T2:拿茶叶...
T1:拿到茶叶:龙井
T1:泡茶...
上茶:龙井
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;当然，我们也可以指定线程池&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;//使用默认线程池
static CompletableFuture&amp;lt;Void&amp;gt; 
  runAsync(Runnable runnable)
static &amp;lt;U&amp;gt; CompletableFuture&amp;lt;U&amp;gt; 
  supplyAsync(Supplier&amp;lt;U&amp;gt; supplier)
//可以指定线程池  
static CompletableFuture&amp;lt;Void&amp;gt; 
  runAsync(Runnable runnable, Executor executor)
static &amp;lt;U&amp;gt; CompletableFuture&amp;lt;U&amp;gt; 
  supplyAsync(Supplier&amp;lt;U&amp;gt; supplier, Executor executor)   
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;CompletionService：针对异步任务的批量执行，有这么一种执行方式&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// 创建阻塞队列
BlockingQueue&amp;lt;Integer&amp;gt; bq =
  new LinkedBlockingQueue&amp;lt;&amp;gt;();
//电商S1报价异步进入阻塞队列  
executor.execute(()-&amp;gt;
  bq.put(f1.get()));
//电商S2报价异步进入阻塞队列  
executor.execute(()-&amp;gt;
  bq.put(f2.get()));
//电商S3报价异步进入阻塞队列  
executor.execute(()-&amp;gt;
  bq.put(f3.get()));
//异步保存所有报价  
for (int i=0; i&amp;lt;3; i++) {
  Integer r = bq.take();
  executor.execute(()-&amp;gt;save(r));
}  

如果f1.get()执行的方式时间很长，会导致阻塞，从而后面的任务执行被耽误了，那么怎么处理这种情况呢  


// 使用CompletionService的方式执行
// 创建线程池
ExecutorService executor = 
  Executors.newFixedThreadPool(3);
// 创建CompletionService
CompletionService&amp;lt;Integer&amp;gt; cs = new 
  ExecutorCompletionService&amp;lt;&amp;gt;(executor);
// 异步向电商S1询价
cs.submit(()-&amp;gt;getPriceByS1());
// 异步向电商S2询价
cs.submit(()-&amp;gt;getPriceByS2());
// 异步向电商S3询价
cs.submit(()-&amp;gt;getPriceByS3());
// 将询价结果异步保存到数据库
for (int i=0; i&amp;lt;3; i++) {
  Integer r = cs.take().get();
  executor.execute(()-&amp;gt;save(r));
}      

CompletionService 的实现原理也是内部维护了一个阻塞队列，当任务执行结束就把任务的执行结果加入到阻塞队列中，不同的是 CompletionService 是把任务执行结果的 Future 对象加入到阻塞队列中，而上面的示例代码是把任务最终的执行结果放入了阻塞队列中，也就是说先执行完成的会被加入到队列中，先处理掉这个任务。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;类似实现的技术方案：Dubbo 中有一种叫做 Forking 的集群模式，这种集群模式下，支持并行地调用多个查询服务，只要有一个成功返回结果，整个服务就可以返回了。例如你需要提供一个地址转坐标的服务，为了保证该服务的高可用和性能，你可以并行地调用 3 个地图服务商的 API，然后只要有 1 个正确返回了结果 r，那么地址转坐标这个服务就可以直接返回 r 了。这种集群模式可以容忍 2 个地图服务商服务异常，但缺点是消耗的资源偏多。&lt;/p&gt;
</description>
        <pubDate>Tue, 19 Jul 2022 00:00:00 +0000</pubDate>
        <link>https://seakingOne.github.io/article/2022/07/19/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B9%8B%E5%BC%82%E6%AD%A5%E7%BC%96%E7%A8%8B%E8%8E%B7%E5%8F%96%E7%BB%93%E6%9E%9C.html</link>
        <guid isPermaLink="true">https://seakingOne.github.io/article/2022/07/19/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B9%8B%E5%BC%82%E6%AD%A5%E7%BC%96%E7%A8%8B%E8%8E%B7%E5%8F%96%E7%BB%93%E6%9E%9C.html</guid>
        
        <category>Java</category>
        
        <category>并发</category>
        
        <category>future</category>
        
        
        <category>Article</category>
        
      </item>
    
      <item>
        <title>并发编程之JDK无锁方案</title>
        <description>&lt;p&gt;其实原子类性能高的秘密很简单，硬件支持而已。CPU 为了解决并发问题，提供了 CAS 指令（CAS，全称是 Compare And Swap，即“比较并交换”）。CAS 指令包含 3 个参数：共享变量的内存地址 A、用于比较的值 B 和共享变量的新值 C；并且只有当内存中地址 A 处的值等于 B 时，才能将内存中地址 A 处的值更新为新值 C。作为一条 CPU 指令，CAS 指令本身是能够保证原子性的。&lt;/p&gt;

&lt;p&gt;看 Java 如何实现原子化的 count += 1，我们使用原子类 AtomicLong 的 getAndIncrement() 方法替代了count += 1，从而实现了线程安全。原子类 AtomicLong 的 getAndIncrement() 方法内部就是基于 CAS 实现的，下面我们来看看 Java 是如何使用 CAS 来实现原子化的count += 1的。在 Java 1.8 版本中，getAndIncrement() 方法会转调 unsafe.getAndAddLong() 方法。这里 this 和 valueOffset 两个参数可以唯一确定共享变量的内存地址&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;final long getAndIncrement() {
  return unsafe.getAndAddLong(
    this, valueOffset, 1L);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;unsafe.getAndAddLong() 方法的源码如下，该方法首先会在内存中读取共享变量的值，之后循环调用 compareAndSwapLong() 方法来尝试设置共享变量的值，直到成功为止。compareAndSwapLong() 是一个 native 方法，只有当内存中共享变量的值等于 expected 时，才会将共享变量的值更新为 x，并且返回 true；否则返回 fasle。compareAndSwapLong 的语义和 CAS 指令的语义的差别仅仅是返回值不同而已。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;public final long getAndAddLong(
  Object o, long offset, long delta){
  long v;
  do {
    // 读取内存中的值
    v = getLongVolatile(o, offset);
  } while (!compareAndSwapLong(
      o, offset, v, v + delta));
  return v;
}
//原子性地将变量更新为x
//条件是内存中的值等于expected
//更新成功则返回true
native boolean compareAndSwapLong(
  Object o, long offset, 
  long expected,
  long x);    
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</description>
        <pubDate>Tue, 19 Jul 2022 00:00:00 +0000</pubDate>
        <link>https://seakingOne.github.io/article/2022/07/19/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B9%8BJDK%E6%97%A0%E9%94%81%E6%96%B9%E6%A1%88.html</link>
        <guid isPermaLink="true">https://seakingOne.github.io/article/2022/07/19/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B9%8BJDK%E6%97%A0%E9%94%81%E6%96%B9%E6%A1%88.html</guid>
        
        <category>Java</category>
        
        <category>并发</category>
        
        <category>无锁方案</category>
        
        
        <category>Article</category>
        
      </item>
    
      <item>
        <title>并发编程之读写缓存</title>
        <description>&lt;p&gt;前面简单了解了下管程与信号量，理论上用这2个可以解决所有的并发问题，那么Java jdk并发包里面还有很多其他的工具类，原因：分场景优化性能，提升易用性。一种常见的使用场景，读多写少场景ReadWriteLock。&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;读写锁，并不是 Java 语言特有的，而是一个广为使用的通用技术，所有的读写锁都遵守以下三条基本原则：允许多个线程同时读共享变量；只允许一个线程写共享变量；如果一个写线程正在执行写操作，此时禁止读线程读共享变量。&lt;br /&gt;
读写锁与互斥锁的一个重要区别就是读写锁允许多个线程同时读共享变量，而互斥锁是不允许的，这是读写锁在读多写少场景下性能优于互斥锁的关键。但读写锁的写操作是互斥的，当一个线程在写共享变量的时候，是不允许其他线程执行写操作和读操作。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;class Cache&amp;lt;K,V&amp;gt; {
  final Map&amp;lt;K, V&amp;gt; m =
    new HashMap&amp;lt;&amp;gt;();
  final ReadWriteLock rwl = 
    new ReentrantReadWriteLock();
  final Lock r = rwl.readLock();
  final Lock w = rwl.writeLock();
 
  V get(K key) {
    V v = null;
    //读缓存
    r.lock();         
    try {
      v = m.get(key); 
    } finally{
      r.unlock();     
    }
    //缓存中存在，返回
    if(v != null) {   
      return v;
    }  
    //缓存中不存在，查询数据库
    w.lock();         
    try {
      //再次验证
      //其他线程可能已经查询过数据库
      v = m.get(key); 
      if(v == null){  
        //查询数据库
        v=省略代码无数
        m.put(key, v);
      }
    } finally{
      w.unlock();
    }
    return v; 
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;StampedLock，ReadWriteLock 支持两种模式：一种是读锁，一种是写锁。而 StampedLock 支持三种模式，分别是：写锁、悲观读锁和乐观读。其中，写锁、悲观读锁的语义和 ReadWriteLock 的写锁、读锁的语义非常类似，允许多个线程同时获取悲观读锁，但是只允许一个线程获取写锁，写锁和悲观读锁是互斥的。不同的是：StampedLock 里的写锁和悲观读锁加锁成功之后，都会返回一个 stamp；然后解锁的时候，需要传入这个 stamp。相关的示例代码如下。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;final StampedLock sl = 
  new StampedLock();
  
// 获取/释放悲观读锁示意代码
long stamp = sl.readLock();
try {
  //省略业务相关代码
} finally {
  sl.unlockRead(stamp);
}

// 获取/释放写锁示意代码
long stamp = sl.writeLock();
try {
  //省略业务相关代码
} finally {
  sl.unlockWrite(stamp);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;StampedLock 的性能之所以比 ReadWriteLock 还要好，其关键是 StampedLock 支持乐观读的方式。ReadWriteLock 支持多个线程同时读，但是当多个线程同时读的时候，所有的写操作会被阻塞；而 StampedLock 提供的乐观读，是允许一个线程获取写锁的，也就是说不是所有的写操作都被阻塞。乐观读这个操作是无锁的，所以相比较 ReadWriteLock 的读锁，乐观读的性能更好一些&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;class Point {
  private int x, y;
  final StampedLock sl = 
    new StampedLock();
  //计算到原点的距离  
  int distanceFromOrigin() {
    // 乐观读
    long stamp = 
      sl.tryOptimisticRead();
    // 读入局部变量，
    // 读的过程数据可能被修改
    int curX = x, curY = y;
    //判断执行读操作期间，
    //是否存在写操作，如果存在，
    //则sl.validate返回false
    if (!sl.validate(stamp)){
      // 升级为悲观读锁
      stamp = sl.readLock();
      try {
        curX = x;
        curY = y;
      } finally {
        //释放悲观读锁
        sl.unlockRead(stamp);
      }
    }
    return Math.sqrt(
      curX * curX + curY * curY);
  }
}    
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</description>
        <pubDate>Mon, 18 Jul 2022 00:00:00 +0000</pubDate>
        <link>https://seakingOne.github.io/article/2022/07/18/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B9%8B%E8%AF%BB%E5%86%99%E7%BC%93%E5%AD%98.html</link>
        <guid isPermaLink="true">https://seakingOne.github.io/article/2022/07/18/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B9%8B%E8%AF%BB%E5%86%99%E7%BC%93%E5%AD%98.html</guid>
        
        <category>Java</category>
        
        <category>并发</category>
        
        <category>读写锁</category>
        
        
        <category>Article</category>
        
      </item>
    
      <item>
        <title>并发编程之管程与信号量</title>
        <description>&lt;p&gt;大家都知道，一般我们所遇到的并发问题的根源来自于数据可见性、编译器的代码执行优化这2点
数据可见性：多核cpu缓存导致出现的问题
编译器优化：优化代码的结构和执行顺序&lt;br /&gt;
常见的优化手段也是比较清楚的，加Volatile关键字，可以禁用cpu的缓存&lt;/p&gt;

&lt;p&gt;而我们今天介绍的管程与信号量，也是解决并发问题的万能钥匙&lt;br /&gt;
Java所采用的管程技术，synchronized, wait,notify, notifyAll这三个方法都是管程的组成部分，管程与信号量是等价的，
但是管程更容易使用，所以Java选择了管程&lt;/p&gt;

&lt;p&gt;信号量：Semaphore
整体来说，Semaphore模型是比较简单的，简单来说，一个计数器，一个等待队列，三个方法（init， down， up）
init用于设置计数器的初始值，down计数器-1，如果当前计数器值&amp;lt;0，当前线程被阻塞，up计数器+1，如果此时计数器值&amp;lt;=0，唤醒
等待队列中得一个线程，并从等待队列中移除，这里的三个方法都是原子性的，比如&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;static int count;
//初始化信号量
static final Semaphore s = new Semaphore(1);
//互斥
static void addOne() {

    s.acquire();
    try {
        count += 1; 
    } finally {
        s.release();
    }

}

信号量是如何保证互斥的，假设2个线程T1和T2同时访问addOne()方法，当他们同时调用acquire（）的时候，由于acquire是一个原子操作，
所以只能有一个线程（T1）把信号量里的计数器减为0，另外一个T2则是把计数器值变为-1,从这边看到，其实信号量适合做一个限流器,限流器使用lock来实现同样需要自定义


class ObjPool&amp;lt;T, R&amp;gt; {
  final List&amp;lt;T&amp;gt; pool;
  // 用信号量实现限流器
  final Semaphore sem;
  // 构造函数
  ObjPool(int size, T t){
    pool = new Vector&amp;lt;T&amp;gt;(){};
    for(int i=0; i&amp;lt;size; i++){
      pool.add(t);
    }
    sem = new Semaphore(size);
  }
  // 利用对象池的对象，调用func
  R exec(Function&amp;lt;T,R&amp;gt; func) {
    T t = null;
    sem.acquire();
    try {
      t = pool.remove(0);
      return func.apply(t);
    } finally {
      pool.add(t);
      sem.release();
    }
  }
}
// 创建对象池
ObjPool&amp;lt;Long, String&amp;gt; pool = 
  new ObjPool&amp;lt;Long, String&amp;gt;(10, 2);
// 通过对象池获取t，之后执行  
pool.exec(t -&amp;gt; {
    System.out.println(t);
    return t.toString();
});
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Java jdk并发包内容很丰富，最明显的还是Lock和Condition2个接口来实现管程，其中Lock用于解决互斥问题，Condition用于解决
同步问题，其实可以看到，Semaphore与管程实现思路是一致的，可以用Semaphore来实现管程，也可以用管程来实现Semaphore，只不过
Semaphore需要手动去实现类似Condition条件变量的功能，来实现类似生产者消费者的功能。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;比如我们使用管程模拟一个生产者消费者模式
ReentrantLock lock = new ReentrantLock();
//条件变量，队列不满
Condition notFull = lock.newCondition();
//条件变量，队列不空
Condition notEmpty = lock.newCondition();

void produceData() {

    lock.lock();
    
    try {
    
        while(队列满了) {
            //生产者阻塞，不加数据
            notFull.await();
        }
        
        // 入队操作
        
        // 入队成功后，有数据，可以消费
        notEmpty.signal();
        
    } finally {
        lock.unlock();
    }

}

void cusData() {

    lock.lock();
    
    try {
    
        while(队列为空) {
            notEmpty.await();
        }
        
        // 消费数据
        
        
        // 消费完成后，可以再加新的数据
        notFull.singal();
    
    } finally {
        lock.unlock();
    }

}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;值得注意的是，如果你使用过dubbo，清楚他的一个调用流程，其实是client阻塞等待获取server端返回的结果，其实调用线程是处理阻塞状态的，也就是异步转换为同步，这个时候dubbo采用的技术
原理也是通过管程实现的。&lt;/p&gt;

</description>
        <pubDate>Sun, 17 Jul 2022 00:00:00 +0000</pubDate>
        <link>https://seakingOne.github.io/article/2022/07/17/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B9%8B%E7%AE%A1%E7%A8%8B%E4%B8%8E%E4%BF%A1%E5%8F%B7%E9%87%8F.html</link>
        <guid isPermaLink="true">https://seakingOne.github.io/article/2022/07/17/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B9%8B%E7%AE%A1%E7%A8%8B%E4%B8%8E%E4%BF%A1%E5%8F%B7%E9%87%8F.html</guid>
        
        <category>Java</category>
        
        <category>并发</category>
        
        
        <category>Article</category>
        
      </item>
    
      <item>
        <title>nginx源码解析之启动流程</title>
        <description>&lt;p&gt;Nginx的主流程的实现函数在./src/core/nginx.c文件中。通过main()函数，我们可以看到整个Nginx启动的流程。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;int ngx_cdecl
main(int argc, char *const *argv) {

    ....
    
    // 比如我们执行./nginx -s stop|start|restart
    if (ngx_get_options(argc, argv) != NGX_OK) {
        return 1;
    }
    
    // 获取当前进程ID
    ngx_pid = ngx_getpid();
    
    // 初始化log，此方法中会创建文件句柄，用于文件读写
    log = ngx_log_init(ngx_prefix);
    
    // 初始化Nginx的init_cycle
    ngx_memzero(&amp;amp;init_cycle, sizeof(ngx_cycle_t));
    init_cycle.log = log;
    ngx_cycle = &amp;amp;init_cycle;
     
    // 创建内存池 默认大小1024
    init_cycle.pool = ngx_create_pool(1024, log);
    if (init_cycle.pool == NULL) {
        return 1;
    }
    
    // 正式初始化，这个是nginx的全局变量，ngx_init_cycle这个方法很重要，在后续单独讨论
    cycle = ngx_init_cycle(&amp;amp;init_cycle);
    
    // 多进程循环机制,这个也在后续中做介绍
    if (ngx_process == NGX_PROCESS_SINGLE) {
        ngx_single_process_cycle(cycle);

    } else {
        ngx_master_process_cycle(cycle);
    }

}

static ngx_int_t
ngx_get_options(int argc, char *const *argv)
{
    u_char     *p;
    ngx_int_t   i;

    for (i = 1; i &amp;lt; argc; i++) {

        p = (u_char *) argv[i];

        if (*p++ != &apos;-&apos;) {
            ngx_log_stderr(0, &quot;invalid option: \&quot;%s\&quot;&quot;, argv[i]);
            return NGX_ERROR;
        }

        while (*p) {

            switch (*p++) {

            case &apos;?&apos;:
            case &apos;h&apos;:
                ngx_show_version = 1;
                ngx_show_help = 1;
                break;

            case &apos;v&apos;:
                ngx_show_version = 1;
                break;

            case &apos;V&apos;:
                ngx_show_version = 1;
                ngx_show_configure = 1;
                break;

            case &apos;t&apos;:
                ngx_test_config = 1;
                break;

            case &apos;T&apos;:
                ngx_test_config = 1;
                ngx_dump_config = 1;
                break;

            case &apos;q&apos;:
                ngx_quiet_mode = 1;
                break;

            case &apos;p&apos;:
                if (*p) {
                    ngx_prefix = p;
                    goto next;
                }

                if (argv[++i]) {
                    ngx_prefix = (u_char *) argv[i];
                    goto next;
                }

                ngx_log_stderr(0, &quot;option \&quot;-p\&quot; requires directory name&quot;);
                return NGX_ERROR;

            case &apos;c&apos;:
                if (*p) {
                    ngx_conf_file = p;
                    goto next;
                }

                if (argv[++i]) {
                    ngx_conf_file = (u_char *) argv[i];
                    goto next;
                }

                ngx_log_stderr(0, &quot;option \&quot;-c\&quot; requires file name&quot;);
                return NGX_ERROR;

            case &apos;g&apos;:
                if (*p) {
                    ngx_conf_params = p;
                    goto next;
                }

                if (argv[++i]) {
                    ngx_conf_params = (u_char *) argv[i];
                    goto next;
                }

                ngx_log_stderr(0, &quot;option \&quot;-g\&quot; requires parameter&quot;);
                return NGX_ERROR;

            case &apos;s&apos;:
                if (*p) {
                    ngx_signal = (char *) p;

                } else if (argv[++i]) {
                    ngx_signal = argv[i];

                } else {
                    ngx_log_stderr(0, &quot;option \&quot;-s\&quot; requires parameter&quot;);
                    return NGX_ERROR;
                }

                if (ngx_strcmp(ngx_signal, &quot;stop&quot;) == 0
                    || ngx_strcmp(ngx_signal, &quot;quit&quot;) == 0
                    || ngx_strcmp(ngx_signal, &quot;reopen&quot;) == 0
                    || ngx_strcmp(ngx_signal, &quot;reload&quot;) == 0)
                {
                    ngx_process = NGX_PROCESS_SIGNALLER;
                    goto next;
                }

                ngx_log_stderr(0, &quot;invalid option: \&quot;-s %s\&quot;&quot;, ngx_signal);
                return NGX_ERROR;

            default:
                ngx_log_stderr(0, &quot;invalid option: \&quot;%c\&quot;&quot;, *(p - 1));
                return NGX_ERROR;
            }
        }

    next:

        continue;
    }

    return NGX_OK;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

</description>
        <pubDate>Wed, 19 Jan 2022 00:00:00 +0000</pubDate>
        <link>https://seakingOne.github.io/nginx/2022/01/19/nginx%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90%E4%B9%8B%E5%90%AF%E5%8A%A8%E6%B5%81%E7%A8%8B.html</link>
        <guid isPermaLink="true">https://seakingOne.github.io/nginx/2022/01/19/nginx%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90%E4%B9%8B%E5%90%AF%E5%8A%A8%E6%B5%81%E7%A8%8B.html</guid>
        
        <category>C/C++</category>
        
        <category>Nginx</category>
        
        <category>启动流程</category>
        
        
        <category>Nginx</category>
        
      </item>
    
      <item>
        <title>redis源码解析之lua脚本</title>
        <description>&lt;p&gt;Redis是从2.6版本引入对Lua脚本的支持，通过在服务器中嵌入lua环境，Redis可以使用lua脚本，直接在服务端上原子执行多个Redis命令&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;比如：
redis&amp;gt; eval &quot;return &apos;hello world&apos;&quot; 0
&quot;hello world&quot;

而使用evalsha命令则可以根据脚本进行求值
redis&amp;gt; eval &quot;return 1+1&quot; 0
2

redis&amp;gt; evalsha &quot;e222sadadsadasdadaasd..&quot; 0
2

或者是通过script load命令载入
redis&amp;gt; script load &quot;return 2*2&quot;
&quot;xxxxxxxx....&quot;

redis&amp;gt; evalsha &quot;xxxxxxxx....&quot; 0
4
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;在此之前我们需要知道服务端是怎么初始化lua的环境的&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;在redis.c/initServer方法中，我们发现scriptingInit()方法
/* Initialize the scripting environment.
 * It is possible to call this function to reset the scripting environment
 * assuming that we call scriptingRelease() before.
 * See scriptingReset() for more information. */
void scriptingInit(void) {
    lua_State *lua = lua_open();

    //加载lua的库
    luaLoadLibraries(lua);
    //移除loadfile方法，防止加载外部文件不安全代码
    luaRemoveUnsupportedFunctions(lua);

    //server中保存当前脚本的关系
    server.lua_scripts = dictCreate(&amp;amp;shaScriptObjectDictType,NULL);

    //table是lua中最基本的数据结构
    lua_newtable(lua);

    /* redis.call */
    lua_pushstring(lua,&quot;call&quot;); //前面为table结构，压入key
    lua_pushcfunction(lua,luaRedisCallCommand); //压入value
    lua_settable(lua,-3);   //弹出key value 设置到table中

    /* redis.pcall */
    lua_pushstring(lua,&quot;pcall&quot;);
    lua_pushcfunction(lua,luaRedisPCallCommand);
    lua_settable(lua,-3);

    /* redis.log and log levels. */
    lua_pushstring(lua,&quot;log&quot;);
    lua_pushcfunction(lua,luaLogCommand);
    lua_settable(lua,-3);

    lua_pushstring(lua,&quot;LOG_DEBUG&quot;);
    lua_pushnumber(lua,REDIS_DEBUG);
    lua_settable(lua,-3);

    lua_pushstring(lua,&quot;LOG_VERBOSE&quot;);
    lua_pushnumber(lua,REDIS_VERBOSE);
    lua_settable(lua,-3);

    lua_pushstring(lua,&quot;LOG_NOTICE&quot;);
    lua_pushnumber(lua,REDIS_NOTICE);
    lua_settable(lua,-3);

    lua_pushstring(lua,&quot;LOG_WARNING&quot;);
    lua_pushnumber(lua,REDIS_WARNING);
    lua_settable(lua,-3);

    /* redis.sha1hex */
    lua_pushstring(lua, &quot;sha1hex&quot;);
    lua_pushcfunction(lua, luaRedisSha1hexCommand);
    lua_settable(lua, -3);

    /* redis.error_reply and redis.status_reply */
    lua_pushstring(lua, &quot;error_reply&quot;);
    lua_pushcfunction(lua, luaRedisErrorReplyCommand);
    lua_settable(lua, -3);
    lua_pushstring(lua, &quot;status_reply&quot;);
    lua_pushcfunction(lua, luaRedisStatusReplyCommand);
    lua_settable(lua, -3);

    /* Finally set the table as &apos;redis&apos; global var. */
    lua_setglobal(lua,&quot;redis&quot;);

    /* Replace math.random and math.randomseed with our implementations. */
    lua_getglobal(lua,&quot;math&quot;);

    lua_pushstring(lua,&quot;random&quot;);
    lua_pushcfunction(lua,redis_math_random);
    lua_settable(lua,-3);

    lua_pushstring(lua,&quot;randomseed&quot;);
    lua_pushcfunction(lua,redis_math_randomseed);
    lua_settable(lua,-3);

    lua_setglobal(lua,&quot;math&quot;);

    /* Add a helper function that we use to sort the multi bulk output of non
     * deterministic commands, when containing &apos;false&apos; elements. */
    //该函数是用于比较元素信息做排序
    {
        char *compare_func =    &quot;function __redis__compare_helper(a,b)\n&quot;
                                &quot;  if a == false then a = &apos;&apos; end\n&quot;
                                &quot;  if b == false then b = &apos;&apos; end\n&quot;
                                &quot;  return a&amp;lt;b\n&quot;
                                &quot;end\n&quot;;
        luaL_loadbuffer(lua,compare_func,strlen(compare_func),&quot;@cmp_func_def&quot;);
        lua_pcall(lua,0,0,0);
    }

    /* Add a helper function we use for pcall error reporting.
     * Note that when the error is in the C function we want to report the
     * information about the caller, that&apos;s what makes sense from the point
     * of view of the user debugging a script. */
    {
        char *errh_func =       &quot;local dbg = debug\n&quot;
                                &quot;function __redis__err__handler(err)\n&quot;
                                &quot;  local i = dbg.getinfo(2,&apos;nSl&apos;)\n&quot;
                                &quot;  if i and i.what == &apos;C&apos; then\n&quot;
                                &quot;    i = dbg.getinfo(3,&apos;nSl&apos;)\n&quot;
                                &quot;  end\n&quot;
                                &quot;  if i then\n&quot;
                                &quot;    return i.source .. &apos;:&apos; .. i.currentline .. &apos;: &apos; .. err\n&quot;
                                &quot;  else\n&quot;
                                &quot;    return err\n&quot;
                                &quot;  end\n&quot;
                                &quot;end\n&quot;;
        luaL_loadbuffer(lua,errh_func,strlen(errh_func),&quot;@err_handler_def&quot;);
        lua_pcall(lua,0,0,0);
    }

    /* Create the (non connected) client that we use to execute Redis commands
     * inside the Lua interpreter.
     * Note: there is no need to create it again when this function is called
     * by scriptingReset(). */
    if (server.lua_client == NULL) {
        server.lua_client = createClient(-1);
        server.lua_client-&amp;gt;flags |= REDIS_LUA_CLIENT;
    }

    /* Lua beginners often don&apos;t use &quot;local&quot;, this is likely to introduce
     * subtle bugs in their code. To prevent problems we protect accesses
     * to global variables. */
    scriptingEnableGlobalsProtection(lua);

    server.lua = lua;
}

-----------------------------------------------------------

//加载库的信息
void luaLoadLibraries(lua_State *lua) {
    luaLoadLib(lua, &quot;&quot;, luaopen_base);
    luaLoadLib(lua, LUA_TABLIBNAME, luaopen_table);
    luaLoadLib(lua, LUA_STRLIBNAME, luaopen_string);
    luaLoadLib(lua, LUA_MATHLIBNAME, luaopen_math);
    luaLoadLib(lua, LUA_DBLIBNAME, luaopen_debug);
    luaLoadLib(lua, &quot;cjson&quot;, luaopen_cjson);
    luaLoadLib(lua, &quot;struct&quot;, luaopen_struct);
    luaLoadLib(lua, &quot;cmsgpack&quot;, luaopen_cmsgpack);
    luaLoadLib(lua, &quot;bit&quot;, luaopen_bit);

#if 0 /* Stuff that we don&apos;t load currently, for sandboxing concerns. */
    luaLoadLib(lua, LUA_LOADLIBNAME, luaopen_package);
    luaLoadLib(lua, LUA_OSLIBNAME, luaopen_os);
#endif
}

-----------------------------------------------------------

/* Remove a functions that we don&apos;t want to expose to the Redis scripting
 * environment. */
void luaRemoveUnsupportedFunctions(lua_State *lua) {
    lua_pushnil(lua);
    lua_setglobal(lua,&quot;loadfile&quot;);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;从上面的模块我们知道lua脚本会执行调度Redis命令，执行Redis必须要有客户端状态，所以为了执行lua脚本中包含的Redis命令，Redis服务器为lua环境创建了一个伪客户端&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;以pcall、call为例执行一个函数
int luaRedisCallCommand(lua_State *lua) {
    return luaRedisGenericCommand(lua,1);
}

int luaRedisPCallCommand(lua_State *lua) {
    return luaRedisGenericCommand(lua,0);
}

#define LUA_CMD_OBJCACHE_SIZE 32
#define LUA_CMD_OBJCACHE_MAX_LEN 64
int luaRedisGenericCommand(lua_State *lua, int raise_error) {
    int j, argc = lua_gettop(lua);
    struct redisCommand *cmd;
    
    //当前这个伪客户端在redis server初始化时创建
    redisClient *c = server.lua_client;
    sds reply;

    /* Cached across calls. */
    static robj **argv = NULL;
    static int argv_size = 0;
    static robj *cached_objects[LUA_CMD_OBJCACHE_SIZE];
    static size_t cached_objects_len[LUA_CMD_OBJCACHE_SIZE];
    static int inuse = 0;   /* Recursive calls detection. */

    /* By using Lua debug hooks it is possible to trigger a recursive call
     * to luaRedisGenericCommand(), which normally should never happen.
     * To make this function reentrant is futile and makes it slower, but
     * we should at least detect such a misuse, and abort. */
    if (inuse) {
        char *recursion_warning =
            &quot;luaRedisGenericCommand() recursive call detected. &quot;
            &quot;Are you doing funny stuff with Lua debug hooks?&quot;;
        redisLog(REDIS_WARNING,&quot;%s&quot;,recursion_warning);
        luaPushError(lua,recursion_warning);
        return 1;
    }
    inuse++;

    /* Require at least one argument */
    if (argc == 0) {
        luaPushError(lua,
            &quot;Please specify at least one argument for redis.call()&quot;);
        inuse--;
        return 1;
    }

    /* Build the arguments vector */
    if (argv_size &amp;lt; argc) {
        argv = zrealloc(argv,sizeof(robj*)*argc);
        argv_size = argc;
    }

    for (j = 0; j &amp;lt; argc; j++) {
        char *obj_s;
        size_t obj_len;
        char dbuf[64];

        if (lua_type(lua,j+1) == LUA_TNUMBER) {
            /* We can&apos;t use lua_tolstring() for number -&amp;gt; string conversion
             * since Lua uses a format specifier that loses precision. */
            lua_Number num = lua_tonumber(lua,j+1);

            obj_len = snprintf(dbuf,sizeof(dbuf),&quot;%.17g&quot;,(double)num);
            obj_s = dbuf;
        } else {
            obj_s = (char*)lua_tolstring(lua,j+1,&amp;amp;obj_len);
            if (obj_s == NULL) break; /* Not a string. */
        }

        /* Try to use a cached object. */
        if (j &amp;lt; LUA_CMD_OBJCACHE_SIZE &amp;amp;&amp;amp; cached_objects[j] &amp;amp;&amp;amp;
            cached_objects_len[j] &amp;gt;= obj_len)
        {
            char *s = cached_objects[j]-&amp;gt;ptr;
            struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));

            argv[j] = cached_objects[j];
            cached_objects[j] = NULL;
            memcpy(s,obj_s,obj_len+1);
            sh-&amp;gt;free += (int)(sh-&amp;gt;len - obj_len);                               WIN_PORT_FIX /* cast (int) */
            sh-&amp;gt;len = (int)obj_len;                                             WIN_PORT_FIX /* cast (int) */
        } else {
            argv[j] = createStringObject(obj_s, obj_len);
        }
    }

    /* Check if one of the arguments passed by the Lua script
     * is not a string or an integer (lua_isstring() return true for
     * integers as well). */
    if (j != argc) {
        j--;
        while (j &amp;gt;= 0) {
            decrRefCount(argv[j]);
            j--;
        }
        luaPushError(lua,
            &quot;Lua redis() command arguments must be strings or integers&quot;);
        inuse--;
        return 1;
    }

    /* Setup our fake client for command execution */
    c-&amp;gt;argv = argv;
    c-&amp;gt;argc = argc;

    /* Command lookup */
    cmd = lookupCommand(argv[0]-&amp;gt;ptr);
    if (!cmd || ((cmd-&amp;gt;arity &amp;gt; 0 &amp;amp;&amp;amp; cmd-&amp;gt;arity != argc) ||
                   (argc &amp;lt; -cmd-&amp;gt;arity)))
    {
        if (cmd)
            luaPushError(lua,
                &quot;Wrong number of args calling Redis command From Lua script&quot;);
        else
            luaPushError(lua,&quot;Unknown Redis command called from Lua script&quot;);
        goto cleanup;
    }
    c-&amp;gt;cmd = cmd;

    /* There are commands that are not allowed inside scripts. */
    if (cmd-&amp;gt;flags &amp;amp; REDIS_CMD_NOSCRIPT) {
        luaPushError(lua, &quot;This Redis command is not allowed from scripts&quot;);
        goto cleanup;
    }

    /* Write commands are forbidden against read-only slaves, or if a
     * command marked as non-deterministic was already called in the context
     * of this script. */
    if (cmd-&amp;gt;flags &amp;amp; REDIS_CMD_WRITE) {
        if (server.lua_random_dirty) {
            luaPushError(lua,
                &quot;Write commands not allowed after non deterministic commands&quot;);
            goto cleanup;
        } else if (server.masterhost &amp;amp;&amp;amp; server.repl_slave_ro &amp;amp;&amp;amp;
                   !server.loading &amp;amp;&amp;amp;
                   !(server.lua_caller-&amp;gt;flags &amp;amp; REDIS_MASTER))
        {
            luaPushError(lua, shared.roslaveerr-&amp;gt;ptr);
            goto cleanup;
        } else if (server.stop_writes_on_bgsave_err &amp;amp;&amp;amp;
                   server.saveparamslen &amp;gt; 0 &amp;amp;&amp;amp;
                   server.lastbgsave_status == REDIS_ERR)
        {
            luaPushError(lua, shared.bgsaveerr-&amp;gt;ptr);
            goto cleanup;
        }
    }

    /* If we reached the memory limit configured via maxmemory, commands that
     * could enlarge the memory usage are not allowed, but only if this is the
     * first write in the context of this script, otherwise we can&apos;t stop
     * in the middle. */
    if (server.maxmemory &amp;amp;&amp;amp; server.lua_write_dirty == 0 &amp;amp;&amp;amp;
        (cmd-&amp;gt;flags &amp;amp; REDIS_CMD_DENYOOM))
    {
        if (freeMemoryIfNeeded() == REDIS_ERR) {
            luaPushError(lua, shared.oomerr-&amp;gt;ptr);
            goto cleanup;
        }
    }

    if (cmd-&amp;gt;flags &amp;amp; REDIS_CMD_RANDOM) server.lua_random_dirty = 1;
    if (cmd-&amp;gt;flags &amp;amp; REDIS_CMD_WRITE) server.lua_write_dirty = 1;

    /* If this is a Redis Cluster node, we need to make sure Lua is not
     * trying to access non-local keys, with the exception of commands
     * received from our master. */
    if (server.cluster_enabled &amp;amp;&amp;amp; !(server.lua_caller-&amp;gt;flags &amp;amp; REDIS_MASTER)) {
        /* Duplicate relevant flags in the lua client. */
        c-&amp;gt;flags &amp;amp;= ~(REDIS_READONLY|REDIS_ASKING);
        c-&amp;gt;flags |= server.lua_caller-&amp;gt;flags &amp;amp; (REDIS_READONLY|REDIS_ASKING);
        if (getNodeByQuery(c,c-&amp;gt;cmd,c-&amp;gt;argv,c-&amp;gt;argc,NULL,NULL) !=
                           server.cluster-&amp;gt;myself)
        {
            luaPushError(lua,
                &quot;Lua script attempted to access a non local key in a &quot;
                &quot;cluster node&quot;);
            goto cleanup;
        }
    }

    /* Run the command */
    call(c,REDIS_CALL_SLOWLOG | REDIS_CALL_STATS);

    /* Convert the result of the Redis command into a suitable Lua type.
     * The first thing we need is to create a single string from the client
     * output buffers. */
    if (listLength(c-&amp;gt;reply) == 0 &amp;amp;&amp;amp; c-&amp;gt;bufpos &amp;lt; REDIS_REPLY_CHUNK_BYTES) {
        /* This is a fast path for the common case of a reply inside the
         * client static buffer. Don&apos;t create an SDS string but just use
         * the client buffer directly. */
        c-&amp;gt;buf[c-&amp;gt;bufpos] = &apos;\0&apos;;
        reply = c-&amp;gt;buf;
        c-&amp;gt;bufpos = 0;
    } else {
        reply = sdsnewlen(c-&amp;gt;buf,c-&amp;gt;bufpos);
        c-&amp;gt;bufpos = 0;
        while(listLength(c-&amp;gt;reply)) {
            robj *o = listNodeValue(listFirst(c-&amp;gt;reply));

            reply = sdscatlen(reply,o-&amp;gt;ptr,sdslen(o-&amp;gt;ptr));
            listDelNode(c-&amp;gt;reply,listFirst(c-&amp;gt;reply));
        }
    }
    if (raise_error &amp;amp;&amp;amp; reply[0] != &apos;-&apos;) raise_error = 0;
    redisProtocolToLuaType(lua,reply);
    /* Sort the output array if needed, assuming it is a non-null multi bulk
     * reply as expected. */
    if ((cmd-&amp;gt;flags &amp;amp; REDIS_CMD_SORT_FOR_SCRIPT) &amp;amp;&amp;amp;
        (reply[0] == &apos;*&apos; &amp;amp;&amp;amp; reply[1] != &apos;-&apos;)) {
            luaSortArray(lua);
    }
    if (reply != c-&amp;gt;buf) sdsfree(reply);
    c-&amp;gt;reply_bytes = 0;

cleanup:
    /* Clean up. Command code may have changed argv/argc so we use the
     * argv/argc of the client instead of the local variables. */
    for (j = 0; j &amp;lt; c-&amp;gt;argc; j++) {
        robj *o = c-&amp;gt;argv[j];

        /* Try to cache the object in the cached_objects array.
         * The object must be small, SDS-encoded, and with refcount = 1
         * (we must be the only owner) for us to cache it. */
        if (j &amp;lt; LUA_CMD_OBJCACHE_SIZE &amp;amp;&amp;amp;
            o-&amp;gt;refcount == 1 &amp;amp;&amp;amp;
            (o-&amp;gt;encoding == REDIS_ENCODING_RAW ||
             o-&amp;gt;encoding == REDIS_ENCODING_EMBSTR) &amp;amp;&amp;amp;
            sdslen(o-&amp;gt;ptr) &amp;lt;= LUA_CMD_OBJCACHE_MAX_LEN)
        {
            struct sdshdr *sh = (void*)(((char*)(o-&amp;gt;ptr))-(sizeof(struct sdshdr)));

            if (cached_objects[j]) decrRefCount(cached_objects[j]);
            cached_objects[j] = o;
            cached_objects_len[j] = sh-&amp;gt;free + sh-&amp;gt;len;
        } else {
            decrRefCount(o);
        }
    }

    if (c-&amp;gt;argv != argv) {
        zfree(c-&amp;gt;argv);
        argv = NULL;
        argv_size = 0;
    }

    if (raise_error) {
        /* If we are here we should have an error in the stack, in the
         * form of a table with an &quot;err&quot; field. Extract the string to
         * return the plain error. */
        lua_pushstring(lua,&quot;err&quot;);
        lua_gettable(lua,-2);
        inuse--;
        return lua_error(lua);
    }
    inuse--;
    return 1;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</description>
        <pubDate>Thu, 06 Jan 2022 00:00:00 +0000</pubDate>
        <link>https://seakingOne.github.io/database/2022/01/06/redis%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90%E4%B9%8Blua%E8%84%9A%E6%9C%AC.html</link>
        <guid isPermaLink="true">https://seakingOne.github.io/database/2022/01/06/redis%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90%E4%B9%8Blua%E8%84%9A%E6%9C%AC.html</guid>
        
        <category>C/C++</category>
        
        <category>redis</category>
        
        <category>lua</category>
        
        
        <category>Database</category>
        
      </item>
    
      <item>
        <title>redis源码解析之事务处理</title>
        <description>&lt;p&gt;Redis通过multi，exec，watch等命令来实现事务功能，事务提供了一种将多个命令请求打包&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;比如：
redis&amp;gt; multi
OK

redis&amp;gt; set &quot;name&quot; &quot;test&quot;
QUEUED

redis&amp;gt; exec
OK
&quot;test&quot;

从上面看出来，一个事务从开始到结束通常会经历下面三个阶段
1）事务开始
2）命令入队列
3）事务执行
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;代码逻辑如下：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;{&quot;multi&quot;,multiCommand,1,&quot;rsF&quot;,0,NULL,0,0,0,0,0},
void multiCommand(redisClient *c) {
    if (c-&amp;gt;flags &amp;amp; REDIS_MULTI) {
        addReplyError(c,&quot;MULTI calls can not be nested&quot;);
        return;
    }
    c-&amp;gt;flags |= REDIS_MULTI;
    addReply(c,shared.ok);
}

//如果是事务提交，在公共的方法中做判断，在redis.c/processCommand方法中
/* Exec the command */
if (c-&amp;gt;flags &amp;amp; REDIS_MULTI &amp;amp;&amp;amp;
    c-&amp;gt;cmd-&amp;gt;proc != execCommand &amp;amp;&amp;amp; c-&amp;gt;cmd-&amp;gt;proc != discardCommand &amp;amp;&amp;amp;
    c-&amp;gt;cmd-&amp;gt;proc != multiCommand &amp;amp;&amp;amp; c-&amp;gt;cmd-&amp;gt;proc != watchCommand)
{
    queueMultiCommand(c);
    addReply(c,shared.queued);
} else {
    call(c,REDIS_CALL_FULL);
    c-&amp;gt;woff = server.master_repl_offset;
    if (listLength(server.ready_keys))
        handleClientsBlockedOnLists();
}

//事务提交
{&quot;exec&quot;,execCommand,1,&quot;sM&quot;,0,NULL,0,0,0,0,0},

void execCommand(redisClient *c) {
    int j;
    robj **orig_argv;
    int orig_argc;
    struct redisCommand *orig_cmd;
    int must_propagate = 0; /* Need to propagate MULTI/EXEC to AOF / slaves? */

    if (!(c-&amp;gt;flags &amp;amp; REDIS_MULTI)) {
        addReplyError(c,&quot;EXEC without MULTI&quot;);
        return;
    }

    /* Check if we need to abort the EXEC because:
     * 1) Some WATCHed key was touched.
     * 2) There was a previous error while queueing commands.
     * A failed EXEC in the first case returns a multi bulk nil object
     * (technically it is not an error but a special behavior), while
     * in the second an EXECABORT error is returned. */
    if (c-&amp;gt;flags &amp;amp; (REDIS_DIRTY_CAS|REDIS_DIRTY_EXEC)) {
        addReply(c, c-&amp;gt;flags &amp;amp; REDIS_DIRTY_EXEC ? shared.execaborterr :
                                                  shared.nullmultibulk);
        discardTransaction(c);
        goto handle_monitor;
    }

    /* Exec all the queued commands */
    unwatchAllKeys(c); /* Unwatch ASAP otherwise we&apos;ll waste CPU cycles */
    orig_argv = c-&amp;gt;argv;
    orig_argc = c-&amp;gt;argc;
    orig_cmd = c-&amp;gt;cmd;
    addReplyMultiBulkLen(c,c-&amp;gt;mstate.count);
    for (j = 0; j &amp;lt; c-&amp;gt;mstate.count; j++) {
        c-&amp;gt;argc = c-&amp;gt;mstate.commands[j].argc;
        c-&amp;gt;argv = c-&amp;gt;mstate.commands[j].argv;
        c-&amp;gt;cmd = c-&amp;gt;mstate.commands[j].cmd;

        /* Propagate a MULTI request once we encounter the first write op.
         * This way we&apos;ll deliver the MULTI/..../EXEC block as a whole and
         * both the AOF and the replication link will have the same consistency
         * and atomicity guarantees. */
        if (!must_propagate &amp;amp;&amp;amp; !(c-&amp;gt;cmd-&amp;gt;flags &amp;amp; REDIS_CMD_READONLY)) {
            execCommandPropagateMulti(c);
            must_propagate = 1;
        }

        call(c,REDIS_CALL_FULL);

        /* Commands may alter argc/argv, restore mstate. */
        c-&amp;gt;mstate.commands[j].argc = c-&amp;gt;argc;
        c-&amp;gt;mstate.commands[j].argv = c-&amp;gt;argv;
        c-&amp;gt;mstate.commands[j].cmd = c-&amp;gt;cmd;
    }
    c-&amp;gt;argv = orig_argv;
    c-&amp;gt;argc = orig_argc;
    c-&amp;gt;cmd = orig_cmd;
    discardTransaction(c);
    /* Make sure the EXEC command will be propagated as well if MULTI
     * was already propagated. */
    if (must_propagate) server.dirty++;

handle_monitor:
    /* Send EXEC to clients waiting data from MONITOR. We do it here
     * since the natural order of commands execution is actually:
     * MUTLI, EXEC, ... commands inside transaction ...
     * Instead EXEC is flagged as REDIS_CMD_SKIP_MONITOR in the command
     * table, and we do it here with correct ordering. */
    if (listLength(server.monitors) &amp;amp;&amp;amp; !server.loading)
        replicationFeedMonitors(c,server.monitors,c-&amp;gt;db-&amp;gt;id,c-&amp;gt;argv,c-&amp;gt;argc);
}


其中redisClient存储了事务状态，即mstate,mstete中包含了事务队列
typedef struct multiState {

    multiCmd *commands;

}

其中的watch部分，是用来检测当前事务是否存在风险
{&quot;watch&quot;,watchCommand,-2,&quot;rsF&quot;,0,NULL,1,-1,1,0,0}

//可以发现watch key是在redisDb这个结构中存储
/* Watch for the specified key */
void watchForKey(redisClient *c, robj *key) {
    list *clients = NULL;
    listIter li;
    listNode *ln;
    watchedKey *wk;

    /* Check if we are already watching for this key */
    listRewind(c-&amp;gt;watched_keys,&amp;amp;li);
    while((ln = listNext(&amp;amp;li))) {
        wk = listNodeValue(ln);
        if (wk-&amp;gt;db == c-&amp;gt;db &amp;amp;&amp;amp; equalStringObjects(key,wk-&amp;gt;key))
            return; /* Key already watched */
    }
    /* This key is not already watched in this DB. Let&apos;s add it */
    clients = dictFetchValue(c-&amp;gt;db-&amp;gt;watched_keys,key);
    if (!clients) {
        clients = listCreate();
        dictAdd(c-&amp;gt;db-&amp;gt;watched_keys,key,clients);
        incrRefCount(key);
    }
    listAddNodeTail(clients,c);
    /* Add the new key to the list of keys watched by this client */
    wk = zmalloc(sizeof(*wk));
    wk-&amp;gt;key = key;
    wk-&amp;gt;db = c-&amp;gt;db;
    incrRefCount(key);
    listAddNodeTail(c-&amp;gt;watched_keys,wk);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</description>
        <pubDate>Tue, 04 Jan 2022 00:00:00 +0000</pubDate>
        <link>https://seakingOne.github.io/database/2022/01/04/redis%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90%E4%B9%8B%E4%BA%8B%E5%8A%A1%E5%A4%84%E7%90%86.html</link>
        <guid isPermaLink="true">https://seakingOne.github.io/database/2022/01/04/redis%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90%E4%B9%8B%E4%BA%8B%E5%8A%A1%E5%A4%84%E7%90%86.html</guid>
        
        <category>C/C++</category>
        
        <category>redis</category>
        
        <category>事务</category>
        
        
        <category>Database</category>
        
      </item>
    
      <item>
        <title>nginx源码解析之数据结构内存池</title>
        <description>&lt;p&gt;由于nginx中其他数据结构相对来说比较简单，我们还是看看核心的数据结构内存池&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;struct ngx_pool_s {
    ngx_pool_data_t       d; 		/* 内存池的数据区域*/
    size_t                max; 		/* 最大每次可分配内存 */
    ngx_pool_t           *current;  /* 指向当前的内存池指针地址。ngx_pool_t链表上最后一个缓存池结构*/
    ngx_chain_t          *chain;	/* 缓冲区链表 */
    ngx_pool_large_t     *large;    /* 存储大数据的链表 */
    ngx_pool_cleanup_t   *cleanup;  /* 可自定义回调函数，清除内存块分配的内存 */
    ngx_log_t            *log;      /* 日志 */
};
typedef struct ngx_pool_s            ngx_pool_t;

typedef struct {
    u_char               *last; /* 内存池中未使用内存的开始节点地址 */
    u_char               *end;  /* 内存池的结束地址 */
    ngx_pool_t           *next; /* 指向下一个内存池 */
    ngx_uint_t            failed;   /* 失败次数 */
} ngx_pool_data_t;

//大块数据内存池
struct ngx_pool_large_s {
    ngx_pool_large_t     *next;   /* 指向下一个存储地址 通过这个地址可以知道当前块长度 */
    void                 *alloc;  /* 数据块指针地址 */
};
typedef struct ngx_pool_large_s  ngx_pool_large_t;

//内存池回收
struct ngx_pool_cleanup_s {
    ngx_pool_cleanup_pt   handler;  /* 清理的回调函数 */
    void                 *data; 	/* 指向存储的数据地址 */
    ngx_pool_cleanup_t   *next; 	/* 下一个ngx_pool_cleanup_t */
};
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;基本的内存方式：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ngx_alloc和ngx_calloc 主要封装了Nginx的内存分配函数malloc。 

void *
ngx_alloc(size_t size, ngx_log_t *log)
{
    void  *p;

    p = malloc(size);
    if (p == NULL) {
        ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
                      &quot;malloc(%uz) failed&quot;, size);
    }

    ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, log, 0, &quot;malloc: %p:%uz&quot;, p, size);

    return p;
}   


void *
ngx_calloc(size_t size, ngx_log_t *log)
{
    void  *p;

    p = ngx_alloc(size, log);

    if (p) {
        //内存数据置为0
        ngx_memzero(p, size);
    }

    return p;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;内存池方式分配：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ngx_pool_t *
ngx_create_pool(size_t size, ngx_log_t *log)
{
    ngx_pool_t  *p;

    p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log);
    if (p == NULL) {
        return NULL;
    }

    //内存开始地址，指向ngx_pool_t结构体之后数据取起始位置
    p-&amp;gt;d.last = (u_char *) p + sizeof(ngx_pool_t);
    p-&amp;gt;d.end = (u_char *) p + size;
    p-&amp;gt;d.next = NULL;
    p-&amp;gt;d.failed = 0;

    size = size - sizeof(ngx_pool_t);
    p-&amp;gt;max = (size &amp;lt; NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL;

    p-&amp;gt;current = p;
    p-&amp;gt;chain = NULL;
    p-&amp;gt;large = NULL;
    p-&amp;gt;cleanup = NULL;
    p-&amp;gt;log = log;

    return p;
}


//销毁内存池
void
ngx_destroy_pool(ngx_pool_t *pool)
{
    ngx_pool_t          *p, *n;
    ngx_pool_large_t    *l;
    ngx_pool_cleanup_t  *c;

    //执行内存池中的回调方法进行销毁
    for (c = pool-&amp;gt;cleanup; c; c = c-&amp;gt;next) {
        if (c-&amp;gt;handler) {
            ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool-&amp;gt;log, 0,
                           &quot;run cleanup: %p&quot;, c);
            c-&amp;gt;handler(c-&amp;gt;data);
        }
    }

#if (NGX_DEBUG)

    /*
     * we could allocate the pool-&amp;gt;log from this pool
     * so we cannot use this log while free()ing the pool
     */

    for (l = pool-&amp;gt;large; l; l = l-&amp;gt;next) {
        ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool-&amp;gt;log, 0, &quot;free: %p&quot;, l-&amp;gt;alloc);
    }

    for (p = pool, n = pool-&amp;gt;d.next; /* void */; p = n, n = n-&amp;gt;d.next) {
        ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, pool-&amp;gt;log, 0,
                       &quot;free: %p, unused: %uz&quot;, p, p-&amp;gt;d.end - p-&amp;gt;d.last);

        if (n == NULL) {
            break;
        }
    }

#endif

    for (l = pool-&amp;gt;large; l; l = l-&amp;gt;next) {
        if (l-&amp;gt;alloc) {
            ngx_free(l-&amp;gt;alloc);
        }
    }

    for (p = pool, n = pool-&amp;gt;d.next; /* void */; p = n, n = n-&amp;gt;d.next) {
        ngx_free(p);

        if (n == NULL) {
            break;
        }
    }
}


//重设内存池
void ngx_reset_pool(ngx_pool_t *pool) {
	ngx_pool_t *p;
	ngx_pool_large_t *l;
 
	/* 清理pool-&amp;gt;large链表（pool-&amp;gt;large为单独的大数据内存块）  */
	for (l = pool-&amp;gt;large; l; l = l-&amp;gt;next) {
		if (l-&amp;gt;alloc) {
			ngx_free(l-&amp;gt;alloc);
		}
	}
 
	pool-&amp;gt;large = NULL;
 
	/* 循环重新设置内存池data区域的 p-&amp;gt;d.last；data区域数据并不擦除*/
	for (p = pool; p; p = p-&amp;gt;d.next) {
		p-&amp;gt;d.last = (u_char *) p + sizeof(ngx_pool_t);
	}
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;使用内存池分配一块内存&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;void *
ngx_palloc(ngx_pool_t *pool, size_t size)
{
#if !(NGX_DEBUG_PALLOC)
    if (size &amp;lt;= pool-&amp;gt;max) {
        //分配小内存
        return ngx_palloc_small(pool, size, 1);
    }
#endif

    //分配大内存
    return ngx_palloc_large(pool, size);
}   


static ngx_inline void *
ngx_palloc_small(ngx_pool_t *pool, size_t size, ngx_uint_t align)
{
    u_char      *m;
    ngx_pool_t  *p;

    p = pool-&amp;gt;current;

    do {
        m = p-&amp;gt;d.last;

        if (align) {
            //对齐操作,会损失内存，提高内存使用速度
            m = ngx_align_ptr(m, NGX_ALIGNMENT);
        }

        if ((size_t) (p-&amp;gt;d.end - m) &amp;gt;= size) {
            p-&amp;gt;d.last = m + size;

            return m;
        }

        p = p-&amp;gt;d.next;

    } while (p);

    //如果没有缓存池空间没有可以容纳大小为size的内存块，则需要重新申请一个缓存池pool节点
    return ngx_palloc_block(pool, size);
}

ngx_pnalloc方法则是非对齐操作分配内存

//大内存分配则是直接通过ngx_alloc分配，通过链表挂载到pool-&amp;gt;large，所以后面的释放内存也由系统做
static void *
ngx_palloc_large(ngx_pool_t *pool, size_t size)
{
    void              *p;
    ngx_uint_t         n;
    ngx_pool_large_t  *large;

    p = ngx_alloc(size, pool-&amp;gt;log);
    if (p == NULL) {
        return NULL;
    }

    n = 0;

    for (large = pool-&amp;gt;large; large; large = large-&amp;gt;next) {
        if (large-&amp;gt;alloc == NULL) {
            large-&amp;gt;alloc = p;
            return p;
        }

        if (n++ &amp;gt; 3) {
            break;
        }
    }

    large = ngx_palloc_small(pool, sizeof(ngx_pool_large_t), 1);
    if (large == NULL) {
        ngx_free(p);
        return NULL;
    }

    large-&amp;gt;alloc = p;
    large-&amp;gt;next = pool-&amp;gt;large;
    pool-&amp;gt;large = large;

    return p;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</description>
        <pubDate>Tue, 04 Jan 2022 00:00:00 +0000</pubDate>
        <link>https://seakingOne.github.io/nginx/2022/01/04/nginx%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90%E4%B9%8B%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%86%85%E5%AD%98%E6%B1%A0.html</link>
        <guid isPermaLink="true">https://seakingOne.github.io/nginx/2022/01/04/nginx%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90%E4%B9%8B%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%86%85%E5%AD%98%E6%B1%A0.html</guid>
        
        <category>C/C++</category>
        
        <category>Nginx</category>
        
        <category>数据结构</category>
        
        
        <category>Nginx</category>
        
      </item>
    
      <item>
        <title>nginx源码解析之模块开发</title>
        <description>&lt;p&gt;首先看一下对应的结构化调用&lt;br /&gt;
&lt;img src=&quot;/static/assets/img/blog/nginx/module.jpg&quot; width=&quot;75%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Nginx模块非常之多，可以认为所有代码都是以模块的形式组织的，包括核心模块和功能模块，对于功能模块的选择，可以在进行configure主动指定，比如新增http_flv模块&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;./configure --with-http_flv_module
执行这个命令之后，生成的objs/ngx_modules.c源文件内就会包含对ngx_http_flv_module模块的引用
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Nginx模块有很多，大体上可以分为4个类别&lt;br /&gt;
handlers:协同完成客户端请求的处理，产生响应数据，比如ngx_http_rewrite_module模块， 用于处理客户端请求的地址重写，ngx_http_static_module模块，负责处理客户端的静态页面请求，ngx_http_log_module模块，负责记录请求日志&lt;br /&gt;
filters:对于handlers产生的数据进行过滤处理，比如模块ngx_http_not_modified_filter_module&lt;br /&gt;
upstream:Nginx可以利用upstream模块充当反向代理的角色，对客户端发送的请求进行转发，比如ngx_http_proxy_module模块&lt;br /&gt;
load-balance:Nginx充当中间角色，由于后端真实服务器往往多于一个，比如ngx_http_upstream_ip_hash_module这样的load balance模块来实现不同的负载均衡算法&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;首先看一下模块的定义:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;struct ngx_module_s {

    //当前模块在同类模块中的序号
    ngx_uint_t            ctx_index;
    
    //当前模块在所有模块中的序号
    ngx_uint_t            index;

    char                 *name;

    ngx_uint_t            spare0;
    ngx_uint_t            spare1;

    ngx_uint_t            version;
    const char           *signature;

    //指向当前模块持有的数据，必须指向ngx_http_module_t接口
    void                 *ctx;
    
    //指向当前模块配置项解析数组
    ngx_command_t        *commands;
    
    //模块类型
    ngx_uint_t            type;

    //模块的各个回调函数
    ngx_int_t           (*init_master)(ngx_log_t *log);

    ngx_int_t           (*init_module)(ngx_cycle_t *cycle);

    ngx_int_t           (*init_process)(ngx_cycle_t *cycle);
    ngx_int_t           (*init_thread)(ngx_cycle_t *cycle);
    void                (*exit_thread)(ngx_cycle_t *cycle);
    void                (*exit_process)(ngx_cycle_t *cycle);

    void                (*exit_master)(ngx_cycle_t *cycle);

    uintptr_t             spare_hook0;
    uintptr_t             spare_hook1;
    uintptr_t             spare_hook2;
    uintptr_t             spare_hook3;
    uintptr_t             spare_hook4;
    uintptr_t             spare_hook5;
    uintptr_t             spare_hook6;
    uintptr_t             spare_hook7;
};

以ngx_http_module_t模块为例，明显可以看到各个函数的回调时机
static ngx_http_module_t  ngx_http_core_module_ctx = {
    ngx_http_core_preconfiguration,        /* preconfiguration */
    ngx_http_core_postconfiguration,       /* postconfiguration */

    ngx_http_core_create_main_conf,        /* create main configuration */
    ngx_http_core_init_main_conf,          /* init main configuration */

    ngx_http_core_create_srv_conf,         /* create server configuration */
    ngx_http_core_merge_srv_conf,          /* merge server configuration */

    ngx_http_core_create_loc_conf,         /* create location configuration */
    ngx_http_core_merge_loc_conf           /* merge location configuration */
};

struct ngx_command_s {

    //配置项名称
    ngx_str_t             name;
    
    //配置项出现的位置 server{}/location{}
    ngx_uint_t            type;
    char               *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
    ngx_uint_t            conf;
    ngx_uint_t            offset;
    void                 *post;
};
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;假如我们现在定义一个简单的http模块&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;#define ngx_command_s ngx_command_t
static ngx_command_t ngx_http_mytest_commands[] = {

    {
        ngx_string(&quot;mytest&quot;),
        NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF,
        ngx_http_mytest,
        NGX_HTTP_LOC_CONF_OFFSET,
        0,
        NULL
    },
    ngx_null_command

}

ngx_http_mytest是ngx_command_t结构体中的set成员，当在某个配置项中出现了mytest配置项，Nginx将会调用ngx_http_mytest方法
static char *
ngx_http_mytest(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) {

    ngx_http_core_loc_conf_t *clcf;
    
    //找到配置项所属的配置块
    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
    
    //当匹配上mytest之后，ngx_http_mytest_handler将会处理和这个请求
    clcf-&amp;gt;handler = ngx_http_mytest_handler;
    
    return NGX_CONF_OK;

}

ngx_http_mytest_handler其实就是对应在11个处理阶段中需要做的事，这个我们后面在讨论

最后定义mytest模块
#define ngx_module_s ngx_module_t;
ngx_module_t ngx_http_mytest_module = {

    NGX_MODULE_V1,
    &amp;amp;ngx_http_mytest_module_ctx,
    ngx_http_mytest_commands,
    NGX_HTTP_MODULE,
    NULL,
    ...
    

}

这样，mytest模块在编译时将会被加入到ngx_modules全局数组中，nginx在启动时，会调用所有模块的初始化回调方法
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;对模块做初始化&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;在nginx.c中的main函数
/* 初始化所有模块；并对所有模块进行编号处理 ngx_modules数却是在自动编译的时候生成的，位于objs/ngx_modules.c文件中 */
if (ngx_preinit_modules() != NGX_OK) {
    return 1;
}    


/**
 * 初始化所有模块；并对所有模块进行编号处理；
 */
ngx_int_t
ngx_preinit_modules(void)
{
    ngx_uint_t  i;
 
    for (i = 0; ngx_modules[i]; i++) {
        ngx_modules[i]-&amp;gt;index = i;
        ngx_modules[i]-&amp;gt;name = ngx_module_names[i];
    }
 
    ngx_modules_n = i;
    ngx_max_module = ngx_modules_n + NGX_MAX_DYNAMIC_MODULES;
 
    return NGX_OK;
}

//cycle贯穿整个nginx生命周期
cycle = ngx_init_cycle(&amp;amp;init_cycle);

    //给模块分配内存
    if (ngx_cycle_modules(cycle) != NGX_OK) {
        ngx_destroy_pool(pool);
        return NULL;
    }
    
    //调用module的初始化方法，回调方法
    if (ngx_init_modules(cycle) != NGX_OK) {
        /* fatal */
        exit(1);
    }

//最终开始回调    
if (ngx_process == NGX_PROCESS_SINGLE) {
    ngx_single_process_cycle(cycle);

} else {
    ngx_master_process_cycle(cycle);
}    
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</description>
        <pubDate>Wed, 29 Dec 2021 00:00:00 +0000</pubDate>
        <link>https://seakingOne.github.io/nginx/2021/12/29/nginx%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90%E4%B9%8B%E6%A8%A1%E5%9D%97%E5%BC%80%E5%8F%91.html</link>
        <guid isPermaLink="true">https://seakingOne.github.io/nginx/2021/12/29/nginx%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90%E4%B9%8B%E6%A8%A1%E5%9D%97%E5%BC%80%E5%8F%91.html</guid>
        
        <category>C/C++</category>
        
        <category>Nginx</category>
        
        <category>模块开发</category>
        
        
        <category>Nginx</category>
        
      </item>
    
      <item>
        <title>nginx源码解析之事件管理机制</title>
        <description>&lt;p&gt;分析Nginx，从最基础的角度出发，也就是事件驱动开始&lt;br /&gt;
首先是事件的的定义&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;//定义事件
typedef struct {
    ngx_int_t  (*add)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);
    ngx_int_t  (*del)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);

    ngx_int_t  (*enable)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);
    ngx_int_t  (*disable)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);

    ngx_int_t  (*add_conn)(ngx_connection_t *c);
    ngx_int_t  (*del_conn)(ngx_connection_t *c, ngx_uint_t flags);

    ngx_int_t  (*notify)(ngx_event_handler_pt handler);

    ngx_int_t  (*process_events)(ngx_cycle_t *cycle, ngx_msec_t timer,
                                 ngx_uint_t flags);

    ngx_int_t  (*init)(ngx_cycle_t *cycle, ngx_msec_t timer);
    void       (*done)(ngx_cycle_t *cycle);
} ngx_event_actions_t;

-----------------------

ngx_event_actions_t   ngx_event_actions;

-----------------------

// 定义每个事件的宏
#define ngx_process_events   ngx_event_actions.process_events 
#define ngx_done_events      ngx_event_actions.done

#define ngx_add_event        ngx_event_actions.add
#define ngx_del_event        ngx_event_actions.del
#define ngx_add_conn         ngx_event_actions.add_conn
#define ngx_del_conn         ngx_event_actions.del_conn

#define ngx_notify           ngx_event_actions.notify

#define ngx_add_timer        ngx_event_add_timer
#define ngx_del_timer        ngx_event_del_timer

//事件具体操作的逻辑放入到此处
static ngx_event_module_t  ngx_epoll_module_ctx = {
    &amp;amp;epoll_name,
    ngx_epoll_create_conf,               /* create configuration */
    ngx_epoll_init_conf,                 /* init configuration */

    {
        ngx_epoll_add_event,             /* add an event */
        ngx_epoll_del_event,             /* delete an event */
        ngx_epoll_add_event,             /* enable an event */
        ngx_epoll_del_event,             /* disable an event */
        ngx_epoll_add_connection,        /* add an connection */
        ngx_epoll_del_connection,        /* delete an connection */
#if (NGX_HAVE_EVENTFD)
        ngx_epoll_notify,                /* trigger a notify */
#else
        NULL,                            /* trigger a notify */
#endif
        ngx_epoll_process_events,        /* process the events */
        ngx_epoll_init,                  /* init the events */
        ngx_epoll_done,                  /* done the events */
    }
};

ngx_event_module_t是新定义的结构
typedef struct {

    //事件模块名称
    ngx_str_t              *name;

    void                 *(*create_conf)(ngx_cycle_t *cycle);
    char                 *(*init_conf)(ngx_cycle_t *cycle, void *conf);

    //事件模块，每个事件模块需要实现10个抽象方法
    ngx_event_actions_t     actions;
} ngx_event_module_t;

//创建epoll的内存结构从内存池中
static void *
ngx_epoll_create_conf(ngx_cycle_t *cycle)
{
    ngx_epoll_conf_t  *epcf;

    epcf = ngx_palloc(cycle-&amp;gt;pool, sizeof(ngx_epoll_conf_t));
    if (epcf == NULL) {
        return NULL;
    }

    epcf-&amp;gt;events = NGX_CONF_UNSET;
    epcf-&amp;gt;aio_requests = NGX_CONF_UNSET;

    return epcf;
}

//初始化事件的信息
static char *
ngx_epoll_init_conf(ngx_cycle_t *cycle, void *conf)
{
    ngx_epoll_conf_t *epcf = conf;

    ngx_conf_init_uint_value(epcf-&amp;gt;events, 512);
    ngx_conf_init_uint_value(epcf-&amp;gt;aio_requests, 32);

    return NGX_CONF_OK;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Nginx连接的定义&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;//被动连接，指的是客户端连接，服务器被动接收
struct ngx_connection_s {
    void               *data;
    ngx_event_t        *read;
    ngx_event_t        *write;

    ngx_socket_t        fd;

    ngx_recv_pt         recv;
    ngx_send_pt         send;
    ngx_recv_chain_pt   recv_chain;
    ngx_send_chain_pt   send_chain;

    ngx_listening_t    *listening;

    off_t               sent;

    ngx_log_t          *log;

    //定义pool的目的是新建的连接从连接池中获取
    ngx_pool_t         *pool;

    int                 type;

    struct sockaddr    *sockaddr;
    socklen_t           socklen;
    ngx_str_t           addr_text;

    ngx_str_t           proxy_protocol_addr;
    in_port_t           proxy_protocol_port;

#if (NGX_SSL || NGX_COMPAT)
    ngx_ssl_connection_t  *ssl;
#endif

    struct sockaddr    *local_sockaddr;
    socklen_t           local_socklen;

    ngx_buf_t          *buffer;

    ngx_queue_t         queue;

    ngx_atomic_uint_t   number;

    ngx_uint_t          requests;

    unsigned            buffered:8;

    unsigned            log_error:3;     /* ngx_connection_log_error_e */

    unsigned            timedout:1;
    unsigned            error:1;
    unsigned            destroyed:1;

    unsigned            idle:1;
    unsigned            reusable:1;
    unsigned            close:1;
    unsigned            shared:1;

    unsigned            sendfile:1;
    unsigned            sndlowat:1;
    unsigned            tcp_nodelay:2;   /* ngx_connection_tcp_nodelay_e */
    unsigned            tcp_nopush:2;    /* ngx_connection_tcp_nopush_e */

    unsigned            need_last_buf:1;

#if (NGX_HAVE_AIO_SENDFILE || NGX_COMPAT)
    unsigned            busy_count:2;
#endif

#if (NGX_THREADS || NGX_COMPAT)
    ngx_thread_task_t  *sendfile_task;
#endif
};    

//作为web服务器，Nginx主动连接上游服务器，使用的是另外一个结构体
struct ngx_peer_connection_s {
    ngx_connection_t                *connection;

    struct sockaddr                 *sockaddr;
    socklen_t                        socklen;
    ngx_str_t                       *name;

    ngx_uint_t                       tries;
    ngx_msec_t                       start_time;

    ngx_event_get_peer_pt            get;
    ngx_event_free_peer_pt           free;
    ngx_event_notify_peer_pt         notify;
    void                            *data;

#if (NGX_SSL || NGX_COMPAT)
    ngx_event_set_peer_session_pt    set_session;
    ngx_event_save_peer_session_pt   save_session;
#endif

    ngx_addr_t                      *local;

    int                              type;
    int                              rcvbuf;

    ngx_log_t                       *log;

    unsigned                         cached:1;
    unsigned                         transparent:1;

                                     /* ngx_connection_log_error_e */
    unsigned                         log_error:2;

    NGX_COMPAT_BEGIN(2)
    NGX_COMPAT_END
};
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;ngx_events_module 核心模块&lt;br /&gt;
我们知道Nginx最核心的代码就是模块，每个模块组装成整个框架&lt;br /&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/* 定义事件核心模块 */
ngx_module_t  ngx_events_module = {
    NGX_MODULE_V1,
    &amp;amp;ngx_events_module_ctx,                /* module context */
    ngx_events_commands,                   /* module directives */
    NGX_CORE_MODULE,                       /* module type */
    NULL,                                  /* init master */
    NULL,                                  /* init module */
    NULL,                                  /* init process */
    NULL,                                  /* init thread */
    NULL,                                  /* exit thread */
    NULL,                                  /* exit process */
    NULL,                                  /* exit master */
    NGX_MODULE_V1_PADDING
};    

ngx_events_commands为配置项指令结构
/* 配置项结构体数组 */
static ngx_command_t  ngx_events_commands[] = {

    { ngx_string(&quot;events&quot;),
      NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,
      ngx_events_block,
      0,
      0,
      NULL },

      ngx_null_command
};
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</description>
        <pubDate>Tue, 28 Dec 2021 00:00:00 +0000</pubDate>
        <link>https://seakingOne.github.io/nginx/2021/12/28/nginx%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90%E4%B9%8B%E4%BA%8B%E4%BB%B6%E7%AE%A1%E7%90%86%E6%9C%BA%E5%88%B6.html</link>
        <guid isPermaLink="true">https://seakingOne.github.io/nginx/2021/12/28/nginx%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90%E4%B9%8B%E4%BA%8B%E4%BB%B6%E7%AE%A1%E7%90%86%E6%9C%BA%E5%88%B6.html</guid>
        
        <category>C/C++</category>
        
        <category>Nginx</category>
        
        <category>事件</category>
        
        
        <category>Nginx</category>
        
      </item>
    
      <item>
        <title>redis源码解析之发布订阅</title>
        <description>&lt;p&gt;Redis发布与订阅功能publish、subscribe、psubscribe等命令组成&lt;br /&gt;
通过指向subscribe命令，客户端可以订阅一个或多个频道，每当有其他客户端向被订阅的频道发送消息，频道的所有订阅者都会收到消息&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;比如客户端A,B,C都执行了命令：
subscribe &quot;new.it&quot;
此时某个客户端执行命令：
publish &quot;new.it&quot; &quot;hello&quot;
那么3个客户端都会受到消息
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;频道的订阅与退订&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;struct redisServer {

    //保存频道的订阅关系 key为频道名称，value为链表
    dict *pubsub_channels;

}    

{&quot;subscribe&quot;,subscribeCommand,-2,&quot;rpslt&quot;,0,NULL,0,0,0,0,0}
//建立绑定关系
int pubsubSubscribeChannel(redisClient *c, robj *channel) {
    dictEntry *de;
    list *clients = NULL;
    int retval = 0;

    //添加数据到server中
    if (dictAdd(c-&amp;gt;pubsub_channels,channel,NULL) == DICT_OK) {
        retval = 1;
        incrRefCount(channel);
        /* Add the client to the channel -&amp;gt; list of clients hash table */
        de = dictFind(server.pubsub_channels,channel);
        if (de == NULL) {
            clients = listCreate();
            dictAdd(server.pubsub_channels,channel,clients);
            incrRefCount(channel);
        } else {
            clients = dictGetVal(de);
        }
        listAddNodeTail(clients,c);
    }
    /* Notify the client */
    addReply(c,shared.mbulkhdr[3]);
    addReply(c,shared.subscribebulk);
    addReplyBulk(c,channel);
    addReplyLongLong(c,clientSubscriptionsCount(c));
    return retval;
}


//发送消息
PUBLISH &amp;lt;channel&amp;gt; &amp;lt;message&amp;gt;
{&quot;publish&quot;,publishCommand,3,&quot;pltrF&quot;,0,NULL,0,0,0,0,0}

void publishCommand(redisClient *c) {

    //查找订阅者
    int receivers = pubsubPublishMessage(c-&amp;gt;argv[1],c-&amp;gt;argv[2]);
    if (server.cluster_enabled)
        clusterPropagatePublish(c-&amp;gt;argv[1],c-&amp;gt;argv[2]);
    else
        forceCommandPropagation(c,REDIS_PROPAGATE_REPL);
    addReplyLongLong(c,receivers);
}

//发布一条消息
int pubsubPublishMessage(robj *channel, robj *message) {
    int receivers = 0;
    dictEntry *de;
    listNode *ln;
    listIter li;

    //查找当前频道下的所有客户端
    de = dictFind(server.pubsub_channels,channel);
    if (de) {
        list *list = dictGetVal(de);
        listNode *ln;
        listIter li;

        listRewind(list,&amp;amp;li);
        while ((ln = listNext(&amp;amp;li)) != NULL) {
            redisClient *c = ln-&amp;gt;value;

            addReply(c,shared.mbulkhdr[3]);
            addReply(c,shared.messagebulk);
            addReplyBulk(c,channel);
            addReplyBulk(c,message);
            receivers++;
        }
    }
    //模式匹配发送数据
    if (listLength(server.pubsub_patterns)) {
        listRewind(server.pubsub_patterns,&amp;amp;li);
        channel = getDecodedObject(channel);
        while ((ln = listNext(&amp;amp;li)) != NULL) {
            pubsubPattern *pat = ln-&amp;gt;value;

            if (stringmatchlen((char*)pat-&amp;gt;pattern-&amp;gt;ptr,
                                (int)sdslen(pat-&amp;gt;pattern-&amp;gt;ptr),                 WIN_PORT_FIX /* cast (int) */
                                (char*)channel-&amp;gt;ptr,
                                (int)sdslen(channel-&amp;gt;ptr),0)) {                 WIN_PORT_FIX /* cast (int) */
                addReply(pat-&amp;gt;client,shared.mbulkhdr[4]);
                addReply(pat-&amp;gt;client,shared.pmessagebulk);
                addReplyBulk(pat-&amp;gt;client,pat-&amp;gt;pattern);
                addReplyBulk(pat-&amp;gt;client,channel);
                addReplyBulk(pat-&amp;gt;client,message);
                receivers++;
            }
        }
        decrRefCount(channel);
    }
    return receivers;
}

//如果是cluster模式，发送cluster消息
void clusterSendPublish(clusterLink *link, robj *channel, robj *message) {
    unsigned char buf[sizeof(clusterMsg)], *payload;
    clusterMsg *hdr = (clusterMsg*) buf;
    uint32_t totlen;
    uint32_t channel_len, message_len;

    channel = getDecodedObject(channel);
    message = getDecodedObject(message);
    channel_len = (uint32_t)sdslen(channel-&amp;gt;ptr);                               WIN_PORT_FIX /* cast (uint32_t) */
    message_len = (uint32_t)sdslen(message-&amp;gt;ptr);                               WIN_PORT_FIX /* cast (uint32_t) */

    clusterBuildMessageHdr(hdr,CLUSTERMSG_TYPE_PUBLISH);
    totlen = sizeof(clusterMsg)-sizeof(union clusterMsgData);
    totlen += sizeof(clusterMsgDataPublish) - 8 + channel_len + message_len;

    hdr-&amp;gt;data.publish.msg.channel_len = htonl(channel_len);
    hdr-&amp;gt;data.publish.msg.message_len = htonl(message_len);
    hdr-&amp;gt;totlen = htonl(totlen);

    /* Try to use the local buffer if possible */
    if (totlen &amp;lt; sizeof(buf)) {
        payload = buf;
    } else {
        payload = zmalloc(totlen);
        memcpy(payload,hdr,sizeof(*hdr));
        hdr = (clusterMsg*) payload;
    }
    memcpy(hdr-&amp;gt;data.publish.msg.bulk_data,channel-&amp;gt;ptr,sdslen(channel-&amp;gt;ptr));
    memcpy(hdr-&amp;gt;data.publish.msg.bulk_data+sdslen(channel-&amp;gt;ptr),
        message-&amp;gt;ptr,sdslen(message-&amp;gt;ptr));

    //一般广播还是集群广播
    if (link)
        clusterSendMessage(link,payload,totlen);
    else
        clusterBroadcastMessage(payload,totlen);

    decrRefCount(channel);
    decrRefCount(message);
    if (payload != buf) zfree(payload);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;查看订阅命令&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;//列出所有的频道信息
PUBSUB channels  

{&quot;pubsub&quot;,pubsubCommand,-2,&quot;pltrR&quot;,0,NULL,0,0,0,0,0},

void pubsubCommand(redisClient *c) {
    if (!strcasecmp(c-&amp;gt;argv[1]-&amp;gt;ptr,&quot;channels&quot;) &amp;amp;&amp;amp;
        (c-&amp;gt;argc == 2 || c-&amp;gt;argc ==3))
    {
        /* PUBSUB CHANNELS [&amp;lt;pattern&amp;gt;] */
        sds pat = (c-&amp;gt;argc == 2) ? NULL : c-&amp;gt;argv[2]-&amp;gt;ptr;
        dictIterator *di = dictGetIterator(server.pubsub_channels);
        dictEntry *de;
        PORT_LONG mblen = 0;
        void *replylen;

        replylen = addDeferredMultiBulkLength(c);
        while((de = dictNext(di)) != NULL) {
            robj *cobj = dictGetKey(de);
            sds channel = cobj-&amp;gt;ptr;

            if (!pat || stringmatchlen(pat, (int)sdslen(pat),                   WIN_PORT_FIX /* cast (int) */
                                       channel, (int)sdslen(channel),0))        WIN_PORT_FIX /* cast (int) */
            {
                addReplyBulk(c,cobj);
                mblen++;
            }
        }
        dictReleaseIterator(di);
        setDeferredMultiBulkLength(c,replylen,mblen);
    } else if (!strcasecmp(c-&amp;gt;argv[1]-&amp;gt;ptr,&quot;numsub&quot;) &amp;amp;&amp;amp; c-&amp;gt;argc &amp;gt;= 2) {
        /* PUBSUB NUMSUB [Channel_1 ... Channel_N] */
        int j;

        addReplyMultiBulkLen(c,(c-&amp;gt;argc-2)*2);
        for (j = 2; j &amp;lt; c-&amp;gt;argc; j++) {
            list *l = dictFetchValue(server.pubsub_channels,c-&amp;gt;argv[j]);

            addReplyBulk(c,c-&amp;gt;argv[j]);
            addReplyLongLong(c,l ? listLength(l) : 0);
        }
    } else if (!strcasecmp(c-&amp;gt;argv[1]-&amp;gt;ptr,&quot;numpat&quot;) &amp;amp;&amp;amp; c-&amp;gt;argc == 2) {
        /* PUBSUB NUMPAT */
        addReplyLongLong(c,listLength(server.pubsub_patterns));
    } else {
        addReplyErrorFormat(c,
            &quot;Unknown PUBSUB subcommand or wrong number of arguments for &apos;%s&apos;&quot;,
            (char*)c-&amp;gt;argv[1]-&amp;gt;ptr);
    }
}   
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</description>
        <pubDate>Wed, 22 Dec 2021 00:00:00 +0000</pubDate>
        <link>https://seakingOne.github.io/database/2021/12/22/redis%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90%E4%B9%8B%E5%8F%91%E5%B8%83%E8%AE%A2%E9%98%85.html</link>
        <guid isPermaLink="true">https://seakingOne.github.io/database/2021/12/22/redis%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90%E4%B9%8B%E5%8F%91%E5%B8%83%E8%AE%A2%E9%98%85.html</guid>
        
        <category>C/C++</category>
        
        <category>redis</category>
        
        <category>发布订阅</category>
        
        
        <category>Database</category>
        
      </item>
    
      <item>
        <title>redis源码解析之集群模式</title>
        <description>&lt;p&gt;Redis集群是Redis提供的分布式数据库方案，集群通过分片来进行数据共享，并提供复制和故障转移功能，需要注意的是redis cluster默认
使用的是0号数据库，并且不能更改&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;1、启动节点&lt;br /&gt;
一个节点就是一个运行在集群模式下的Redis服务器，Redis服务器在启动时会根据cluster-enable配置选项是否为yes来决定是否开启服务器
集群模式&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;在redis.c/initServer()方法中进行cluster初始化，代码1892行
if (server.cluster_enabled) clusterInit();

//初始化成功之后
if (listenToPort(server.port+REDIS_CLUSTER_PORT_INCR,
        server.cfd,&amp;amp;server.cfd_count) == REDIS_ERR)
{
    exit(1);
} else {
    int j;

    for (j = 0; j &amp;lt; server.cfd_count; j++) {
        if (aeCreateFileEvent(server.el, server.cfd[j], AE_READABLE,
            clusterAcceptHandler, NULL) == AE_ERR)
                redisPanic(&quot;Unrecoverable error creating Redis Cluster &quot;
                            &quot;file event.&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;2、集群数据结构&lt;br /&gt;
clusterNode结构保存了一个节点的当前状态，比如节点的创建时间，节点的名字，节点当前配置纪元，节点的ip和port等等&lt;br /&gt;
每个节点都会使用clusterNode结构来记录自己的状态，并为集群中其他node节点创建一个clusterNode结构，以此来记录其他节点的状态：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;#define REDIS_CLUSTER_NAMELEN 40    /* sha1 hex length */
#define REDIS_IP_STR_LEN 46 /* INET6_ADDRSTRLEN is 46, but we need to be sure */
struct clusterNode {

    //创建节点的时间
    mstime_t ctime;
    
    //节点的名字
    char name[REDIS_CLUSTER_NAMELEN];
    
    //节点标识，角色，主节点或者从节点
    int flag;
    
    //配置纪元，实现故障转移
    uint64_t configEpoch;
    
    //节点的ip地址
    char ip[REDIS_IP_STR_LEN];
    
    //节点端口
    int port;
    
    //保存了连接节点的所有信息
    clusterLink *link;

}

struct clusterLink {

    //连接创建时间
    mstime_t ctime;

    //tcp套接字描述符
    int fd;
    
    //输入缓冲区，保存着从其他节点接受到的信息
    sds rcvbuf;
    
    //与这个连接相关联的节点，如果没有就为null
    struct clusterNode *node;

} 

最终rediserver是与cluster属性关联，cluster结构为clusterState
struct clusterState {
    
    //当前节点
    clusterNode *myself;
    
    //配置纪元，故障转移
    uint64_t currentEpoch;
    
    //节点状态 REDIS_CLUSTER_OK, REDIS_CLUSTER_FAIL
    int state;     
    
    //集群至少处理一个槽的节点的数量 
    int size;            
    
    //集群中所有节点
    dict *nodes;         
    
    ...
    
} clusterState;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;3、cluster meet命令解析&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;//向一个节点node发送cluster meet命令，可以让node节点与ip和port所指定的节点握手，
//握手成功后，就归到当前所在的集群中
cluster meet &amp;lt;ip&amp;gt; &amp;lt;port&amp;gt;

//查看当前cluster的节点
cluster nodes

{&quot;cluster&quot;,clusterCommand,-2,&quot;ar&quot;,0,NULL,0,0,0,0,0}

//当cluster meet操作时源码
if (!strcasecmp(c-&amp;gt;argv[1]-&amp;gt;ptr,&quot;meet&quot;) &amp;amp;&amp;amp; c-&amp;gt;argc == 4) {
    long long port;

    if (getLongLongFromObject(c-&amp;gt;argv[3], &amp;amp;port) != REDIS_OK) {
        addReplyErrorFormat(c,&quot;Invalid TCP port specified: %s&quot;,
                            (char*)c-&amp;gt;argv[3]-&amp;gt;ptr);
        return;
    }

    if (clusterStartHandshake(c-&amp;gt;argv[2]-&amp;gt;ptr,port) == 0 &amp;amp;&amp;amp;
        errno == EINVAL)
    {
        addReplyErrorFormat(c,&quot;Invalid node address specified: %s:%s&quot;,
                        (char*)c-&amp;gt;argv[2]-&amp;gt;ptr, (char*)c-&amp;gt;argv[3]-&amp;gt;ptr);
    } else {
        addReply(c,shared.ok);
    }
}

clusterStartHandshake也就是正式进行节点之间的通信，返回2种形式结果
EAGAIN - There is already an handshake in progress for this address.
EINVAL - IP or port are not valid.

/* Add the node with a random address (NULL as first argument to
     * createClusterNode()). Everything will be fixed during the
     * handshake. */
n = createClusterNode(NULL,REDIS_NODE_HANDSHAKE|REDIS_NODE_MEET);
memcpy(n-&amp;gt;ip,norm_ip,sizeof(n-&amp;gt;ip));
n-&amp;gt;port = port;
clusterAddNode(n);

clusterAddNode添加node节点到hash table中

//在server.c中的serverCron方法中，执行具体的cluster逻辑
run_with_period(100) {
    if (server.cluster_enabled) clusterCron();
}

//连接创建成功之后，出发了读事件，如果有数据返回的话
aeCreateFileEvent(server.el,link-&amp;gt;fd,AE_READABLE,clusterReadHandler,link);
//主节点返回MEET信息给从节点
clusterSendPing(link, node-&amp;gt;flags &amp;amp; REDIS_NODE_MEET ?
                    CLUSTERMSG_TYPE_MEET : CLUSTERMSG_TYPE_PING);
//发送数据到从节点
clusterSendMessage(link,buf,totlen);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;4、槽指派&lt;br /&gt;
Redis集群通过分片的方式保存数据，集群的整个数据库被分为16384个slot，数据库中的每个key都属于这16384个slot中的其中一个，集群中的每个节点可以处理0个或者16384个slot&lt;br /&gt;
当数据库中的16384个slot都有节点在处理的时候，集群处于上线状态，否则处理fail状态&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;//查看集群状态
&amp;gt; cluster info
   
//命令将槽0到5000指派给当前节点负责   
&amp;gt; cluster addslots 0 1 2 ... 5000

//槽的指派在clusterNode节点中
struct clusterNode {

    unsigned char slots[16384/8];
    
    int numslots;

}

slots属性是一个二进制位数组，这个数组的长度为16384/8=2048个字节，共包含16384个二进制位
如下图：
字节 |    slots[0]    |  slots[1]-slots[2047] |  
索引 |0|1|2|3|4|5|6|7 |8|9|10|11|12...|16383  |
 值  |1|1|1|1|1|1|1|1 |0|0|0|0|0|0...| 0      |

如果索引的值为1，表示当前节点处理那些卡槽，当然卡槽的信息会被传播到各个节点
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;在对数据库中的16384个槽都分配之后，集群就会进入上线状态，这时候客户端就可以向集群中的节点发送数据&lt;br /&gt;
计算key在那个槽&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;追溯源码是在redis client连接server之后从client模块读取命令数据
clusterNode *n = getNodeByQuery(c,c-&amp;gt;cmd,c-&amp;gt;argv,c-&amp;gt;argc,&amp;amp;hashslot,&amp;amp;error_code);
    ==&amp;gt;int thisslot = keyHashSlot((char*)thiskey-&amp;gt;ptr,(int)sdslen(thiskey-&amp;gt;ptr)); 
        ==&amp;gt;if (n != myself &amp;amp;&amp;amp; error_code) *error_code = REDIS_CLUSTER_REDIR_MOVED;
    
逻辑流程为：当节点计算出来所属的槽
keyindex = getKeysFromCommand(mcmd,margv,margc,&amp;amp;numkeys);

robj *thiskey = margv[keyindex[j]];
int thisslot = keyHashSlot((char*)thiskey-&amp;gt;ptr,
                           sdslen(thiskey-&amp;gt;ptr));

//如果当前客户端请求路由的key是由本server执行，那么返回当前节点，否则返回 
//if (n != myself &amp;amp;&amp;amp; error_code) *error_code = REDIS_CLUSTER_REDIR_MOVED;
//表示当前客户端需要请求到另外一个节点MOVE操作
n = server.cluster-&amp;gt;slots[slot];
if (c-&amp;gt;flags &amp;amp; REDIS_READONLY &amp;amp;&amp;amp;
    cmd-&amp;gt;flags &amp;amp; REDIS_CMD_READONLY &amp;amp;&amp;amp;
    nodeIsSlave(myself) &amp;amp;&amp;amp;
    myself-&amp;gt;slaveof == n)
{
    return myself;
}

//在客户端的逻辑中则会判断
/* Check if we need to connect to a different node and reissue the
     * request. */
if (config.cluster_mode &amp;amp;&amp;amp; reply-&amp;gt;type == REDIS_REPLY_ERROR &amp;amp;&amp;amp;
    (!strncmp(reply-&amp;gt;str,&quot;MOVED&quot;,5) || !strcmp(reply-&amp;gt;str,&quot;ASK&quot;)))
    
MOVE返回格式为 MOVED &amp;lt;slot&amp;gt; &amp;lt;ip&amp;gt;:&amp;lt;port&amp;gt;    
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</description>
        <pubDate>Sat, 20 Nov 2021 00:00:00 +0000</pubDate>
        <link>https://seakingOne.github.io/database/2021/11/20/redis%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90%E4%B9%8B%E9%9B%86%E7%BE%A4%E6%A8%A1%E5%BC%8F.html</link>
        <guid isPermaLink="true">https://seakingOne.github.io/database/2021/11/20/redis%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90%E4%B9%8B%E9%9B%86%E7%BE%A4%E6%A8%A1%E5%BC%8F.html</guid>
        
        <category>C/C++</category>
        
        <category>redis</category>
        
        <category>cluster</category>
        
        
        <category>Database</category>
        
      </item>
    
      <item>
        <title>redis源码解析之哨兵</title>
        <description>&lt;p&gt;Sentinel是Redis高可用性的解决方案：由一个或多个Sentinel实例组成的Sentinel系统可以监控多个主服务器，以及这些主服务器下的所有
从服务器，如下图所示：&lt;br /&gt;
&lt;img src=&quot;/static/assets/img/blog/redis/sentinel/sentinel-1.png&quot; width=&quot;65%&quot; /&gt; &lt;br /&gt;&lt;/p&gt;

&lt;p&gt;从图中可以看到，当前主服务器server1，三个从服务是server2，server3，server4，而Sentinel系统监视所有四个服务器&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;假设这时，主服务器server1进入下线状态，那么从服务器server2、server3、server4对主服务器的复制会被终止，并且Sentinel系统会察觉server1已经下线&lt;br /&gt;
会执行故障转移操作:&lt;br /&gt;
1）、Sentinel系统会挑选server1属下的一个从服务器，并将这个被选中的从服务器升级为新的主服务器&lt;br /&gt;
2）、Sentinel系统会向server1下所有的从服务器发送新的复制指令，让他们成为新的主服务器的从服务器，当所有从服务器都开始复制新的主服务器时，故障转移操作执行完毕&lt;br /&gt;
3）、另外，Sentinel还会继续监视已下线的server1，并在他重新上线时，将他设置为新的主服务器的从服务器&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;启动初始化Sentinel&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ redis-sentinel /path/to/your/sentinel.conf
或者命令
$ redis-server /path/to/your/sentinel.conf --sentinel

源码在初始化server的时候可以看到
server.sentinel_mode = checkForSentinelMode(argc,argv);
=====&amp;gt;
    int checkForSentinelMode(int argc, char **argv) {
        int j;
    
        if (strstr(argv[0],&quot;redis-sentinel&quot;) != NULL) return 1;
        for (j = 1; j &amp;lt; argc; j++)
            if (!strcmp(argv[j],&quot;--sentinel&quot;)) return 1;
        return 0;
    }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;具体初始化逻辑在于:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/* We need to init sentinel right now as parsing the configuration file
     * in sentinel mode will have the effect of populating the sentinel
     * data structures with master nodes to monitor. */
if (server.sentinel_mode) {
    initSentinelConfig();
    initSentinel();
}    

在哨兵模式中，执行的语句有限制
struct redisCommand sentinelcmds[] = {
    {&quot;ping&quot;,pingCommand,1,&quot;&quot;,0,NULL,0,0,0,0,0},
    {&quot;sentinel&quot;,sentinelCommand,-2,&quot;&quot;,0,NULL,0,0,0,0,0},
    {&quot;subscribe&quot;,subscribeCommand,-2,&quot;&quot;,0,NULL,0,0,0,0,0},
    {&quot;unsubscribe&quot;,unsubscribeCommand,-1,&quot;&quot;,0,NULL,0,0,0,0,0},
    {&quot;psubscribe&quot;,psubscribeCommand,-2,&quot;&quot;,0,NULL,0,0,0,0,0},
    {&quot;punsubscribe&quot;,punsubscribeCommand,-1,&quot;&quot;,0,NULL,0,0,0,0,0},
    {&quot;publish&quot;,sentinelPublishCommand,3,&quot;&quot;,0,NULL,0,0,0,0,0},
    {&quot;info&quot;,sentinelInfoCommand,-1,&quot;&quot;,0,NULL,0,0,0,0,0},
    {&quot;role&quot;,sentinelRoleCommand,1,&quot;l&quot;,0,NULL,0,0,0,0,0},
    {&quot;client&quot;,clientCommand,-2,&quot;rs&quot;,0,NULL,0,0,0,0,0},
    {&quot;shutdown&quot;,shutdownCommand,-1,&quot;&quot;,0,NULL,0,0,0,0,0}
};

初始化Sentinel时候，需要初始化Sentinel状态
struct sentinelState {

    uint64_t current_epoch;     /* 当前纪元，用于实现故障转移 */
    
    // 保存了所有被这个sentinel监视的主服务器，key为主服务器的name，value为指向sentinelRedisInstance结构的指针
    dict *masters;

} 

typedef struct sentinelRedisInstance {

    //标识符，实例当前的状态
    int flags;
    
    //实例的名称
    char* name;
    
    //实例的地址
    sentinelAddr* addr;

    ...
}   
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;如下如所示，结构:&lt;br /&gt;
&lt;img src=&quot;/static/assets/img/blog/redis/sentinel/sentinel-2.png&quot; width=&quot;65%&quot; /&gt; &lt;br /&gt;&lt;/p&gt;

&lt;p&gt;初始化sentinel的最后一步就是创建连向监视主服务器的网络连接，sentinel将成为主服务器的客户端，它可以向主服务器发送命令，该部分在serverCron中&lt;br /&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/* Run the Sentinel timer if we are in sentinel mode. */
run_with_period(100) {
    if (server.sentinel_mode) sentinelTimer();
}

===&amp;gt;
    void sentinelTimer(void) {
        sentinelCheckTiltCondition();
        sentinelHandleDictOfRedisInstances(sentinel.masters);
        sentinelRunPendingScripts();
        sentinelCollectTerminatedScripts();
        sentinelKillTimedoutScripts();
    
        /* We continuously change the frequency of the Redis &quot;timer interrupt&quot;
         * in order to desynchronize every Sentinel from every other.
         * This non-determinism avoids that Sentinels started at the same time
         * exactly continue to stay synchronized asking to be voted at the
         * same time again and again (resulting in nobody likely winning the
         * election because of split brain voting). */
        server.hz = REDIS_DEFAULT_HZ + rand() % REDIS_DEFAULT_HZ;
    }
    
    ===&amp;gt;
        sentinelHandleDictOfRedisInstances(ri);
        
        ===&amp;gt;
            sentinelReconnectInstance(ri);
            
            ===&amp;gt;
                ri-&amp;gt;cc = redisAsyncConnectBind(ri-&amp;gt;addr-&amp;gt;ip,ri-&amp;gt;addr-&amp;gt;port,REDIS_BIND_ADDR);
                连接成功之后发送&quot;ping&quot;
                /* Send a PING ASAP when reconnecting. */
                sentinelSendPing(ri);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;说到Redis的同步异步流程，需要了解redis的Hiredis模块，它涵盖了所有的同步异步操作&lt;br /&gt;
所谓的同步操作，就是以阻塞的方式向Redis服务器建链，发送命令，接收命令回复。使用同步操作API，主要涉及以下三个API函数：&lt;br /&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;//创建连接
redisContext *redisConnect(const char *ip, int port);
//发送redis命令
void *redisCommand(redisContext *c, const char *format, ...);
//接收返回的数据
void freeReplyObject(void *reply);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;异步操作流程:&lt;br /&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;redisContext *redisConnectBindNonBlock(const char *ip, int port, const char *source_addr); 

在sentinelReconnectInstance中可以看到后续的事件绑定操作
redisAeAttach(server.el,ri-&amp;gt;cc);
==&amp;gt;
    redisContext *c = &amp;amp;(ac-&amp;gt;c);
    redisAeEvents *e;

    /* Nothing should be attached when something is already attached */
    if (ac-&amp;gt;ev.data != NULL)
        return REDIS_ERR;

    /* Create container for context and r/w events */
    e = (redisAeEvents*)zmalloc(sizeof(*e));
    e-&amp;gt;context = ac;
    e-&amp;gt;loop = loop;
    e-&amp;gt;fd = c-&amp;gt;fd;
    e-&amp;gt;reading = e-&amp;gt;writing = 0;

    /* Register functions to start/stop listening for events */
    ac-&amp;gt;ev.addRead = redisAeAddRead;
    ac-&amp;gt;ev.delRead = redisAeDelRead;
    ac-&amp;gt;ev.addWrite = redisAeAddWrite;
    ac-&amp;gt;ev.delWrite = redisAeDelWrite;
    ac-&amp;gt;ev.cleanup = redisAeCleanup;
    ac-&amp;gt;ev.data = e;
    ===&amp;gt;
        redisAeAddRead方法中创建了文件事件，从而和之前的epoll挂上钩了
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;其实除了上面建立通信的连接之外，还有一个订阅连接，这个链接专门用于订阅主服务器的__sentinel__:hello频道&lt;br /&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;#define SENTINEL_HELLO_CHANNEL &quot;__sentinel__:hello&quot;
//绑定流程和之前一样
ri-&amp;gt;pc = redisAsyncConnectBind(ri-&amp;gt;addr-&amp;gt;ip,ri-&amp;gt;addr-&amp;gt;port,REDIS_BIND_ADDR);
retval = redisAsyncCommand(ri-&amp;gt;pc,
    sentinelReceiveHelloMessages, NULL, &quot;SUBSCRIBE %s&quot;,
        SENTINEL_HELLO_CHANNEL);
if (retval != REDIS_OK) {
    /* If we can&apos;t subscribe, the Pub/Sub connection is useless
     * and we can simply disconnect it and try again. */
    sentinelKillLink(ri,ri-&amp;gt;pc);
    return;
}          
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;当2个连接都创建成功后，需要去获取服务器相关的信息了&lt;br /&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;sentinelHandleRedisInstance.c/sentinelSendPeriodicCommands
/* Send INFO to masters and slaves, not sentinels. */
retval = redisAsyncCommand(ri-&amp;gt;cc,
    sentinelInfoReplyCallback, NULL, &quot;INFO&quot;);
if (retval == REDIS_OK) ri-&amp;gt;pending_commands++;  

//回调方法sentinelInfoReplyCallback中会处理redis server返回的数据信息并且处理
//一方面是服务器的信息，如果run_id，记录的是服务器的运行id
//另一方面，从服务器信息，从服务器都是以&quot;slave&quot; 字符串开头记录行记录
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;频道信息发送&lt;br /&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;else if ((now - ri-&amp;gt;last_pong_time) &amp;gt; ping_period) {
    /* Send PING to all the three kinds of instances. */
    sentinelSendPing(ri);
} else if ((now - ri-&amp;gt;last_pub_time) &amp;gt; SENTINEL_PUBLISH_PERIOD) {
    /* PUBLISH hello messages to all the three kinds of instances. */
    sentinelSendHello(ri);
} 

频道信息的主体是payload，包括了当前sentinel的信息
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;接下来的部分&lt;br /&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;//检测每个实例下是否出现了断开的情况
sentinelCheckSubjectivelyDown(ri);    

/* Check if we are in need for a reconnection of one of the
 * links, because we are detecting low activity.
 *
 * 1) Check if the command link seems connected, was connected not less
 *    than SENTINEL_MIN_LINK_RECONNECT_PERIOD, but still we have a
 *    pending ping for more than half the timeout. */
if (ri-&amp;gt;cc &amp;amp;&amp;amp;
    (mstime() - ri-&amp;gt;cc_conn_time) &amp;gt; SENTINEL_MIN_LINK_RECONNECT_PERIOD &amp;amp;&amp;amp;
    ri-&amp;gt;last_ping_time != 0 &amp;amp;&amp;amp; /* Ther is a pending ping... */
    /* The pending ping is delayed, and we did not received
     * error replies as well. */
    (mstime() - ri-&amp;gt;last_ping_time) &amp;gt; (ri-&amp;gt;down_after_period/2) &amp;amp;&amp;amp;
    (mstime() - ri-&amp;gt;last_pong_time) &amp;gt; (ri-&amp;gt;down_after_period/2))
{
    sentinelKillLink(ri,ri-&amp;gt;cc);
}

/* 2) Check if the pubsub link seems connected, was connected not less
 *    than SENTINEL_MIN_LINK_RECONNECT_PERIOD, but still we have no
 *    activity in the Pub/Sub channel for more than
 *    SENTINEL_PUBLISH_PERIOD * 3.
 */
if (ri-&amp;gt;pc &amp;amp;&amp;amp;
    (mstime() - ri-&amp;gt;pc_conn_time) &amp;gt; SENTINEL_MIN_LINK_RECONNECT_PERIOD &amp;amp;&amp;amp;
    (mstime() - ri-&amp;gt;pc_last_activity) &amp;gt; (SENTINEL_PUBLISH_PERIOD*3))
{
    sentinelKillLink(ri,ri-&amp;gt;pc);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;对于不断重连在于外层轮询中去创建连接，轮询中已经包含了slaves，sentinels模块等:&lt;br /&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;void sentinelHandleDictOfRedisInstances(dict *instances) {
    dictIterator *di;
    dictEntry *de;
    sentinelRedisInstance *switch_to_promoted = NULL;

    /* There are a number of things we need to perform against every master. */
    di = dictGetIterator(instances);
    while((de = dictNext(di)) != NULL) {
        sentinelRedisInstance *ri = dictGetVal(de);

        sentinelHandleRedisInstance(ri);
        if (ri-&amp;gt;flags &amp;amp; SRI_MASTER) {
            sentinelHandleDictOfRedisInstances(ri-&amp;gt;slaves);
            sentinelHandleDictOfRedisInstances(ri-&amp;gt;sentinels);
            if (ri-&amp;gt;failover_state == SENTINEL_FAILOVER_STATE_UPDATE_CONFIG) {
                switch_to_promoted = ri;
            }
        }
    }
    if (switch_to_promoted)
        sentinelFailoverSwitchToPromotedSlave(switch_to_promoted);
    dictReleaseIterator(di);
}   
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;故障转移的源码逻辑&lt;br /&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;1）、从已下线的主服务器属下的所有从服务器里面，挑选一个从服务器，并转换为主服务器
case SENTINEL_FAILOVER_STATE_SELECT_SLAVE:
     sentinelFailoverSelectSlave(ri);
break;

//此处从 从服务器中选择一个节点作为主服务器
void sentinelFailoverSelectSlave(sentinelRedisInstance *ri) {
    sentinelRedisInstance *slave = sentinelSelectSlave(ri);

    /* We don&apos;t handle the timeout in this state as the function aborts
     * the failover or go forward in the next state. */
    if (slave == NULL) {
        sentinelEvent(REDIS_WARNING,&quot;-failover-abort-no-good-slave&quot;,ri,&quot;%@&quot;);
        sentinelAbortFailover(ri);
    } else {
        sentinelEvent(REDIS_WARNING,&quot;+selected-slave&quot;,slave,&quot;%@&quot;);
        slave-&amp;gt;flags |= SRI_PROMOTED;
        ri-&amp;gt;promoted_slave = slave;
        ri-&amp;gt;failover_state = SENTINEL_FAILOVER_STATE_SEND_SLAVEOF_NOONE;
        ri-&amp;gt;failover_state_change_time = mstime();
        sentinelEvent(REDIS_NOTICE,&quot;+failover-state-send-slaveof-noone&quot;,
            slave, &quot;%@&quot;);
    }
}

2）、让已下线主服务器下的所有从服务器改为复制新的主服务器
sentinel.c/sentinelFailoverReconfNextSlave

/* Send SLAVEOF &amp;lt;new master&amp;gt;. */
retval = sentinelSendSlaveOf(slave,
        master-&amp;gt;promoted_slave-&amp;gt;addr-&amp;gt;ip,
        master-&amp;gt;promoted_slave-&amp;gt;addr-&amp;gt;port);
            

3）、已下线的主服务器设置为新的主服务器的从服务器     
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</description>
        <pubDate>Tue, 19 Oct 2021 00:00:00 +0000</pubDate>
        <link>https://seakingOne.github.io/database/2021/10/19/redis%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90%E4%B9%8B%E5%93%A8%E5%85%B5.html</link>
        <guid isPermaLink="true">https://seakingOne.github.io/database/2021/10/19/redis%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90%E4%B9%8B%E5%93%A8%E5%85%B5.html</guid>
        
        <category>C/C++</category>
        
        <category>redis</category>
        
        <category>服务端</category>
        
        
        <category>Database</category>
        
      </item>
    
      <item>
        <title>redis源码解析之多机复制</title>
        <description>&lt;p&gt;在Redis中，用户可以通过slaveof命令或者设置slaveof选项，让一个服务器去复制另一个服务器，我们称被复制的服务器为主服务器&lt;br /&gt;
，假设我们现在有2个Redis服务器，地址为127.0.0.1:6379和127.0.0.1:16379，如果我们向服务器127.0.0.1:16379发送以下命令：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;127.0.0.1:16379&amp;gt; slaveof 127.0.0.1:6379
OK
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;那么服务器127.0.0.1:16379就是127.0.0.1:6379的从服务器，当然所有在主服务器上的操作，在从服务器中也会执行一次&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;Redis的复制分为同步和命令传播2个操作，同步操作用于将从服务器的数据库状态更新至主服务器当前所处的状态&lt;br /&gt;
命令传播则是用于在主服务器的数据库状态被修改，出现主从数据不一致时，让主从服务器重新处于一致状态&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;同步操作步骤：&lt;br /&gt;
1、从服务向主服务器发送SYNC命令&lt;br /&gt;
2、收到SYNC命令的主服务器执行BGSAVE命令，在后台生成一个RDB文件，并使用缓冲区记录从现在开始执行的所有写命令&lt;br /&gt;
3、当主服务器的BGSAVE命令执行完毕后，主服务器会将BGSAVE生成的RDB文件传输给从服务器，从服务器载入RDB文件，开始执行写操作&lt;br /&gt;
4、主服务器将记录在缓冲区的所有写操作命令发送给从服务器，从服务器执行命令&lt;br /&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;我们知道在redis.c中定义了命令需要执行的方法{&quot;slaveof&quot;,slaveofCommand,3,&quot;ast&quot;,0,NULL,0,0,0,0,0}
void slaveofCommand(redisClient *c) {
    /* 表示集群模式下该节点不能作为从节点 */
    if (server.cluster_enabled) {
        addReplyError(c,&quot;SLAVEOF not allowed in cluster mode.&quot;);
        return;
    }

    /* 如果当前是SLAVEOF NO ONE命令，则表示取消复制模式 */
    if (!strcasecmp(c-&amp;gt;argv[1]-&amp;gt;ptr,&quot;no&quot;) &amp;amp;&amp;amp;
        !strcasecmp(c-&amp;gt;argv[2]-&amp;gt;ptr,&quot;one&quot;)) {
        if (server.masterhost) {
            replicationUnsetMaster();
            sds client = catClientInfoString(sdsempty(),c);
            redisLog(REDIS_NOTICE,
                &quot;MASTER MODE enabled (user request from &apos;%s&apos;)&quot;,client);
            sdsfree(client);
        }
    } else {
        PORT_LONG port;

        if ((getLongFromObjectOrReply(c, c-&amp;gt;argv[2], &amp;amp;port, NULL) != REDIS_OK))
            return;

        /* 校验当前是否已经进入复制模式了，则直接返回 */
        if (server.masterhost &amp;amp;&amp;amp; !strcasecmp(server.masterhost,c-&amp;gt;argv[1]-&amp;gt;ptr)
            &amp;amp;&amp;amp; server.masterport == port) {
            redisLog(REDIS_NOTICE,&quot;SLAVE OF would result into synchronization with the master we are already connected with. No operation performed.&quot;);
            addReplySds(c,sdsnew(&quot;+OK Already connected to specified master\r\n&quot;));
            return;
        }
        /* 该方法中会set主服务器ip与host，需要注意的是SYNC命令是异步的，所有会直接返回连接成功，实际的复制工作在返回OK之后 */
        replicationSetMaster(c-&amp;gt;argv[1]-&amp;gt;ptr, (int)port);                       WIN_PORT_FIX /* cast (int) */
        sds client = catClientInfoString(sdsempty(),c);
        redisLog(REDIS_NOTICE,&quot;SLAVE OF %s:%d enabled (user request from &apos;%s&apos;)&quot;,
            server.masterhost, server.masterport, client);
        sdsfree(client);
    }
    addReply(c,shared.ok);
}

server中保存结构
struct redisServer {

    char *masterhost;
    
    int masterport;

}   
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;真正的任务调度是在serverCron中操作:&lt;br /&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/* Replication cron function -- used to reconnect to master and
     * to detect transfer failures. */
run_with_period(1000) replicationCron();  //表示每1s执行复制操作   

套接字的连接
/* Check if we should connect to a MASTER */
if (server.repl_state == REDIS_REPL_CONNECT) {
    redisLog(REDIS_NOTICE,&quot;Connecting to MASTER %s:%d&quot;,
        server.masterhost, server.masterport);
    if (connectWithMaster() == REDIS_OK) {
        redisLog(REDIS_NOTICE,&quot;MASTER &amp;lt;-&amp;gt; SLAVE sync started&quot;);
    }
}

在connectWithMaster方法中aeCreateFileEvent(server.el,fd,AE_READABLE|AE_WRITABLE,syncWithMaster,NULL)创建了建立的事件

发送一个PING来检查主机是否能够无错误地回复 
if (server.repl_state == REDIS_REPL_CONNECTING) {
    redisLog(REDIS_NOTICE,&quot;Non blocking connect for SYNC fired the event.&quot;);
    /* Delete the writable event so that the readable event remains
     * registered and we can wait for the PONG reply. */
    aeDeleteFileEvent(server.el,fd,AE_WRITABLE);
    server.repl_state = REDIS_REPL_RECEIVE_PONG;
    /* Send the PING, don&apos;t check for errors at all, we have the timeout
     * that will take care about this. */
    err = sendSynchronousCommand(SYNC_CMD_WRITE,fd,&quot;PING&quot;,NULL);
    if (err) goto write_error;
    WIN32_ONLY(WSIOCP_QueueNextRead(fd);)
    return;
}

主服务器在接到 PING 信息后 会返回 +PONG\r\n，触发 从服务器写事件，调用 之前我们新建的从服务器读事件，回调 syncWithMaster() 进行处理
if (server.repl_state == REDIS_REPL_RECEIVE_PONG) {
    err = sendSynchronousCommand(SYNC_CMD_READ,fd,NULL);

    /* We accept only two replies as valid, a positive +PONG reply
     * (we just check for &quot;+&quot;) or an authentication error.
     * Note that older versions of Redis replied with &quot;operation not
     * permitted&quot; instead of using a proper error code, so we test
     * both. */
    if (err[0] != &apos;+&apos; &amp;amp;&amp;amp;
        strncmp(err,&quot;-NOAUTH&quot;,7) != 0 &amp;amp;&amp;amp;
        strncmp(err,&quot;-ERR operation not permitted&quot;,28) != 0)
    {
        redisLog(REDIS_WARNING,&quot;Error reply to PING from master: &apos;%s&apos;&quot;,err);
        sdsfree(err);
        goto error;
    } else {
        redisLog(REDIS_NOTICE,
            &quot;Master replied to PING, replication can continue...&quot;);
    }
    sdsfree(err);
    server.repl_state = REDIS_REPL_SEND_AUTH;
}

鉴权部分
if (server.repl_state == REDIS_REPL_SEND_AUTH) {
    if (server.masterauth) {
        err = sendSynchronousCommand(SYNC_CMD_WRITE,fd,&quot;AUTH&quot;,server.masterauth,NULL);
        if (err) goto write_error;
        server.repl_state = REDIS_REPL_RECEIVE_AUTH;
        WIN32_ONLY(WSIOCP_QueueNextRead(fd);)
        return;
    } else {
        server.repl_state = REDIS_REPL_SEND_PORT;
    }
}

/* Receive AUTH reply. */
if (server.repl_state == REDIS_REPL_RECEIVE_AUTH) {
    err = sendSynchronousCommand(SYNC_CMD_READ,fd,NULL);
    if (err[0] == &apos;-&apos;) {
        redisLog(REDIS_WARNING,&quot;Unable to AUTH to MASTER: %s&quot;,err);
        sdsfree(err);
        goto error;
    }
    sdsfree(err);
    server.repl_state = REDIS_REPL_SEND_PORT;
}

从服务器上的端口信息发送给主服务器
if (server.repl_state == REDIS_REPL_SEND_PORT) {
    sds port = sdsfromlonglong(server.port);
    err = sendSynchronousCommand(SYNC_CMD_WRITE,fd,&quot;REPLCONF&quot;,
            &quot;listening-port&quot;,port, NULL);
    sdsfree(port);
    if (err) goto write_error;
    sdsfree(err);
    server.repl_state = REDIS_REPL_RECEIVE_PORT;
    return;
}

/* Receive REPLCONF listening-port reply. */
if (server.repl_state == REDIS_REPL_RECEIVE_PORT) {
    err = sendSynchronousCommand(SYNC_CMD_READ,fd,NULL);
    /* Ignore the error if any, not all the Redis versions support
     * REPLCONF listening-port. */
    if (err[0] == &apos;-&apos;) {
        redisLog(REDIS_NOTICE,&quot;(Non critical) Master does not understand &quot;
                              &quot;REPLCONF listening-port: %s&quot;, err);
    }
    sdsfree(err);
    server.repl_state = REDIS_REPL_SEND_CAPA;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;PSYNC命令，全量同步与增量同步&lt;br /&gt;
基本上后续的复制操作都是基于PSYNC实现&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;1）、如果从服务器没有复制过主服务器，或者没有执行过slaveof no one命令，那么从服务器在开始新的复制时将向主服务器
发送PSYNC ? -1 命令，主动请求主服务器进行完整重同步

//从此处可以看出来，节点之间的数据复制都是传递的rdb文件
if (slaves_waiting &amp;amp;&amp;amp; max_idle &amp;gt; server.repl_diskless_sync_delay) {
    /* Start a BGSAVE. Usually with socket target, or with disk target
     * if there was a recent socket -&amp;gt; disk config change. */
    startBgsaveForReplication(mincapa);
}

2）、如果从服务器是第一次复制，发送PSYNC ? -1，否则发送PSYNC &amp;lt;runid&amp;gt;  &amp;lt;offset&amp;gt;
if (server.cached_master) {
    psync_runid = server.cached_master-&amp;gt;replrunid;
    snprintf(psync_offset,sizeof(psync_offset),&quot;%lld&quot;, server.cached_master-&amp;gt;reploff+1);
    redisLog(REDIS_NOTICE,&quot;Trying a partial resynchronization (request %s:%s).&quot;, psync_runid, psync_offset);
} else {
    redisLog(REDIS_NOTICE,&quot;Partial resynchronization not possible (no cached master)&quot;);
    psync_runid = &quot;?&quot;;
    memcpy(psync_offset,&quot;-1&quot;,3);
}

3）、后续基于前面操作是否执行重同步   

4）、检测命令丢失
replicationFeedSlaves():会将执行的命令以协议的传输格式写到从服务器 client 的输出缓冲区中
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</description>
        <pubDate>Wed, 22 Sep 2021 00:00:00 +0000</pubDate>
        <link>https://seakingOne.github.io/database/2021/09/22/redis%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90%E4%B9%8B%E5%A4%9A%E6%9C%BA%E5%A4%8D%E5%88%B6.html</link>
        <guid isPermaLink="true">https://seakingOne.github.io/database/2021/09/22/redis%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90%E4%B9%8B%E5%A4%9A%E6%9C%BA%E5%A4%8D%E5%88%B6.html</guid>
        
        <category>C/C++</category>
        
        <category>redis</category>
        
        <category>服务端</category>
        
        
        <category>Database</category>
        
      </item>
    
      <item>
        <title>redis源码解析之redis服务端</title>
        <description>&lt;p&gt;Redis服务器的处理流程：&lt;br /&gt;
一个命令请求从发送到获得回复的过程中，如果我们执行下面命令:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;redis &amp;gt; set KEY VALUE
OK
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;其中的操作有：&lt;br /&gt;
1）客户端向服务器发送命令请求&lt;br /&gt;
2）服务起接收并处理客户端发送来的命令请求，在数据库中进行设置操作，并产生命令回复OK&lt;br /&gt;
3）服务器将命令回复OK发送给客户端&lt;br /&gt;
4）客户端显示内容打印给用户看&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;接下来详细介绍第2步的操作&lt;br /&gt;
服务器读取套接字中的命令请求，并把内容保存到客户端状态的输入缓冲区中，也就是redisServer/clients，对输入缓冲区的命令请求进行分析
，提取出命令请求中包含的命令参数，以及命令参数的个数，然后分别将参数和参数个数保存到clients中 （argv,argc）;当套接字读事件时，
调用命令执行器，执行客户端的命令&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/assets/img/blog/redis/server/server.png&quot; width=&quot;65%&quot; /&gt; &lt;br /&gt;&lt;/p&gt;

&lt;p&gt;之后，分析程序将对输入缓冲区中的协议进行分析：&lt;br /&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;*3\r\n$3\r\nSET\r\n$3\r\nKEY\r\n$5\r\nVALUE\r\n
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;并将得出的分析结构保存到客户端状态的argv属性和argc属性里面，如下图所示:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/assets/img/blog/redis/server/server-1.png&quot; width=&quot;65%&quot; /&gt; &lt;br /&gt;&lt;/p&gt;

&lt;p&gt;代码流程跟踪:&lt;br /&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;redis.c/initServer/aeCreateFileEvent(acceptTcpHandler) -&amp;gt; networking.c/acceptTcpHandler/acceptCommonHandler 
-&amp;gt; networking.c/acceptCommonHandler/createClient -&amp;gt; networking.c/createClient/aeCreateFileEvent(readQueryFromClient)
-&amp;gt; networking.c/readQueryFromClient

其中server.ipfd数组大小为16，因为redis默认为16个数据库，所以需要创建16个监听的文件句柄，做epoll轮询
命令参数的解析在networking.c/readQueryFromClient/processInputBuffer，把参数存储到argv和argc中去
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;当客户端连接建立和参数解析完成之后，命令执行器开始工作，去查找命令的实现，代码在&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;networking.c/readQueryFromClient/processInputBuffer -&amp;gt; networking.c/processInputBuffer/processCommand 

通过里层的lookupCommand方法查看对应的命令函数，通过从redisCommand中取出对应的数据，源码如下：
struct redisCommand redisCommandTable[] = {
    {&quot;get&quot;,getCommand,2,&quot;rF&quot;,0,NULL,1,1,1,0,0},
    {&quot;set&quot;,setCommand,-3,&quot;wm&quot;,0,NULL,1,1,1,0,0},
    {&quot;setnx&quot;,setnxCommand,3,&quot;wmF&quot;,0,NULL,1,1,1,0,0},
    {&quot;setex&quot;,setexCommand,4,&quot;wm&quot;,0,NULL,1,1,1,0,0},
    {&quot;psetex&quot;,psetexCommand,4,&quot;wm&quot;,0,NULL,1,1,1,0,0},
    {&quot;append&quot;,appendCommand,3,&quot;wm&quot;,0,NULL,1,1,1,0,0},
    {&quot;strlen&quot;,strlenCommand,2,&quot;rF&quot;,0,NULL,1,1,1,0,0},
    {&quot;del&quot;,delCommand,-2,&quot;w&quot;,0,NULL,1,-1,1,0,0},
    {&quot;exists&quot;,existsCommand,-2,&quot;rF&quot;,0,NULL,1,-1,1,0,0},
    {&quot;setbit&quot;,setbitCommand,4,&quot;wm&quot;,0,NULL,1,1,1,0,0},
    {&quot;getbit&quot;,getbitCommand,3,&quot;rF&quot;,0,NULL,1,1,1,0,0},
    {&quot;setrange&quot;,setrangeCommand,4,&quot;wm&quot;,0,NULL,1,1,1,0,0},
    {&quot;getrange&quot;,getrangeCommand,4,&quot;r&quot;,0,NULL,1,1,1,0,0},
    {&quot;substr&quot;,getrangeCommand,4,&quot;r&quot;,0,NULL,1,1,1,0,0},
    {&quot;incr&quot;,incrCommand,2,&quot;wmF&quot;,0,NULL,1,1,1,0,0},
    {&quot;decr&quot;,decrCommand,2,&quot;wmF&quot;,0,NULL,1,1,1,0,0},
    {&quot;mget&quot;,mgetCommand,-2,&quot;r&quot;,0,NULL,1,-1,1,0,0},
    {&quot;rpush&quot;,rpushCommand,-3,&quot;wmF&quot;,0,NULL,1,1,1,0,0},
    {&quot;lpush&quot;,lpushCommand,-3,&quot;wmF&quot;,0,NULL,1,1,1,0,0},
    {&quot;rpushx&quot;,rpushxCommand,3,&quot;wmF&quot;,0,NULL,1,1,1,0,0},
    {&quot;lpushx&quot;,lpushxCommand,3,&quot;wmF&quot;,0,NULL,1,1,1,0,0},
    {&quot;linsert&quot;,linsertCommand,5,&quot;wm&quot;,0,NULL,1,1,1,0,0},
    {&quot;rpop&quot;,rpopCommand,2,&quot;wF&quot;,0,NULL,1,1,1,0,0},
    {&quot;lpop&quot;,lpopCommand,2,&quot;wF&quot;,0,NULL,1,1,1,0,0},
    {&quot;brpop&quot;,brpopCommand,-3,&quot;ws&quot;,0,NULL,1,1,1,0,0},
    {&quot;brpoplpush&quot;,brpoplpushCommand,4,&quot;wms&quot;,0,NULL,1,2,1,0,0},
    {&quot;blpop&quot;,blpopCommand,-3,&quot;ws&quot;,0,NULL,1,-2,1,0,0},
    {&quot;llen&quot;,llenCommand,2,&quot;rF&quot;,0,NULL,1,1,1,0,0},
    {&quot;lindex&quot;,lindexCommand,3,&quot;r&quot;,0,NULL,1,1,1,0,0},
    {&quot;lset&quot;,lsetCommand,4,&quot;wm&quot;,0,NULL,1,1,1,0,0},
    {&quot;lrange&quot;,lrangeCommand,4,&quot;r&quot;,0,NULL,1,1,1,0,0},
    {&quot;ltrim&quot;,ltrimCommand,4,&quot;w&quot;,0,NULL,1,1,1,0,0},
    {&quot;lrem&quot;,lremCommand,4,&quot;w&quot;,0,NULL,1,1,1,0,0},
    {&quot;rpoplpush&quot;,rpoplpushCommand,3,&quot;wm&quot;,0,NULL,1,2,1,0,0},
    {&quot;sadd&quot;,saddCommand,-3,&quot;wmF&quot;,0,NULL,1,1,1,0,0},
    {&quot;srem&quot;,sremCommand,-3,&quot;wF&quot;,0,NULL,1,1,1,0,0},
    {&quot;smove&quot;,smoveCommand,4,&quot;wF&quot;,0,NULL,1,2,1,0,0},
    {&quot;sismember&quot;,sismemberCommand,3,&quot;rF&quot;,0,NULL,1,1,1,0,0},
    {&quot;scard&quot;,scardCommand,2,&quot;rF&quot;,0,NULL,1,1,1,0,0},
    {&quot;spop&quot;,spopCommand,2,&quot;wRsF&quot;,0,NULL,1,1,1,0,0},
    {&quot;srandmember&quot;,srandmemberCommand,-2,&quot;rR&quot;,0,NULL,1,1,1,0,0},
    {&quot;sinter&quot;,sinterCommand,-2,&quot;rS&quot;,0,NULL,1,-1,1,0,0},
    {&quot;sinterstore&quot;,sinterstoreCommand,-3,&quot;wm&quot;,0,NULL,1,-1,1,0,0},
    {&quot;sunion&quot;,sunionCommand,-2,&quot;rS&quot;,0,NULL,1,-1,1,0,0},
    {&quot;sunionstore&quot;,sunionstoreCommand,-3,&quot;wm&quot;,0,NULL,1,-1,1,0,0},
    {&quot;sdiff&quot;,sdiffCommand,-2,&quot;rS&quot;,0,NULL,1,-1,1,0,0},
    {&quot;sdiffstore&quot;,sdiffstoreCommand,-3,&quot;wm&quot;,0,NULL,1,-1,1,0,0},
    {&quot;smembers&quot;,sinterCommand,2,&quot;rS&quot;,0,NULL,1,1,1,0,0},
    {&quot;sscan&quot;,sscanCommand,-3,&quot;rR&quot;,0,NULL,1,1,1,0,0},
    {&quot;zadd&quot;,zaddCommand,-4,&quot;wmF&quot;,0,NULL,1,1,1,0,0},
    {&quot;zincrby&quot;,zincrbyCommand,4,&quot;wmF&quot;,0,NULL,1,1,1,0,0},
    {&quot;zrem&quot;,zremCommand,-3,&quot;wF&quot;,0,NULL,1,1,1,0,0},
    {&quot;zremrangebyscore&quot;,zremrangebyscoreCommand,4,&quot;w&quot;,0,NULL,1,1,1,0,0},
    {&quot;zremrangebyrank&quot;,zremrangebyrankCommand,4,&quot;w&quot;,0,NULL,1,1,1,0,0},
    {&quot;zremrangebylex&quot;,zremrangebylexCommand,4,&quot;w&quot;,0,NULL,1,1,1,0,0},
    {&quot;zunionstore&quot;,zunionstoreCommand,-4,&quot;wm&quot;,0,zunionInterGetKeys,0,0,0,0,0},
    {&quot;zinterstore&quot;,zinterstoreCommand,-4,&quot;wm&quot;,0,zunionInterGetKeys,0,0,0,0,0},
    {&quot;zrange&quot;,zrangeCommand,-4,&quot;r&quot;,0,NULL,1,1,1,0,0},
    {&quot;zrangebyscore&quot;,zrangebyscoreCommand,-4,&quot;r&quot;,0,NULL,1,1,1,0,0},
    {&quot;zrevrangebyscore&quot;,zrevrangebyscoreCommand,-4,&quot;r&quot;,0,NULL,1,1,1,0,0},
    {&quot;zrangebylex&quot;,zrangebylexCommand,-4,&quot;r&quot;,0,NULL,1,1,1,0,0},
    {&quot;zrevrangebylex&quot;,zrevrangebylexCommand,-4,&quot;r&quot;,0,NULL,1,1,1,0,0},
    {&quot;zcount&quot;,zcountCommand,4,&quot;rF&quot;,0,NULL,1,1,1,0,0},
    {&quot;zlexcount&quot;,zlexcountCommand,4,&quot;rF&quot;,0,NULL,1,1,1,0,0},
    {&quot;zrevrange&quot;,zrevrangeCommand,-4,&quot;r&quot;,0,NULL,1,1,1,0,0},
    {&quot;zcard&quot;,zcardCommand,2,&quot;rF&quot;,0,NULL,1,1,1,0,0},
    {&quot;zscore&quot;,zscoreCommand,3,&quot;rF&quot;,0,NULL,1,1,1,0,0},
    {&quot;zrank&quot;,zrankCommand,3,&quot;rF&quot;,0,NULL,1,1,1,0,0},
    {&quot;zrevrank&quot;,zrevrankCommand,3,&quot;rF&quot;,0,NULL,1,1,1,0,0},
    {&quot;zscan&quot;,zscanCommand,-3,&quot;rR&quot;,0,NULL,1,1,1,0,0},
    {&quot;hset&quot;,hsetCommand,4,&quot;wmF&quot;,0,NULL,1,1,1,0,0},
    {&quot;hsetnx&quot;,hsetnxCommand,4,&quot;wmF&quot;,0,NULL,1,1,1,0,0},
    {&quot;hget&quot;,hgetCommand,3,&quot;rF&quot;,0,NULL,1,1,1,0,0},
    {&quot;hmset&quot;,hmsetCommand,-4,&quot;wm&quot;,0,NULL,1,1,1,0,0},
    {&quot;hmget&quot;,hmgetCommand,-3,&quot;r&quot;,0,NULL,1,1,1,0,0},
    {&quot;hincrby&quot;,hincrbyCommand,4,&quot;wmF&quot;,0,NULL,1,1,1,0,0},
    {&quot;hincrbyfloat&quot;,hincrbyfloatCommand,4,&quot;wmF&quot;,0,NULL,1,1,1,0,0},
    {&quot;hdel&quot;,hdelCommand,-3,&quot;wF&quot;,0,NULL,1,1,1,0,0},
    {&quot;hlen&quot;,hlenCommand,2,&quot;rF&quot;,0,NULL,1,1,1,0,0},
    {&quot;hkeys&quot;,hkeysCommand,2,&quot;rS&quot;,0,NULL,1,1,1,0,0},
    {&quot;hvals&quot;,hvalsCommand,2,&quot;rS&quot;,0,NULL,1,1,1,0,0},
    {&quot;hgetall&quot;,hgetallCommand,2,&quot;r&quot;,0,NULL,1,1,1,0,0},
    {&quot;hexists&quot;,hexistsCommand,3,&quot;rF&quot;,0,NULL,1,1,1,0,0},
    {&quot;hscan&quot;,hscanCommand,-3,&quot;rR&quot;,0,NULL,1,1,1,0,0},
    {&quot;incrby&quot;,incrbyCommand,3,&quot;wmF&quot;,0,NULL,1,1,1,0,0},
    {&quot;decrby&quot;,decrbyCommand,3,&quot;wmF&quot;,0,NULL,1,1,1,0,0},
    {&quot;incrbyfloat&quot;,incrbyfloatCommand,3,&quot;wmF&quot;,0,NULL,1,1,1,0,0},
    {&quot;getset&quot;,getsetCommand,3,&quot;wm&quot;,0,NULL,1,1,1,0,0},
    {&quot;mset&quot;,msetCommand,-3,&quot;wm&quot;,0,NULL,1,-1,2,0,0},
    {&quot;msetnx&quot;,msetnxCommand,-3,&quot;wm&quot;,0,NULL,1,-1,2,0,0},
    {&quot;randomkey&quot;,randomkeyCommand,1,&quot;rR&quot;,0,NULL,0,0,0,0,0},
    {&quot;select&quot;,selectCommand,2,&quot;rlF&quot;,0,NULL,0,0,0,0,0},
    {&quot;move&quot;,moveCommand,3,&quot;wF&quot;,0,NULL,1,1,1,0,0},
    {&quot;rename&quot;,renameCommand,3,&quot;w&quot;,0,NULL,1,2,1,0,0},
    {&quot;renamenx&quot;,renamenxCommand,3,&quot;wF&quot;,0,NULL,1,2,1,0,0},
    {&quot;expire&quot;,expireCommand,3,&quot;wF&quot;,0,NULL,1,1,1,0,0},
    {&quot;expireat&quot;,expireatCommand,3,&quot;wF&quot;,0,NULL,1,1,1,0,0},
    {&quot;pexpire&quot;,pexpireCommand,3,&quot;wF&quot;,0,NULL,1,1,1,0,0},
    {&quot;pexpireat&quot;,pexpireatCommand,3,&quot;wF&quot;,0,NULL,1,1,1,0,0},
    {&quot;keys&quot;,keysCommand,2,&quot;rS&quot;,0,NULL,0,0,0,0,0},
    {&quot;scan&quot;,scanCommand,-2,&quot;rR&quot;,0,NULL,0,0,0,0,0},
    {&quot;dbsize&quot;,dbsizeCommand,1,&quot;rF&quot;,0,NULL,0,0,0,0,0},
    {&quot;auth&quot;,authCommand,2,&quot;rsltF&quot;,0,NULL,0,0,0,0,0},
    {&quot;ping&quot;,pingCommand,-1,&quot;rtF&quot;,0,NULL,0,0,0,0,0},
    {&quot;echo&quot;,echoCommand,2,&quot;rF&quot;,0,NULL,0,0,0,0,0},
    {&quot;save&quot;,saveCommand,1,&quot;ars&quot;,0,NULL,0,0,0,0,0},
    {&quot;bgsave&quot;,bgsaveCommand,1,&quot;ar&quot;,0,NULL,0,0,0,0,0},
    {&quot;bgrewriteaof&quot;,bgrewriteaofCommand,1,&quot;ar&quot;,0,NULL,0,0,0,0,0},
    {&quot;shutdown&quot;,shutdownCommand,-1,&quot;arlt&quot;,0,NULL,0,0,0,0,0},
    {&quot;lastsave&quot;,lastsaveCommand,1,&quot;rRF&quot;,0,NULL,0,0,0,0,0},
    {&quot;type&quot;,typeCommand,2,&quot;rF&quot;,0,NULL,1,1,1,0,0},
    {&quot;multi&quot;,multiCommand,1,&quot;rsF&quot;,0,NULL,0,0,0,0,0},
    {&quot;exec&quot;,execCommand,1,&quot;sM&quot;,0,NULL,0,0,0,0,0},
    {&quot;discard&quot;,discardCommand,1,&quot;rsF&quot;,0,NULL,0,0,0,0,0},
    {&quot;sync&quot;,syncCommand,1,&quot;ars&quot;,0,NULL,0,0,0,0,0},
    {&quot;psync&quot;,syncCommand,3,&quot;ars&quot;,0,NULL,0,0,0,0,0},
    {&quot;replconf&quot;,replconfCommand,-1,&quot;arslt&quot;,0,NULL,0,0,0,0,0},
    {&quot;flushdb&quot;,flushdbCommand,1,&quot;w&quot;,0,NULL,0,0,0,0,0},
    {&quot;flushall&quot;,flushallCommand,1,&quot;w&quot;,0,NULL,0,0,0,0,0},
    {&quot;sort&quot;,sortCommand,-2,&quot;wm&quot;,0,sortGetKeys,1,1,1,0,0},
    {&quot;info&quot;,infoCommand,-1,&quot;rlt&quot;,0,NULL,0,0,0,0,0},
    {&quot;monitor&quot;,monitorCommand,1,&quot;ars&quot;,0,NULL,0,0,0,0,0},
    {&quot;ttl&quot;,ttlCommand,2,&quot;rF&quot;,0,NULL,1,1,1,0,0},
    {&quot;pttl&quot;,pttlCommand,2,&quot;rF&quot;,0,NULL,1,1,1,0,0},
    {&quot;persist&quot;,persistCommand,2,&quot;wF&quot;,0,NULL,1,1,1,0,0},
    {&quot;slaveof&quot;,slaveofCommand,3,&quot;ast&quot;,0,NULL,0,0,0,0,0},
    {&quot;role&quot;,roleCommand,1,&quot;lst&quot;,0,NULL,0,0,0,0,0},
    {&quot;debug&quot;,debugCommand,-2,&quot;as&quot;,0,NULL,0,0,0,0,0},
    {&quot;config&quot;,configCommand,-2,&quot;art&quot;,0,NULL,0,0,0,0,0},
    {&quot;subscribe&quot;,subscribeCommand,-2,&quot;rpslt&quot;,0,NULL,0,0,0,0,0},
    {&quot;unsubscribe&quot;,unsubscribeCommand,-1,&quot;rpslt&quot;,0,NULL,0,0,0,0,0},
    {&quot;psubscribe&quot;,psubscribeCommand,-2,&quot;rpslt&quot;,0,NULL,0,0,0,0,0},
    {&quot;punsubscribe&quot;,punsubscribeCommand,-1,&quot;rpslt&quot;,0,NULL,0,0,0,0,0},
    {&quot;publish&quot;,publishCommand,3,&quot;pltrF&quot;,0,NULL,0,0,0,0,0},
    {&quot;pubsub&quot;,pubsubCommand,-2,&quot;pltrR&quot;,0,NULL,0,0,0,0,0},
    {&quot;watch&quot;,watchCommand,-2,&quot;rsF&quot;,0,NULL,1,-1,1,0,0},
    {&quot;unwatch&quot;,unwatchCommand,1,&quot;rsF&quot;,0,NULL,0,0,0,0,0},
    {&quot;cluster&quot;,clusterCommand,-2,&quot;ar&quot;,0,NULL,0,0,0,0,0},
    {&quot;restore&quot;,restoreCommand,-4,&quot;wm&quot;,0,NULL,1,1,1,0,0},
    {&quot;restore-asking&quot;,restoreCommand,-4,&quot;wmk&quot;,0,NULL,1,1,1,0,0},
    {&quot;migrate&quot;,migrateCommand,-6,&quot;w&quot;,0,migrateGetKeys,0,0,0,0,0},
    {&quot;asking&quot;,askingCommand,1,&quot;r&quot;,0,NULL,0,0,0,0,0},
    {&quot;readonly&quot;,readonlyCommand,1,&quot;rF&quot;,0,NULL,0,0,0,0,0},
    {&quot;readwrite&quot;,readwriteCommand,1,&quot;rF&quot;,0,NULL,0,0,0,0,0},
    {&quot;dump&quot;,dumpCommand,2,&quot;r&quot;,0,NULL,1,1,1,0,0},
    {&quot;object&quot;,objectCommand,3,&quot;r&quot;,0,NULL,2,2,2,0,0},
    {&quot;client&quot;,clientCommand,-2,&quot;rs&quot;,0,NULL,0,0,0,0,0},
    {&quot;eval&quot;,evalCommand,-3,&quot;s&quot;,0,evalGetKeys,0,0,0,0,0},
    {&quot;evalsha&quot;,evalShaCommand,-3,&quot;s&quot;,0,evalGetKeys,0,0,0,0,0},
    {&quot;slowlog&quot;,slowlogCommand,-2,&quot;r&quot;,0,NULL,0,0,0,0,0},
    {&quot;script&quot;,scriptCommand,-2,&quot;rs&quot;,0,NULL,0,0,0,0,0},
    {&quot;time&quot;,timeCommand,1,&quot;rRF&quot;,0,NULL,0,0,0,0,0},
    {&quot;bitop&quot;,bitopCommand,-4,&quot;wm&quot;,0,NULL,2,-1,1,0,0},
    {&quot;bitcount&quot;,bitcountCommand,-2,&quot;r&quot;,0,NULL,1,1,1,0,0},
    {&quot;bitpos&quot;,bitposCommand,-3,&quot;r&quot;,0,NULL,1,1,1,0,0},
    {&quot;wait&quot;,waitCommand,3,&quot;rs&quot;,0,NULL,0,0,0,0,0},
    {&quot;command&quot;,commandCommand,0,&quot;rlt&quot;,0,NULL,0,0,0,0,0},
    {&quot;pfselftest&quot;,pfselftestCommand,1,&quot;r&quot;,0,NULL,0,0,0,0,0},
    {&quot;pfadd&quot;,pfaddCommand,-2,&quot;wmF&quot;,0,NULL,1,1,1,0,0},
    {&quot;pfcount&quot;,pfcountCommand,-2,&quot;r&quot;,0,NULL,1,-1,1,0,0},
    {&quot;pfmerge&quot;,pfmergeCommand,-2,&quot;wm&quot;,0,NULL,1,-1,1,0,0},
    {&quot;pfdebug&quot;,pfdebugCommand,-3,&quot;w&quot;,0,NULL,0,0,0,0,0},
    {&quot;latency&quot;,latencyCommand,-2,&quot;arslt&quot;,0,NULL,0,0,0,0,0}
};
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;当所有的属性都解析完毕之后，开始调用函数指针方法，方法为:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;redis.c中
/* Call() is the core of Redis execution of a command */
void call(redisClient *c, int flags); 
类似执行client-&amp;gt;cmd-&amp;gt;proc(client);

方法执行完成之后，命令回复保存到客户端的输出缓冲区中，由于是set命令，为固定大小缓冲区，
//固定大小缓冲区
char buf[];
int bufpos;

//可变大小缓冲区
list *reply; 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;执行之后的后续操作，比如开启了慢日志查询，那么会判断是否需要记录日志，也是在call方法中执行的：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/* Log the command into the Slow log if needed, and populate the
     * per-command statistics that we show in INFO commandstats. */
if (flags &amp;amp; REDIS_CALL_SLOWLOG &amp;amp;&amp;amp; c-&amp;gt;cmd-&amp;gt;proc != execCommand) {
    char *latency_event = (c-&amp;gt;cmd-&amp;gt;flags &amp;amp; REDIS_CMD_FAST) ?
                          &quot;fast-command&quot; : &quot;command&quot;;
    latencyAddSampleIfNeeded(latency_event,duration/1000);
    slowlogPushEntryIfNeeded(c-&amp;gt;argv,c-&amp;gt;argc,duration);
}   

如果开启了AOF持久化功能，如果其他从服务器正在复制当前这个服务器，这个命令会被传播给其他从服务器
/* Propagate the command into the AOF and replication link */
if (flags &amp;amp; REDIS_CALL_PROPAGATE) {
    int flags = REDIS_PROPAGATE_NONE;

    if (c-&amp;gt;flags &amp;amp; REDIS_FORCE_REPL) flags |= REDIS_PROPAGATE_REPL;
    if (c-&amp;gt;flags &amp;amp; REDIS_FORCE_AOF) flags |= REDIS_PROPAGATE_AOF;
    if (dirty)
        flags |= (REDIS_PROPAGATE_REPL | REDIS_PROPAGATE_AOF);
    if (flags != REDIS_PROPAGATE_NONE)
        propagate(c-&amp;gt;cmd,c-&amp;gt;db-&amp;gt;id,c-&amp;gt;argv,c-&amp;gt;argc,flags);
}

/* Restore the old FORCE_AOF/REPL flags, since call can be executed
 * recursively. */
c-&amp;gt;flags &amp;amp;= ~(REDIS_FORCE_AOF|REDIS_FORCE_REPL);
c-&amp;gt;flags |= client_old_flags &amp;amp; (REDIS_FORCE_AOF|REDIS_FORCE_REPL);

/* Handle the alsoPropagate() API to handle commands that want to propagate
 * multiple separated commands. */
if (server.also_propagate.numops) {
    int j;
    redisOp *rop;

    for (j = 0; j &amp;lt; server.also_propagate.numops; j++) {
        rop = &amp;amp;server.also_propagate.ops[j];
        propagate(rop-&amp;gt;cmd, rop-&amp;gt;dbid, rop-&amp;gt;argv, rop-&amp;gt;argc, rop-&amp;gt;target);
    }
    redisOpArrayFree(&amp;amp;server.also_propagate);
} 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;serverCron函数，这个在之前的AOF和RDB操作以及事件中已经说过，它是一种时间事件；更新服务器时间缓存，redis中不少功能
需要获取系统的当前时间，&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;struct redisServer {

    // 保存了秒级精确的系统当前UNIX时间戳
    time_t unixtime;
    
    // 保存了毫秒级精确的系统当前UNIX时间戳
    long long mstime;

}

/* Update the time cache. */
updateCachedTime();
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;serverCron函数默认会以100毫秒一次的频率更新unixtime属性和mstime属性，所以他们的精确度不是很高。&lt;br /&gt;
LRU时钟，服务器状态中的lrulock属性保存了服务器的LRU时钟，这个属性和上面介绍的unixtime属性、mstime属性一样，都是服务器缓存中的一种：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;struct redisServer {

    // key的空转时间，默认10s更新一次时钟缓存
    unsigned lrulock:22;

}

每个redis对象都有一个lru属性，这个lru属性保存了对象最后一次被命令访问的时间
struct redisObject {

    unsigned lru:22;

}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;当服务器要计算一个key的空转时间，程序会用服务器的lrulock属性记录的时间戳减去对象的lru属性记录的时间，得出的计算结果就是这个对象的空转时间&lt;br /&gt;
再就是更新服务器每秒执行命令次数，这是一种数据采样&lt;br /&gt;
管理客户端资源，serverCron函数每次会执行clientsCron函数，主要处理超时的客户端以及关闭超出输出缓冲区的客户端&lt;br /&gt;
管理数据库资源、持久化操作等等&lt;/p&gt;

</description>
        <pubDate>Fri, 10 Sep 2021 00:00:00 +0000</pubDate>
        <link>https://seakingOne.github.io/database/2021/09/10/redis%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90%E4%B9%8Bredis%E6%9C%8D%E5%8A%A1%E7%AB%AF.html</link>
        <guid isPermaLink="true">https://seakingOne.github.io/database/2021/09/10/redis%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90%E4%B9%8Bredis%E6%9C%8D%E5%8A%A1%E7%AB%AF.html</guid>
        
        <category>C/C++</category>
        
        <category>redis</category>
        
        <category>服务端</category>
        
        
        <category>Database</category>
        
      </item>
    
      <item>
        <title>redis源码解析之redis客户端</title>
        <description>&lt;p&gt;Redis服务器是典型的一对多服务器程序：一个服务器可以与多个客户端建立网络连接。&lt;br /&gt;
Redis服务器状态结构的clients属性是一个链表，这个链表保存了所有与服务器连接的客户端的状态结构&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;struct redisServer {

    //一个链表，保存了所有客户端状态
    list *clients;

}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;套接字描述符，客户端状态的fd属性记录了客户端使用的套接字描述符:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;struct redisClient {

    //客户端名字
    robj *name;

    //套接字
    int fd;
    
    //客户端的角色
    int flags;

    //客户端状态输入缓冲区（保存客户端发送的命令请求）
    sds querybuf
}    

/* Client flags */
#define REDIS_SLAVE (1&amp;lt;&amp;lt;0)   /* This client is a slave server */
#define REDIS_MASTER (1&amp;lt;&amp;lt;1)  /* This client is a master server */
#define REDIS_MONITOR (1&amp;lt;&amp;lt;2) /* This client is a slave monitor, see MONITOR */
#define REDIS_MULTI (1&amp;lt;&amp;lt;3)   /* This client is in a MULTI context */
#define REDIS_BLOCKED (1&amp;lt;&amp;lt;4) /* The client is waiting in a blocking operation */
#define REDIS_DIRTY_CAS (1&amp;lt;&amp;lt;5) /* Watched keys modified. EXEC will fail. */
#define REDIS_CLOSE_AFTER_REPLY (1&amp;lt;&amp;lt;6) /* Close after writing entire reply. */
#define REDIS_UNBLOCKED (1&amp;lt;&amp;lt;7) /* This client was unblocked and is stored in
                                  server.unblocked_clients */
#define REDIS_LUA_CLIENT (1&amp;lt;&amp;lt;8) /* This is a non connected client used by Lua */
#define REDIS_ASKING (1&amp;lt;&amp;lt;9)     /* Client issued the ASKING command */
#define REDIS_CLOSE_ASAP (1&amp;lt;&amp;lt;10)/* Close this client ASAP */
#define REDIS_UNIX_SOCKET (1&amp;lt;&amp;lt;11) /* Client connected via Unix domain socket */
#define REDIS_DIRTY_EXEC (1&amp;lt;&amp;lt;12)  /* EXEC will fail for errors while queueing */
#define REDIS_MASTER_FORCE_REPLY (1&amp;lt;&amp;lt;13)  /* Queue replies even if is master */
#define REDIS_FORCE_AOF (1&amp;lt;&amp;lt;14)   /* Force AOF propagation of current cmd. */
#define REDIS_FORCE_REPL (1&amp;lt;&amp;lt;15)  /* Force replication of current cmd. */
#define REDIS_PRE_PSYNC (1&amp;lt;&amp;lt;16)   /* Instance don&apos;t understand PSYNC. */
#define REDIS_READONLY (1&amp;lt;&amp;lt;17)    /* Cluster client is in read-only state. */
#define REDIS_PUBSUB (1&amp;lt;&amp;lt;18)      /* Client is in Pub/Sub mode. */

根据客户端类型的不同，fd属性的值可以是-1或者大于-1的整数，伪客户端的fd属性为-1，表示命令请求来源于AOF文件或者LUA脚本，而不是网络，
这种客户端不需要套接字连接，自然也不需要记录套接字描述符，目前Redis服务器会在2个地方用到伪客户端，一个用于载入AOF文件并还原数据库状态，
而另外一个用于执行LUA脚本中包含Redis命令。
普通客户端的fd属性的值&amp;gt;-1的整数。

&amp;gt; CLIENT list
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;举个例子，假如客户端发送命令请求 set key value，那么客户端状态的querybuf属性将一个包含以下内容的sds值&lt;br /&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;输入缓冲区的大小会根据输入内容动态的缩小或扩大，但它的最大大小不能超过1GB，否则服务器将会关闭这个客户端。&lt;br /&gt;
命令与命令参数，将服务器将客户端的命令请求保存到客户端状态的querybuf属性之后，服务器将会对命令请求的内容进行分析，并且将参数保存到客户端状态的
argv属性和argc属性：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;struct redisClient {

    //argv属性是一个数组，数组中的每个项都是一个字符串对象
    robj **argv;
    
    //是argv数组的长度
    int argc;

}        

结构图如下：
-----------
redisClient
-----------            -----------
    argv      ----&amp;gt;      argv[0]
-----------            ----------- 
    argc               StringObject
     3                     &quot;SET&quot; 
-----------            ------------
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;命令的实现函数，当程序在命令表中成功找到argv[0]所对应的redisCommand结构时，它会将客户端状态的cmd指针指向这个结构&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;struct redisClient {

    struct redisCommand *cmd;

}

----    
dict
----       ------------
&quot;set&quot; ---&amp;gt; redisCommand 
----       ------------
&quot;get&quot;          ....
----       ------------
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;输出缓冲区，执行命令所得的命令回复会被保存到客户端状态的输出缓冲区里面，每个客户端有2个输出缓冲区，一个缓冲区的大小是固定的，另一个缓冲区的大小
是可变的。&lt;br /&gt;
固定大小的缓冲区是保存比较固定的回复，比如OK、整数、错误消息等。&lt;br /&gt;
可变大小的缓冲区用于保存比较大的回复，比如一个非常长的字符串值。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;struct redisClient {

    //固定大小缓冲区
    char buf[];
    int bufpos;
    
    //可变大小缓冲区
    list *reply;

}     
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

</description>
        <pubDate>Sun, 05 Sep 2021 00:00:00 +0000</pubDate>
        <link>https://seakingOne.github.io/database/2021/09/05/redis%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90%E4%B9%8Bredis%E5%AE%A2%E6%88%B7%E7%AB%AF.html</link>
        <guid isPermaLink="true">https://seakingOne.github.io/database/2021/09/05/redis%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90%E4%B9%8Bredis%E5%AE%A2%E6%88%B7%E7%AB%AF.html</guid>
        
        <category>C/C++</category>
        
        <category>redis</category>
        
        <category>客户端</category>
        
        
        <category>Database</category>
        
      </item>
    
      <item>
        <title>redis源码解析之redis事件</title>
        <description>&lt;p&gt;众所周知，Redis服务器是一个事件驱动程序，服务器会处理以下2类事件：&lt;br /&gt;
文件事件，Redis通过套接字与客户端进行连接，文件事件是服务器对套接字操作的抽象，服务器会通过监听并处理这些事件来完成一系列
网络通信操作&lt;br /&gt;
Redis的I/O多路复用程序的所有功能都是通过包装select、epoll、evport、kqueue这些函数库来完成的，Redis编译的饿时候根据
不同的操作系统初始化不同的库&lt;br /&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;源码分析：
networking.c/acceptTcpHandler函数是Redis的连接应答处理器，这个处理器用于对连接服务器监听套接字的客户端进行应答
对于socket.h/accpet的包装

当Redis服务器进行初始化的时候，程序会将连接应答处理器和服务器监听套接字的AE_READABLE事件关联起来，当socket.h/connect
函数连接服务器套接字的时候，会产生AE_READABLE事件，引发连接应答处理器执行，并且执行相应的套接字应答事件
源码中是，redis.c/initServer

/* Create an event handler for accepting new connections in TCP and Unix
     * domain sockets. */
for (j = 0; j &amp;lt; server.ipfd_count; j++) {
    if (aeCreateFileEvent(server.el, server.ipfd[j], AE_READABLE,
        acceptTcpHandler,NULL) == AE_ERR)
        {
            redisPanic(
                &quot;Unrecoverable error creating server.ipfd file event.&quot;);
        }
}

命令请求处理器，networking.c/readQueryFromClient函数是Redis的命令请求处理器，这个处理器负责从套接字中读入客户端发送
的请求内容

redisClient *createClient(int fd){

    
    if (fd != -1) {
        anetNonBlock(NULL,fd);
        anetEnableTcpNoDelay(NULL,fd);
        if (server.tcpkeepalive)
            anetKeepAlive(NULL,fd,server.tcpkeepalive);
        if (aeCreateFileEvent(server.el,fd,AE_READABLE,
            readQueryFromClient, c) == AE_ERR)
        {
            close(fd);
            zfree(c);
            return NULL;
        }
    }
}

命令回复处理器，networking.c/sendReplyToClient函数是Redis的命令回复处理器，这个处理器负责将服务器执行命令后的数据返回给客户端
int prepareClientToWrite(redisClient *c) {
    /* If it&apos;s the Lua client we always return ok without installing any
     * handler since there is no socket at all. */
    if (c-&amp;gt;flags &amp;amp; REDIS_LUA_CLIENT) return REDIS_OK;

    /* Masters don&apos;t receive replies, unless REDIS_MASTER_FORCE_REPLY flag
     * is set. */
    if ((c-&amp;gt;flags &amp;amp; REDIS_MASTER) &amp;amp;&amp;amp;
        !(c-&amp;gt;flags &amp;amp; REDIS_MASTER_FORCE_REPLY)) return REDIS_ERR;

    if (c-&amp;gt;fd &amp;lt;= 0) return REDIS_ERR; /* Fake client for AOF loading. */

    /* Only install the handler if not already installed and, in case of
     * slaves, if the client can actually receive writes. */
    if (c-&amp;gt;bufpos == 0 &amp;amp;&amp;amp; listLength(c-&amp;gt;reply) == 0 &amp;amp;&amp;amp;
        (c-&amp;gt;replstate == REDIS_REPL_NONE ||
         (c-&amp;gt;replstate == REDIS_REPL_ONLINE &amp;amp;&amp;amp; !c-&amp;gt;repl_put_online_on_ack)))
    {
        /* Try to install the write handler. */
        if (aeCreateFileEvent(server.el, c-&amp;gt;fd, AE_WRITABLE,
                sendReplyToClient, c) == AE_ERR)
        {
            freeClientAsync(c);
            return REDIS_ERR;
        }
    }

    /* Authorize the caller to queue in the output buffer of this client. */
    return REDIS_OK;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;时间事件，定时定点做某些事，比如之前的AOF和RDB定时数据持久化&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;源码也是在redis.c/initServer函数中，初始化了时间事件
/* Create the serverCron() time event, that&apos;s our main way to process
     * background operations. */
if(aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL) == AE_ERR) {
    redisPanic(&quot;Can&apos;t create the serverCron time event.&quot;);
    exit(1);
}

aeProcessEvents中
/* Check time events */
if (flags &amp;amp; AE_TIME_EVENTS)
    processed += processTimeEvents(eventLoop);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;不管是文件事件还是时间事件，都是在main的主进程中阻塞等待事件的处理，具体的执行入口为ae.c/aeMain 
-&amp;gt; ae.c/aeProcessEvents -&amp;gt; 文件事件/时间事件&lt;/p&gt;
</description>
        <pubDate>Sun, 29 Aug 2021 00:00:00 +0000</pubDate>
        <link>https://seakingOne.github.io/database/2021/08/29/redis%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90%E4%B9%8Bredis%E4%BA%8B%E4%BB%B6.html</link>
        <guid isPermaLink="true">https://seakingOne.github.io/database/2021/08/29/redis%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90%E4%B9%8Bredis%E4%BA%8B%E4%BB%B6.html</guid>
        
        <category>C/C++</category>
        
        <category>redis</category>
        
        <category>事件</category>
        
        
        <category>Database</category>
        
      </item>
    
      <item>
        <title>redis源码解析之redis AOF持久化</title>
        <description>&lt;p&gt;AOF是Redis另外一种持久化方式，是通过保存Redis服务器所执行的写命令来记录数据库状态的&lt;br /&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;比如：
set msg &quot;hello&quot;

对于上面执行的写命令来说，AOF文件内容为：
2\r\n$6\r\nSELECT\r\n$1\r\n0\r\n
3\r\n$3\r\nSET\r\n$3\r\nmsg\r\n$5\r\nhello\r\n
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;AOF持久化功能处于打开状态时，服务器在执行完之后，会以协议格式将被执行的写命令追加到服务器状态的aof_buf
缓冲区的末尾&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;struct redisServer {

    // aof缓冲区
    sds aof_buf;

}    

/* AOF postponed flush: Try at every cron cycle if the slow fsync
     * completed. */
if (server.aof_flush_postponed_start) flushAppendOnlyFile(0);

/* AOF write errors: in this case we have a buffer to flush as well and
     * clear the AOF error in case of success to make the DB writable again,
     * however to try every second is enough in case of &apos;hz&apos; is set to
     * an higher frequency. */
run_with_period(1000) {
    if (server.aof_last_write_status == REDIS_ERR)
        flushAppendOnlyFile(0);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;flushAppendOnlyFile的函数行为由服务器所配置appendfsync选项的值来决定&lt;br /&gt;
always,将aof_buf缓冲区的所有内容写入并同步到AOF文件中;&lt;br /&gt;
everysec,将aof_buf缓冲区的所有内容写入到AOF文件，如果上次同步的时候距离现在一秒钟，那么再次对AOF文件进行同步，这个操作由一个线程掌控;&lt;br /&gt;
no,将aof_buf缓冲区的所有内容写到AOF文件中，但并不对AOF文件进行同步，何时同步由操作系统决定。&lt;br /&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;#define REDIS_BIO_AOF_FSYNC     1 /* Deferred AOF fsync. */

void flushAppendOnlyFile(int force) {
    ssize_t nwritten;
    int sync_in_progress = 0;
    mstime_t latency;

    if (sdslen(server.aof_buf) == 0) return;

    // 每秒进行同步
    if (server.aof_fsync == AOF_FSYNC_EVERYSEC)
        sync_in_progress = bioPendingJobsOfType(REDIS_BIO_AOF_FSYNC) != 0;
        
}

 /* Perform the fsync if needed. */
if (server.aof_fsync == AOF_FSYNC_ALWAYS) {
    /* aof_fsync is defined as fdatasync() for Linux in order to avoid
     * flushing metadata. */
    latencyStartMonitor(latency);
    aof_fsync(server.aof_fd); /* Let&apos;s try to get this data on the disk */
    latencyEndMonitor(latency);
    latencyAddSampleIfNeeded(&quot;aof-fsync-always&quot;,latency);
    server.aof_last_fsync = server.unixtime;
} else if ((server.aof_fsync == AOF_FSYNC_EVERYSEC &amp;amp;&amp;amp;
            server.unixtime &amp;gt; server.aof_last_fsync)) {
    if (!sync_in_progress) aof_background_fsync(server.aof_fd);
    server.aof_last_fsync = server.unixtime;
}  
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;AOF文件的载入与数据还原&lt;br /&gt;
因为AOF文件里面包含了重建数据库状态所需的所有写命令，所以服务器只要读入并重新执行一遍AOF文件里面保存的写命令，源码在redis.c/initServer方法&lt;br /&gt;
Redis读取AOF文件并还原数据库状态的详细步骤如下：&lt;br /&gt;
&lt;img src=&quot;/static/assets/img/blog/redis/aof_process.png&quot; width=&quot;65%&quot; /&gt; &lt;br /&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;一般来说，会优先加载AOF文件：
/* Open the AOF file if needed. */
    if (server.aof_state == REDIS_AOF_ON) {
#ifdef _WIN32
        server.aof_fd = open(server.aof_filename,
                               O_WRONLY|O_APPEND|O_CREAT|_O_BINARY,_S_IREAD|_S_IWRITE);
#else
        server.aof_fd = open(server.aof_filename,
                               O_WRONLY|O_APPEND|O_CREAT,0644);
#endif
        if (server.aof_fd == -1) {
            redisLog(REDIS_WARNING, &quot;Can&apos;t open the append-only file: %s&quot;,
                strerror(errno));
            exit(1);
        }
    }
    
/* Function called at startup to load RDB or AOF file in memory. */
void loadDataFromDisk(void) {
    PORT_LONGLONG start = ustime();
    if (server.aof_state == REDIS_AOF_ON) {
        //loadAppendOnlyFile为AOF加载持久化文件
        if (loadAppendOnlyFile(server.aof_filename) == REDIS_OK)
            redisLog(REDIS_NOTICE,&quot;DB loaded from append only file: %.3f seconds&quot;,(float)(ustime()-start)/1000000);
    } else {
        if (rdbLoad(server.rdb_filename) == REDIS_OK) {
            redisLog(REDIS_NOTICE,&quot;DB loaded from disk: %.3f seconds&quot;,
                (float)(ustime()-start)/1000000);
        } else if (errno != ENOENT) {
            redisLog(REDIS_WARNING,&quot;Fatal error loading the DB: %s. Exiting.&quot;,strerror(errno));
            exit(1);
        }
    }
}  


int loadAppendOnlyFile(char *filename) {

    struct redisClient *fakeClient;
    
    .....
    
    //创建一个Redis伪客户端   
    fakeClient = createFakeClient();
}      


/* In Redis commands are always executed in the context of a client, so in
 * order to load the append only file we need to create a fake client. */
struct redisClient *createFakeClient(void) {
    struct redisClient *c = zmalloc(sizeof(*c));

    selectDb(c,0);
    c-&amp;gt;fd = -1;   //创建伪客户端，所有文件句柄为-1
    c-&amp;gt;name = NULL;
    c-&amp;gt;querybuf = sdsempty();
    c-&amp;gt;querybuf_peak = 0;
    c-&amp;gt;argc = 0;
    c-&amp;gt;argv = NULL;
    c-&amp;gt;bufpos = 0;
    c-&amp;gt;flags = 0;
    c-&amp;gt;btype = REDIS_BLOCKED_NONE;
    /* We set the fake client as a slave waiting for the synchronization
     * so that Redis will not try to send replies to this client. */
    c-&amp;gt;replstate = REDIS_REPL_WAIT_BGSAVE_START;
    c-&amp;gt;reply = listCreate();
    c-&amp;gt;reply_bytes = 0;
    c-&amp;gt;obuf_soft_limit_reached_time = 0;
    c-&amp;gt;watched_keys = listCreate();
    c-&amp;gt;peerid = NULL;
    listSetFreeMethod(c-&amp;gt;reply,decrRefCountVoid);
    listSetDupMethod(c-&amp;gt;reply,dupClientReplyValue);
    initClientMultiState(c);
    return c;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;AOF重写，由于AOF持久化是通过保存被执行的写命令来记录数据状态的，所以AOF文件内容会越来越多，AOF重写命令是
通过对同一个key的多次操作最终保存为一个记录&lt;br /&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;代码如下：
触发aof重写时机
/* Trigger an AOF rewrite if needed */
 if (server.rdb_child_pid == -1 &amp;amp;&amp;amp;
     server.aof_child_pid == -1 &amp;amp;&amp;amp;
     server.aof_rewrite_perc &amp;amp;&amp;amp;
     server.aof_current_size &amp;gt; server.aof_rewrite_min_size)
 {
    long long base = server.aof_rewrite_base_size ?
                    server.aof_rewrite_base_size : 1;
    long long growth = (server.aof_current_size*100/base) - 100;
    if (growth &amp;gt;= server.aof_rewrite_perc) {
        redisLog(REDIS_NOTICE,&quot;Starting automatic rewriting of AOF on %lld%% growth&quot;,growth);
        rewriteAppendOnlyFileBackground();
    }
 }
 
 具体执行逻辑，还是通过fork子进程来触发重写，具体的逻辑在aof.c/rewriteAppendOnlyFile方法中，
 大概的流程为
 创建新的AOF文件，然后遍历数据库，拿到所有的key，根据key得到的value判断当前的数据类型，不同的类型
 执行不同的重写方法
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;但是这里有个问题，AOF重写的时候数据可能不是最新的，因为在子进程进行AOF重写期间，服务器进程可以继续处理命令请求&lt;br /&gt;
为了解决这种数据不一致问题，Redis服务器设置了一个AOF重写缓冲区，&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;在redisServer中定义：
list *aof_rewrite_buf_blocks;   /* Hold changes during an AOF rewrite. */ 
sds aof_buf;      /* AOF buffer, written before entering the event loop */
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;说明在服务器执行期间，会做三个操作：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;1、执行客户端发来的命令
2、执行后的写命令追加到AOF缓冲区
3、执行后的写命令追加到AOF重写缓冲区  

当子进程完成AOF重写工作之后，他会向父进程发送一个信号，父进程收到信号后，会执行AOF重写缓冲区数据，然后之后
执行文件替换，完成整个操作。代码在aof.c/backgroundRewriteDoneHandler
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</description>
        <pubDate>Mon, 23 Aug 2021 00:00:00 +0000</pubDate>
        <link>https://seakingOne.github.io/database/2021/08/23/redis%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90%E4%B9%8Bredis-AOF%E6%8C%81%E4%B9%85%E5%8C%96.html</link>
        <guid isPermaLink="true">https://seakingOne.github.io/database/2021/08/23/redis%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90%E4%B9%8Bredis-AOF%E6%8C%81%E4%B9%85%E5%8C%96.html</guid>
        
        <category>C/C++</category>
        
        <category>redis</category>
        
        <category>持久化</category>
        
        
        <category>Database</category>
        
      </item>
    
      <item>
        <title>redis源码解析之redis RDB持久化</title>
        <description>&lt;p&gt;对于rdb的文件的创建&lt;br /&gt;
有2个命令可以用于生成RDB文件，一个是save，一个是bgsave&lt;/p&gt;

&lt;p&gt;save命令会阻塞Redis服务器进程，知道RDB文件创建完成为止，服务器期间不能处理任何的请求，其二rdb的持久化存储不具备实时性&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;源码如下：
void saveCommand(redisClient *c) {
    if (server.rdb_child_pid != -1) {
        addReplyError(c,&quot;Background save already in progress&quot;);
        return;
    }
    if (rdbSave(server.rdb_filename) == REDIS_OK) {
        addReply(c,shared.ok);
    } else {
        addReply(c,shared.err);
    }
}

/* Save the DB on disk. Return REDIS_ERR on error, REDIS_OK on success. */
int rdbSave(char *filename) {
    char tmpfile[256];
    FILE *fp;
    rio rdb;
    int error;

    snprintf(tmpfile,256,&quot;temp-%d.rdb&quot;, (int) getpid());
    fp = fopen(tmpfile,IF_WIN32(&quot;wb&quot;,&quot;w&quot;));
    if (!fp) {
        redisLog(REDIS_WARNING, &quot;Failed opening .rdb for saving: %s&quot;,
            strerror(errno));
        return REDIS_ERR;
    }

    rioInitWithFile(&amp;amp;rdb,fp);
    if (rdbSaveRio(&amp;amp;rdb,&amp;amp;error) == REDIS_ERR) {
        errno = error;
        goto werr;
    }

    /* Make sure data will not remain on the OS&apos;s output buffers */
    if (fflush(fp) == EOF) goto werr;
    if (fsync(fileno(fp)) == -1) goto werr;
    if (fclose(fp) == EOF) goto werr;

    /* Use RENAME to make sure the DB file is changed atomically only
     * if the generate DB file is ok. */
    if (rename(tmpfile,filename) == -1) {
        redisLog(REDIS_WARNING,&quot;Error moving temp DB file on the final destination: %s&quot;, strerror(errno));
        unlink(tmpfile);
        return REDIS_ERR;
    }
    redisLog(REDIS_NOTICE,&quot;DB saved on disk&quot;);
    server.dirty = 0;
    server.lastsave = time(NULL);
    server.lastbgsave_status = REDIS_OK;
    return REDIS_OK;

werr:
    redisLog(REDIS_WARNING,&quot;Write error saving DB on disk: %s&quot;, strerror(errno));
    fclose(fp);
    unlink(tmpfile);
    return REDIS_ERR;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;而bgsave会创建一个子进程来执行生成RDB文件操作&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;源码如下：
void bgsaveCommand(redisClient *c) {
    if (server.rdb_child_pid != -1) {
        addReplyError(c,&quot;Background save already in progress&quot;);
    } else if (server.aof_child_pid != -1) {
        addReplyError(c,&quot;Can&apos;t BGSAVE while AOF log rewriting is in progress&quot;);
    } else if (rdbSaveBackground(server.rdb_filename) == REDIS_OK) {
        addReplyStatus(c,&quot;Background saving started&quot;);
    } else {
        addReply(c,shared.err);
    }
} 

rdbSaveBackground方法间接调用了上面的rdbSave方法

int rdbSaveBackground(char *filename) {
    pid_t childpid;
    PORT_LONGLONG start;

    if (server.rdb_child_pid != -1) return REDIS_ERR;

    server.dirty_before_bgsave = server.dirty;
    server.lastbgsave_try = time(NULL);

    start = ustime();
#ifdef _WIN32
    childpid = BeginForkOperation_Rdb(filename, &amp;amp;server, sizeof(server), dictGetHashFunctionSeed());
#else
    if ((childpid = fork()) == 0) {
        int retval;

        /* Child */
        closeListeningSockets(0);
        redisSetProcTitle(&quot;redis-rdb-bgsave&quot;);
        retval = rdbSave(filename);
        if (retval == REDIS_OK) {
            size_t private_dirty = zmalloc_get_private_dirty();

            if (private_dirty) {
                redisLog(REDIS_NOTICE,
                    &quot;RDB: %zu MB of memory used by copy-on-write&quot;,
                    private_dirty/(1024*1024));
            }
        }
        exitFromChild((retval == REDIS_OK) ? 0 : 1);
    } else {
#endif
        /* Parent */
        server.stat_fork_time = ustime()-start;
        server.stat_fork_rate = (double) zmalloc_used_memory() * 1000000 / server.stat_fork_time / (1024*1024*1024); /* GB per second. */
        latencyAddSampleIfNeeded(&quot;fork&quot;,server.stat_fork_time/1000);
        if (childpid == -1) {
            server.lastbgsave_status = REDIS_ERR;
            redisLog(REDIS_WARNING,&quot;Can&apos;t save in background: fork: %s&quot;,
                strerror(errno));
            return REDIS_ERR;
        }
        redisLog(REDIS_NOTICE,&quot;Background saving started by pid %d&quot;,childpid);
        server.rdb_save_time_start = time(NULL);
        server.rdb_child_pid = childpid;
        server.rdb_child_type = REDIS_RDB_CHILD_TYPE_DISK;
        updateDictResizePolicy();
        return REDIS_OK;
#ifndef _WIN32
    }
#endif
    return REDIS_OK; /* unreached */
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;除了命令保存数据之外，Redis回自动间隔性保存&lt;br /&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;举个例子，我们向服务器提供以下配置：
save 900 1
save 300 10
save 60 10000

只要满足3个条件，rdb保存会自动触发
服务器在900秒之内，有1次数据修改
服务器在300秒之内，有10次数据修改
服务器在60秒之内，有10000次数据修改


这些条件被保存在RedisServer中
struct redisServer {

    ...
    
    //记录距离上一次成功执行save/bgsave之后，服务器对数据库进行了多少次修改
    long dirty
    
    //unix时间戳，记录上一次执行save成功的时间
    time_t lastsave;
    
    //记录保存条件的数组
    struct saveParam *saveParam;
    
    ...

}

struct saveParam {

    //秒数
    time_t seconds;

    //修改数
    int changes;

}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;对于轮询检查是否满足条件，和上一章的一样，在redis.c/serverCron方法中执行，由此可见Redis默认持久化是通过RDB实现&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;int serverCron(struct aeEventLoop *eventLoop, PORT_LONGLONG id, void *clientData) {

    .......
    
     /* If there is not a background saving/rewrite in progress check if
             * we have to save/rewrite now */
     for (j = 0; j &amp;lt; server.saveparamslen; j++) {
        struct saveparam *sp = server.saveparams+j;

        /* Save if we reached the given amount of changes,
         * the given amount of seconds, and if the latest bgsave was
         * successful or if, in case of an error, at least
         * REDIS_BGSAVE_RETRY_DELAY seconds already elapsed. */
        if (server.dirty &amp;gt;= sp-&amp;gt;changes &amp;amp;&amp;amp;
            server.unixtime-server.lastsave &amp;gt; sp-&amp;gt;seconds &amp;amp;&amp;amp;
            (server.unixtime-server.lastbgsave_try &amp;gt;
             REDIS_BGSAVE_RETRY_DELAY ||
             server.lastbgsave_status == REDIS_OK))
        {
            redisLog(REDIS_NOTICE,&quot;%d changes in %d seconds. Saving...&quot;,
                sp-&amp;gt;changes, (int)sp-&amp;gt;seconds);
            rdbSaveBackground(server.rdb_filename);
            break;
        }
     }

}  
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;关于rdb的文件结构：&lt;br /&gt;
&lt;img src=&quot;/static/assets/img/blog/redis/rdb_struct.png&quot; width=&quot;65%&quot; /&gt; &lt;br /&gt;
Redis文件的最开头是REDIS部分，这个部分长度为5个字节，保存着”REDIS”五个字符，程序可以在载入文件的时候
，快速检查文件是否为rdb文件。从源码中我们也可以看出蛛丝马迹&lt;br /&gt;
&lt;img src=&quot;/static/assets/img/blog/redis/rdb_struct-1.png&quot; width=&quot;65%&quot; /&gt; &lt;br /&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;REDIS_RDB_VERSION 6
可以看出当前db版本为0006
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;databases部分，一个rdb文件的databases部分可以保存任意多个非空数据库&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;假设0号数据库和3号数据库非空，则结构如下：
REDIS | db_version | database 0 | database 3 | EOF | check_sum    
                         |
           SELECT DB | db_number | key_value_pairs
                                          |
                        不带过期时间：type | key | value
                        带过期时间：EXPIREME_MS | ms | type | key | value  
           
SELECT DB长度为1字节，当读入程序遇到这个值，它会知道接下来是一个数据库号码，db_number保存着数据库
号码，根据号码的不同，长度可以是1字节，2字节或5字节，key_value_pairs保存了数据库中的所有键值对，
如果有过期时间也会带过期时间 ,type为数据类型，可以解析value的类型  

源码如下：
for (j = 0; j &amp;lt; server.dbnum; j++) {
    redisDb *db = server.db+j;
    dict *d = db-&amp;gt;dict;
    if (dictSize(d) == 0) continue;
    di = dictGetSafeIterator(d);
    if (!di) return REDIS_ERR;

    /* Write the SELECT DB opcode */
    if (rdbSaveType(rdb,REDIS_RDB_OPCODE_SELECTDB) == -1) goto werr;
    if (rdbSaveLen(rdb,j) == -1) goto werr;

    /* Iterate this DB writing every entry */
    while((de = dictNext(di)) != NULL) {
        sds keystr = dictGetKey(de);
        robj key, *o = dictGetVal(de);
        PORT_LONGLONG expire;

        initStaticStringObject(key,keystr);
        expire = getExpire(db,&amp;amp;key);
        if (rdbSaveKeyValuePair(rdb,&amp;amp;key,o,expire,now) == -1) goto werr;
    }
    dictReleaseIterator(di);
}      
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;EOF长度为1字节，这个字段标识着RDB文件的结束&lt;br /&gt;
check_sum是一个8字节的无符号整数，保存着一个校验和，这个校验和是对前面4个部分的内容计算得出，服务器在载入rdb文件的时候，会做校验&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;源码如下：
/* EOF opcode */
if (rdbSaveType(rdb,REDIS_RDB_OPCODE_EOF) == -1) goto werr;

/* CRC64 checksum. It will be zero if checksum computation is disabled, the
 * loading code skips the check in this case. */
cksum = rdb-&amp;gt;cksum;
memrev64ifbe(&amp;amp;cksum);
if (rioWrite(rdb,&amp;amp;cksum,8) == 0) goto werr;    
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;font color=&quot;red&quot;&gt;(从源码中获取一些信息，需要注意的特殊情况,在bgSave执行过程中，客户端发送的save命令
会被服务器拒绝，然后aof的重写bgrewriteaof和bgsave也不能同时执行，因为这2个命令都是通过子进程去执行，出于
性能的考虑这不是一个好的选择)&lt;/font&gt;

&lt;p&gt;由于RDB持久化是基于内存快照，所以是内存的全量持久化，并且会替换到对应的文件中去。&lt;/p&gt;
</description>
        <pubDate>Wed, 18 Aug 2021 00:00:00 +0000</pubDate>
        <link>https://seakingOne.github.io/database/2021/08/18/redis%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90%E4%B9%8Bredis-RDB%E6%8C%81%E4%B9%85%E5%8C%96.html</link>
        <guid isPermaLink="true">https://seakingOne.github.io/database/2021/08/18/redis%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90%E4%B9%8Bredis-RDB%E6%8C%81%E4%B9%85%E5%8C%96.html</guid>
        
        <category>C/C++</category>
        
        <category>redis</category>
        
        <category>持久化</category>
        
        
        <category>Database</category>
        
      </item>
    
      <item>
        <title>redis源码解析之redis数据库</title>
        <description>&lt;p&gt;Redis服务器将所有数据库都保存在服务器状态redis.h/redisServer结构的db数组中，
db数组的每个项都是一个redis.h/redisDb结构，每个redisDb结构代表一个数据库：&lt;br /&gt;
&lt;img src=&quot;/static/assets/img/blog/redis/redisdb.jpg&quot; width=&quot;75%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;在初始化服务器时，程序会根据服务器状态的dbnum属性来确定创建多少个数据库：&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;struct redisServer &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    ....
    
    int dbnum&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;                      /&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; Total number of configured DBs &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;/
    
    ....

&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;dbnum属性的值有服务器配置的database决定 ，默认情况下，为16，所以Redis默认会创建16个数据库&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;typedef struct redisDb &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    dict &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;dict&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;                 /&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; The keyspace &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;this DB &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;/
    dict &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;expires&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;              /&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; Timeout of keys with a &lt;span class=&quot;nb&quot;&gt;timeout set&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;/
    dict &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;blocking_keys&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;        /&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; Keys with clients waiting &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;data &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;BLPOP&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;/
    dict &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;ready_keys&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;           /&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; Blocked keys that received a PUSH &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;/
    dict &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;watched_keys&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;         /&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; WATCHED keys &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;MULTI/EXEC CAS &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;/
    struct evictionPoolEntry &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;eviction_pool&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;    /&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; Eviction pool of keys &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;/
    int &lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;                     /&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; Database ID &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;/
    long long avg_ttl&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;          /&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; Average TTL, just &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;stats &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;/
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; redisDb&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;结构如下：
 -----
|redis|    
 -----
|.....|
 -----
| db  | -&amp;gt; db[0] | db[1] | ... | db[15]
 -----
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;在服务器内部，客户端状态redisClient结构的db属性记录了客户端当前的目标数据库，这个属性是指向redisDb结构的指针&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;struct redisClient &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    //记录客户端当前正在使用的数据库
    redisDb &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;db&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;     
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;这也正是select 切换数据库的原理
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;数据库键空间，在上面redisDb结构中dict字典保存了数据库中的所有键值对&lt;br /&gt;
举个例子：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;set message &quot;hello world&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;那么在数据结构中表示为:&lt;br /&gt; &lt;br /&gt;
&lt;img src=&quot;/static/assets/img/blog/redis/redisDb_dict.jpg&quot; width=&quot;75%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;添加新的键值对到数据库，实际上也就是将键值对添加到dict指针里面去&lt;/p&gt;

&lt;p&gt;对于键的生存时间或过期时间&lt;br /&gt;
通过expire或者pexpire命令，客户端可以以秒或者毫秒精度为数据中的某个键设置生存时间&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;set key value
expire key 5

// 表示5秒之后失效
// redisDb中正好保存了过期时间，字段为expires
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;结构图如下：&lt;br /&gt;
&lt;img src=&quot;/static/assets/img/blog/redis/redisDb_expire.jpg&quot; width=&quot;75%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Redis主体上有3种过期键删除策略&lt;/p&gt;

&lt;p&gt;定时删除&lt;br /&gt;
定时删除对于内存是友好的，通过定时器，定时删除策略可以保证过期键会尽可能被删除，并释放过期键所占用的内存。
另一方面，定时删除策略的缺点是，它对cpu是不友好的，需要创建大量的定时器，在现阶段来说并不现实。&lt;/p&gt;

&lt;p&gt;惰性删除&lt;br /&gt;
惰性删除对于CPU时间是友好的，程序只有在取出key的时候才进行过期检查，反之对于内存是不友好的。&lt;br /&gt;
过期键的惰性删除策略在db.c/expireIfNeeded函数实现，所有读写数据库的Redis命令在执行之前都会调用expireIfNeeded
函数对输入键进行检查。&lt;br /&gt;
&lt;img src=&quot;/static/assets/img/blog/redis/key_expired.png&quot; width=&quot;75%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;定期删除&lt;br /&gt;
定期删除是2种情况的折中和整合&lt;br /&gt;
过期键的定期删除策略由redis.c/activeExpireCycle函数实现的，每当Redis的服务器周期性操作redis.c/serverCron函数
执行时，activeExpireCycle函数就会被调用，分多次遍历服务器中的每个数据库，从数据库的expired字段中随机检查一部分key
的过期时间，并删除其中的过期key。&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;void databasesCron&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;void&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    /&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; Expire keys by random sampling. Not required &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;slaves
        &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; as master will synthesize DELs &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;us. &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;/
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;server.active_expire_enabled &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; server.masterhost &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; NULL&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
        activeExpireCycle&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;ACTIVE_EXPIRE_CYCLE_SLOW&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;        


&lt;span class=&quot;nt&quot;&gt;-------------activeExpireCycle---------------&lt;/span&gt;

/&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; Continue to expire &lt;span class=&quot;k&quot;&gt;if &lt;/span&gt;at the end of the cycle more than 25%
            &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; of the keys were expired. &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;/
&lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    unsigned long num, slots&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    long long now, ttl_sum&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    int ttl_samples&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    /&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; If there is nothing to expire try next DB ASAP. &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;/
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;((&lt;/span&gt;num &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; dictSize&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;db-&amp;gt;expires&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; 0&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        db-&amp;gt;avg_ttl &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 0&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;nb&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    slots &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; dictSlots&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;db-&amp;gt;expires&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    now &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; mstime&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    /&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; When there are less than 1% filled slots getting random
        &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; keys is expensive, so stop here waiting &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;better times...
        &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; The dictionary will be resized asap. &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;/
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;num &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; slots &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; DICT_HT_INITIAL_SIZE &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;num&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;100/slots &amp;lt; 1&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    /&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; The main collection cycle. Sample random keys among keys
        &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; with an expire &lt;span class=&quot;nb&quot;&gt;set&lt;/span&gt;, checking &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;expired ones. &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;/
    expired &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 0&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    ttl_sum &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 0&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    ttl_samples &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 0&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;num &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
        num &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;num--&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        dictEntry &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;de&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        long long ttl&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;((&lt;/span&gt;de &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; dictGetRandomKey&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;db-&amp;gt;expires&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; NULL&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        ttl &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; dictGetSignedIntegerVal&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;de&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-now&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;activeExpireCycleTryExpire&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;db,de,now&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt; expired++&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;ttl &amp;lt; 0&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; ttl &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 0&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        ttl_sum +&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; ttl&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        ttl_samples++&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    /&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; Update the average TTL stats &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;this database. &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;/
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;ttl_samples&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        long long avg_ttl &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; ttl_sum/ttl_samples&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;db-&amp;gt;avg_ttl &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; 0&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; db-&amp;gt;avg_ttl &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; avg_ttl&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        /&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; Smooth the value averaging with the previous one. &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;/
        db-&amp;gt;avg_ttl &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;db-&amp;gt;avg_ttl+avg_ttl&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;/2&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    /&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; We can&lt;span class=&quot;s1&quot;&gt;&apos;t block forever here even if there are many keys to
        * expire. So after a given amount of milliseconds return to the
        * caller waiting for the other active expire cycle. */
    iteration++;
    if ((iteration &amp;amp; 0xf) == 0) { /* check once every 16 iterations. */
        long long elapsed = ustime()-start;

        latencyAddSampleIfNeeded(&quot;expire-cycle&quot;,elapsed/1000);
        if (elapsed &amp;gt; timelimit) timelimit_exit = 1;
    }
    if (timelimit_exit) return;
    /* We don&apos;&lt;/span&gt;t repeat the cycle &lt;span class=&quot;k&quot;&gt;if &lt;/span&gt;there are less than 25% of keys
        &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; found expired &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;the current DB. &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;/
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;expired &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP/4&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

</description>
        <pubDate>Fri, 13 Aug 2021 00:00:00 +0000</pubDate>
        <link>https://seakingOne.github.io/database/2021/08/13/redis%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90%E4%B9%8Bredis%E6%95%B0%E6%8D%AE%E5%BA%93.html</link>
        <guid isPermaLink="true">https://seakingOne.github.io/database/2021/08/13/redis%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90%E4%B9%8Bredis%E6%95%B0%E6%8D%AE%E5%BA%93.html</guid>
        
        <category>C/C++</category>
        
        <category>redis</category>
        
        <category>内存</category>
        
        
        <category>Database</category>
        
      </item>
    
      <item>
        <title>redis源码解析之redis对象</title>
        <description>&lt;h2 id=&quot;注意所有源码分析基于redis30&quot;&gt;注意：所有源码分析基于redis3.0&lt;/h2&gt;

&lt;p&gt;Redis中有基本的数据结构，如sds,双端队列,双端链表,字典,压缩列表,集合等等。&lt;/p&gt;

&lt;p&gt;Redis中并没有直接使用这些数据结构来实现键值对数据库，而是基于这些数据结构创建了一个对象系统，这个系统中
包含字符串对象，列表对象，哈希对象，集合对象和有序集合对象。&lt;/p&gt;

&lt;p&gt;Redis中每个对象都由一个RedisObject结构表示，该结构中和保存数据有关的三个熟悉为：&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;typedef struct redisObject &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    // 类型
    unsigned &lt;span class=&quot;nb&quot;&gt;type&lt;/span&gt;:4&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    
    // 编码
    unsinged encoding:4&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    
    // 指向底层实现数据结构的指针
    void &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;ptr&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;更加详细的类型定义如下
/* Object types */
#define REDIS_STRING 0
#define REDIS_LIST 1
#define REDIS_SET 2
#define REDIS_ZSET 3
#define REDIS_HASH 4
#define REDIS_HASH_ZIPMAP 9
#define REDIS_LIST_ZIPLIST 10
#define REDIS_SET_INTSET 11
#define REDIS_ZSET_ZIPLIST 12
#define REDIS_HASH_ZIPLIST 13

#define REDIS_ENCODING_RAW 0     简单动态字符串
#define REDIS_ENCODING_INT 1     整数
#define REDIS_ENCODING_HT 2      字典
#define REDIS_ENCODING_ZIPMAP 3  /* Encoded as zipmap */
#define REDIS_ENCODING_LINKEDLIST 4 双端链表
#define REDIS_ENCODING_ZIPLIST 5 压缩列表
#define REDIS_ENCODING_INTSET 6  整数集合
#define REDIS_ENCODING_SKIPLIST 7  跳跃表和集合
#define REDIS_ENCODING_EMBSTR 8  embstr编码的简单动态字符串（sds）   
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;对于字符串对象编码可以是int,raw,embstr &lt;br /&gt;
1 如果一个字符串对象保存的是整数值，并且这个整数值可以用long表示,那么字符串对象会将整数值保存在字符串对象
结构中的ptr属性里面（void* 转换为long），并将字符串编码设置为int&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;假设我们执行 set number 10086
保存结构如下：

-----------redisObject-----------
type   |  encoding  |  ptr | ....
                        |    
                      10086
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;2 如果字符串对象保存的是一个字符串值，并且这个字符串值长度&amp;gt;32字节，那么字符串对象会使用raw来编码，即sds&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;假设我们执行 set str &quot;11212121.....&quot;
保存结构如下：

-----------redisObject-----------
type   |  encoding  |  ptr | ....
                        |
            ---------sdshdr--------
            free  |  len  |  buf
              |       |       |
              0      37     11212121.....

那么一起看下sds的结构
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
struct sdshdr &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    // 记录buf数组中的已使用的字节数量
    int len&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    // 记录buf数组中没有使用的字节数量
    int free&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    // 字节数组，用于保存字符串
    char buf[]&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;3 如果字符串对象保存的是一个字符串值，并且这个字符串值长度&amp;lt;=32字节，那么字符串对象会使用embstr来编码&lt;br /&gt;
 3.1 embstr编码是专门用于保存短字符串的一种编码优化方式，这种编码与raw编码一样，都是用sds结构来表示字符串
对象，但raw会调用2次内存分配函数来创建RedisObject和sds结构，而embstr是分配一次内存即可&lt;/p&gt;

&lt;p&gt;对于其他数据结构涞水类型情况，都是通过ptr指向底层数据结构&lt;/p&gt;

&lt;p&gt;其二，介绍一下RedisObject内存回收&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;typedef struct redisObject &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        
    // 引用计算 
    int refcount&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; 对象的引用计数会随着对象的使用状态发生变化：
 1.1 创建新对象，引用计数为1
 1.2 被其他程序引用+1 (incrRefCount)
 1.3 程序不再使用-1   (decrRefCount)
 1.4 引用计数变为0，对象内存被释放
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;除了用于实现引用计数之外，对象的引用计数还带有共享对象的作用，当然一般只针对于数字&lt;/p&gt;

&lt;p&gt;其三，关于lru属性&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;typedef struct redisObject &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            
    // 改属性记录了对象最后一次被访问的时间     
    unsigned lru:24
            
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;OBJECT IDLETIME key命令可以打印出key的空转时长，通过当前时间减去lru时间得出的
除了这个作用之外，如果服务器打开了maxmemory选项，并且服务器回收内存算法为volatile-lru或者allkeys-lru，那么空转时间较长的
key会被优先释放。     
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

</description>
        <pubDate>Fri, 06 Aug 2021 00:00:00 +0000</pubDate>
        <link>https://seakingOne.github.io/database/2021/08/06/redis%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90%E4%B9%8Bredis%E5%AF%B9%E8%B1%A1.html</link>
        <guid isPermaLink="true">https://seakingOne.github.io/database/2021/08/06/redis%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90%E4%B9%8Bredis%E5%AF%B9%E8%B1%A1.html</guid>
        
        <category>C/C++</category>
        
        <category>redis</category>
        
        <category>内存</category>
        
        
        <category>Database</category>
        
      </item>
    
      <item>
        <title>C中内存分配器分析</title>
        <description>&lt;p&gt;glibc ptmalloc2
ptmalloc2即是我们当前使用的glibc malloc版本。&lt;/p&gt;

&lt;p&gt;调用原理:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/assets/img/blog/linux/memory/x86_process_address_space.png&quot; width=&quot;75%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;上图是 x86_64 下 Linux 进程的默认地址空间, 对 heap 的操作, 操作系统提供了brk()系统调用，设置了Heap的上边界； 对mmap映射区域的操作,操作系统供了mmap()和munmap()函数。
因为系统调用的代价很高，不可能每次申请内存都从内核分配空间，尤其是对于小内存分配。 而且因为mmap的区域容易被munmap释放，所以一般大内存采用mmap()，小内存使用brk()&lt;/p&gt;

&lt;p&gt;多线程支持:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Ptmalloc2有一个主分配区(main arena)， 有多个非主分配区。 非主分配区只能使用mmap向操作系统批发申请HEAP_MAX_SIZE（64位系统为64MB）大小的虚拟内存。 当某个线程调用malloc的时候，会先查看线程私有变量中是否已经存在一个分配区，如果存在则尝试加锁，如果加锁失败则遍历arena链表试图获取一个没加锁的arena， 如果依然获取不到则创建一个新的非主分配区。
free()的时候也要获取锁。分配小块内存容易产生碎片，ptmalloc在整理合并的时候也要对arena做加锁操作。在线程多的时候，锁的开销就会增大。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;ptmalloc内存管理&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;用户请求分配的内存在ptmalloc中使用chunk表示， 每个chunk至少需要8个字节额外的开销。 用户free掉的内存不会马上归还操作系统，ptmalloc会统一管理heap和mmap区域的空闲chunk，避免了频繁的系统调用
ptmalloc 将相似大小的 chunk 用双向链表链接起来, 这样的一个链表被称为一个 bin。Ptmalloc 一共 维护了 128 个 bin,并使用一个数组来存储这些 bin(如下图所示)。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;/static/assets/img/blog/linux/memory/ptmalloc-structure.png&quot; width=&quot;75%&quot; /&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;数组中的第一个为 unsorted bin, 数组中从 2 开始编号的前 64 个 bin 称为 small bins, 同一个small bin中的chunk具有相同的大小。small bins后面的bin被称作large bins。

当free一个chunk并放入bin的时候， ptmalloc 还会检查它前后的 chunk 是否也是空闲的, 如果是的话,ptmalloc会首先把它们合并为一个大的 chunk, 然后将合并后的 chunk 放到 unstored bin 中。 另外ptmalloc 为了提高分配的速度,会把一些小的(不大于64B) chunk先放到一个叫做 fast bins 的容器内。

当bins和fast bins都不能满足分配需要的时候, ptmalloc会设法在top chunk中分出一块内存给用户, 如果top chunk本身不够大, 分配程序会重新mmap分配一块内存chunk, 并将 top chunk 迁移到新的chunk上，并用单链表链接起来。如果free()的chunk恰好 与 top chunk 相邻,那么这两个 chunk 就会合并成新的 top chunk，如果top chunk大小大于某个阈值才还给操作系统。主分配区类似，不过通过sbrk()分配和调整top chunk的大小，只有heap顶部连续内存空闲超过阈值的时候才能回收内存。

需要分配的 chunk 足够大,而且 fast bins 和 bins 都不能满足要求,甚至 top chunk 本身也不能满足分配需求时,ptmalloc 会使用 mmap 来直接使用内存映射来将页映射到进程空间
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;/static/assets/img/blog/linux/memory/ptmalloc-alloc-flow.png&quot; width=&quot;75%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;ptmalloc的缺陷&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;后分配的内存先释放,因为 ptmalloc 收缩内存是从 top chunk 开始,如果与 top chunk 相邻的 chunk 不能释放, top chunk 以下的 chunk 都无法释放。
多线程锁开销大， 需要避免多线程频繁分配释放。
内存从thread的areana中分配， 内存不能从一个arena移动到另一个arena， 就是说如果多线程使用内存不均衡，容易导致内存的浪费。 比如说线程1使用了300M内存，完成任务后glibc没有释放给操作系统，线程2开始创建了一个新的arena， 但是线程1的300M却不能用了。
每个chunk至少8字节的开销很大
不定期分配长生命周期的内存容易造成内存碎片，不利于回收。 64位系统最好分配32M以上内存，这是使用mmap的阈值
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

</description>
        <pubDate>Fri, 23 Jul 2021 00:00:00 +0000</pubDate>
        <link>https://seakingOne.github.io/linux/2021/07/23/C%E4%B8%AD%E5%86%85%E5%AD%98%E5%88%86%E9%85%8D%E5%99%A8%E5%88%86%E6%9E%90.html</link>
        <guid isPermaLink="true">https://seakingOne.github.io/linux/2021/07/23/C%E4%B8%AD%E5%86%85%E5%AD%98%E5%88%86%E9%85%8D%E5%99%A8%E5%88%86%E6%9E%90.html</guid>
        
        <category>Linux</category>
        
        
        <category>Linux</category>
        
      </item>
    
      <item>
        <title>OpenGL光照介绍（一）</title>
        <description>&lt;p&gt;现实世界的光照是极其复杂的，而且会受到诸多因素的影响，这是我们有限的计算能力所无法模拟的。
因此OpenGL的光照使用的是简化的模型，对现实的情况进行近似，这样处理起来会更容易一些，而且看起来也差不多一样。
这些光照模型都是基于我们对光的物理特性的理解。其中一个模型被称为冯氏光照模型(Phong Lighting Model)。
冯氏光照模型的主要结构由3个分量组成：环境(Ambient)、漫反射(Diffuse)和镜面(Specular)光照。
下面这张图展示了这些光照分量看起来的样子&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/assets/img/blog/opengl/basic_lighting_phong.png&quot; width=&quot;75%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;环境光照(Ambient Lighting)：即使在黑暗的情况下，世界上通常也仍然有一些光亮（月亮、远处的光），所以物体几乎永远不会是完全黑暗的。为了模拟这个，我们会使用一个环境光照常量，它永远会给物体一些颜色。
漫反射光照(Diffuse Lighting)：模拟光源对物体的方向性影响(Directional Impact)。它是冯氏光照模型中视觉上最显著的分量。物体的某一部分越是正对着光源，它就会越亮。
镜面光照(Specular Lighting)：模拟有光泽物体上面出现的亮点。镜面光照的颜色相比于物体的颜色会更倾向于光的颜色。&lt;/p&gt;

&lt;p&gt;环境光照：
光通常都不是来自于同一个光源，而是来自于我们周围分散的很多光源，即使它们可能并不是那么显而易见。光的一个属性是，它可以向很多方向发散并反弹，从而能够到达不是非常直接临近的点。所以，光能够在其它的表面上反射，对一个物体产生间接的影响。考虑到这种情况的算法叫做全局照明(Global Illumination)算法，但是这种算法既开销高昂又极其复杂。
由于我们现在对那种又复杂又开销高昂的算法不是很感兴趣，所以我们将会先使用一个简化的全局照明模型，即环境光照。正如你在上一节所学到的，我们使用一个很小的常量（光照）颜色，添加到物体片段的最终颜色中，这样子的话即便场景中没有直接的光源也能看起来存在有一些发散的光。
把环境光照添加到场景里非常简单。我们用光的颜色乘以一个很小的常量环境因子，再乘以物体的颜色，然后将最终结果作为片段的颜色：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;片段着色器代码
void main()
{
    //ambientStrength表示当前环境下的光照强度
    float ambientStrength = 0.1;
    //lightColor表示光照颜色
    vec3 ambient = ambientStrength * lightColor;

    vec3 result = ambient * objectColor;
    FragColor = vec4(result, 1.0);
}   
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;大概是这个样子:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/assets/img/blog/opengl/ambient_lighting.png&quot; width=&quot;75%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;漫反射光照:
环境光照本身不能提供最有趣的结果，但是漫反射光照就能开始对物体产生显著的视觉影响了。漫反射光照使物体上与光线方向越接近的片段能从光源处获得更多的亮度。为了能够更好的理解漫反射光照，请看下图&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/assets/img/blog/opengl/diffuse_light.png&quot; width=&quot;75%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;图左上方有一个光源，它所发出的光线落在物体的一个片段上。我们需要测量这个光线是以什么角度接触到这个片段的。如果光线垂直于物体表面，这束光对物体的影响会最大化&lt;/p&gt;

&lt;p&gt;那么，计算漫反射光照需要什么？&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;法向量：一个垂直于顶点表面的向量。
顶点着色器中加入：
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal; //代表法向量

片段着色器：
out vec3 Normal; //法向量

void main()
{
    gl_Position = projection * view * model * vec4(aPos, 1.0);
    Normal = aNormal;
}

定向的光线：作为光源的位置与片段的位置之间向量差的方向向量。为了计算这个光线，我们需要光的位置向量和片段的位置向量。

计算漫反射光照
uniform vec3 lightPos; //定义光源位置

-----------------------------------------

out vec3 FragPos;  //片段的位置
out vec3 Normal;

void main()
{
    gl_Position = projection * view * model * vec4(aPos, 1.0);
    FragPos = vec3(model * vec4(aPos, 1.0)); //在世界空间坐标系中的位置
    Normal = aNormal;
}

计算光源和片段位置之间的方向向量
//法线和最终的方向向量都进行标准化
vec3 norm = normalize(Normal);
vec3 lightDir = normalize(lightPos - FragPos);

下一步，我们对norm和lightDir向量进行点乘，计算光源对当前片段实际的漫发射影响。结果值再乘以光的颜色，得到漫反射分量
float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = diff * lightColor;

有了环境光分量和漫反射分量，我们把它们相加，然后把结果乘以物体的颜色，来获得片段最后的输出颜色
vec3 result = (ambient + diffuse) * objectColor;
FragColor = vec4(result, 1.0);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;如果你的应用(和着色器)编译成功了，你可能看到类似的输出：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/assets/img/blog/opengl/basic_lighting_diffuse.png&quot; width=&quot;75%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;镜面光照:&lt;/p&gt;

&lt;p&gt;镜面光照也是依据光的方向向量和物体的法向量来决定的，但是它也依赖于观察方向，例如玩家是从什么方向看着这个片段的。镜面光照是基于光的反射特性。如果我们想象物体表面像一面镜子一样，那么，无论我们从哪里去看那个表面所反射的光，镜面光照都会达到最大化。你可以从下面的图片看到效果&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/assets/img/blog/opengl/basic_lighting_specular_theory.png&quot; width=&quot;75%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;我们通过反射法向量周围光的方向来计算反射向量。然后我们计算反射向量和视线方向的角度差，如果夹角越小，那么镜面光的影响就会越大。它的作用效果就是，当我们去看光被物体所反射的那个方向的时候，我们会看到一个高光。&lt;/p&gt;

&lt;p&gt;观察向量是镜面光照附加的一个变量，我们可以使用观察者世界空间位置和片段的位置来计算它。之后，我们计算镜面光强度，用它乘以光源的颜色，再将它加上环境光和漫反射分量&lt;/p&gt;

&lt;p&gt;为了得到观察者的世界空间坐标，我们简单地使用摄像机对象的位置坐标代替（它当然就是观察者）。所以我们把另一个uniform添加到片段着色器，把相应的摄像机位置坐标传给片段着色器：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;uniform vec3 viewPos;
lightingShader.setVec3(&quot;viewPos&quot;, camera.Position);

//现在我们已经获得所有需要的变量，可以计算高光强度了。首先，我们定义一个镜面强度(Specular Intensity)变量，给镜面高光一个中等亮度颜色，让它不要产生过度的影响。
float specularStrength = 0.5;

//计算视角到片段的向量
vec3 viewDir = normalize(viewPos - FragPos);
//计算光的反射向量
vec3 reflectDir = reflect(-lightDir, norm);

float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32);
vec3 specular = specularStrength * spec * lightColor;

vec3 result = (ambient + diffuse + specular) * objectColor;
FragColor = vec4(result, 1.0);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;我们先计算视线方向与反射方向的点乘（并确保它不是负值），然后取它的32次幂。这个32是高光的反光度(Shininess)。一个物体的反光度越高，反射光的能力越强，散射得越少，高光点就会越小。在下面的图片里，你会看到不同反光度的视觉效果影响&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/assets/img/blog/opengl/basic_lighting_specular_shininess.png&quot; width=&quot;75%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;材质：&lt;/p&gt;

&lt;p&gt;在现实世界里，每个物体会对光产生不同的反应。比如说，钢看起来通常会比陶瓷花瓶更闪闪发光，木头箱子也不会像钢制箱子那样对光产生很强的反射。每个物体对镜面高光也有不同的反应。有些物体反射光的时候不会有太多的散射(Scatter)，因而产生一个较小的高光点，而有些物体则会散射很多，产生一个有着更大半径的高光点。如果我们想要在OpenGL中模拟多种类型的物体，我们必须为每个物体分别定义一个材质(Material)属性。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;#version 330 core
struct Material {
    //环境光照
    vec3 ambient;
    //漫反射光照
    vec3 diffuse;
    //镜面反射光照
    vec3 specular;
    //反光度
    float shininess;
}; 

uniform Material material; 

//在片段着色器中，我们创建一个结构体(Struct)来储存物体的材质属性。我们也可以把它们储存为独立的uniform值，但是作为一个结构体来储存会更有条理一些。我们首先定义结构体的布局(Layout)，然后使用刚创建的结构体为类型，简单地声明一个uniform变量。

-------------------------------------------------

void main()
{    
    // 环境光
    vec3 ambient = lightColor * material.ambient;

    // 漫反射 
    vec3 norm = normalize(Normal);
    vec3 lightDir = normalize(lightPos - FragPos);
    float diff = max(dot(norm, lightDir), 0.0);
    vec3 diffuse = lightColor * (diff * material.diffuse);

    // 镜面光
    vec3 viewDir = normalize(viewPos - FragPos);
    vec3 reflectDir = reflect(-lightDir, norm);  
    float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
    vec3 specular = lightColor * (spec * material.specular);  

    vec3 result = ambient + diffuse + specular;
    FragColor = vec4(result, 1.0);
}

lightingShader.setVec3(&quot;material.ambient&quot;,  1.0f, 0.5f, 0.31f);
lightingShader.setVec3(&quot;material.diffuse&quot;,  1.0f, 0.5f, 0.31f);
lightingShader.setVec3(&quot;material.specular&quot;, 0.5f, 0.5f, 0.5f);
lightingShader.setFloat(&quot;material.shininess&quot;, 32.0f);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;运行程序，你应该会得到下面这样的结果：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/assets/img/blog/opengl/materials_with_material.png&quot; width=&quot;75%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;这个物体太亮了。物体过亮的原因是环境光、漫反射和镜面光这三个颜色对任何一个光源都会去全力反射。光源对环境光、漫反射和镜面光分量也具有着不同的强度，
 我们可以用同样的方式修改光源的漫反射和镜面光强度。这和我们在上一节中所做的极为相似，你可以说我们已经创建了一些光照属性来影响每个单独的光照分量。我们希望为光照属性创建一个与材质结构体类似的结构体&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;struct Light {
    vec3 position;

    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
};

uniform Light light;   

vec3 ambient  = light.ambient * material.ambient;
vec3 diffuse  = light.diffuse * (diff * material.diffuse);
vec3 specular = light.specular * (spec * material.specular);

lightingShader.setVec3(&quot;light.ambient&quot;,  0.2f, 0.2f, 0.2f);
lightingShader.setVec3(&quot;light.diffuse&quot;,  0.5f, 0.5f, 0.5f); // 将光照调暗了一些以搭配场景
lightingShader.setVec3(&quot;light.specular&quot;, 1.0f, 1.0f, 1.0f); 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;现在看起来就比较正常了：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/assets/img/blog/opengl/materials_light.png&quot; width=&quot;75%&quot; /&gt;&lt;/p&gt;

</description>
        <pubDate>Thu, 01 Jul 2021 00:00:00 +0000</pubDate>
        <link>https://seakingOne.github.io/opengl/2021/07/01/OpenGL%E5%85%89%E7%85%A7%E4%BB%8B%E7%BB%8D-%E4%B8%80.html</link>
        <guid isPermaLink="true">https://seakingOne.github.io/opengl/2021/07/01/OpenGL%E5%85%89%E7%85%A7%E4%BB%8B%E7%BB%8D-%E4%B8%80.html</guid>
        
        <category>C/C++</category>
        
        <category>3D</category>
        
        <category>渲染</category>
        
        <category>光照</category>
        
        
        <category>Opengl</category>
        
      </item>
    
      <item>
        <title>OpenGL 3D渲染入门</title>
        <description>&lt;p&gt;博主最近三个月基本都在学习OpenGL，主要是想了解视频、图片渲染背后的原理，基本现在算是入门了吧
这算是我个人总结入门篇的内容，有兴趣的大佬可以一起探讨，好了，开始进入主题。&lt;/p&gt;

&lt;p&gt;1、OpenGL是个什么东西?&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;OpenGL本身并不是一个API，它仅仅是一个由Khronos组织制定并维护的规范(Specification)
OpenGL规范严格规定了每个函数该如何执行，以及它们的输出值。
至于内部具体每个函数是如何实现(Implement)的，将由OpenGL库的开发者自行决定（译注：这里开发者是指编写OpenGL库的人）。
因为OpenGL规范并没有规定实现的细节，具体的OpenGL库允许使用不同的实现，只要其功能和结果与规范相匹配（亦即，作为用户不会感受到功能上的差异）。
实际的OpenGL库的开发者通常是显卡的生产商。
你购买的显卡所支持的OpenGL版本都为这个系列的显卡专门开发的。
当你使用Apple系统的时候，OpenGL库是由Apple自身维护的。
在Linux下，有显卡生产商提供的OpenGL库，也有一些爱好者改编的版本。这也意味着任何时候OpenGL库表现的行为与规范规定的不一致时，
基本都是库的开发者留下的bug。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;2、GLAD： 一个拓展加载库，用来为我们加载并设定所有OpenGL函数指针，从而让我们能够使用所有（现代）OpenGL函数&lt;/p&gt;

&lt;p&gt;3、对于设计窗口的设计，大家可以有很多选择，比如glfw、sdl等（sdl一般多用于游戏开发）&lt;/p&gt;

&lt;p&gt;4、可以从选择一个简单的图形开始，有几个重要的概念：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;a、Vertex Buffer Object：顶点数据缓冲 一个调用显存并存储所有顶点数据供显卡使用的缓冲对象
b、Vertex Array Object： 顶点数组对象 存储缓冲区和顶点属性状态
c、Element Buffer Object：索引缓存对象 一个存储索引供索引化绘制使用的缓冲对象
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;5、着色器的概念，着色器这个东西很重要，从我们传入顶点数据开始，到交由着色器，再由显卡渲染出来数据，
   大家可以看这个图&lt;a href=&quot;https://learnopengl-cn.github.io/img/01/04/pipeline.png&quot;&gt;着色器图&lt;/a&gt;&lt;br /&gt;
   对了，如果我们需要自定义着色器开发，需要了解一下&lt;a href=&quot;https://www.khronos.org/opengl/wiki/Core_Language_(GLSL)&quot;&gt;GLSL&lt;/a&gt;语言，
   它和C语言其实很类似，里面还包括Uniform等&lt;/p&gt;

&lt;p&gt;6、纹理：一种包裹着物体的特殊类型图像，给物体精细的视觉效果，说白了就是较少cpu对于图形计算，通过纹理
   传输数据压缩，由GPU操作，带来性能上的提升&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;   纹理缠绕(Texture Wrapping)： 定义了一种当纹理顶点超出范围(0, 1)时指定OpenGL如何采样纹理的模式。
   纹理过滤(Texture Filtering)： 定义了一种当有多种纹素选择时指定OpenGL如何采样纹理的模式。这通常在纹理被放大情况下发生。
   多级渐远纹理(Mipmaps)： 被存储的材质的一些缩小版本，根据距观察者的距离会使用材质的合适大小
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;7、GLM库，一个为OpenGL打造的数学库，向量、矩阵计算，平移、旋转、缩放等等（事实证明，数学很重要）
   坐标系统，3D构建的过程，
   V(clip) = M(projection) * M(view) * M(model) * V(local)
             投影矩阵         观察矩阵    模型矩阵    局部向量，我们的坐标位置&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;局部空间(Local Space)： 一个物体的初始空间。所有的坐标都是相对于物体的原点的。
世界空间(World Space)： 所有的坐标都相对于全局原点。
观察空间(View Space)： 所有的坐标都是从摄像机的视角观察的。
裁剪空间(Clip Space)： 所有的坐标都是从摄像机视角观察的，但是该空间应用了投影。这个空间应该是一个顶点坐标最终的空间，作为顶点着色器的输出。OpenGL负责处理剩下的事情（裁剪/透视除法）。
屏幕空间(Screen Space)： 所有的坐标都由屏幕视角来观察。坐标的范围是从0到屏幕的宽/高。             
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/seakingOne/Linux_Learing/tree/main/OpenGL&quot;&gt;code is here&lt;/a&gt;&lt;/p&gt;
</description>
        <pubDate>Sun, 30 May 2021 00:00:00 +0000</pubDate>
        <link>https://seakingOne.github.io/opengl/2021/05/30/OpenGL-3D%E6%B8%B2%E6%9F%93%E5%85%A5%E9%97%A8.html</link>
        <guid isPermaLink="true">https://seakingOne.github.io/opengl/2021/05/30/OpenGL-3D%E6%B8%B2%E6%9F%93%E5%85%A5%E9%97%A8.html</guid>
        
        <category>C/C++</category>
        
        <category>3D</category>
        
        <category>渲染</category>
        
        
        <category>Opengl</category>
        
      </item>
    
      <item>
        <title>严小米日常</title>
        <description>&lt;p&gt;20210322-20210411&lt;/p&gt;

&lt;p&gt;一、版式设计&lt;/p&gt;

&lt;p&gt;1、&lt;a href=&quot;https://mp.weixin.qq.com/s/bINcAv-6xevrfzLhlKiNGA&quot;&gt;标题排版(春分艺术字体小技巧)&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;2、板式结构
   &lt;a href=&quot;https://mp.weixin.qq.com/s/gCpaFmGCEkkA0SdtjjvE7Q&quot;&gt;曲线(抛物线、S形、环绕型、O形、弧线形)&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;散构：将画面中视觉要素以分散的形式排列与编排（疏散型、密集型、）&lt;/p&gt;

&lt;p&gt;重复（反复型、渐变型）&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://mp.weixin.qq.com/s/MZhDn-q5cIhne8P0KRsWGA&quot;&gt;版式设计，骨骼编排法&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;切入：将视觉元素的局部放置在版面中，以此构成切入式的布局结构&lt;/p&gt;

&lt;p&gt;二、&lt;a href=&quot;https://mp.weixin.qq.com/s/hZITLwuQ_vKwmgkrsTP3jA&quot;&gt;字体设计&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;三、CDR的简单了解，快捷键了解&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;一、常用CDR快捷键
【Ctrl】：
【Ctrl】 +【X】剪切
【Ctrl】 +【C】复制
【Ctrl】 +【V】粘贴
【Ctrl】 +【A】全选
【Ctrl】 +【S】保存
【Ctrl】 +【O】打开
【Ctrl】 +【N】新建
【Ctrl】 +【F4】关闭
【Ctrl】 +【Z】取消
【Ctrl】 + 【Shift】 +【Z】恢复
【Ctrl]+U】下划线
【Ctrl]+。】中英标点符号切换
【Shift】：
【Shift】 +【Delete】永久删除
【Shift】 +【空格】半\全角切换
【Alt】：
【Alt】 +【Tab】在打开的程序之间切换
【Alt】 +【F4】关闭当前程序
【Windows】：
【Windows】 +【D】显示桌面
【Windows】 +【M】最小化所有窗口
【Windows】 +【E】开启“资源管理器”
【F2】 重新命名
二、CDR-实用快捷键
【Ctrl】 + 【Tab】转换窗口
【Ctrl】 + 【E】导出
【Ctrl】 + 【I】导入
【Ctrl】 + 【J】打开“选项”对话框
【Ctrl】+【D】再制选定对象并以指定的距离偏移
【Ctrl】+【R】复制距离、角度、数值、比例、颜色等
【Ctrl】+【Shift】+【A】复制属性（CDR9）
拖动一个对象的同时单击鼠标右键】 拖动复制
原点再制 【+】
【Ctrl】+【T】打开文本属性的格式
【Ctrl】+【Shift】+【T】打开文本对话框
【Shift]+ 【F3】更改大小写
【Ctrl】+【Shift】+【，】单行微调字间距（缩小）
【Ctrl】+【Shift】+【。】单行微调字间距（加大）
【Ctrl】+小键盘【2/4】将字体大小减小为上一个字体大小设置
【Ctrl】+小键盘【6/8】将字体大小增大为上一个字体大小设置
【Ctrl】+【H】变更文字对齐方式分散对齐
【Ctrl】+【N】变更文字对齐方式为不对齐
【Ctrl】+【J】变更文字对齐方式为完全对齐
【Ctrl】+【L】变更文字对齐方式为向左对齐
【Ctrl】+【E】变更文字对齐方式为置中对齐
【Ctrl】+【R】变更文字对齐方式为向右对齐
【Ctrl】+【F8】美术字与段落文本转换
【Shift】+【PageDown】将选择的对象放置到后面
【Shift】+【PageUp】将选择的对象放置到前面
【Ctrl】+【PageDown】将选定对象放置到向后一个位置
【Ctrl】+【PageUp】将选定对象放置到向前一个位置
【Ctrl】+【L】结合选择的对象
【Ctrl】+【K】拆分选择的对象
【Ctrl】+【G】将选择的对象组成群组
【Ctrl】+【U】取消选择对象群组
【Ctrl】+【Q】将选择的对象转换成曲线
【Ctrl】+【Shift】+【Q】将轮廓转换成对象
【F2】临时放大镜
【F3】缩小绘图中的图形
【F4】缩放全部的对象到最大
【Shift】+【F2】缩放选定的对象到最大
鼠标中键+拖动 快速移动
【Alt】+【↑,↓,←,→】将镜头相对于绘画移
【F9】显示绘图的全屏预览
【Shift】+【F4】显示整个可打印页面
【H】平移绘图
【T】将选择对象上对齐
【B】将选择对象下对齐
【L】左对齐选定的对象
【R】将选择对象右对齐
【E】水平对齐选择对象的中心
【C】垂直对齐选择对象的中心
【P】对齐选择对象的中心到页中心
【Alt】+【A】+【A】打开“对齐与分布”对话框
【F12】打开“轮廓笔”对话框
【Shift】+【F12】打开“轮廓颜色”对话框
【F11】将渐变填充应用到对象
【Shift】+【F11】将标准填色应用至物件
【F10】编辑对象的节点
【G】将填充添加到对象
【Alt】+【F7】打开「位置泊坞窗口」
【Alt】+【F8】打开「旋转泊坞窗口」
【Alt】+【F9】打开「缩放与镜像泊坞窗口」
【Alt】+【F10】打开「大小泊坞窗口」
【Ctrl】+【F11】打开「符号泊坞窗口」
【Alt】+【F3】打开「滤镜泊坞窗口」
【Alt】+【F2】包含指定线性度量线属性的功能
三、对象选择技巧
1、 按空格键可以快速切换到“挑选”工具。
2、 按shift键并逐一单击要选择的对象，可连续选择多个对象
3、 选定隐藏在一系列对象后面的单个对象,按住Alt ，然后利用“挑选”工具单击最前面的对象， 直到选定所需的对象。
4、 按Shift+TAB键，会按绘制顺序选择对象。
5、 按Shift多选时，如果不慎误选，可按Shift再次单击误选对象取消之。
6、 单击时按住Ctrl键可在群组中选定单个对象。
7、 单击时按住Alt 键可从一系列对象中选定单个对象。
8、 单击时按住Alt + Ctrl 键可从群组对象中选定单个对象。
9、 在一对象内选择：按住Alt 框选，再用Shift键点击对象。
10、全部选取文本、节点、对象、辅助线等：选择编辑/全选。
11、按住ALT键使用选取工具，不全部圈住对象也能选定对象。

corelDRAW技巧大全

1、corelDRAW 我试过最简单的复制属性方法（补充）
记住是在“选取工具”的前提下用下面的方法。
复制填色：按住Shift键，然后右击要复制的填色对像不放，拖到要填充的对像上就OK了。复制外框：跟上面差不多，相信有的人已悟出了，就是按的键不同，是按住Alt键不放，然后右击要复制的外框填色对像不放，拖到要填充的对像上就OK了。
那么要同时复制怎么办呢，当然是同时按住两个键咯。

2、假如有3个对像在一个坐标上，你要选中最下面的那个，那就按住&quot;alt&quot;键不放，在最上层单击两下就能选中最底层的元件了。
3、一键恢复CorelDraw默认设置！绝！
CorelDraw时间用长了，难免会出现这样那样的问题，比如没了工具栏啊，或者启动后老出现出错提示啊，怎么办，俺教你一招绝活，在启动CorelDraw之前按住（记住不是按一下，而是按住不放）键盘[F8键]，再打开CorelDraw软件，会出如下图：
问你是否要恢复默认设置，这时你点确定，启动后一切正常！！！
4、CD图框精确裁剪快捷编辑技巧, 对已经图框精确裁剪的图案再编辑的时候,把光标放在要编辑的图案中间,用用手按做CTRL健,跟着光标双击图案就可以进入进入编辑图案了,完成编辑之后再将光标放在空白的地方,手再按CTRL健双击光标就可以完成编辑了

corelDRAW文字排版实战技巧
三、
1、corelDRAW排文字有两种方式：美工文字和段落文字，一般情况下排标题及文字比较少而且不用强求对齐的可用美工文字方式。如果有大段文字而且要分行及对齐的请一定用段落文字。

2、文字的来源也有两种方式：一是自己直接在corelDRAW中输入。二是外来的已经存好的如Word文档或记事本等方式，甚至还有其他排版软件里需要拷过来的。可能大家遇到最多的就是Word文档中的文字如何很好地应用到corelDRAW中的问题了。这里我有一个小技巧：
a 首先观察Word文档中每段文字的结尾是否有一个向下的灰色箭头（这种情况多出现在从网上拷贝的文章直接粘贴到Word中）。（如图1）
如果有请你注意了，这个小箭头如果不先在Word中处理好而直接拷文字到corelDRAW中会有苦头吃的。方法：在word中将光标移到灰色小箭头前，按回车（enter）键再按Delete键一个一个将其删除。如果不这样做你会发现将文字直接拷到corelDRAW中并选完全对齐时就会出现如图中的情况了，是不是头大了？（如图2 ）

b 按ctrl+A全选文字，点击如图3的“正文”按钮（这是word2000的样式，wordXP就要点“清除格式”），再按ctrl+C拷贝文字，最后粘贴到corelDRAW中。这个小技巧真的是我自己摸出来的。至于为什么这样做，你自己去尝试不这样做和这样做的不同吧。前天看到有人说要将word的文字拷贝到记事本里再拷到corelDRAW中，也就是借道记事本，其实不必。
需要文字对齐的请一定用段落文本方式。将word中的文字拷贝后，在corelDRAW里用文字工具画一个文本框按ctrl+V将刚才拷贝的文字粘贴进来，这时就可以设置文字格式了，按ctrl+T调出文字格式面板。我说的corelDRAW排文字功能强大就在这里了。（如图3）

我们来仔细观察这个面板，发现功能还是挺多的。
a 字体面板：设定字体、字号等。一般在属性栏里就可以设置。需要文字对齐的请一定用段落文本方式。将word中的文字拷贝后，在corelDRAW里用文字工具画一个文本框按ctrl+V将刚才拷贝的文字粘贴进来，这时就可以设置文字格式了，按ctrl+T调出文字格式面板。我说的corelDRAW排文字功能强大就在这里了。（如图4）
b 对齐面板：设定对方式的。如果是段落文本就一定要选中“完全齐行”了。右下角
有一个“缩排”选项，其中“第一行”是设每一段文字第一行空两格的地方。一般用9号字，输入7mm就行了，字号越大，此处输入的数字也就相应增大。此面板其他地方就用默认的好了。（如图5）

c 间距面板：设定字距、行距的地方。我一般是这样设定的：字符：0 字：不管它 、行：140 、 段落前：不管它 、 段落后：140 、 不同语言间距：不管它 （如图6）

这里特别需要注意的是“段落后”的数值一定要与上面行距的数值一样，如果不一样，就会出现下图的情况。（如图7）

d 定位点面板：不用管它（说实在我也不知此处是干什么的）。（如图8）

e 框架与栏面板：分栏及设定栏间距。排报纸及文字较多这里就有用了。栏数、栏宽、栏间距都可以根据自己的需要去设定数值（如图9），具体数值自己摸索吧。

f 效果面板：这里其实是设项目符号和首字下沉的。在排一些画册时会用到（如图10）。

h 规则面板：这里也是一个很重要的功能。我以前也一直不明白这是干什么用的。但后来有一次被我无意中发现了。不知大家有没有碰到过这种情况：本来是在句末的标点符号突然跑到下一行去了。（如图11）出现这种情况是不符合中文排版的规则的，想改变它就要在这里设置了。将“开始字符”、“跟随字符”、“其他字符”都钩选，就不会出现这种情况了。

以上讲的文字格式的设置是corelDRAW9的。corelDRAW11或corelDRAW12的面板会有些不一样，但也大同小异。真正应用好了以上的设置技巧，我相信你会对corelDRAW的文字排版功能刮目相看。其实编写软件的人已经将我们在实际应用中的很多功能都考虑到了，只是我们没有深度挖掘而已。
四、其他实用技巧：
1、能用段落文字尽量不要用美工文字，尤其在排画册和报纸时，因为美工文字的对齐很麻烦。但有人会说了：在corelDRAW9里段落文字不能转曲线，后期输出可能会出错。是的，这是corelDRAW9缺陷。到了corelDRAW11、corelDRAW12就好了，可以转曲了。但在corelDRAW9中方法还是有的，比如：先画一个覆盖文本框的矩形，然后ALT+F3调出滤镜，选透明度，选取黑色、填100%，勾选“冻结”（最重要的步骤），应用，然后解除群组，去掉不要的东西。我也是上了这坛子后才知道这种方法的，以前我也不知道。但我一般只将美工文字转曲，段落文字根本不转。因为我一般不会用很生僻的字体，况且现在输出公司的字体一般还是比较全的，也很少碰到缺字体的情况。我试过了上面的方法，其实用起来也很麻烦，尤其是很多段落文字的。在此我再介绍一种所有文字都不用转曲的方法：在文件菜单里有一个“输出前准备“的功能。使用这个功能可以将需要输出的文件和文件中所使用的所有字体自收集保存到一个文件夹里，最后一起抄给输出公司就行了，绝不用担心少了字体。（如图12）转曲的主要目的就是担心输出公司没有自己使用的字体，现在你连字体都抄给他了，还用担心出错吗？不信你试试。用过freehand的朋友就知道，那里有一个“收集输出”功能，与corelDRAW的这个功能有异曲同工之妙。

2、段落文本连排（说法可能不专业，别见笑啊）：由于corelDRAW不能像freehand那样可以将所有的页面显示在同一个作业窗口中，而是一个页面一个页面的显示。这就给我们进行多页面画册的排版带来一定的不便，但习惯了也就好了。但如果有较长的文章一个页面排不完，需要延伸到下一个页面，这时就要使用“段落文本连排”功能了。方法：点击文本框下部中间的小方框，出现一个小方框带个向右下的箭头时切换到下页面直接拉出一个文本框，上页排不完的文字就自动移到下一页了。如果你缩小上一页的文本框，里面的文字会随之移到下一页。这种功能是不是很好呢？多试试吧。如图13 另外请注意：如果文本框下部中间的小方框是空心的，表示这段文字已结束；如果小方框里带一个向下的小三角，表示这段文字还有一些内容隐藏起来了。

3、图文混排（文本绕图）：这在corelDRAW还是很容易的。如图14

4、这种情况碰到过没有？如图15在排中英文混在一起的文字时经常会看到上一行的字间距明显比下一行的宽很多，原因就在那个英文单词上。我的解决方法是将光标移到英文单词后按空格键将其和后面的中文字断开，英文单词就会跑到上一行，这样字间距就会正常了。

5、“ctrl+shift+&amp;gt;”或“ctrl+shift+&amp;lt;”组合键可以对字间距进行微调。
当然，任何软件都有缺陷，没有十全十美的软件，corelDRAW也不例外，不然也就不会有更新的版本不断的推出来了。就corelDRAW在文字排版这一点上讲，也是有不如人意的地方，我至今找不到解决方法，比如：
1、corelDRAW11或12中有很多英文字体不可用。我看了哈哈品老兄发过的《CorelDRAW复合字体的解决之道》这篇文章（置顶的文章里可以找到），其实也不能解决问题，这种方法只是针对直接在corelDRAW中输入文字，但对于从word中复制过来的文字则不起作用，还是一样有很多字体用不了，尤其是英文。还有“炎龙”同志提供的在“文本/书写工具/语言 中选美国英语和勾选另存为默认的书写工具语言”的方法我也试过了，也是不管用。

2、corelDRAW12中复制文字属性时，只有字号和字距和行距发生变化，而字体就是不变，百思不得其解。

CDR 12
显示导航窗口(Navigator window) 【N】
运行 Visual Basic 应用程序的编辑器 【Alt】+【F11】
保存当前的图形 【Ctrl】+【S】
打开编辑文本对话框 【Ctrl】+【Shift】+【T】
擦除图形的一部分或将一个对象分为两个封闭路径 【X】
撤消上一次的操作 【Ctrl】+【Z】
撤消上一次的操作 【Alt】+【Backspase】
垂直定距对齐选择对象的中心 【Shift】+【A】
垂直分散对齐选择对象的中心 【Shift】+【C】
垂直对齐选择对象的中心 【C】
将文本更改为垂直排布（切换式） 【Ctrl】+【.】
打开一个已有绘图文档 【Ctrl】+【O】
打印当前的图形 【Ctrl】+【P】
打开“大小工具卷帘” 【Alt】+【F10】
运行缩放动作然后返回前一个工具 【F2】
运行缩放动作然后返回前一个工具 【Z】
导出文本或对象到另一种格式 【Ctrl】+【E】
导入文本或对象 【Ctrl】+【I】
发送选择的对象到后面 【Shift】+【B】
将选择的对象放置到后面 【Shift】+【PageDown】
发送选择的对象到前面 【Shift】+【T】
将选择的对象放置到前面 【Shift】+【PageUp】
发送选择的对象到右面 【Shift】+【R】
发送选择的对象到左面 【Shift】+【L】
将文本对齐基线 【Alt】+【F12】
将对象与网格对齐 (切换) 【Ctrl】+【Y】
对齐选择对象的中心到页中心 【P】
绘制对称多边形 【Y】
拆分选择的对象 【Ctrl】+【K】
将选择对象的分散对齐舞台水平中心 【Shift】+【P】
将选择对象的分散对齐页面水平中心 【Shift】+【E】
打开“封套工具卷帘” 【Ctrl】+【F7】
打开“符号和特殊字符工具卷帘” 【Ctrl】+【F11】
复制选定的项目到剪贴板 【Ctrl】+【C】
复制选定的项目到剪贴板 【Ctrl】+【Ins】
设置文本属性的格式 【Ctrl】+【T】
恢复上一次的“撤消”操作 【Ctrl】+【Shift】+【Z】
剪切选定对象并将它放置在“剪贴板”中 【Ctrl】+【X】
剪切选定对象并将它放置在“剪贴板”中 【Shift】+【Del】
将字体大小减小为上一个字体大小设置。 【Ctrl】+小键盘【2】
将渐变填充应用到对象 【F11】
结合选择的对象 【Ctrl】+【L】
绘制矩形；双击该工具便可创建页框 【F6】
打开“轮廓笔”对话框 【F12】
打开“轮廓图工具卷帘” 【Ctrl】+【F9】
绘制螺旋形；双击该工具打开“选项”对话框的“工具框”标签 【A】
启动“拼写检查器”；检查选定文本的拼写 【Ctrl】+【F12】
在当前工具和挑选工具之间切换 【Ctrl】+【Space】
取消选择对象或对象群组所组成的群组 【Ctrl】+【U】
显示绘图的全屏预览 【F9】
将选择的对象组成群组 【Ctrl】+【G】
删除选定的对象 【Del】
将选择对象上对齐 【T】
将字体大小减小为字体大小列表中上一个可用设置 【Ctrl】+小键盘【4】
转到上一页 【PageUp】
将镜头相对于绘画上移 【Alt】+【↑】
生成“属性栏”并对准可被标记的第一个可视项 【Ctrl】+【Backspase】
打开“视图管理器工具卷帘” 【Ctrl】+【F2】
在最近使用的两种视图质量间进行切换 【Shift】+【F9】
用“手绘”模式绘制线条和曲线 【F5】
使用该工具通过单击及拖动来平移绘图 【H】
按当前选项或工具显示对象或工具的属性 【Alt】+【Backspase】
刷新当前的绘图窗口 【Ctrl】+【W】
水平对齐选择对象的中心 【E】
将文本排列改为水平方向 【Ctrl】+【,】
打开“缩放工具卷帘” 【Alt】+【F9】
缩放全部的对象到最大 【F4】
缩放选定的对象到最大 【Shift】+【F2】
缩小绘图中的图形 【F3】
将填充添加到对象；单击并拖动对象实现喷泉式填充 【G】
打开“透镜工具卷帘” 【Alt】+【F3】
打开“图形和文本样式工具卷帘” 【Ctrl】+【F5】
退出 CorelDRAW 并提示保存活动绘图 【Alt】+【F4】
绘制椭圆形和圆形 【F7】
绘制矩形组 【D】
将对象转换成网状填充对象 【M】
打开“位置工具卷帘” 【Alt】+【F7】
添加文本(单击添加“美术字”；拖动添加“段落文本”) 【F8】
将选择对象下对齐 【B】
将字体大小增加为字体大小列表中的下一个设置 【Ctrl】+小键盘6
转到下一页 【PageDown】
将镜头相对于绘画下移 【Alt】+【↓】
包含指定线性标注线属性的功能 【Alt】+【F2】
添加/移除文本对象的项目符号 (切换) 【Ctrl】+M
将选定对象按照对象的堆栈顺序放置到向后一个位置 【Ctrl】+【PageDown】
将选定对象按照对象的堆栈顺序放置到向前一个位置 【Ctrl】+【PageUp】
使用“超微调”因子向上微调对象 【Shift】+【↑】
向上微调对象 【↑】
使用“细微调”因子向上微调对象 【Ctrl】+【↑】
使用“超微调”因子向下微调对象 【Shift】+【↓】
向下微调对象 【↓】
使用“细微调”因子向下微调对象 【Ctrl】+【↓】
使用“超微调”因子向右微调对象 【Shift】+【←】
向右微调对象 【←】
使用“细微调”因子向右微调对象 【Ctrl】+【←】
使用“超微调”因子向左微调对象 【Shift】+【→】
向左微调对象 【→】
使用“细微调”因子向左微调对象 【Ctrl】+【→】
创建新绘图文档 【Ctrl】+【N】
编辑对象的节点；双击该工具打开“节点编辑卷帘窗” 【F10】
打开“旋转工具卷帘” 【Alt】+【F8】
打开设置 CorelDRAW 选项的对话框 【Ctrl】+【J】
【Ctrl】+【A】
打开“轮廓颜色”对话框 【Shift】+【F12】
给对象应用均匀填充 【Shift】+【F11】
显示整个可打印页面 【Shift】+【F4】
将选择对象右对齐 【R】
将镜头相对于绘画右移 【Alt】+【←】
再制选定对象并以指定的距离偏移 【Ctrl】+【D】
将字体大小增加为下一个字体大小设置。 【Ctrl】+小键盘【8】
将“剪贴板”的内容粘贴到绘图中 【Ctrl】+【V】
将“剪贴板”的内容粘贴到绘图中 【Shift】+【Ins】
启动“这是什么?”帮助 【Shift】+【F1】
重复上一次操作 【Ctrl】+【R】
转换美术字为段落文本或反过来转换 【Ctrl】+【F8】
将选择的对象转换成曲线 【Ctrl】+【Q】
将轮廓转换成对象 【Ctrl】+【Shift】+【Q】
使用固定宽度、压力感应、书法式或预置的“自然笔”样式来绘制曲线 【I】
左对齐选定的对象 【L】
将镜头相对于绘画左移 【Alt】+【→】
文本编辑

显示所有可用/活动的 HTML 字体大小的列表 【Ctrl】+【Shift】+【H】
将文本对齐方式更改为不对齐 【Ctrl】+【N】
在绘画中查找指定的文本 【Alt】+【F3】
更改文本样式为粗体 【Ctrl】+【B】
将文本对齐方式更改为行宽的范围内分散文字 【Ctrl】+【H】
更改选择文本的大小写 【Shift】+【F3】
将字体大小减小为上一个字体大小设置。 【Ctrl】+小键盘【2】
将文本对齐方式更改为居中对齐 【Ctrl】+【E】
将文本对齐方式更改为两端对齐 【Ctrl】+【J】
将所有文本字符更改为小型大写字符 【Ctrl】+【Shift】+【K】
删除文本插入记号右边的字 【Ctrl】+【Del】
删除文本插入记号右边的字符 【Del】
将字体大小减小为字体大小列表中上一个可用设置 【Ctrl】+小键盘【4】
将文本插入记号向上移动一个段落 【Ctrl】+【↑】
将文本插入记号向上移动一个文本框 【PageUp】
将文本插入记号向上移动一行 【↑】
添加/移除文本对象的首字下沉格式 (切换) 【Ctrl】+【Shift】+【D】
选定“文本”标签，打开“选项”对话框 【Ctrl】+【F10】
更改文本样式为带下划线样式 【Ctrl】+【U】
将字体大小增加为字体大小列表中的下一个设置 【Ctrl】+小键盘【6】
将文本插入记号向下移动一个段落 【Ctrl】+【↓】
将文本插入记号向下移动一个文本框 【PageDown】
将文本插入记号向下移动一行 【↓】
显示非打印字符 【Ctrl】+【Shift】+【C】
向上选择一段文本 【Ctrl】+【Shift】+【↑】
向上选择一个文本框 【Shift】+【PageUp】
向上选择一行文本 【Shift】+【↑】
向下选择一段文本 【Ctrl】+【Shift】+【↓】
向下选择一个文本框 【Shift】+【PageDown】
向下选择一行文本 【Shift】+【↓】
更改文本样式为斜体 【Ctrl】+【I】
选择文本结尾的文本 【Ctrl】+【Shift】+【PageDown】
选择文本开始的文本 【Ctrl】+【Shift】+【PageUp】
选择文本框开始的文本 【Ctrl】+【Shift】+【Home】
选择文本框结尾的文本 【Ctrl】+【Shift】+【End】
选择行首的文本 【Shift】+【Home】
选择行尾的文本 【Shift】+【End】
选择文本插入记号右边的字 【Ctrl】+【Shift】+【←】
选择文本插入记号右边的字符 【Shift】+【←】
选择文本插入记号左边的字 【Ctrl】+【Shift】+【→】
选择文本插入记号左边的字符 【Shift】+【→】
显示所有绘画样式的列表 【Ctrl】+【Shift】+【S】
将文本插入记号移动到文本开头 【Ctrl】+【PageUp】
将文本插入记号移动到文本框结尾 【Ctrl】+End
将文本插入记号移动到文本框开头 【Ctrl】+【Home】
将文本插入记号移动到行首 【Home】
将文本插入记号移动到行尾 【End】
移动文本插入记号到文本结尾 【Ctrl】+【PageDown】
将文本对齐方式更改为右对齐 【Ctrl】+【R】
将文本插入记号向右移动一个字 【Ctrl】+【←】
将文本插入记号向右移动一个字符 【←】
将字体大小增加为下一个字体大小设置。 【Ctrl】+小键盘【8】
显示所有可用/活动字体粗细的列表 【Ctrl】+【Shift】+【W】
显示一包含所有可用/活动字体尺寸的列表 【Ctrl】+【Shift】+【P】
显示一包含所有可用/活动字体的列表 【Ctrl】+【Shift】+【F】
将文本对齐方式更改为左对齐 【Ctrl】+【L】
将文本插入记号向左移动一个字 【Ctrl】+【→】
将文本插入记号向左移动一个字符 【→】
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href=&quot;https://gitee.com/ForthEspada/CS-Books#01c&quot;&gt;其他资源&lt;/a&gt;&lt;/p&gt;

</description>
        <pubDate>Sat, 27 Mar 2021 00:00:00 +0000</pubDate>
        <link>https://seakingOne.github.io/life/2021/03/27/%E4%B8%A5%E5%B0%8F%E7%B1%B3%E6%97%A5%E5%B8%B8.html</link>
        <guid isPermaLink="true">https://seakingOne.github.io/life/2021/03/27/%E4%B8%A5%E5%B0%8F%E7%B1%B3%E6%97%A5%E5%B8%B8.html</guid>
        
        <category>严小米日常</category>
        
        
        <category>Life</category>
        
      </item>
    
      <item>
        <title>AAC介绍</title>
        <description>&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;ADTS(Audio Data Transport Stream)头之于AAC, AAC音频文件的每一帧都由一个ADTS头和AAC ES(AAC音频数据)组成。
&lt;img src=&quot;/static/assets/img/blog/ffmpeg/aac/aac.jpg&quot; width=&quot;65%&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;ADTS头分析
ADTS头包含了AAC文件的采样率、通道数、帧数据长度等信息。ADTS头分为固定头信息和可变头信息两个部分，固定头信息在每个帧中的是一样的，可变头信息在各个帧中并不是固定值。ADTS头一般是7个字节((28+28)/ 8)长度，如果需要对数据进行CRC校验，则会有2个Byte的校验码，所以ADTS头的实际长度是7个字节或9个字节。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&quot;/static/assets/img/blog/ffmpeg/aac/aac1.jpg&quot; width=&quot;65%&quot; /&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    int audio_object_type = 2;
    int sampling_frequency_index = 7;
    int channel_config = 2;

    int adtsLen = dataLen + 7;
        
    szAdtsHeader[0] = 0xff;         //syncword:0xfff                          高8bits
    szAdtsHeader[1] = 0xf0;         //syncword:0xfff                          低4bits
    szAdtsHeader[1] |= (0 &amp;lt;&amp;lt; 3);    //MPEG Version:0 for MPEG-4,1 for MPEG-2  1bit
    szAdtsHeader[1] |= (0 &amp;lt;&amp;lt; 1);    //Layer:0                                 2bits 
    szAdtsHeader[1] |= 1;           //protection absent:1                     1bit

    szAdtsHeader[2] = (audio_object_type - 1)&amp;lt;&amp;lt;6;            //profile:audio_object_type - 1                      2bits
    szAdtsHeader[2] |= (sampling_frequency_index &amp;amp; 0x0f)&amp;lt;&amp;lt;2; //sampling frequency index:sampling_frequency_index  4bits 
    szAdtsHeader[2] |= (0 &amp;lt;&amp;lt; 1);                             //private bit:0                                      1bit
    szAdtsHeader[2] |= (channel_config &amp;amp; 0x04)&amp;gt;&amp;gt;2;           //channel configuration:channel_config               高1bit

    szAdtsHeader[3] = (channel_config &amp;amp; 0x03)&amp;lt;&amp;lt;6;     //channel configuration:channel_config      低2bits
    szAdtsHeader[3] |= (0 &amp;lt;&amp;lt; 5);                      //original：0                               1bit
    szAdtsHeader[3] |= (0 &amp;lt;&amp;lt; 4);                      //home：0                                   1bit
    szAdtsHeader[3] |= (0 &amp;lt;&amp;lt; 3);                      //copyright id bit：0                       1bit  
    szAdtsHeader[3] |= (0 &amp;lt;&amp;lt; 2);                      //copyright id start：0                     1bit
    szAdtsHeader[3] |= ((adtsLen &amp;amp; 0x1800) &amp;gt;&amp;gt; 11);           //frame length：value   高2bits

    szAdtsHeader[4] = (uint8_t)((adtsLen &amp;amp; 0x7f8) &amp;gt;&amp;gt; 3);     //frame length:value    中间8bits
    szAdtsHeader[5] = (uint8_t)((adtsLen &amp;amp; 0x7) &amp;lt;&amp;lt; 5);       //frame length:value    低3bits
    szAdtsHeader[5] |= 0x1f;                                 //buffer fullness:0x7ff 高5bits
    szAdtsHeader[6] = 0xfc;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;syncword：帧同步标识一个帧的开始，固定为0xFFF&lt;/p&gt;

&lt;p&gt;ID：MPEG 标示符。0表示MPEG-4，1表示MPEG-2&lt;/p&gt;

&lt;p&gt;layer：固定为’00’&lt;/p&gt;

&lt;p&gt;protection_absent：标识是否进行误码校验。0表示有CRC校验，1表示没有CRC校验&lt;/p&gt;

&lt;p&gt;profile：标识使用哪个级别的AAC。1: AAC Main 2:AAC LC (Low Complexity) 3:AAC SSR (Scalable Sample Rate) 4:AAC LTP (Long Term Prediction)&lt;/p&gt;

&lt;p&gt;sampling_frequency_index：标识使用的采样率的下标&lt;/p&gt;

&lt;p&gt;private_bit：私有位，编码时设置为0，解码时忽略&lt;/p&gt;

&lt;p&gt;channel_configuration：标识声道数&lt;/p&gt;

&lt;p&gt;original_copy：编码时设置为0，解码时忽略&lt;/p&gt;

&lt;p&gt;home：编码时设置为0，解码时忽略&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.jianshu.com/p/b4e7a634e758&quot;&gt;you can see here: External blog&lt;/a&gt;&lt;/p&gt;

</description>
        <pubDate>Thu, 25 Mar 2021 00:00:00 +0000</pubDate>
        <link>https://seakingOne.github.io/article/2021/03/25/AAC%E4%BB%8B%E7%BB%8D.html</link>
        <guid isPermaLink="true">https://seakingOne.github.io/article/2021/03/25/AAC%E4%BB%8B%E7%BB%8D.html</guid>
        
        <category>AAC</category>
        
        
        <category>Article</category>
        
      </item>
    
      <item>
        <title>FFmpeg base command</title>
        <description>&lt;p&gt;The command here is based on Windows(the personal computer), and Mac operations are similar.&lt;/p&gt;

&lt;p&gt;ffmpeg基本操作命令&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;-version 显示版本
-demuxers 显示可用的demuxers（视音频分离器）
-muxers 显示可用的muxers（多路复用器，视音频复用器（Muxer）即是将视频压缩数据（例如H. 264）和音频压缩数据（例如AAC）合并到一个封装格式数据）
-devices 显示可用的设备
-codecs 显示所有的编解码器
-deoders 显示所有的解码器
-encoders 显示所有的编码器
-bsfs 显示比特流filter（？）
-formats 显示可用的格式（比如mp3，mp4）
-protocols 显示可用的协议
-filter 显示可用的过滤器
-pix_fmts 显示可用的像素格式
-sample_fmts 显示可用的采样格式
-layouts 显示channel的名称（单声道，多声道）
-colors 显示识别的颜色名称
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;ffmpeg常用参数&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;-f fmt(输入输出，一般后面为设备名称)
-i url(输入，输入文件的url地址)
-y(全局参数，覆盖输出文件而不询问)
-n(全局参数，不要覆盖输出文件，如果文件存在，立即退出)
-c(编解码，ffmpeg -i INPUT -map 0 -c:v libx264 -c:a copy OUTPUT)
-filter(过滤流)
视频参数：
-r[: stream_specifier] fps(设置帧率，hz值)  
-s[: stream_specifier] (设置分辨率)  
-aspect[: stream_specifier] (设置方面指定的视频显示宽高比。aspect 可以是浮点数字符
                             串，也可以是 num：den 形式的字符串，其中 num 和 den
                             是宽高比的分子和分母。例如“4：3”，“16：9”，“1.3333”和
                             “1.7777”是有效的参数值。如果与-vcodec 副本一起使用，
                             则会影响存储在容器级别的宽高比，但不会影响存储在编码
                             帧中的宽高比)
-vn(禁用视屏录制)
-vcodec(编解码器)
音频参数：
-ar(设置音频采样频率。对于输出流，它默认设置为相应
    输入流的频率。对于输入流，此选项仅适用于音频捕
    获设备和原始分路器，并映射到相应的分路器选件。)
-ac(设置音频的通道数)
-an(禁止录音)
-acodec(设置音频编解码器)
-sample_fmt(设置音频采样格式。使用-sample_fmts 获取支持的样
            本格式列表。)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;举个例子：
Get devices&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ffmpeg -devices

in windows:  D  dshow           DirectShow capture

             D  gdigrab         GDI API Windows frame grabber
             
             D  lavfi           Libavfilter virtual input device
             
             E sdl,sdl2        SDL2 output device
             
             D  vfwcap          VfW video capture
             
in macOS:    E audiotoolbox    AudioToolbox output device
             D  avfoundation    AVFoundation input device
             D  lavfi           Libavfilter virtual input device        
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Capture desktop screen (silent):&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;windows: ffmpeg -f gdigrab -i desktop -r 30 fileName.mp4/.yuv/or other 

-f: the input source
-i desktop: Screen capture video
   
macOS: ffmpeg -f avfoundation -i 1 -r 30 fileName.mp4/.yuv/or other
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To capture audio:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;windows: ffmpeg -list_devices true -f dshow -i dummy (List the device name)

         [dshow @ 0000025c3d3ddc00] DirectShow video devices (some may be both video and audio devices)
         
         [dshow @ 0000025c3d3ddc00]  &quot;Integrated Webcam&quot;
            
         [dshow @ 0000025c3d3ddc00]     Alternative name &quot;@device_pnp_\\?\usb#vid_0c45&amp;amp;pid_6723&amp;amp;mi_00#6&amp;amp;12e96a9&amp;amp;0&amp;amp;0000#{65e8773d-8f56-11d0-a3b9-00a0c9223196}\global&quot;
            
         [dshow @ 0000025c3d3ddc00] DirectShow audio devices
            
         [dshow @ 0000025c3d3ddc00]  &quot;Microphone (Realtek(R) Audio)&quot;
            
         [dshow @ 0000025c3d3ddc00]     Alternative name &quot;@device_cm_{33D9A762-90C8-11D0-BD43-00A0C911CE86}\wave_{944A0A5A-4ED9-4B5F-89AF-4CB5262162A9}&quot; 

         start record:  ffmpeg.exe -f dshow -i audio=&quot;Microphone (Realtek(R) Audio)&quot; 1.mp3
         
macOS:   ffmpeg -f avfoundation -list_devices true -i &quot;&quot;

         [AVFoundation indev @ 0x7f8766400e80] AVFoundation video devices:
         [AVFoundation indev @ 0x7f8766400e80] [0] FaceTime HD Camera
         [AVFoundation indev @ 0x7f8766400e80] [1] Capture screen 0
         [AVFoundation indev @ 0x7f8766400e80] AVFoundation audio devices:
         [AVFoundation indev @ 0x7f8766400e80] [0] Built-in Microphone       
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;of course, we can record audio and screen both&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;windows: ffmpeg.exe -f gdigrab -i desktop -f dshow -i audio=&quot;Microphone (Realtek(R) Audio)&quot; -r 30 -s 1280*740 1.mp4

macOS: ffmpeg -f avfoundation -video_size 1280x720 -framerate 30 -i 1:0 -r 30 1.mp4
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;from videos extract video and audio:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ffmpeg -i 1.mp4 -vcodec copy -an 1-1.mp4

ffmpeg -i 1.mp4 -acodec copy -vn 1-1.mp3
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Multimedia format conversion:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ffmpeg -i 1.mp4 -vcodec copy -acodec copy 1.flv
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Extract the original data format:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ffmpeg.exe -i out.mp4 -vn -ar 44100 -ac 2 -f s16le out.pcm
-ar: audiu rate音频采样率
-ac: audiu channel 音频声道数为2
-f：音频的数据存储格式s16le: s:有符号16位lettle end

ffmpeg.exe -i 1.mp4 -an -c:v rawvideo -pix_fmt yuv420p out.yuv   
-an： 不提取音频
-c:v： 对视频进行编码，使用rawvideo（原始视频）格式进行编码
-pix_fmt yuv420p： 输出的YUV像素格式 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;其他：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;滤镜处理： 
    都是针对解码后（原始）的数据处理 
    像素裁剪：ffmpeg -i 1.mp4 -vf crop=in_w-200:in_h-200 -c:libx264 -c:a copy out.mp4


音视频裁剪(裁剪10s)：ffmpeg -i 1.mp4 -ss 00:00:00 -t 10 out.ts
多个视频合并(input.txt中文件格式为file &apos;1.ts&apos;)：ffmpeg -f concat -i input.txt out.mp4
图片视频互转：ffmpeg -i 1.mp4 -r 1 -f image2 image-%3d.jpeg 
           ffmpeg -i image-%3d.jpeg out.mp4
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href=&quot;https://seakingone.github.io/static/assets/img/blog/ffmpeg/ffmpeg命令大全.pdf&quot;&gt;here you can see word doc(中文版)&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://juejin.cn/post/6844903732891615246&quot;&gt;here you can see FFmpeg in C use of video/audio&lt;/a&gt;&lt;/p&gt;
</description>
        <pubDate>Fri, 12 Mar 2021 00:00:00 +0000</pubDate>
        <link>https://seakingOne.github.io/article/2021/03/12/FFmpeg-base-command.html</link>
        <guid isPermaLink="true">https://seakingOne.github.io/article/2021/03/12/FFmpeg-base-command.html</guid>
        
        <category>FFmpeg</category>
        
        
        <category>Article</category>
        
      </item>
    
      <item>
        <title>C++ common network libraries</title>
        <description>&lt;p&gt;Personal summary about C++ common network programming class library:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ACE：C++面向对象网络变成工具包

Boost.Asio：用于网络和底层I/O编程的跨平台的C++库 

Casablanca：C++ REST SDK 

cpp-netlib：高级网络编程的开源库集合 

Dyad.c : C语言的异步网络 

libcurl :多协议文件传输库 

Mongoose：非常轻量级的网络服务器 

Muduo ：用于 Linux 多线程服务器的C++非阻塞网络库 

net_skeleton ：C/C++的TCP 客户端/服务器库 

nope.c ：基于 c语言 的超轻型软件平台，用于可扩展的服务器端和网络应用。 对于C编程人员，可以考虑 Node.js 
 
Onion :C语言HTTP服务器库，其设计为轻量级，易使用。 

POCO：用于构建网络和基于互联网应用程序的C++类库，可以运行在桌面，服务器，移动和 嵌入式 系统。 

RakNet：为游戏开发人员提供的跨平台的开源C++网络引擎。 

Tuf o ：用于Qt之上的C++构建的异步Web框架。
 
WebSocket++ ：基于C++/Boost Aiso的websocket 客户端/服务器库 

ZeroMQ ：高速，模块化的异步通信库
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

</description>
        <pubDate>Thu, 04 Mar 2021 00:00:00 +0000</pubDate>
        <link>https://seakingOne.github.io/article/2021/03/04/C++-common-network-libraries.html</link>
        <guid isPermaLink="true">https://seakingOne.github.io/article/2021/03/04/C++-common-network-libraries.html</guid>
        
        <category>C++</category>
        
        
        <category>Article</category>
        
      </item>
    
      <item>
        <title>GraalVM alternative JVM</title>
        <description>&lt;p&gt;Frist, Let’s talk about why our service module is migrating from the Groovy engine to the JS engine?&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;1).  To match platform, the JS engine is used uniformly;

2).  The JDK8 Nashorn engine is inferior to the JDK11 GraalVM engine for performance reasons.Yes, GraalVM JavaScript is as fast as Nashorn–up to 6x faster!

     You can see[Nashorn removal: GraalVM to the rescue!]: https://medium.com/graalvm/nashorn-removal-graalvm-to-the-rescue-d4da3605b6cb.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Know GraalVM&lt;/p&gt;

&lt;p&gt;GraalVM takes a different approach, using a single virtual machine to support different languages.The GraalVM base is a Java HotSpot virtual machine based on the OpenJDK.The Graal Compiler is a just-in-time (JIT) Compiler implemented based on the JVM Compiler Interface to translate Java bytecode into local machine code.The source code for Java and other JVM languages such as Groovy, Kotlin, and Scala runs directly on GraalVM’s Java virtual machine after being translated into Java bytecode.The Truffle framework for creating other language implementations is included in GraalVM.GraalVM’s support for JavaScript, Ruby, Python, R, and C/C++ languages is based on the Truffle framework.Truffle framework is an open language implementation framework.Other languages can run on GraalVM through the Truffle framework, and even the application’s own proprietary language.&lt;/p&gt;

&lt;p&gt;The architecture of GraalVM is as follows:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/assets/img/blog/graalvm/vm1.png&quot; width=&quot;75%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/assets/img/blog/graalvm/vm2.png&quot; width=&quot;75%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Some possible attention points&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    1).  we use jdk11 instead of GraalVM, so we should reference jar by maven,and have to config -XX:+UnlockExperimentalVMOptions -XX:+EnableJVMCI -XX:+UseJVMCICompiler enable graal compilerIf you need add Python/R/Ruby etc,you have two choose，frist:use GraalVM instead of JVM, second:impl interface Engine extension custom parsing.

    2).  JS system or customized library, You need to be careful not to repeat the name of the method, otherwise you may have method overrides and end up with an error, After all, native JS is not like other JS frameworks. In our code, third lib load by same Context, So the first load will be compiled.

    3).  multi-tenant solution, in our service, ervery tenant have a Context, It is automatically released when the calculation is completed. If you need, API allows code to be  not cached across multiple contexts. you can see: https://www.bookstack.cn/read/graalvm/b00513b10cc379de.md

    4).  of course, about resource access problem: https://www.graalvm.org/reference-manual/embed-languages/#access-restrictions

         It is possible to configure fine-grained access privileges for guest applications. The configuration can be provided using the Context.Builder class when constructing a new context. The following access parameters may be configured:
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Allow access to other languages using allowPolyglotAccess.&lt;/p&gt;

&lt;p&gt;Allow and customize access to host objects using allowHostAccess.&lt;/p&gt;

&lt;p&gt;Allow and customize host lookup to host types using allowHostLookup.&lt;/p&gt;

&lt;p&gt;Allow host class loading using allowHostClassLoading.&lt;/p&gt;

&lt;p&gt;Allow the creation of threads using allowCreateThread.&lt;/p&gt;

&lt;p&gt;Allow access to native APIs using allowNativeAccess.&lt;/p&gt;

&lt;p&gt;Allow access to IO using allowIO and proxy file accesses using fileSystem.&lt;/p&gt;

&lt;p&gt;as JS:&lt;/p&gt;

&lt;p&gt;ok，now wo can exec test code, perform 5 times,the time key display:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/assets/img/blog/graalvm/vm3.png&quot; width=&quot;75%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;In essence, the JavaScript engine of GraalVM is a plain Java application. Running it on any JVM (JDK 8 or higher) is possible, but, for a better result, it should be GraalVM or a compatible JVMCI-enabled JDK using the GraalVM compiler. This mode gives the JavaScript engine full access to Java at runtime, but also requires the JVM to first (just-in-time) compile the JavaScript engine when executed, just like any other Java application.&lt;/p&gt;

&lt;p&gt;Running in a Native Image means that the JavaScript engine, including all its dependencies from, e.g., the JDK, is pre-compiled into a native binary. This will tremendously speed up the startup of any JavaScript application, as GraalVM can immediately start to compile JavaScript code, without itself requiring to be compiled first. This mode, however, will only give GraalVM access to Java classes known at the time of image creation. Most significantly, this means that the JavaScript-to-Java interoperability features are not available in this mode, as they would require dynamic class loading and execution of arbitrary Java code at runtime.&lt;/p&gt;

&lt;p&gt;you can see: &lt;a href=&quot;https://www.graalvm.org/reference-manual/js/FAQ/#performance&quot;&gt;performance case&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;data access link:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://dev.to/muhammadridwan/namespace-in-js-5dbj&quot;&gt;js namespace&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.bookstack.cn/read/graalvm/b00513b10cc379de.md&quot;&gt;multi-tenant solution&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.graalvm.org/reference-manual/embed-languages/#access-restrictions&quot;&gt;assess&lt;/a&gt;&lt;/p&gt;

</description>
        <pubDate>Sat, 12 Dec 2020 00:00:00 +0000</pubDate>
        <link>https://seakingOne.github.io/article/2020/12/12/GraalVm.html</link>
        <guid isPermaLink="true">https://seakingOne.github.io/article/2020/12/12/GraalVm.html</guid>
        
        <category>GraalVM</category>
        
        
        <category>Article</category>
        
      </item>
    
      <item>
        <title>RabbitMQ messaging reliability delivery solution/RabbitMQ消息可靠性投递解决方案</title>
        <description>&lt;p&gt;When it comes to the reliable delivery of messages, it is inevitable that they will be encountered in practical work. For example, some core businesses need to ensure that messages are not lost. Next, let’s look at a flow chart of reliable delivery to illustrate the concept of reliable delivery:&lt;/p&gt;

&lt;p&gt;谈到消息的可靠性投递，无法避免的，在实际的工作中会经常碰到，比如一些核心业务需要保障消息不丢失，接下来我们看一个可靠性投递的流程图，说明可靠性投递的概念：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/assets/img/blog/rabbitmq/rabbit.png&quot; width=&quot;75%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Step 1: First store the message information (business data) in the database, and then store the message record in a message log table (or a message log table from another homologous database).&lt;/p&gt;

&lt;p&gt;Step 1： 首先把消息信息(业务数据）存储到数据库中，紧接着，我们再把这个消息记录也存储到一张消息记录表里（或者另外一个同源数据库的消息记录表）&lt;/p&gt;

&lt;p&gt;Step 2: Send a message to the MQ Broker node (confirm is sent and an asynchronous result is returned)&lt;/p&gt;

&lt;p&gt;Step 2：发送消息到MQ Broker节点（采用confirm方式发送，会有异步的返回结果）&lt;/p&gt;

&lt;p&gt;Step 3 and 4: The producer side accepts the confirmed message result returned by the MQ Broker node, and then updates the message status in the message log table.For example, the default Status = 0, after receiving a message to confirm the success, update to 1!&lt;/p&gt;

&lt;p&gt;Step 3、4：生产者端接受MQ Broker节点返回的Confirm确认消息结果，然后进行更新消息记录表里的消息状态。比如默认Status = 0 当收到消息确认成功后，更新为1即可！&lt;/p&gt;

&lt;p&gt;Step 5: However, in the process of message confirmation, the echo message may fail or fail due to network flash, MQ Broker side exception, etc.At this time, the sender (producer) to the message reliability delivery, to ensure that the message is not lost, 100% of the delivery success!(there is a limit case is broken, the Broker to return to the success of the confirmation message, but the production end due to network failure received, this time to delivery may lead to duplication, need consumers to do idempotent processing) so we need to have a regular tasks, (such as pull every 5 minutes once somewhere in the middle of the message, of course, this news can set a timeout, such as more than 1 minute Status = 0, also explains 1 minute within the time window, our message has not been confirmed, then will be timed tasks pull out)&lt;/p&gt;

&lt;p&gt;Step 5：但是在消息确认这个过程中可能由于网络闪断、MQ Broker端异常等原因导致 回送消息失败或者异常。这个时候就需要发送方（生产者）对消息进行可靠性投递了，保障消息不丢失，100%的投递成功！（有一种极限情况是闪断，Broker返回的成功确认消息，但是生产端由于网络闪断没收到，这个时候重新投递可能会造成消息重复，需要消费端去做幂等处理）所以我们需要有一个定时任务，（比如每5分钟拉取一下处于中间状态的消息，当然这个消息可以设置一个超时时间，比如超过1分钟 Status = 0 ，也就说明了1分钟这个时间窗口内，我们的消息没有被确认，那么会被定时任务拉取出来）&lt;/p&gt;

&lt;p&gt;Step 6: Next, we repost the message in the intermediate state to retry send and continue to send the message to MQ. Of course, there may also be a variety of reasons leading to the failure of sending&lt;/p&gt;

&lt;p&gt;Step 6：接下来我们把中间状态的消息进行重新投递 retry send，继续发送消息到MQ ，当然也可能有多种原因导致发送失败&lt;/p&gt;

&lt;p&gt;Step 7: We can set the maximum number of attempts, such as 3 posts, and still fail, then we can set the final Status to Status = 2, and finally leave the problem to be solved manually (or dump the message to the failure table).&lt;/p&gt;

&lt;p&gt;Step 7：我们可以采用设置最大努力尝试次数，比如投递了3次，还是失败，那么我们可以将最终状态设置为Status = 2 ，最后 交由人工解决处理此类问题（或者把消息转储到失败表中）.&lt;/p&gt;
</description>
        <pubDate>Thu, 03 Jan 2019 00:00:00 +0000</pubDate>
        <link>https://seakingOne.github.io/article/2019/01/03/rabbitmq-reliable.html</link>
        <guid isPermaLink="true">https://seakingOne.github.io/article/2019/01/03/rabbitmq-reliable.html</guid>
        
        <category>RabbitMQ</category>
        
        
        <category>Article</category>
        
      </item>
    
      <item>
        <title>my design life</title>
        <description>&lt;p&gt;this is my design pdf file&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://seakingone.github.io/static/assets/img/blog/product/product.pdf&quot;&gt;my-design-life.pdf&lt;/a&gt;&lt;/p&gt;
</description>
        <pubDate>Mon, 31 Dec 2018 00:00:00 +0000</pubDate>
        <link>https://seakingOne.github.io/life/2018/12/31/my-design-life.html</link>
        <guid isPermaLink="true">https://seakingOne.github.io/life/2018/12/31/my-design-life.html</guid>
        
        <category>dh</category>
        
        <category>design</category>
        
        <category>huanhuan</category>
        
        
        <category>Life</category>
        
      </item>
    
      <item>
        <title>3 Steps (2 minutes) to Setup Your Personal Website with Jalpc</title>
        <description>&lt;p&gt;Everyone wants to have a personal website, you can display your infomation to public, post blogs and make friends. If you are CS engineer, haveing a self website will benefit your interview.&lt;/p&gt;

&lt;p&gt;So, if you like this website &lt;a href=&quot;https://jarrekk.github.io/Jalpc/&quot;&gt;https://jarrekk.github.io/Jalpc/&lt;/a&gt; or &lt;a href=&quot;http://www.jarrekk.com&quot;&gt;http://www.jarrekk.com&lt;/a&gt; and are willing to have a website, here is a way to build your website in 3 steps(2 minutes). Following are steps to setup your website(make sure you have basic knowledge of &lt;a href=&quot;https://jekyllrb.com/&quot;&gt;Jekyll&lt;/a&gt; and &lt;a href=&quot;https://pages.github.com/&quot;&gt;GitHub Pages&lt;/a&gt;, if you want to custom css/js &lt;a href=&quot;https://github.com/npm/npm&quot;&gt;NPM&lt;/a&gt; is needed):&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;Fork &lt;a href=&quot;https://github.com/jarrekk/Jalpc&quot;&gt;this project – Jalpc&lt;/a&gt; at &lt;a href=&quot;https://github.com&quot;&gt;GitHub&lt;/a&gt;. If you want to edit website at github, do it as following gif or clone forked repository. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git clone git@github.com:github_username/Jalpc.git&lt;/code&gt;.&lt;/p&gt;

    &lt;p&gt;&lt;!-- ![edit](/static/assets/img/blog/3steps/edit.gif) --&gt;
 &lt;img src=&quot;/static/assets/img/blog/3steps/edit.gif&quot; width=&quot;75%&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Enter into repository directory and edit following file list:&lt;/p&gt;

    &lt;ul&gt;
      &lt;li&gt;
        &lt;p&gt;&lt;strong&gt;_config.yml&lt;/strong&gt;: edit ‘Website settings’, ‘author’, ‘comment’ and ‘analytics’ items.&lt;/p&gt;
      &lt;/li&gt;
      &lt;li&gt;
        &lt;p&gt;&lt;strong&gt;_data/landing.yml&lt;/strong&gt;: custom sections of index page.&lt;/p&gt;
      &lt;/li&gt;
      &lt;li&gt;
        &lt;p&gt;&lt;strong&gt;_data/index/&lt;/strong&gt;: edit sections’ data to yours at index page, please notice comment at each file.&lt;/p&gt;
      &lt;/li&gt;
      &lt;li&gt;
        &lt;p&gt;&lt;strong&gt;_data/blog.yml&lt;/strong&gt;: edit navbar(categories) of blog page, if you have different/more blog page, copy &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;blog/python.html&lt;/code&gt; and change it to your category HTML file, and edit &lt;strong&gt;Python&lt;/strong&gt;, &lt;strong&gt;/python/&lt;/strong&gt; to your category name at items &lt;strong&gt;title&lt;/strong&gt; and &lt;strong&gt;permalink&lt;/strong&gt;, make sure title is the same as permalink but capitalized first letter(except HTML).&lt;/p&gt;
      &lt;/li&gt;
      &lt;li&gt;
        &lt;p&gt;&lt;strong&gt;CNAME&lt;/strong&gt;: If you wanna release website at your own domain name: edit it and create &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gh-pages&lt;/code&gt; branch; if you want to use &lt;em&gt;github_username.github.io&lt;/em&gt;: leave it blank.&lt;/p&gt;
      &lt;/li&gt;
      &lt;li&gt;
        &lt;p&gt;Go to repo’s settings panel, config &lt;strong&gt;GitHub Pages&lt;/strong&gt; section to make sure website is released.&lt;/p&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Push changes to your github repository and view your website, done!&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;From now on, you can post your blog to this website by creating md files at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;post/&lt;/code&gt; directory and push it to GitHub, you can clear files at this directory before you post blogs.&lt;/p&gt;

&lt;p&gt;If you like this repository, I appreciate you star this repository. Please don’t hesitate to mail me or post issues on GitHub if you have any questions. Hope you have a happy blog time!😊&lt;/p&gt;
</description>
        <pubDate>Mon, 31 Dec 2018 00:00:00 +0000</pubDate>
        <link>https://seakingOne.github.io/article/2018/12/31/3-steps-to-setup-website-with-Jalpc.html</link>
        <guid isPermaLink="true">https://seakingOne.github.io/article/2018/12/31/3-steps-to-setup-website-with-Jalpc.html</guid>
        
        <category>dh</category>
        
        
        <category>Article</category>
        
      </item>
    
      <item>
        <title>code snippet test</title>
        <description>&lt;p&gt;This is a raw snippet:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;hello world
123
This is a text snippet
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This is a JavaScript snippet:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;const add = (a, b) =&amp;gt; a + b
const minus = (a, b) =&amp;gt; a - b

console.log(add(100,200))  // 300
console.log(minus(100,200))  // -100
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This is a Python snippet:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;def say_hello():
    print(&quot;hello world!&quot;)

say_hello()   // &quot;hello world!&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;p&gt;Side note comment: applied a bug fix similar to &lt;a href=&quot;https://github.com/Atlas7/atlas7.github.io/commit/6659f4a47f6ec66987adb0f683a9c6f3842252ae#diff-818954a41dbfb01af70050a459c603b9&quot;&gt;this commit&lt;/a&gt; to ensure code snippet does not collapse unexpectly upon clicking on it. This issue is tracked &lt;a href=&quot;https://github.com/jarrekk/Jalpc/issues/97&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
</description>
        <pubDate>Fri, 21 Dec 2018 00:00:00 +0000</pubDate>
        <link>https://seakingOne.github.io/article/2018/12/21/test-code-snippets.html</link>
        <guid isPermaLink="true">https://seakingOne.github.io/article/2018/12/21/test-code-snippets.html</guid>
        
        <category>dh</category>
        
        
        <category>Article</category>
        
      </item>
    
  </channel>
</rss>
