Welcome to mirror list, hosted at ThFree Co, Russian Federation.

index.html « netty « 11 « 2019 - github.com/xiaoheiAh/hugo-theme-pure.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 5352a166e59da90005c5009881a78b759df078ce (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
<!DOCTYPE html>
<html lang="zh">
  <head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
    <title>
        [学习笔记] Netty - 赵小黑的博客
      </title>
    <head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
  <meta name="viewport"
    content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no, minimal-ui">
  <meta name="renderer" content="webkit">
  <meta http-equiv="Cache-Control" content="no-transform" />
  <meta http-equiv="Cache-Control" content="no-siteapp" />
  <meta name="apple-mobile-web-app-capable" content="yes">
  <meta name="apple-mobile-web-app-status-bar-style" content="black">
  <meta name="format-detection" content="telephone=no,email=no,adress=no">
  
  <meta name="theme-color" content="#000000" />
  
  <meta http-equiv="window-target" content="_top" />
  
  
  <meta name="description" content="" />
  <meta name="generator" content="Hugo 0.58.0 with theme pure" />
  <title>[学习笔记] Netty - 赵小黑的博客</title>
  

  <link rel="stylesheet" href="https://xiaohei.im/hugo-theme-pure/css/style.css">
  <link rel="stylesheet" href="https://cdn.staticfile.org/highlight.js/9.15.10/styles/github.min.css"> 
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/gitalk@1/dist/gitalk.css">
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/tocbot/4.4.2/tocbot.css">
  <meta property="og:title" content="[学习笔记] Netty" />
<meta property="og:description" content="" />
<meta property="og:type" content="article" />
<meta property="og:url" content="https://xiaohei.im/hugo-theme-pure/2019/11/netty/" />
<meta property="article:published_time" content="2019-11-29T18:40:27+08:00" />
<meta property="article:modified_time" content="2019-11-29T18:40:27+08:00" />
<meta itemprop="name" content="[学习笔记] Netty">
<meta itemprop="description" content="">


<meta itemprop="datePublished" content="2019-11-29T18:40:27&#43;08:00" />
<meta itemprop="dateModified" content="2019-11-29T18:40:27&#43;08:00" />
<meta itemprop="wordCount" content="4804">



<meta itemprop="keywords" content="netty," />
<meta name="twitter:card" content="summary"/>
<meta name="twitter:title" content="[学习笔记] Netty"/>
<meta name="twitter:description" content=""/>

  <!--[if lte IE 9]>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/classlist/1.1.20170427/classList.min.js"></script>
    <![endif]-->

  <!--[if lt IE 9]>
      <script src="https://cdn.jsdelivr.net/npm/html5shiv@3.7.3/dist/html5shiv.min.js"></script>
      <script src="https://cdn.jsdelivr.net/npm/respond.js@1.4.2/dest/respond.min.js"></script>
    <![endif]-->

</head>
  </head>
  

  <body class="main-center theme-black" itemscope itemtype="http://schema.org/WebPage"><header class="header" itemscope itemtype="http://schema.org/WPHeader">
    <div class="slimContent">
      <div class="navbar-header">
        <div class="profile-block text-center">
          <a id="avatar" href="https://github.com/xiaoheiAh" target="_blank">
            <img class="img-circle img-rotate" src="https://xiaohei.im/hugo-theme-pure/avatar.png" width="200" height="200">
          </a>
          <h2 id="name" class="hidden-xs hidden-sm">赵小黑</h2>
          <h3 id="title" class="hidden-xs hidden-sm hidden-md">Java Developer</h3>
          <small id="location" class="text-muted hidden-xs hidden-sm"><i class="icon icon-map-marker"></i>Shanghai, China</small>
        </div><div class="search" id="search-form-wrap">
    <form class="search-form sidebar-form">
        <div class="input-group">
            <input type="text" class="search-form-input form-control" placeholder="搜索" />
            <span class="input-group-btn">
                <button type="submit" class="search-form-submit btn btn-flat" onclick="return false;"><i
                        class="icon icon-search"></i></button>
            </span>
        </div>
        <div class="ins-search">
            <div class="ins-search-mask"></div>
            <div class="ins-search-container">
                <div class="ins-input-wrapper">
                    <input type="text" class="ins-search-input" placeholder="想要查找什么..."
                        x-webkit-speech />
                    <button type="button" class="close ins-close ins-selectable" data-dismiss="modal"
                        aria-label="Close"><span aria-hidden="true">×</span></button>
                </div>
                <div class="ins-section-wrapper">
                    <div class="ins-section-container"></div>
                </div>
            </div>
        </div>
    </form>
</div>
        <button class="navbar-toggle collapsed" type="button" data-toggle="collapse" data-target="#main-navbar" aria-controls="main-navbar" aria-expanded="false">
          <span class="sr-only">Toggle navigation</span>
          <span class="icon-bar"></span>
          <span class="icon-bar"></span>
          <span class="icon-bar"></span>
        </button>
      </div>
      <nav id="main-navbar" class="collapse navbar-collapse" itemscope itemtype="http://schema.org/SiteNavigationElement" role="navigation">
        <ul class="nav navbar-nav main-nav  menu-highlight">
            <li class="menu-item menu-item-home">
                <a href="/hugo-theme-pure/">
                    <i class="icon icon-home-fill"></i>
                  <span class="menu-title">主页</span>
                </a>
            </li>
            <li class="menu-item menu-item-archives">
                <a href="/hugo-theme-pure/posts">
                    <i class="icon icon-archives-fill"></i>
                  <span class="menu-title">归档</span>
                </a>
            </li>
            <li class="menu-item menu-item-categories">
                <a href="/hugo-theme-pure/categories">
                    <i class="icon icon-folder"></i>
                  <span class="menu-title">分类</span>
                </a>
            </li>
            <li class="menu-item menu-item-tags">
                <a href="/hugo-theme-pure/tags">
                    <i class="icon icon-tags"></i>
                  <span class="menu-title">标签</span>
                </a>
            </li>
            <li class="menu-item menu-item-about">
                <a href="/hugo-theme-pure/about">
                    <i class="icon icon-cup-fill"></i>
                  <span class="menu-title">关于</span>
                </a>
            </li>
        </ul>
      </nav>
    </div>
  </header>
  <aside class="sidebar" itemscope itemtype="http://schema.org/WPSideBar">
  <div class="slimContent">
    
      <div class="widget">
    <h3 class="widget-title">公告</h3>
    <div class="widget-body">
        <div id="board">
            <div class="content"><p>自用科学上网节点推荐(便宜又好用)<a href="https://tianlinzhao.com/aff.php?aff=4969" target="_blank" style="background-color:#FFFF00">点这里跳转</a>
            </div>
        </div>
    </div>
</div>

      <div class="widget">
    <h3 class="widget-title"> 分类</h3>
    <div class="widget-body">
        <ul class="category-list">
            <li class="category-list-item"><a href="https://xiaohei.im/hugo-theme-pure/categories/corejava/" class="category-list-link">corejava</a><span class="category-list-count">7</span></li>
            <li class="category-list-item"><a href="https://xiaohei.im/hugo-theme-pure/categories/hystrix/" class="category-list-link">hystrix</a><span class="category-list-count">2</span></li>
            <li class="category-list-item"><a href="https://xiaohei.im/hugo-theme-pure/categories/leetcode/" class="category-list-link">leetcode</a><span class="category-list-count">3</span></li>
            <li class="category-list-item"><a href="https://xiaohei.im/hugo-theme-pure/categories/redis/" class="category-list-link">redis</a><span class="category-list-count">10</span></li>
            <li class="category-list-item"><a href="https://xiaohei.im/hugo-theme-pure/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/" class="category-list-link">学习笔记</a><span class="category-list-count">1</span></li>
            <li class="category-list-item"><a href="https://xiaohei.im/hugo-theme-pure/categories/%E6%B6%88%E6%81%AF%E9%98%9F%E5%88%97/" class="category-list-link">消息队列</a><span class="category-list-count">4</span></li>
        </ul>
    </div>
</div>
      <div class="widget">
    <h3 class="widget-title"> 标签</h3>
    <div class="widget-body">
        <ul class="tag-list">
            
            
            <li class="tag-list-item"><a href="https://xiaohei.im/hugo-theme-pure/tags/collections/" class="tag-list-link">collections</a><span
                    class="tag-list-count">7</span></li>
            
            
            <li class="tag-list-item"><a href="https://xiaohei.im/hugo-theme-pure/tags/hugo/" class="tag-list-link">hugo</a><span
                    class="tag-list-count">1</span></li>
            
            
            <li class="tag-list-item"><a href="https://xiaohei.im/hugo-theme-pure/tags/hystrix/" class="tag-list-link">hystrix</a><span
                    class="tag-list-count">1</span></li>
            
            
            <li class="tag-list-item"><a href="https://xiaohei.im/hugo-theme-pure/tags/leetcode/" class="tag-list-link">leetcode</a><span
                    class="tag-list-count">3</span></li>
            
            
            <li class="tag-list-item"><a href="https://xiaohei.im/hugo-theme-pure/tags/netty/" class="tag-list-link">netty</a><span
                    class="tag-list-count">1</span></li>
            
            
            <li class="tag-list-item"><a href="https://xiaohei.im/hugo-theme-pure/tags/rabbitmq/" class="tag-list-link">rabbitmq</a><span
                    class="tag-list-count">4</span></li>
            
            
            <li class="tag-list-item"><a href="https://xiaohei.im/hugo-theme-pure/tags/redis/" class="tag-list-link">redis</a><span
                    class="tag-list-count">10</span></li>
            
            
            <li class="tag-list-item"><a href="https://xiaohei.im/hugo-theme-pure/tags/rust/" class="tag-list-link">rust</a><span
                    class="tag-list-count">3</span></li>
            
            
            <li class="tag-list-item"><a href="https://xiaohei.im/hugo-theme-pure/tags/rxjava/" class="tag-list-link">rxjava</a><span
                    class="tag-list-count">2</span></li>
            
            
            <li class="tag-list-item"><a href="https://xiaohei.im/hugo-theme-pure/tags/%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81/" class="tag-list-link">分布式锁</a><span
                    class="tag-list-count">1</span></li>
            
            
            <li class="tag-list-item"><a href="https://xiaohei.im/hugo-theme-pure/tags/%E5%93%8D%E5%BA%94%E5%BC%8F%E7%BC%96%E7%A8%8B/" class="tag-list-link">响应式编程</a><span
                    class="tag-list-count">1</span></li>
            
            
            <li class="tag-list-item"><a href="https://xiaohei.im/hugo-theme-pure/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/" class="tag-list-link">数据结构</a><span
                    class="tag-list-count">1</span></li>
            
        </ul>

    </div>
</div>
      
<div class="widget">
    <h3 class="widget-title">最新文章</h3>
    <div class="widget-body">
        <ul class="recent-post-list list-unstyled no-thumbnail">
            <li>
                <div class="item-inner">
                    <p class="item-title">
                        <a href="https://xiaohei.im/hugo-theme-pure/2019/11/netty/" class="title">[学习笔记] Netty</a>
                    </p>
                    <p class="item-date">
                        <time datetime="2019-11-29 18:40:27 &#43;0800 CST" itemprop="datePublished">2019-11-29</time>
                    </p>
                </div>
            </li>
            <li>
                <div class="item-inner">
                    <p class="item-title">
                        <a href="https://xiaohei.im/hugo-theme-pure/2019/11/cluster/" class="title">Redis HA - Cluster</a>
                    </p>
                    <p class="item-date">
                        <time datetime="2019-11-24 11:48:17 &#43;0800 CST" itemprop="datePublished">2019-11-24</time>
                    </p>
                </div>
            </li>
            <li>
                <div class="item-inner">
                    <p class="item-title">
                        <a href="https://xiaohei.im/hugo-theme-pure/2019/11/sentinel/" class="title">Redis HA - 哨兵模式</a>
                    </p>
                    <p class="item-date">
                        <time datetime="2019-11-23 17:56:15 &#43;0800 CST" itemprop="datePublished">2019-11-23</time>
                    </p>
                </div>
            </li>
            <li>
                <div class="item-inner">
                    <p class="item-title">
                        <a href="https://xiaohei.im/hugo-theme-pure/2019/11/replication/" class="title">Redis-复制功能探索</a>
                    </p>
                    <p class="item-date">
                        <time datetime="2019-11-16 14:24:40 &#43;0800 CST" itemprop="datePublished">2019-11-16</time>
                    </p>
                </div>
            </li>
            <li>
                <div class="item-inner">
                    <p class="item-title">
                        <a href="https://xiaohei.im/hugo-theme-pure/2019/11/event/" class="title">Redis-事件</a>
                    </p>
                    <p class="item-date">
                        <time datetime="2019-11-14 15:01:45 &#43;0800 CST" itemprop="datePublished">2019-11-14</time>
                    </p>
                </div>
            </li>
        </ul>
    </div>
</div>
  </div>
</aside>

    
    
<aside class="sidebar sidebar-toc collapse" id="collapseToc" itemscope itemtype="http://schema.org/WPSideBar">
  <div class="slimContent">
    <h4 class="toc-title">文章目录</h4>
    <nav id="toc" class="js-toc toc">

    </nav>
  </div>
</aside>
<main class="main" role="main"><div class="content">
  <article id="-" class="article article-type-" itemscope
    itemtype="http://schema.org/BlogPosting">
    
    <div class="article-header">
      <h1 itemprop="name">
  <a
    class="article-title"
    href="/hugo-theme-pure/2019/11/netty/"
    >[学习笔记] Netty</a
  >
</h1>

      <div class="article-meta">
        <span class="article-date">
  <i class="icon icon-calendar-check"></i>
<a href="https://xiaohei.im/hugo-theme-pure/2019/11/netty/" class="article-date">
  <time datetime="2019-11-29 18:40:27 &#43;0800 CST" itemprop="datePublished">2019-11-29</time>
</a>
</span><span class="article-category">
  <i class="icon icon-folder"></i>
  <a class="article-category-link" href="/hugo-theme-pure/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"> 学习笔记 </a>
</span>  
  <span class="article-tag">
    <i class="icon icon-tags"></i>
    <a class="article-tag-link" href="/hugo-theme-pure/tags/netty/"> netty </a>
  </span>

	<span class="article-read hidden-xs">
	    <i class="icon icon-eye-fill" aria-hidden="true"></i>
	    <span id="busuanzi_container_page_pv">
			<span id="busuanzi_value_page_pv">0</span>
		</span>
	</span>
        <span class="post-comment"><i class="icon icon-comment"></i> <a href="/hugo-theme-pure/2019/11/netty/#comments"
            class="article-comment-link">评论</a></span>
		<span class="post-wordcount hidden-xs" itemprop="wordCount">字数统计:4804字</span>
		<span class="post-readcount hidden-xs" itemprop="timeRequired">阅读时长:10分 </span>
      </div>
    </div>
    <div class="article-entry marked-body js-toc-content" itemprop="articleBody">
      <blockquote>
<p>Netty 是一个异步事件驱动的网络应用框架,用于快速开发可维护的高性能服务器和客户端。</p>
</blockquote>

<p><strong>NIO:</strong> selector 模型,用一个线程监听多个连接的读写请求,减少线程资源的浪费.</p>

<h3 id="netty-优点">netty 优点</h3>

<ol>
<li>使用 JDK 自带的NIO需要了解太多的概念,编程复杂,一不小心 bug 横飞</li>
<li>Netty 底层 IO 模型随意切换,而这一切只需要做微小的改动,改改参数,Netty可以直接从 NIO 模型变身为 IO 模型</li>
<li>Netty 自带的拆包解包,异常检测等机制让你从NIO的繁重细节中脱离出来,让你只需要关心业务逻辑</li>
<li>Netty 解决了 JDK 的很多包括空轮询在内的 Bug</li>
<li>Netty 底层对线程,selector 做了很多细小的优化,精心设计的 reactor 线程模型做到非常高效的并发处理</li>
<li>自带各种协议栈让你处理任何一种通用协议都几乎不用亲自动手</li>
<li>Netty 社区活跃,遇到问题随时邮件列表或者 issue</li>
<li>Netty 已经历各大 RPC 框架,消息中间件,分布式通信中间件线上的广泛验证,健壮性无比强大</li>
</ol>

<h3 id="server端">Server端</h3>

<pre><code class="language-java">// 负责服务端的启动
ServerBootstrap serverBootstrap = new ServerBootstrap();
// 负责接收新连接
NioEventLoopGroup boss = new NioEventLoopGroup();
// 负责读取数据及业务逻辑处理
NioEventLoopGroup worker = new NioEventLoopGroup();

serverBootstrap.group(boss, worker)
  // 指定服务端 IO 模型为 NIO
  .channel(NioServerSocketChannel.class)
  // 业务逻辑处理
  .childHandler(new ChannelInitializer&lt;NioSocketChannel&gt;() {
    protected void initChannel(NioSocketChannel ch) throws Exception {
      ch.pipeline().addLast(new StringDecoder());
      ch.pipeline().addLast(new SimpleChannelInboundHandler&lt;String&gt;() {
        protected void channelRead0(ChannelHandlerContext channelHandlerContext, String s) throws Exception {
          System.out.println(s);
        }
      });
    }
  })
  .bind(8000);
</code></pre>

<ul>
<li><strong>NioSocketChannel/NioServerSocketChannel</strong> Netty 对 NIO 类型连接的抽象</li>
</ul>

<h4 id="handler-childhandler">handler() &amp; childHandler()</h4>

<ul>
<li>handler() 用于指定服务器端在启动过程中的一些逻辑</li>
<li>childHandler() 用于指定处理新连接数据的读写逻辑</li>
</ul>

<h4 id="attr-childattr">attr() &amp; childAttr()</h4>

<p>分别可以给服务端连接,客户端连接指定相应的属性,后续通过 <code>channel.attr()</code> 可以拿到.</p>

<h4 id="option-childoption">option() &amp; childOption()</h4>

<ul>
<li><p>option() 用于给服务端连接设定一系列的属性,最常见的是 <code>so_backlog</code></p>

<pre><code class="language-java">// 表示系统用于临时存放已完成三次握手的请求的队列的最大长度,如果连接建立频繁,服务器处理创建新连接较慢,可以适当调大这个参数
serverBootstrap.option(ChannelOption.SO_BACKLOG, 1024)
</code></pre></li>

<li><p>childOption() 给每条连接设置一些属性</p>

<pre><code class="language-java">serverBootstrap
			// 是否开启TCP底层心跳机制,true为开启
      .childOption(ChannelOption.SO_KEEPALIVE, true)
			// 是否开启Nagle算法,true表示关闭,false表示开启,通俗地说,如果要求高实时性,有数据发送时就马上发送,就关闭,如果需要减少发送次数减少网络交互,就开启。
      .childOption(ChannelOption.TCP_NODELAY, true)
</code></pre></li>
</ul>

<h3 id="client端">Client端</h3>

<p>带连接失败重试的客户端,失败重试延迟为 2 的幂次.</p>

<pre><code class="language-java">// 客户端启动
Bootstrap bootstrap = new Bootstrap();
// 线程模型
NioEventLoopGroup group = new NioEventLoopGroup();

bootstrap.group(group)
  // 指定 IO 模型为 NIO
  .channel(NioSocketChannel.class)
  // 业务逻辑处理
  .handler(new ChannelInitializer&lt;Channel&gt;() {
    protected void initChannel(Channel ch) throws Exception {
      ch.pipeline().addLast(new StringEncoder());
    }
  });
connect(bootstrap,&quot;127.0.0.1&quot;, 8000, MAX_RETRY);

private static void connect(Bootstrap bootstrap, String host, int port, int retry) {
    bootstrap.connect(host, port).addListener(future -&gt; {
        if (future.isSuccess()) {
            System.out.println(&quot;连接成功!&quot;);
        } else if (retry == 0) {
            System.err.println(&quot;重试次数已用完,放弃连接!&quot;);
        } else {
            // 第几次重连
            int order = (MAX_RETRY - retry) + 1;
            // 本次重连的间隔
            int delay = 1 &lt;&lt; order;
            System.err.println(new Date() + &quot;: 连接失败,第&quot; + order + &quot;次重连……&quot;);
            bootstrap.config().group().schedule(() -&gt; connect(bootstrap, host, port, retry - 1), delay, TimeUnit
                    .SECONDS);
        }
    });
}
</code></pre>

<h4 id="其他方法">其他方法</h4>

<ul>
<li>attr() 客户端绑定属性</li>
<li>option() 设置客户端 TCP 连接</li>
</ul>

<h3 id="bytebuf">ByteBuf</h3>

<p>netty 中的数据都是以 <code>ByteBuf</code> 为单位的,所有需要写出的数据都必须塞到 <code>ByteBuf</code> 中.</p>

<p><img src="https://cdn.jsdelivr.net/gh/xiaoheiAh/imgs@master/20191202111539.png" alt="ByteBuf-数据结构(掘金小册配图)" /></p>

<ol>
<li><code>ByteBuf</code> 是一个字节容器,容器里面的的数据分为三个部分,第一个部分是已经丢弃的字节,这部分数据是无效的;第二部分是可读字节,这部分数据是 <code>ByteBuf</code> 的主体数据, 从 <code>ByteBuf</code> 里面读取的数据都来自这一部分;最后一部分的数据是可写字节,所有写到 <code>ByteBuf</code> 的数据都会写到这一段。最后一部分虚线表示的是该 <code>ByteBuf</code> 最多还能扩容多少容量</li>
<li>以上三段内容是被两个指针给划分出来的,从左到右,依次是读指针(<code>readerIndex</code>)、写指针(<code>writerIndex</code>),然后还有一个变量 <code>capacity</code>,表示 <code>ByteBuf</code> 底层内存的总容量</li>
<li>从 ByteBuf 中每读取一个字节,<code>readerIndex</code> 自增1,<code>ByteBuf</code> 里面总共有 <code>writerIndex-readerIndex</code> 个字节可读, 由此可以推论出当 <code>readerIndex</code> 与 <code>writerIndex</code> 相等的时候,<code>ByteBuf</code> 不可读</li>
<li>写数据是从 <code>writerIndex</code> 指向的部分开始写,每写一个字节,<code>writerIndex</code> 自增1,直到增到 <code>capacity</code>,这个时候,表示 <code>ByteBuf</code> 已经不可写了</li>
<li><code>ByteBuf</code> 里面其实还有一个参数 <code>maxCapacity</code>,当向 <code>ByteBuf</code> 写数据的时候,如果容量不足,那么这个时候可以进行扩容,直到 <code>capacity</code> 扩容到 <code>maxCapacity</code>,超过 <code>maxCapacity</code> 就会报错</li>
</ol>

<h4 id="bytebuf-容量相关api">ByteBuf 容量相关API</h4>

<h5 id="capacity">capacity()</h5>

<p>表示 ByteBuf 底层占用了多少字节的内存(包括丢弃的字节、可读字节、可写字节),不同的底层实现机制有不同的计算方式,后面我们讲 ByteBuf 的分类的时候会讲到</p>

<h5 id="maxcapacity">maxCapacity()</h5>

<p>表示 ByteBuf 底层最大能够占用多少字节的内存,当向 ByteBuf 中写数据的时候,如果发现容量不足,则进行扩容,直到扩容到 maxCapacity,超过这个数,就抛异常</p>

<h5 id="readablebytes-与-isreadable">readableBytes() 与 isReadable()</h5>

<p>readableBytes() 表示 ByteBuf 当前可读的字节数,它的值等于 writerIndex-readerIndex,如果两者相等,则不可读,isReadable() 方法返回 false</p>

<h5 id="writablebytes-iswritable-与-maxwritablebytes">writableBytes()、 isWritable() 与 maxWritableBytes()</h5>

<p>writableBytes() 表示 ByteBuf 当前可写的字节数,它的值等于 capacity-writerIndex,如果两者相等,则表示不可写,isWritable() 返回 false,但是这个时候,并不代表不能往 ByteBuf 中写数据了, 如果发现往 ByteBuf 中写数据写不进去的话,Netty 会自动扩容 ByteBuf,直到扩容到底层的内存大小为 maxCapacity,而 maxWritableBytes() 就表示可写的最大字节数,它的值等于 maxCapacity-writerIndex</p>

<h4 id="bytebuf-读写指针相关-api">ByteBuf 读写指针相关 API</h4>

<h5 id="readerindex-与-readerindex-int">readerIndex() 与 readerIndex(int)</h5>

<p>前者表示返回当前的读指针 readerIndex, 后者表示设置读指针</p>

<h5 id="writeindex-与-writeindex-int">writeIndex() 与 writeIndex(int)</h5>

<p>前者表示返回当前的写指针 writerIndex, 后者表示设置写指针</p>

<h5 id="markreaderindex-与-resetreaderindex">markReaderIndex() 与 resetReaderIndex()</h5>

<p>前者表示把当前的读指针保存起来,后者表示把当前的读指针恢复到之前保存的值</p>

<h5 id="markwriterindex-与-resetwriterindex">markWriterIndex() 与 resetWriterIndex()</h5>

<p>同上,但是针对写指针</p>

<h4 id="bytebuf-读写-api">ByteBuf 读写 API</h4>

<h5 id="writebytes-byte-src-与-buffer-readbytes-byte-dst">writeBytes(byte[] src) 与 buffer.readBytes(byte[] dst)</h5>

<p>writeBytes() 表示把字节数组 src 里面的数据全部写到 ByteBuf,而 readBytes() 指的是把 ByteBuf 里面的数据全部读取到 dst,这里 dst 字节数组的大小通常等于 readableBytes(),而 src 字节数组大小的长度通常小于等于 writableBytes()</p>

<h5 id="writebyte-byte-b-与-buffer-readbyte">writeByte(byte b) 与 buffer.readByte()</h5>

<p>writeByte() 表示往 ByteBuf 中写一个字节,而 buffer.readByte() 表示从 ByteBuf 中读取一个字节,类似的 API 还有 writeBoolean()、writeChar()、writeShort()、writeInt()、writeLong()、writeFloat()、writeDouble() 与 readBoolean()、readChar()、readShort()、readInt()、readLong()、readFloat()、readDouble()</p>

<p>与读写 API 类似的 API 还有 getBytes、getByte() 与 setBytes()、setByte() 系列,唯一的区别就是 get/set 不会改变读写指针,而 read/write 会改变读写指针,这点在解析数据的时候千万要注意</p>

<h5 id="release-与-retain">release() 与 retain()</h5>

<p>由于 Netty 使用了 <strong>堆外内存</strong>,而堆外内存是不被 jvm 直接管理的,也就是说申请到的内存无法被垃圾回收器直接回收,所以需要我们<strong>手动回收</strong>。有点类似于c语言里面,申请到的内存必须手工释放,否则会造成内存泄漏。</p>

<p>Netty 的 ByteBuf 是通过引用计数的方式管理的,如果一个 ByteBuf 没有地方被引用到,需要回收底层内存。默认情况下,当创建完一个 ByteBuf,它的引用为1,然后每次调用 retain() 方法, 它的引用就加一, release() 方法原理是将引用计数减一,减完之后如果发现引用计数为0,则直接回收 ByteBuf 底层的内存。</p>

<h5 id="slice-duplicate-copy">slice()、duplicate()、copy()</h5>

<p>这三个方法通常情况会放到一起比较,这三者的返回值都是一个新的 ByteBuf 对象</p>

<ol>
<li>slice() 方法从原始 ByteBuf 中截取一段,这段数据是从 readerIndex 到 writeIndex,同时,返回的新的 ByteBuf 的最大容量 maxCapacity 为原始 ByteBuf 的 readableBytes()</li>
<li>duplicate() 方法把整个 ByteBuf 都截取出来,包括所有的数据,指针信息</li>
<li>slice() 方法与 duplicate() 方法的相同点是:<strong>底层内存以及引用计数与原始的 ByteBuf 共享</strong>,也就是说经过 slice() 或者 duplicate() 返回的 ByteBuf 调用 write 系列方法都会影响到 原始的 ByteBuf,但是它们都维持着与原始 ByteBuf 相同的内存引用计数和不同的读写指针</li>
<li>slice() 方法与 duplicate() 不同点就是:slice() 只截取从 readerIndex 到 writerIndex 之间的数据,它返回的 ByteBuf 的最大容量被限制到 原始 ByteBuf 的 readableBytes(), 而 duplicate() 是把整个 ByteBuf 都与原始的 ByteBuf 共享</li>
<li>slice() 方法与 duplicate() 方法不会拷贝数据,它们只是通过改变读写指针来改变读写的行为,而最后一个方法 copy() 会直接从原始的 ByteBuf 中拷贝所有的信息,包括读写指针以及底层对应的数据,因此,<strong>往 copy() 返回的 ByteBuf 中写数据不会影响到原始的 ByteBuf</strong></li>
<li>slice() 和 duplicate() 不会改变 ByteBuf 的引用计数,所以原始的 ByteBuf 调用 release() 之后发现引用计数为零,就开始释放内存,调用这两个方法返回的 ByteBuf 也会被释放,这个时候如果再对它们进行读写,就会报错。因此,我们可以通过调用一次 retain() 方法 来增加引用,表示它们对应的底层的内存多了一次引用,引用计数为2,在释放内存的时候,需要调用两次 release() 方法,将引用计数降到零,才会释放内存</li>
<li>这三个方法均维护着自己的读写指针,与原始的 ByteBuf 的读写指针无关,相互之间不受影响</li>
</ol>

<h3 id="pipeline-channelhandler">Pipeline &amp; ChannelHandler</h3>

<p><code>pipeline</code> 的数据结构为 双向链表, 节点的类型是一个 <code>ChannelHandlerContext</code> 包含着 每一个 <code>channel</code> 的上下文信息, <code>contenxt</code> 中包裹着一个 <code>handler</code> 用于处理用户的逻辑,<code>pipeline</code> 利用 <strong>责任链</strong> 的模式执行完所有的 <code>handler</code>.</p>

<p><img src="https://cdn.jsdelivr.net/gh/xiaoheiAh/imgs@master/20191202194426.png" alt="掘金小册-pipeline构成" /></p>

<h4 id="内置的-channelhandler">内置的 ChannelHandler</h4>

<ol>
<li><strong>ByteToMessageDecoder</strong></li>
</ol>

<p>二进制 -&gt; Java 对象转换,重写 <code>decode</code> 方法即可.默认情况下 <code>ByteBuf</code> 使用的是对外内存,通过引用计数判断是否需要清除.而该 <code>Decoder</code> 可以自动释放内存无需关心.</p>

<ol>
<li><strong>SimpleChannelInboundHandler</strong></li>
</ol>

<p>自动选择对应的消息进行处理,自动传递对象</p>

<ol>
<li><strong>MessageToByteEncoder</strong></li>
</ol>

<p>对象 -&gt; 二进制</p>

<h3 id="粘包-拆包">粘包 &amp; 拆包</h3>

<blockquote>
<p><a href="https://www.cnblogs.com/wade-luffy/p/6165671.html">https://www.cnblogs.com/wade-luffy/p/6165671.html</a></p>
</blockquote>

<p>TCP 的传输是基于字节流的,没有明显的分界,有可能会把应用层的多个包合在一块发出去(<strong>粘包</strong>),有可能把一个过大的包分多次发出(<strong>拆包</strong>),粘包/拆包是相对的,一方拆包,一方就要粘包.</p>

<h4 id="tcp粘包-拆包发生的原因">TCP粘包/拆包发生的原因</h4>

<p>问题产生的原因有三个,分别如下。</p>

<p>(1)应用程序write写入的字节大小大于套接口发送缓冲区大小;</p>

<p>(2)进行MSS大小的TCP分段;</p>

<p>(3)以太网帧的payload大于MTU进行IP分片。</p>

<h4 id="解决策略">解决策略</h4>

<p>通过应用层设计通用的结构保证.</p>

<ol>
<li>消息定长,例如每个报文的大小为固定长度200字节,如果不够,空位补空格;</li>
<li>在包尾增加回车换行符进行分割,例如FTP协议;</li>
<li>将消息分为消息头和消息体,消息头中包含表示消息总长度(或者消息体长度)的字段,通常设计思路为消息头的第一个字段使用int32来表示消息的总长度;</li>
<li>更复杂的应用层协议</li>
</ol>

<h4 id="netty-解决方案">netty 解决方案</h4>

<p>netty 提供了多种拆包器,满足用户的需求,不需要自己来对 <code>TCP</code> 流进行处理.</p>

<ol>
<li><p>固定长度拆包器 <strong>FixedLengthFrameDecoder</strong></p></li>

<li><p>行拆包器 <strong>LineBasedFrameDecoder</strong></p></li>
</ol>

<p>数据包以换行符作为分隔.</p>

<ol>
<li>分隔符拆包器 <strong>DelimiterBasedFrameDecoder</strong></li>
</ol>

<p>行拆包器的通用版,自定义分隔符</p>

<ol>
<li>基于长度域拆包器 <strong>LengthFieldBasedFrameDecoder</strong></li>
</ol>

<p>自定义的协议中包含长度域字段,即可使用来拆包</p>

<blockquote>
<p>每次的包不是定长的,怎么就能通过位移确认长度域,进而确定长度?</p>

<p>答: 通过设置一个完整包的开始标志,确定是一个新包就可以了.比如通常会设置一个魔数,拆包前先判断是不是我们定义的包.然后再去通过位移定位到长度域.</p>
</blockquote>

<h3 id="channelhandler-生命周期">ChannelHandler 生命周期</h3>

<p><img src="https://cdn.jsdelivr.net/gh/xiaoheiAh/imgs@master/20191203165854.png" alt="生命周期图" /></p>

<ol>
<li><code>handlerAdded()</code> :指的是当检测到新连接之后,调用 <code>ch.pipeline().addLast(new xxxHandler());</code> 之后的回调,表示在当前的 channel 中,已经成功添加了一个 handler 处理器。</li>
<li><code>channelRegistered()</code>:这个回调方法,表示当前的 channel 的所有的逻辑处理已经和某个 NIO 线程建立了绑定关系,accept 到新的连接,然后创建一个线程来处理这条连接的读写,Netty 里面是使用了线程池的方式,只需要从线程池里面去抓一个线程绑定在这个 channel 上即可,这里的 NIO 线程通常指的是 <code>NioEventLoop</code>,不理解没关系,后面我们还会讲到。</li>
<li><code>channelActive()</code>:当 channel 的所有的业务逻辑链准备完毕(也就是说 channel 的 pipeline 中已经添加完所有的 handler)以及绑定好一个 NIO 线程之后,这条连接算是真正激活了,接下来就会回调到此方法。</li>
<li><code>channelRead()</code>:客户端向服务端发来数据,每次都会回调此方法,表示有数据可读。</li>
<li><code>channelReadComplete()</code>:服务端每次读完一次完整的数据之后,回调该方法,表示数据读取完毕。</li>
<li><code>channelInactive()</code>: 表面这条连接已经被关闭了,这条连接在 TCP 层面已经不再是 <strong>ESTABLISH</strong> 状态了</li>
<li><code>channelUnregistered()</code>: 既然连接已经被关闭,那么与这条连接绑定的线程就不需要对这条连接负责了,这个回调就表明与这条连接对应的 NIO 线程移除掉对这条连接的处理</li>
<li><code>handlerRemoved()</code>:最后,我们给这条连接上添加的所有的业务逻辑处理器都给移除掉。</li>
</ol>

<h3 id="心跳-空闲检测">心跳 &amp; 空闲检测</h3>

<h4 id="idlestatehandler">IdleStateHandler</h4>

<p>空闲检测(一段时间内是否有读写).</p>

<h4 id="实现一个心跳">实现一个心跳</h4>

<pre><code class="language-java">public class HeartBeatTimerHandler extends ChannelInboundHandlerAdapter {

    private static final int HEARTBEAT_INTERVAL = 5;

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        scheduleSendHeartBeat(ctx);

        super.channelActive(ctx);
    }

    private void scheduleSendHeartBeat(ChannelHandlerContext ctx) {
        ctx.executor().schedule(() -&gt; {

            if (ctx.channel().isActive()) {
                ctx.writeAndFlush(new HeartBeatRequestPacket());
                scheduleSendHeartBeat(ctx);
            }

        }, HEARTBEAT_INTERVAL, TimeUnit.SECONDS);
    }
}
</code></pre>

<h3 id="性能优化方案">性能优化方案</h3>

<ol>
<li>共享 handler  <code>@ChannelHandler.Sharable</code></li>
<li>压缩 handler - 合并编解码器 —— MessageToMessageCodec</li>
<li>虽然有状态的 handler 不能搞单例,但是你可以绑定到 channel 属性上,强行单例</li>
<li>缩短事件传播路径—— 放 Map 里,在第一个 handler 里根据指令来找具体 handler。</li>
<li>更改事件传播源—— 用 ctx.writeAndFlush() 不要用 ctx.channel().writeAndFlush()</li>
<li>减少阻塞主线程的操作—— 使用业务线程池,RPC 优化重点</li>
<li>计算耗时,使用回调 Future</li>
</ol>
    </div>
    <div class="article-footer">
<blockquote class="mt-2x">
  <ul class="post-copyright list-unstyled">
    <li class="post-copyright-link hidden-xs">
      <strong>本文链接: </strong>
      <a href="https://xiaohei.im/hugo-theme-pure/2019/11/netty/" title="[学习笔记] Netty" target="_blank" rel="external">https://xiaohei.im/hugo-theme-pure/2019/11/netty/</a>
    </li>
    <li class="post-copyright-license">
      <strong>License:</strong><a href="http://creativecommons.org/licenses/by/4.0/deed.zh" target="_blank" rel="external">CC BY 4.0 CN</a>
    </li>
  </ul>
</blockquote>

<div class="panel panel-default panel-badger">
  <div class="panel-body">
    <figure class="media">
      <div class="media-left">
        <a href="https://github.com/xiaoheiAh" target="_blank" class="img-burn thumb-sm visible-lg">
          <img src="https://xiaohei.im/hugo-theme-pure/avatar.png" class="img-rounded w-full" alt="">
        </a>
      </div>
      <div class="media-body">
        <h3 class="media-heading"><a href="https://github.com/xiaoheiAh" target="_blank"><span class="text-dark">赵小黑</span><small class="ml-1x">Java Developer</small></a></h3>
        <div>好好学习~天天向上~</div>
      </div>
    </figure>
  </div>
</div>
    </div>
  </article>
<section id="comments">
</section>

</div><nav class="bar bar-footer clearfix" data-stick-bottom>
    <div class="bar-inner">
        <ul class="pager pull-left">
            <li class="prev">
                <a href="https://xiaohei.im/hugo-theme-pure/2019/11/cluster/" title="Redis HA - Cluster"><i
                        class="icon icon-angle-left"
                        aria-hidden="true"></i><span>&nbsp;&nbsp;下一篇</span></a>
            </li>
            
            <li class="toggle-toc">
                <a class="toggle-btn collapsed" data-toggle="collapse" href="#collapseToc" aria-expanded="false"
                    title="文章目录" role="button">
                    <span>[&nbsp;</span><span>文章目录</span>
                    <i class="text-collapsed icon icon-anchor"></i>
                    <i class="text-in icon icon-close"></i>
                    <span>]</span>
                </a>
            </li>
        </ul>
        
        <button type="button" class="btn btn-fancy btn-donate pop-onhover bg-gradient-warning" data-toggle="modal"
            data-target="#donateModal"><span>赏</span></button>
        
        <div class="bar-right">
            <div class="share-component" data-sites="weibo,qq,wechat,facebook,twitter"
                data-mobile-sites="weibo,qq,qzone"></div>
        </div>
    </div>
</nav>


<div class="modal modal-center modal-small modal-xs-full fade" id="donateModal" tabindex="-1" role="dialog">
    <div class="modal-dialog" role="document">
        <div class="modal-content donate">
            <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span
                    aria-hidden="true">&times;</span></button>
            <div class="modal-body">
                <div class="donate-box">
                    <div class="donate-head">
                        <p>感谢您的支持,我会继续努力的!</p>
                    </div>
                    <div class="tab-content">
                        <div role="tabpanel" class="tab-pane fade active in" id="alipay">
                            <div class="donate-payimg">
                                <img src="https://xiaohei.im/hugo-theme-pure/donate/alipayimg.png"
                                    alt="扫码支持" title="扫一扫" />
                            </div>
                            <p class="text-muted mv">扫码打赏, 多少你说了算~</p>
                            <p class="text-grey">打开支付宝扫一扫,即可进行扫码打赏哦~</p>
                        </div>
                        <div role="tabpanel" class="tab-pane fade" id="wechatpay">
                            <div class="donate-payimg">
                                <img src="https://xiaohei.im/hugo-theme-pure/donate/wechatpayimg.png"
                                    alt="扫码支持" title="扫一扫" />
                            </div>
                            <p class="text-muted mv">扫码打赏, 多少你说了算~</p>
                            <p class="text-grey">打开微信扫一扫,即可进行扫码打赏哦</p>
                        </div>
                    </div>
                    <div class="donate-footer">
                        <ul class="nav nav-tabs nav-justified" role="tablist">
                            <li role="presentation" class="active">
                                <a href="#alipay" id="alipay-tab" role="tab" data-toggle="tab" aria-controls="alipay"
                                    aria-expanded="true"><i class="icon icon-alipay"></i> 支付宝</a>
                            </li>
                            <li role="presentation" class="">
                                <a href="#wechatpay" role="tab" id="wechatpay-tab" data-toggle="tab"
                                    aria-controls="wechatpay" aria-expanded="false"><i class="icon icon-wepay"></i>
                                    微信支付</a>
                            </li>
                        </ul>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>
</main><footer class="footer" itemscope itemtype="http://schema.org/WPFooter">
<ul class="social-links">
    <li><a href="https://github.com/xiaoheiAh" target="_blank" title="github" data-toggle=tooltip data-placement=top >
            <i class="icon icon-github"></i></a></li>
    <li><a href="https://xiaohei.im/index.xml" target="_blank" title="rss" data-toggle=tooltip data-placement=top >
            <i class="icon icon-rss"></i></a></li>
</ul>
  <div class="copyright">
    &copy;2017  -
    2019
    <div class="publishby">
        Theme by <a href="https://github.com/xiaoheiAh" target="_blank"> xiaoheiAh </a>base on<a href="https://github.com/xiaoheiAh/hugo-theme-pure" target="_blank"> pure</a>.
    </div>
  </div>
</footer>

<script src="https://cdn.jsdelivr.net/npm/jquery@3.4.1/dist/jquery.min.js"></script>
<script>
    window.jQuery || document.write('<script src="js/jquery.min.js"><\/script>')
</script>
<script type="text/javascript" src="https://cdn.staticfile.org/highlight.js/9.15.10/highlight.min.js"></script>
<script type="text/javascript" src="https://cdn.staticfile.org/highlight.js/9.15.10/languages/rust.min.js"></script>
<script type="text/javascript"
    src="https://cdn.staticfile.org/highlight.js/9.15.10/languages/dockerfile.min.js"></script>
<script>
    hljs.configure({
        tabReplace: '    ', 
        classPrefix: ''     
        
    })
    hljs.initHighlightingOnLoad();
</script>
<script type="text/javascript" src="https://xiaohei.im/hugo-theme-pure/js/application.js"></script>
<script type="text/javascript" src="https://xiaohei.im/hugo-theme-pure/js/plugin.js"></script>
<script>
    (function (window) {
        var INSIGHT_CONFIG = {
            TRANSLATION: {
                POSTS: '文章',
                PAGES: '页面',
                CATEGORIES: '分类',
                TAGS: '标签',
                UNTITLED: '(未命名)',
            },
            ROOT_URL: 'https:\/\/xiaohei.im\/hugo-theme-pure',
            CONTENT_URL: 'https:\/\/xiaohei.im\/hugo-theme-pure\/searchindex.json ',
        };
        window.INSIGHT_CONFIG = INSIGHT_CONFIG;
    })(window);
</script>
<script type="text/javascript" src="https://xiaohei.im/hugo-theme-pure/js/insight.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tocbot/4.4.2/tocbot.min.js"></script>
<script>
    tocbot.init({
        
        tocSelector: '.js-toc',
        
        contentSelector: '.js-toc-content',
        
        headingSelector: 'h1, h2, h3',
        
        hasInnerContainers: true,
    });
</script>

<script async src="https://busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>

<script src="https://cdn.jsdelivr.net/npm/gitalk@1.4.0/dist/gitalk.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/blueimp-md5@2.10.0/js/md5.min.js"></script>
<script type="text/javascript">
    var gitalk = new Gitalk({
        clientID: 'e38fc798c72a7e4e1386',
        clientSecret: 'e151aa3b7b98d3cfaa1f096b88fdd7897e2c8007',
        repo: 'xiaoheiAh.github.io',
        owner: 'xiaoheiAh',
        admin: ['xiaoheiAh'],
        id: md5(location.pathname),
        distractionFreeMode: true
    });
    gitalk.render('comments');
</script>
<script type="application/javascript">
var doNotTrack = false;
if (!doNotTrack) {
	window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date;
	ga('create', 'UA-98254666-1', 'auto');
	
	ga('send', 'pageview');
}
</script>
<script async src='https://www.google-analytics.com/analytics.js'></script>

  </body>
</html>