-
Notifications
You must be signed in to change notification settings - Fork 0
/
local-search.xml
1427 lines (687 loc) · 674 KB
/
local-search.xml
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
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>LVS、Keepalived、Haproxy概念以及架构总结</title>
<link href="/2022/09/18/lvs-keepalived-haproxy/"/>
<url>/2022/09/18/lvs-keepalived-haproxy/</url>
<content type="html"><![CDATA[<p>因为对整体概念的不熟悉,导致对线网组件的架构不清晰,这边主要对于一些概念作区分总结,主要摘抄于<a href="https://www.cnblogs.com/f-ck-need-u/p/7576137.html#lvs">骏马金龙 - 博客园 (cnblogs.com)</a>的博客,还有就是其他的一些文章,都同意写在文章最后面。</p><h2 id="1-LVS"><a href="#1-LVS" class="headerlink" title="1. LVS"></a>1. LVS</h2><p><img src="/images/lvs/lvs%E6%8B%93%E6%89%91%E5%9B%BE.png" alt="LVS拓扑图"></p><p>网站架构中,负载均衡技术是实现网站架构<strong>伸缩性</strong>的主要手段之一。所谓”伸缩性”,是指可以不断向集群中添加新的服务器来提升性能、缓解不断增加的并发用户访问压力。通俗地讲,就是一头牛拉不动时,就用两头、三头、更多头牛来拉。</p><p>负载均衡有好几种方式:http URL重定向、DNS的A记录负载均衡、反向代理负载均衡、IP负载均衡和链路层负载。本文所述为LVS,它的VS/NAT和VS/TUN模式是IP负载均衡的优秀代表,而它的VS/DR模式则是链路层负载均衡的优秀代表。</p><p>LVS是章文嵩开发的一个国产开源负载均衡软件。LVS最初是他在大学期间的玩具,随着后来使用的用户越来越多,LVS也越来越完善,最终集成到了Linux的内核中。有不少开源牛人都为LVS开发过辅助工具和辅助组件,最出名的就是Alexandre为LVS编写的Keepalived,<strong>它最初专门用于监控LVS,后来加入了通过VRRP实现高可用的功能。</strong></p><p>LVS的全称是Linux virtual server,即Linux虚拟服务器。之所以是虚拟服务器,是因为LVS自身是个负载均衡器(director),不直接处理请求,而是将请求转发至位于它后端真正的服务器realserver上。</p><p>LVS是四层(传输层tcp/udp)、七层(应用层)的负载均衡工具,只不过大众一般都使用它的四层负载均衡功能ipvs,而七层的内容分发负载工具ktcpvs(kernel tcp virtual server)不怎么完善,使用的人并不多。</p><p>ipvs是集成在内核中的框架,可以通过用户空间的程序<code>ipvsadm</code>工具来管理,该工具可以定义一些规则来管理内核中的ipvs。就像iptables和netfilter的关系一样。</p><h2 id="2-LVS-Keepalived"><a href="#2-LVS-Keepalived" class="headerlink" title="2. LVS+Keepalived"></a>2. LVS+Keepalived</h2><p>KeepAlived主要有两个功能:</p><ol><li>能够对RealServer进行健康状况检查,支持4层、5层和7层协议进行健康检查</li></ol><p><img src="/images/lvs/keepalived+lvs.png" alt="健康检查"></p><ol start="2"><li>对负载均衡调度器实现高可用,防止Director单点故障</li></ol><p><img src="/images/lvs/keepalived+lvs2.png" alt="健康检查+高可用"></p><p>在keepalived设计之初,它只是LVS周边的一个辅助工具,<strong>用于LVS的监控状况检查,因此它和LVS的兼容性非常好。如果某一个realserver节点宕了,keepalived会将此节点从管理列表中踢出去</strong>,当此节点恢复后又将此节点加回管理列表,这样能够就让realserver负载均衡变的智能化。但是,此时的调度器存在单点故障的可能性,因此有必要对其实现高可用。</p><p>实现LVS高可用可以使用多种软件来实现,如heartbeat,但是heartbeat本身不能实现ipvs的健康状况检查,需要搭配Ldirectord(安装完heartbeat就有了)来进行健康检查。<strong>所幸的是keepalived后来也加入了高可用的功能</strong>,而且配置起来也相当简单。相比于heartbeat+Ldirectord,keepalived的检查速度极快,故障转移也极快,布置也简单的多。所以一般来说,要管理ipvs,都会选择使用keepalived。</p><p>keepalived实现故障转移的功能是通过VRRP(virtual router redundancy protocol虚拟路由器冗余协议)协议来实现的。 在keepalived正常工作的时候,主节点(master)会不断的发送心跳信息给备节点(backup),当备节点不能在一定时间内收到主节点的心跳信息时,备节点会认为主节点宕了,然后会接管主节点上的资源,并继续向外提供服务保证其可用性。当主节点恢复的时候,备节点会自动让出资源并再次自动成为备节点。</p><p>注意,使用keepalived监控、高可用LVS集群时(即常说的keepalived+lvs),并不需要在Director上使用ipvsadm等管理工具额外配置ipvs规则。<strong>因为keepalived中集合了管理ipvs规则的组件</strong>(即ipvs wrapper),<strong>可以直接在keepalived的配置文件中配置ipvs相关规则,在解析配置文件时会通过特定的组件将规则发送到内核中的ipvs模块</strong>。</p><h2 id="3-Keepalived-amp-Haproxy"><a href="#3-Keepalived-amp-Haproxy" class="headerlink" title="3. Keepalived&Haproxy"></a>3. Keepalived&Haproxy</h2><p><img src="/images/lvs/ha_diagram.gif" alt="keepavlied+haproxy"></p><p>上面说过,Keepalived软件起初是专为LVS负载均衡软件设计的,用来管理并监控LVS集群系统中各个服务节点的状态,后来又加入了可以实现高可用的VRRP功能。因此,Keepalived除了能够管理LVS软件外,还可以作为其他服务(例如:Nginx、Haproxy、MySQL等)的高可用解决方案软件。</p><p>keepalived分为<strong>vrrp实例的心跳检查</strong>和<strong>后端服务的健康检查</strong>。如果要配置后端服务,则后端服务只能是LVS(也就是上面说的下线了护会剔除,这就是健康检查)。<strong>但vrrp能独立于lvs存在,例如keepalive结合haproxy、mysql等服务实现它们的高可用。</strong></p><ol><li><p>vrrp实例的心跳检查(lvs,haproxy,mysql…)</p><ul><li><p>除了LVS,vrrp只能通过脚本来实现vrrp的健康检查,并通过vrrp实例脚本结束keepalived进程来中断该实例的心跳通告。<strong>此时virtual server部分的配置需省略。</strong></p></li><li><p>对于LVS,无需使用任何脚本,因为所有keepalived节点会同时对<strong>后端服务(即LVS)</strong>进行健康检查,并同时从LVS规则中剔除下线的服务。如果所有后端服务都不健康,那就没有必要切换keepalived。</p></li></ul></li><li><p>后端Real Server的健康检查(只能是LVS)</p><ul><li>一般采用TCP_CHECK、HTTP_GET、SSL_GET进行健康检查。</li><li>但也能自写脚本进行后端服务的健康检查,这种模式称为MISC_CHECK。</li></ul></li></ol><h2 id="4-相关资料"><a href="#4-相关资料" class="headerlink" title="4. 相关资料"></a>4. 相关资料</h2><ul><li><p>LVS中文官方手册:<a href="http://www.linuxvirtualserver.org/zh/index.html%E3%80%82%E8%BF%99%E4%B8%AA%E6%89%8B%E5%86%8C%E5%AF%B9%E4%BA%8E%E4%BA%86%E8%A7%A3lvs%E7%9A%84%E8%83%8C%E6%99%AF%E7%9F%A5%E8%AF%86%E5%BE%88%E6%9C%89%E5%B8%AE%E5%8A%A9%E3%80%82">http://www.linuxvirtualserver.org/zh/index.html。这个手册对于了解lvs的背景知识很有帮助。</a></p></li><li><p>LVS英文官方手册:<a href="http://www.linuxvirtualserver.org/Documents.html%E3%80%82%E8%BF%99%E4%B8%AA%E6%89%8B%E5%86%8C%E6%AF%94%E8%BE%83%E5%85%A8%E9%9D%A2%EF%BC%8C%E5%AF%B9%E4%BA%8E%E4%BA%86%E8%A7%A3%E5%92%8C%E5%AD%A6%E4%B9%A0lvs%E7%9A%84%E5%8E%9F%E7%90%86%E3%80%81%E9%85%8D%E7%BD%AE%E5%BE%88%E6%9C%89%E5%B8%AE%E5%8A%A9%E3%80%82">http://www.linuxvirtualserver.org/Documents.html。这个手册比较全面,对于了解和学习lvs的原理、配置很有帮助。</a></p></li><li><p><a href="https://wsgzao.github.io/post/lvs-keepalived/">LVS 和 Keepalived 的原理介绍和配置实践 | HelloDog (wsgzao.github.io)</a></p></li><li><p><a href="https://www.cnblogs.com/rexcheny/p/10778567.html">Keepalived部署与配置详解 - 昀溪 - 博客园 (cnblogs.com)</a></p></li><li><p><a href="https://cloud.tencent.com/developer/article/1644893">高可用篇之Keepalived (HAProxy+keepalived 搭建高可用负载均衡集群) - 腾讯云开发者社区-腾讯云 (tencent.com)</a></p></li><li><p><a href="https://www.cnblogs.com/jmcui/p/13055283.html">虚拟IP原理及使用 - JMCui - 博客园 (cnblogs.com)</a></p></li></ul>]]></content>
<categories>
<category>文章分类</category>
</categories>
<tags>
<tag>Linux</tag>
</tags>
</entry>
<entry>
<title>编码、加密和散列之间的区别</title>
<link href="/2022/04/16/encode-encrypt-hash/"/>
<url>/2022/04/16/encode-encrypt-hash/</url>
<content type="html"><![CDATA[<p>翻译自<a href="https://medium.com/swlh/the-difference-between-encoding-encryption-and-hashing-878c606a7aff">The Difference Between Encoding, Encryption and Hashing</a></p><hr><p>在我们的日常生活中,有很多关于数据保护和安全的讨论。我们生活在一个可以说数据是新的“货币”的时代。就像我们拥有的任何其他货币一样,我们永远不会希望我们的数据落入他人手中,尤其是有不良意图的人。如果你认为你的个人数据在网上是安全的,那么请相信我,我的朋友,你大错特错了。</p><p>好吧,老实说,大多数正在阅读这个文章的人可能已经意识到了这个事实(如果你还没有意识到,你好!醒醒!)。你可能还听说过本文标题中所写的文字,即编码、加密和散列。一种常见的误解是,这些词指的是同一件事,但实际上它们指的是各自自己的技术,这些技术彼此截然不同。因此,让我们戴上好奇的帽子,尝试了解它们的实际含义。</p><h2 id="1-编码"><a href="#1-编码" class="headerlink" title="1. 编码"></a>1. 编码</h2><p>所以首先,让我们把事情弄清楚,与加密和散列不同,编码不用于安全目的。它是一种将数据从一种格式转换为另一种格式的技术,以便不同的系统可以理解和使用它。编码中没有使用任何“密钥”。同样的算法也被用来解码最初用于对其进行编码的数据。由于这个原因,如果攻击者拥有编码数据,他们很容易解码数据。此类算法的示例是 ASCII、Unicode、Base64 等</p><h3 id="1-1-为什么我们需要编码?"><a href="#1-1-为什么我们需要编码?" class="headerlink" title="1.1 为什么我们需要编码?"></a>1.1 为什么我们需要编码?</h3><p>为了理解这一点,让我们以 BASE64 编码技术为例。在电子邮件的最初几年,数据主要是基于文本的。因此,为了通过互联网传输,文本数据被转换为二进制格式,在接收端,这个二进制数据再次被转换回文本格式。但随着时间的推移,发送附件以及多媒体以及基于文本的电子邮件的需求就出现了。这些附件的二进制数据在原始形式中被破坏的可能性很高。这主要是因为原始二进制文件包含一些字符,这些字符在某些语言中的处理方式不同。例如,在 C/C++ 中,“Null”字符被视为文件的结尾。因此,发送包含“Null”字节的二进制数据最终会阻止文件被读取完成,并会导致数据损坏。为了解决这个问题,BASE64 编码技术应运而生。</p><h3 id="1-2-Base64-编码如何工作?"><a href="#1-2-Base64-编码如何工作?" class="headerlink" title="1.2 Base64 编码如何工作?"></a>1.2 Base64 编码如何工作?</h3><p>让我们举一个将“abc”转换为Base64的例子。</p><ol><li><p>使用ASCII 表,将字符串中的字符转换为十进制。</p><p><img src="/images/encode_encrypt_hash/ASCII_Table.png"></p><p> a = 97,b = 98 , c = 99。</p></li><li><p>将这些十进制数中的每一个转换为等效的 8 位二进制形式:</p><p>a = 0110 0001,b = 0110 0010 , c = 0110 0011。</p><p>即 abc = 0110 0001 0110 0010 0110 0011</p></li><li><p>将它们分成每组 6 位:</p><p>011000 — 010110 — 001001 — 100011</p></li><li><p>将每个二进制转换为其等效的十进制形式:</p><p>011000 = 24,010110 = 22,001001 = 9 , 100011 = 35</p></li><li><p>使用 Base64 表,将这些十进制数中的每一个转换成它们对应的 Base64 字符:</p><p><img src="/images/encode_encrypt_hash/Base64_Table.png"></p><p>所以,24 = ‘Y’,23 = ‘X’,9 = ‘J’, 35 = ‘j’。</p></li></ol><p>因此,Base64 中的“abc”=>“YXJj”。</p><p>注意:有时不可能将组合的二进制分成每组 6 位的精确组。在这种情况下,最后添加 0 以使其成为精确的 24 位倍数。这个过程称为“填充”。为了对填充的数据进行编码,如果有 6 个完全填充的位,则将其映射为“=”。</p><p>补充:编码时,每 3 个字节一组,共 8bit*3=24bit,划分成 4 组,即每 6bit 代表一个编码后的索引值(所以下面填充至24的倍数位)</p><p>示例:“a:aa” => 011000 010011 101001 100001 011000 01xxxx xxxxxx xxxxxx => “YTphYQ==”</p><h2 id="2-加密"><a href="#2-加密" class="headerlink" title="2. 加密"></a>2. 加密</h2><p>无法想象没有加密的互联网。如果没有它,互联网将是一个不太安全的地方。互联网上的数据使用加密保持机密和安全。加密使攻击者无法读取和解码数据,并防止数据被盗。</p><p>加密使用“加密密钥”。使用密钥,数据在发送端加密,使用相同或不同的密钥,数据在接收端解密。根据用于加密/解密信息的密钥类型,加密分为两类:</p><ul><li>对称密钥加密</li><li>非对称密钥加密</li></ul><h3 id="2-1-对称加密"><a href="#2-1-对称加密" class="headerlink" title="2.1 对称加密"></a>2.1 对称加密</h3><p>对称密钥加密方法是直截了当的。在将数据发送给接收者之前,发送者使用私钥对数据进行加密。这个私钥只有发送者和接收者知道。因此,一旦接收者获得加密数据,他或她就会使用发送者的相同私钥对其进行解密。由于发送者和接收者使用相同的密钥,这被称为“对称密钥”加密。</p><p><img src="/images/encode_encrypt_hash/Symmetric_Key_Encryption.gif"></p><p>是的,使用这种技术进行加密有很多好处,例如,这个系统的简单性提供了后勤优势,因为它需要更少的计算能力。此外,仅通过增加密钥的长度就可以轻松地帮助提高系统的安全级别。但是我们已经看到了使用这种技术的问题。接收者如何知道最初用来加密数据的密钥是什么?为了让他知道密钥,发送者也必须发送密钥。有问题吗?是的!!发送者也必须将密钥传输给接收者,如果是通过不安全的连接完成的,那么他们的密钥很有可能被任何攻击者截获。</p><h3 id="2-2-非对称密钥"><a href="#2-2-非对称密钥" class="headerlink" title="2.2 非对称密钥"></a>2.2 非对称密钥</h3><p>为了解决使用对称密钥加密技术的问题,非对称密钥加密技术诞生了。与以前的技术相比,这是一种相对较新的技术。正如你可以从名称本身猜到的那样,这种技术涉及两个不同的密钥。一个密钥用于加密数据,称为公钥,互联网上几乎每个人都知道。另一个密钥用于解密数据,称为私钥,只有接收方知道,必须保密。因此,使用两个不同的密钥使系统更加安全,并且攻击者很难破解它。</p><p><img src="/images/encode_encrypt_hash/Asymmetric_Key_Encryption.jpeg"></p><p>公钥和私钥在一起才是锁的真实钥匙。他们就像是数学概念上相互关联的两个非常大的素数。也就是说,任何用公钥加密的东西,都只能用相关的私钥解密。</p><p>一些众所周知的非对称密钥加密算法是:</p><ul><li><em>RSA</em></li><li><em>DSS(数字签名标准)</em></li><li><em>椭圆曲线密码学</em></li></ul><h2 id="3-散列(哈希,摘要)"><a href="#3-散列(哈希,摘要)" class="headerlink" title="3. 散列(哈希,摘要)"></a>3. 散列(哈希,摘要)</h2><p>散列是确保通过网络发送的信息完整性的过程。这意味着它确保即使更改了单个事物,你也可以知道它已更改。散列可以保护你的数据免受潜在的更改,因此你的数据甚至不会被更改一丁点。</p><p>散列基本上是通过散列算法从输入字符串生成的字符串。无论输入字符串的大小如何,此散列字符串始终具有固定长度。散列也可以被认为是“单向加密”,即一旦散列的数据永远不能恢复到原来的形式。</p><blockquote><p>“Behind every successful hash algorithm, there is a great hash function” — Some nerd on the Internet</p></blockquote><p>散列函数是散列算法的核心。该散列函数以固定长度接收数据,因此通过将输入划分为固定大小的块来向函数提供输入。这些块被称为“数据块”。如果输入数据的大小小于块的大小,则使用“填充”。</p><p><img src="/images/encode_encrypt_hash/hash.png"></p><p>散列过程也称为“雪崩效应”。如下:</p><p>消息一次处理一个块。输入提供给第一个块,然后将第一个块的输出与第二个块的输入一起提供给函数。对于第三个块遵循相同的过程,其中第二个块的输出与第三个块的输入一起提供给函数。最终输出是所有块的组合值。</p><p>这样的话,如果在消息之间的任何地方更改或操纵单个比特位,则整个散列值将更改。</p><p>一些流行的散列算法是:</p><ul><li>Message Digest (MD) Algorithm</li><li>Secure Hash Algorithm (SHA)</li><li>RACE Integrity Primitives Evaluation Message Digest (RIPEMD)</li><li>Whirlpool</li><li>Cyclic Redundancy Check (CRC)</li></ul><h2 id="4-总结"><a href="#4-总结" class="headerlink" title="4. 总结"></a>4. 总结</h2><ul><li>编码是将数据从一种格式转换为另一种格式的过程散列是一种通过将数据转换为固定长度的字符串来确保数据完整性的技术</li><li>加密是使用密钥将信息转换为密码以保持机密性的过程</li><li>散列是一种通过将数据转换为固定长度的字符串来确保数据完整性的技术</li></ul>]]></content>
<categories>
<category>文章分类</category>
</categories>
<tags>
<tag>Encrypt</tag>
<tag>Hash</tag>
<tag>Encode</tag>
</tags>
</entry>
<entry>
<title>Django.makemigrations和Alembic.revision的区别</title>
<link href="/2022/03/16/django-makemigrations-vs-alembic-revision/"/>
<url>/2022/03/16/django-makemigrations-vs-alembic-revision/</url>
<content type="html"><![CDATA[<p>ORM 将关系数据库映射到<a href="https://realpython.com/python3-object-oriented-programming/">面向对象编程</a>的世界。但是仅仅在 Python 文件中定义一个模型类并不能使数据库表神奇地出现。</p><p>创建数据库表来存储你的模型是<strong>数据库迁移</strong>的工作。此外,每当对模型进行更改(例如添加字段)时,也必须更改数据库。然而你无需在 SQL 中手动定义数据库表,数据库迁移通常与模型齐头并进。</p><p>这篇文章主要讲的是Django和Alemic对模型更改检测方式的不同,从配置开始讲讲。</p><h2 id="1-配置"><a href="#1-配置" class="headerlink" title="1. 配置"></a>1. 配置</h2><h3 id="1-1-Django配置"><a href="#1-1-Django配置" class="headerlink" title="1.1 Django配置"></a>1.1 Django配置</h3><p>从 1.7 版开始,Django 内置了对数据库迁移的支持。内置就有内置的便利性,我们只需要在<code>INSTALLED_APPS</code>加入我们新创建的APP。</p><h3 id="1-2-Alembic配置"><a href="#1-2-Alembic配置" class="headerlink" title="1.2 Alembic配置"></a>1.2 Alembic配置</h3><p>这里Alembic与SQLAlchemy搭配使用。我们需安装alembic以及初始化文件夹</p><figure class="highlight bash"><table><tr><td class="gutter"><div class="code-wrapper"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></div></td><td class="code"><pre><code class="hljs bash">$ pip install alembic<br>$ alembic init migrations<br></code></pre></td></tr></table></figure><p><strong><del>配置alembic.ini</del></strong> </p><p>这里不用配置<code>sqlalchemy.url</code>了!!,</p><p><code># sqlalchemy.url = postgresql://moweriot:[email protected]/app</code></p><p>因为要根据部署环境动态的变动该属性,可以直接在<code>env.py</code>的</p><p><code>config.set_main_option("sqlalchemy.url", DB_URI)</code>进行<code>DB_URI</code>覆盖</p><p><strong>配置env.py</strong></p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> db <span class="hljs-keyword">import</span> DB_URI<br><span class="hljs-keyword">from</span> models <span class="hljs-keyword">import</span> Base<br><br><span class="hljs-comment"># this is the Alembic Config object, which provides</span><br><span class="hljs-comment"># access to the values within the .ini file in use.</span><br>config = context.config<br><br><span class="hljs-comment"># this will overwrite the ini-file sqlalchemy.url path</span><br><span class="hljs-comment"># with the path given in the config of the main code</span><br>config.set_main_option(<span class="hljs-string">"sqlalchemy.url"</span>, DB_URI)<br><br><span class="hljs-comment"># Interpret the config file for Python logging.</span><br><span class="hljs-comment"># This line sets up loggers basically.</span><br>fileConfig(config.config_file_name)<br><br><span class="hljs-comment"># add your model's MetaData object here</span><br><span class="hljs-comment"># for 'autogenerate' support</span><br><span class="hljs-comment"># from myapp import mymodel</span><br><span class="hljs-comment"># target_metadata = mymodel.Base.metadata</span><br>target_metadata = Base.metadata<br></code></pre></td></tr></table></figure><h2 id="2-生成迁移"><a href="#2-生成迁移" class="headerlink" title="2. 生成迁移"></a>2. 生成迁移</h2><h3 id="2-1-makemigarations"><a href="#2-1-makemigarations" class="headerlink" title="2.1 makemigarations"></a>2.1 makemigarations</h3><p>Django使用<code>python manage.py makemigrations</code>创建迁移。</p><p>工作流程是:</p><blockquote><p>our models will be scanned and compared to the versions currently contained in your migration files, and then a new set of migrations will be written out. Make sure to read the output to see what <code>makemigrations</code> thinks you have changed - it’s not perfect, and for complex changes it might not be detecting what you expect.</p></blockquote><p>这里可以看到,makemigrations是通过扫描比较当前的模型类和当前版本的migrations文件,得出一组新的迁移。这个自动生成过程有缺点:“它并不完美,对于复杂的更改,它可能无法检测到期望的内容”</p><h3 id="2-2-revision"><a href="#2-2-revision" class="headerlink" title="2.2 revision"></a>2.2 revision</h3><p>而对于Alembic我们使用<code>revision</code>命令</p><p><code> alembic revision --autogenerate -m "create all initial tables"</code></p><p>工作流程是:</p><blockquote><p>Alembic can view the status of the database and compare against the table metadata in the application, generating the “obvious” migrations based on a comparison. This is achieved using the <code>--autogenerate</code> option to the <code>alembic revision</code> command, which places so-called <em>candidate</em> migrations into our new migrations file. We review and modify these by hand as needed, then proceed normally.</p></blockquote><p>和Django不同,这里Alembic是通过查看数据库的状态并与应用程序中的表元数据进行比较,根据比较生成“明显”的迁移。这是使用 <code>alembic revision </code>命令的<code>--autogenerate</code>选项来实现的,它将所谓的<strong>候选迁移</strong>放入我们的新迁移文件中。我们会根据需要手动审查和修改这些内容,然后正常进行。</p><p>为了使用<code>autogenerate</code>,我们首先需要修改我们的env.py,使它能够访问包含目标的表元数据对象。这在上面的配置中已经有体现了:</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> myapp.mymodel <span class="hljs-keyword">import</span> Base<br>target_metadata = Base.metadata<br></code></pre></td></tr></table></figure><p>这个自动生成也有缺点:它也是不完美的,始终需要手动查看和更正“自动生成的<strong>候选迁移</strong>”</p><h2 id="3-提交迁移"><a href="#3-提交迁移" class="headerlink" title="3. 提交迁移"></a>3. 提交迁移</h2><h3 id="3-1-migrate"><a href="#3-1-migrate" class="headerlink" title="3.1 migrate"></a>3.1 migrate</h3><p>Django使用<code>python manage.py migrate</code>同步数据库迁移内容。</p><p>当然还有更多功能:因为种种原因想撤掉迁移回退到某个早期的数据库模式,这里具体不展开。</p><h3 id="3-2-upgrade"><a href="#3-2-upgrade" class="headerlink" title="3.2 upgrade"></a>3.2 upgrade</h3><p>Alembic使用<code>alembic upgrade head</code>同步数据库迁移内容。</p><p>当然,它也可以回退撤销到某个历史版本。其实看看Alembic的命令,倒是有几分Git的风味。</p><blockquote><p>在git中可以将amelbic相关文件提交,这样在部署的时候直接拉下来,<code>alembic upgrade head</code>至最新版本即可,无需初始化以及生成相关语句</p></blockquote><h3 id="3-3-便捷性比较"><a href="#3-3-便捷性比较" class="headerlink" title="3.3 便捷性比较"></a>3.3 便捷性比较</h3><p>Django因为是自带的数据库迁移,那么肯定更方便一点,比如,当提交迁移时,数据库本来存在的一些字段数据和现在的迁移有冲突,那么会提醒你解决冲突。</p><p>但是Alembic需要手动来更改字段内容:</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">upgrade</span>():<br> <span class="hljs-comment"># ### commands auto generated by Alembic - please adjust! ###</span><br> op.add_column(<span class="hljs-string">'cut_data'</span>, sa.Column(<span class="hljs-string">'is_complete'</span>, sa.Boolean(), nullable=<span class="hljs-literal">True</span>))<br> op.execute(<span class="hljs-string">"UPDATE cut_data SET is_complete = true"</span>)<br></code></pre></td></tr></table></figure><p>还是那一点,Alembic始终需要手动查看和更正“自动生成的<strong>候选迁移</strong>”,而Django更偏于自动。</p><p>但往往在手动过程中更让初学者懂得其中具体包含的操作含义。</p><h2 id="相关资源"><a href="#相关资源" class="headerlink" title="相关资源"></a>相关资源</h2><ul><li><a href="https://docs.djangoproject.com/en/4.0/topics/migrations/">https://docs.djangoproject.com/en/4.0/topics/migrations/</a></li><li><a href="https://realpython.com/django-migrations-a-primer/">https://realpython.com/django-migrations-a-primer/</a></li><li><a href="https://alembic.sqlalchemy.org/en/latest/autogenerate.html">https://alembic.sqlalchemy.org/en/latest/autogenerate.html</a></li></ul>]]></content>
<categories>
<category>文章分类</category>
</categories>
<tags>
<tag>Django</tag>
<tag>Python</tag>
<tag>Alembic</tag>
</tags>
</entry>
<entry>
<title>FastAPI多方式部署以及压测过程记录</title>
<link href="/2021/06/12/fastapi-deploy/"/>
<url>/2021/06/12/fastapi-deploy/</url>
<content type="html"><![CDATA[<p>上一篇文翻译了部署的相关概念后,对自己真正实践部署FastAPI有了更多的信心。特别是理解其中的细节以及每个服务所起的的作用(What, Why, How)。</p><p>这边就记录下Web服务部署阿里云时候的一些过程。</p><h2 id="1-Gunicorn与Uvicorn"><a href="#1-Gunicorn与Uvicorn" class="headerlink" title="1. Gunicorn与Uvicorn"></a>1. Gunicorn与Uvicorn</h2><p>虽然我们可以直接Uvicorn裸跑,而且这不仅在调试时候,生产非高并发也足够了。</p><p>因为Uvicorn也可以选择worker参数,轻量级方式,只是不提供任何进程监控。处理工作进程的能力比Gunicorn更有限。</p><p>如果想在这个级别(Python级别)有一个<strong>进程管理器</strong>,那么尝试用Gunicorn作为进程管理器可能更好一些。</p><p>在生产环境中,Gunicorn 可能是运行和管理 Uvicorn 最简单的方式。Uvicorn 包括一个Gunicorn工人类,这意味着尽可能少的配置。</p><h3 id="1-1-使用"><a href="#1-1-使用" class="headerlink" title="1.1 使用"></a>1.1 使用</h3><p>你可以使用 Gunicorn 管理 Uvicorn 并运行多个这些并发进程。这样,就可以最大限度地利用并发和并行性</p><p>命令如下:</p><p><code>gunicorn main:app --workers 17 --worker-class uvicorn.workers.UvicornWorker --bind 0.0.0.0:8989</code></p><p>我们参照 Gunicorn 的官方文档,把worker数量设置为 <code>2 * CPU 核心数 + 1</code>,我的机器是8核,所以此处为17。</p><h3 id="1-2-日志处理"><a href="#1-2-日志处理" class="headerlink" title="1.2 日志处理"></a>1.2 日志处理</h3><p>如果是使用Gunicorn运行,我们需要将FastAPI日志和Uvicorn日志<strong>整合</strong>到Gunicorn日志并输出。</p><figure class="highlight python"><table><tr><td class="gutter"><div class="code-wrapper"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></div></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">"__main__"</span>:<br> <span class="hljs-comment"># 修改uvicorn访问日志时间格式,加上时间戳,方便查看</span><br> log_config = uvicorn.config.LOGGING_CONFIG<br> log_config[<span class="hljs-string">"formatters"</span>][<span class="hljs-string">"access"</span>][<span class="hljs-string">"fmt"</span>] = <span class="hljs-string">"%(asctime)s - %(levelname)s - %(message)s"</span><br> <span class="hljs-comment"># fastapi本身日志,无handler,加一个</span><br> ch = logging.StreamHandler(stream=<span class="hljs-literal">None</span>)<br> fastapi_logger.addHandler(ch)<br> fastapi_logger.setLevel(logging.DEBUG)<br> uvicorn.run(<span class="hljs-string">"__main__:app"</span>, host=<span class="hljs-string">"0.0.0.0"</span>, port=<span class="hljs-number">8989</span>, reload=<span class="hljs-literal">True</span>, workers=<span class="hljs-number">1</span>)<br><span class="hljs-keyword">else</span>:<br> <span class="hljs-comment"># 获取gunicorn日志</span><br> gunicorn_error_logger = logging.getLogger(<span class="hljs-string">"gunicorn.error"</span>) <span class="hljs-comment"># 默认是info</span><br> <span class="hljs-comment"># uvicorn日志, 可以考虑注释,提高性能</span><br> uvicorn_access_logger = logging.getLogger(<span class="hljs-string">"uvicorn.access"</span>)<br> uvicorn_access_logger.handlers = gunicorn_error_logger.handlers<br> uvicorn_access_logger.setLevel(gunicorn_error_logger.level)<br> <span class="hljs-comment"># fastapi本身日志</span><br> fastapi_logger.handlers = gunicorn_error_logger.handlers<br> fastapi_logger.setLevel(gunicorn_error_logger.level)<br></code></pre></td></tr></table></figure><blockquote><p>Tips:Python中logging模块StreamHandle默认是标准stderr输出!</p></blockquote><p>终端上就会显示FastAPI,Uvicorn以及Gunicorn这三者的日志,后续无论使用Docker还是Supervisor部署,日志都会统一,最多再做个重定向输出,一劳永逸。</p><h2 id="2-容器部署"><a href="#2-容器部署" class="headerlink" title="2. 容器部署"></a>2. 容器部署</h2><p>部署 FastAPI 应用程序时,一种常见的方法是构建Linux 容器映像。通常使用Docker完成。然后可以通过几种可能的方式之一部署该容器映像。</p><p>使用 Linux 容器有几个优点,包括安全性、可复制性、简单性等。</p><h3 id="2-1-官方-Docker-镜像"><a href="#2-1-官方-Docker-镜像" class="headerlink" title="2.1 官方 Docker 镜像"></a>2.1 官方 Docker 镜像</h3><p>有一个<a href="https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker">官方的 Docker 镜像</a>,包含了Uvicorn和Gunicorn。</p><p>包含一个<strong>自动调整</strong>机制,用于根据可用的 CPU 内核设置<strong>工作进程的数量。</strong></p><p>它具有<strong>合理的默认值</strong>,但您仍然可以使用<strong>环境变量</strong>或配置文件更改和更新所有配置。</p><p>它还支持在容器启动之前运行<code>prestart.sh</code>(下面的启动脚本中有相关代码),做一些前期操作,例如数据库迁移。</p><h3 id="2-2-如何使用"><a href="#2-2-如何使用" class="headerlink" title="2.2 如何使用"></a>2.2 如何使用</h3><p>项目里有一个文件 requirements.txt,那么Dockerfile就类似于:</p><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs dockerfile"><span class="hljs-keyword">FROM</span> tiangolo/uvicorn-gunicorn-fastapi:python3.<span class="hljs-number">9</span><br><br><span class="hljs-keyword">COPY</span><span class="language-bash"> ./requirements.txt /app/requirements.txt</span><br><br><span class="hljs-keyword">RUN</span><span class="language-bash"> pip install --no-cache-dir --upgrade -r /app/requirements.txt</span><br><br><span class="hljs-keyword">COPY</span><span class="language-bash"> ./app /app</span><br></code></pre></td></tr></table></figure><p>入口文件在<code>/app/app/main.py</code> 或者在<code>/app/main.py</code>中</p><p>然后就可以制作镜像了<code>docker build -t myimage ./</code></p><p>如果涉及到PgSQL或者Redis之类的,那么Docker-Compose也完全可以来整一整,不展开。</p><h3 id="2-3-容器配置源代码"><a href="#2-3-容器配置源代码" class="headerlink" title="2.3 容器配置源代码"></a>2.3 容器配置源代码</h3><p>Docker部署非常简单,但也非常弹性, 可以通过不同环境变量控制一些设置。在这<a href="https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker">官方Docker 镜像</a>文档中我们就可以看到。具体参数不谈,这边就记录下相关内部代码。</p><h4 id="2-3-1-容器启动时的脚本"><a href="#2-3-1-容器启动时的脚本" class="headerlink" title="2.3.1 容器启动时的脚本"></a>2.3.1 容器启动时的脚本</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-comment">#! start.sh</span><br><span class="hljs-comment">#! /usr/bin/env sh</span><br><span class="hljs-built_in">set</span> -e<br><br><span class="hljs-keyword">if</span> [ -f /app/app/main.py ]; <span class="hljs-keyword">then</span><br> DEFAULT_MODULE_NAME=app.main<br><span class="hljs-keyword">elif</span> [ -f /app/main.py ]; <span class="hljs-keyword">then</span><br> DEFAULT_MODULE_NAME=main<br><span class="hljs-keyword">fi</span><br>MODULE_NAME=<span class="hljs-variable">${MODULE_NAME:-<span class="hljs-variable">$DEFAULT_MODULE_NAME</span>}</span><br>VARIABLE_NAME=<span class="hljs-variable">${VARIABLE_NAME:-app}</span><br><span class="hljs-built_in">export</span> APP_MODULE=<span class="hljs-variable">${APP_MODULE:-"$MODULE_NAME:$VARIABLE_NAME"}</span><br><br><span class="hljs-keyword">if</span> [ -f /app/gunicorn_conf.py ]; <span class="hljs-keyword">then</span><br> DEFAULT_GUNICORN_CONF=/app/gunicorn_conf.py<br><span class="hljs-keyword">elif</span> [ -f /app/app/gunicorn_conf.py ]; <span class="hljs-keyword">then</span><br> DEFAULT_GUNICORN_CONF=/app/app/gunicorn_conf.py<br><span class="hljs-keyword">else</span><br> DEFAULT_GUNICORN_CONF=/gunicorn_conf.py<br><span class="hljs-keyword">fi</span><br><span class="hljs-built_in">export</span> GUNICORN_CONF=<span class="hljs-variable">${GUNICORN_CONF:-<span class="hljs-variable">$DEFAULT_GUNICORN_CONF</span>}</span><br><span class="hljs-built_in">export</span> WORKER_CLASS=<span class="hljs-variable">${WORKER_CLASS:-"uvicorn.workers.UvicornWorker"}</span><br><br><span class="hljs-comment"># If there's a prestart.sh script in the /app directory or other path specified, run it before starting</span><br>PRE_START_PATH=<span class="hljs-variable">${PRE_START_PATH:-/app/prestart.sh}</span><br><span class="hljs-built_in">echo</span> <span class="hljs-string">"Checking for script in <span class="hljs-variable">$PRE_START_PATH</span>"</span><br><span class="hljs-keyword">if</span> [ -f <span class="hljs-variable">$PRE_START_PATH</span> ] ; <span class="hljs-keyword">then</span><br> <span class="hljs-built_in">echo</span> <span class="hljs-string">"Running script <span class="hljs-variable">$PRE_START_PATH</span>"</span><br> . <span class="hljs-string">"<span class="hljs-variable">$PRE_START_PATH</span>"</span><br><span class="hljs-keyword">else</span> <br> <span class="hljs-built_in">echo</span> <span class="hljs-string">"There is no script <span class="hljs-variable">$PRE_START_PATH</span>"</span><br><span class="hljs-keyword">fi</span><br><br><span class="hljs-comment"># Start Gunicorn</span><br><span class="hljs-built_in">exec</span> gunicorn -k <span class="hljs-string">"<span class="hljs-variable">$WORKER_CLASS</span>"</span> -c <span class="hljs-string">"<span class="hljs-variable">$GUNICORN_CONF</span>"</span> <span class="hljs-string">"<span class="hljs-variable">$APP_MODULE</span>"</span><br></code></pre></td></tr></table></figure><h4 id="2-3-2-Gunicorn配置脚本"><a href="#2-3-2-Gunicorn配置脚本" class="headerlink" title="2.3.2 Gunicorn配置脚本"></a>2.3.2 Gunicorn配置脚本</h4><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">import</span> json<br><span class="hljs-keyword">import</span> multiprocessing<br><span class="hljs-keyword">import</span> os<br><br>workers_per_core_str = os.getenv(<span class="hljs-string">"WORKERS_PER_CORE"</span>, <span class="hljs-string">"1"</span>)<br>max_workers_str = os.getenv(<span class="hljs-string">"MAX_WORKERS"</span>)<br>use_max_workers = <span class="hljs-literal">None</span><br><span class="hljs-keyword">if</span> max_workers_str:<br> use_max_workers = <span class="hljs-built_in">int</span>(max_workers_str)<br>web_concurrency_str = os.getenv(<span class="hljs-string">"WEB_CONCURRENCY"</span>, <span class="hljs-literal">None</span>)<br><br>host = os.getenv(<span class="hljs-string">"HOST"</span>, <span class="hljs-string">"0.0.0.0"</span>)<br>port = os.getenv(<span class="hljs-string">"PORT"</span>, <span class="hljs-string">"80"</span>)<br>bind_env = os.getenv(<span class="hljs-string">"BIND"</span>, <span class="hljs-literal">None</span>)<br>use_loglevel = os.getenv(<span class="hljs-string">"LOG_LEVEL"</span>, <span class="hljs-string">"info"</span>)<br><span class="hljs-keyword">if</span> bind_env:<br> use_bind = bind_env<br><span class="hljs-keyword">else</span>:<br> use_bind = <span class="hljs-string">f"<span class="hljs-subst">{host}</span>:<span class="hljs-subst">{port}</span>"</span><br><br>cores = multiprocessing.cpu_count()<br>workers_per_core = <span class="hljs-built_in">float</span>(workers_per_core_str)<br>default_web_concurrency = workers_per_core * cores<br><span class="hljs-keyword">if</span> web_concurrency_str:<br> web_concurrency = <span class="hljs-built_in">int</span>(web_concurrency_str)<br> <span class="hljs-keyword">assert</span> web_concurrency > <span class="hljs-number">0</span><br><span class="hljs-keyword">else</span>:<br> web_concurrency = <span class="hljs-built_in">max</span>(<span class="hljs-built_in">int</span>(default_web_concurrency), <span class="hljs-number">2</span>)<br> <span class="hljs-keyword">if</span> use_max_workers:<br> web_concurrency = <span class="hljs-built_in">min</span>(web_concurrency, use_max_workers)<br>accesslog_var = os.getenv(<span class="hljs-string">"ACCESS_LOG"</span>, <span class="hljs-string">"-"</span>)<br>use_accesslog = accesslog_var <span class="hljs-keyword">or</span> <span class="hljs-literal">None</span><br>errorlog_var = os.getenv(<span class="hljs-string">"ERROR_LOG"</span>, <span class="hljs-string">"-"</span>)<br>use_errorlog = errorlog_var <span class="hljs-keyword">or</span> <span class="hljs-literal">None</span><br>graceful_timeout_str = os.getenv(<span class="hljs-string">"GRACEFUL_TIMEOUT"</span>, <span class="hljs-string">"120"</span>)<br>timeout_str = os.getenv(<span class="hljs-string">"TIMEOUT"</span>, <span class="hljs-string">"120"</span>)<br>keepalive_str = os.getenv(<span class="hljs-string">"KEEP_ALIVE"</span>, <span class="hljs-string">"5"</span>)<br><br><span class="hljs-comment"># Gunicorn config variables</span><br>loglevel = use_loglevel<br>workers = web_concurrency<br>bind = use_bind<br>errorlog = use_errorlog<br>worker_tmp_dir = <span class="hljs-string">"/dev/shm"</span><br>accesslog = use_accesslog<br>graceful_timeout = <span class="hljs-built_in">int</span>(graceful_timeout_str)<br>timeout = <span class="hljs-built_in">int</span>(timeout_str)<br>keepalive = <span class="hljs-built_in">int</span>(keepalive_str)<br><br><br><span class="hljs-comment"># For debugging and testing</span><br>log_data = {<br> <span class="hljs-string">"loglevel"</span>: loglevel,<br> <span class="hljs-string">"workers"</span>: workers,<br> <span class="hljs-string">"bind"</span>: bind,<br> <span class="hljs-string">"graceful_timeout"</span>: graceful_timeout,<br> <span class="hljs-string">"timeout"</span>: timeout,<br> <span class="hljs-string">"keepalive"</span>: keepalive,<br> <span class="hljs-string">"errorlog"</span>: errorlog,<br> <span class="hljs-string">"accesslog"</span>: accesslog,<br> <span class="hljs-comment"># Additional, non-gunicorn variables</span><br> <span class="hljs-string">"workers_per_core"</span>: workers_per_core,<br> <span class="hljs-string">"use_max_workers"</span>: use_max_workers,<br> <span class="hljs-string">"host"</span>: host,<br> <span class="hljs-string">"port"</span>: port,<br>}<br><span class="hljs-built_in">print</span>(json.dumps(log_data))<br></code></pre></td></tr></table></figure><h2 id="3-压力测试"><a href="#3-压力测试" class="headerlink" title="3. 压力测试"></a>3. 压力测试</h2><p>ab就是Apache Benchmark的缩写,顾名思义它是Apache组织开发的一款web压力测试工具,优点是使用方便,统计功能强大。</p><p>首先是安装,Ubuntu和CentOS目前都提供自动安装命令 (至少Ubuntu 14, CentOS 6可以)</p><ul><li><p>Ubuntu: <code>sudo apt-get install apache2-utils </code></p></li><li><p>CentOS:<code>yum install httpd-tools</code></p></li></ul><h3 id="3-1-使用"><a href="#3-1-使用" class="headerlink" title="3.1 使用"></a>3.1 使用</h3><p> ab 一般<strong>常用参数</strong>就是 -n, -t ,和 -c。</p><p> -c (concurrency)表示用多少并发来进行测试;</p><p> -t 表示测试持续多长时间;</p><p>-n 表示要发送多少次测试请求。</p><p>一般 -t 或者 -n 选一个用。</p><p>例如模拟GET请求进行测试:</p><p><code> ab -n 20000 -c 1000 http://0.0.0.0:8989/firmware/latest?model=xxx</code></p><h3 id="3-2-结果分析"><a href="#3-2-结果分析" class="headerlink" title="3.2 结果分析"></a>3.2 结果分析</h3><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-attr">Server Software:</span> <span class="hljs-string">uvicorn</span><br><span class="hljs-attr">Server Hostname:</span> <span class="hljs-number">192.168</span><span class="hljs-number">.52</span><span class="hljs-number">.224</span><br><span class="hljs-attr">Server Port:</span> <span class="hljs-number">8989</span><br><br><span class="hljs-attr">Document Path:</span> <span class="hljs-string">/firmware/latest?model=RLM2003EI</span><br><span class="hljs-attr">Document Length:</span> <span class="hljs-number">204</span> <span class="hljs-string">bytes</span><br><br><span class="hljs-attr">Concurrency Level:</span> <span class="hljs-number">1000</span><br><span class="hljs-attr">Time taken for tests:</span> <span class="hljs-number">3.843</span> <span class="hljs-string">seconds</span><br><span class="hljs-attr">Complete requests:</span> <span class="hljs-number">20000</span><br><span class="hljs-attr">Failed requests:</span> <span class="hljs-number">0</span><br><span class="hljs-attr">Total transferred:</span> <span class="hljs-number">6600000</span> <span class="hljs-string">bytes</span><br><span class="hljs-attr">HTML transferred:</span> <span class="hljs-number">4080000</span> <span class="hljs-string">bytes</span><br><span class="hljs-attr">Requests per second:</span> <span class="hljs-number">5204.07</span> [<span class="hljs-comment">#/sec] (mean)</span><br><span class="hljs-attr">Time per request:</span> <span class="hljs-number">192.157</span> [<span class="hljs-string">ms</span>] <span class="hljs-string">(mean)</span><br><span class="hljs-attr">Time per request:</span> <span class="hljs-number">0.192</span> [<span class="hljs-string">ms</span>] <span class="hljs-string">(mean</span>, <span class="hljs-string">across</span> <span class="hljs-string">all</span> <span class="hljs-string">concurrent</span> <span class="hljs-string">requests)</span><br><span class="hljs-attr">Transfer rate:</span> <span class="hljs-number">1677.09</span> [<span class="hljs-string">Kbytes/sec</span>] <span class="hljs-string">received</span><br><br><span class="hljs-string">Connection</span> <span class="hljs-string">Times</span> <span class="hljs-string">(ms)</span><br> <span class="hljs-string">min</span> <span class="hljs-string">mean</span>[<span class="hljs-string">+/-sd</span>] <span class="hljs-string">median</span> <span class="hljs-string">max</span><br><span class="hljs-attr">Connect:</span> <span class="hljs-number">0</span> <span class="hljs-number">2</span> <span class="hljs-number">4.5</span> <span class="hljs-number">0</span> <span class="hljs-number">31</span><br><span class="hljs-attr">Processing:</span> <span class="hljs-number">1</span> <span class="hljs-number">186</span> <span class="hljs-number">285.7</span> <span class="hljs-number">77</span> <span class="hljs-number">3721</span><br><span class="hljs-attr">Waiting:</span> <span class="hljs-number">1</span> <span class="hljs-number">183</span> <span class="hljs-number">285.6</span> <span class="hljs-number">74</span> <span class="hljs-number">3721</span><br><span class="hljs-attr">Total:</span> <span class="hljs-number">1</span> <span class="hljs-number">187</span> <span class="hljs-number">286.8</span> <span class="hljs-number">78</span> <span class="hljs-number">3730</span><br><br><span class="hljs-string">Percentage</span> <span class="hljs-string">of</span> <span class="hljs-string">the</span> <span class="hljs-string">requests</span> <span class="hljs-string">served</span> <span class="hljs-string">within</span> <span class="hljs-string">a</span> <span class="hljs-string">certain</span> <span class="hljs-string">time</span> <span class="hljs-string">(ms)</span><br> <span class="hljs-number">50</span><span class="hljs-string">%</span> <span class="hljs-number">78</span><br> <span class="hljs-number">66</span><span class="hljs-string">%</span> <span class="hljs-number">138</span><br> <span class="hljs-number">75</span><span class="hljs-string">%</span> <span class="hljs-number">193</span><br> <span class="hljs-number">80</span><span class="hljs-string">%</span> <span class="hljs-number">245</span><br> <span class="hljs-number">90</span><span class="hljs-string">%</span> <span class="hljs-number">505</span><br> <span class="hljs-number">95</span><span class="hljs-string">%</span> <span class="hljs-number">783</span><br> <span class="hljs-number">98</span><span class="hljs-string">%</span> <span class="hljs-number">1092</span><br> <span class="hljs-number">99</span><span class="hljs-string">%</span> <span class="hljs-number">1470</span><br> <span class="hljs-number">100</span><span class="hljs-string">%</span> <span class="hljs-number">3730</span> <span class="hljs-string">(longest</span> <span class="hljs-string">request)</span><br></code></pre></td></tr></table></figure><p>我们对产生的结果进行分析,具体不展开,详细看<a href="https://www.cnblogs.com/gumuzi/p/5617232.html">Apache ab性能测试结果分析</a></p><h3 id="3-3-socket-Too-many-open-files"><a href="#3-3-socket-Too-many-open-files" class="headerlink" title="3.3 socket: Too many open files"></a>3.3 socket: Too many open files</h3><p><code>ulimit -n</code> 查看<strong>最大文件打开数</strong>,一般情况下都是1024</p><p>我们可以<code>ulimit -n 65535</code>,临时设置下最大文件数,重启终端无效。</p><p>永久设置方法:<code>vim /etc/security/limits.conf </code> 加入 <code>* - nofile 8192</code></p>]]></content>
<categories>
<category>文章分类</category>
</categories>
<tags>
<tag>Python</tag>
<tag>Gunicorn</tag>
<tag>Fastapi</tag>
</tags>
</entry>
<entry>
<title>部署概念</title>
<link href="/2021/05/20/deployments-concepts/"/>
<url>/2021/05/20/deployments-concepts/</url>
<content type="html"><![CDATA[<blockquote><p>之前部署Web应用程序时总是只知道一些工具,比如Docker,Supervisor,Nginx之类的,按照网上教程拼拼凑凑部署完成,对其中完整的概念知之甚少,没做到“知其然知其所以然”,在FastAPI官网看到了一些关于部署概念的文字,太棒了!所以翻译把它搬运过来。</p></blockquote><p>在部署FastAPI应用程序时,或者实际上,任何类型的Web API,有几个概念是你可能关心的,利用它们可以找到最合适的方法来部署你的应用程序。</p><p>一些重要的概念:</p><ul><li>Security - HTTPS </li><li>Running on startup </li><li>Restarts </li><li>Replication (the number of processes running) </li><li>Memory </li><li>Previous Steps Before Starting</li></ul><p>我们将看到它们会如何影响部署。</p><p>当然,最终的目标是能够以一种安全的方式为你的API客户提供服务,避免中断,并尽可能高效地使用计算资源(例如远程服务器/虚拟机)🚀</p><p>我将在这里告诉你更多关于这些概念的信息,希望这将给你一个直觉,让你决定如何在非常不同的环境中部署API,甚至可能在未来还不存在的环境中部署。</p><p>通过考虑这些概念,你将能够评估和设计部署你自己的API的最佳方式。</p><p>在接下来的章节中,我将给你更多部署FastAPI应用程序的具体方法。</p><p>但现在,让我们检查一下这些重要的概念性想法。这些概念也适用于任何其他类型的Web API。💡</p><h2 id="1-安全-HTTPS"><a href="#1-安全-HTTPS" class="headerlink" title="1. 安全 - HTTPS"></a>1. 安全 - HTTPS</h2><p>我们了解到HTTPS如何为你的API提供加密。</p><p>我们还看到,HTTPS通常是由你的应用服务器外部的一个组件提供的,即TLS终端代理。</p><p>而且必须有一个负责更新HTTPS证书的东西,它可能是同一个组件,也可能是不同的东西。</p><h3 id="1-1-用于HTTPS的工具实例"><a href="#1-1-用于HTTPS的工具实例" class="headerlink" title="1.1 用于HTTPS的工具实例"></a>1.1 用于HTTPS的工具实例</h3><p>你可以用作TLS终端代理的一些工具包括:</p><ul><li><p>Traefik</p><p>自动处理证书续订 ✨</p></li><li><p>Caddy</p><p>自动处理证书续订 ✨</p></li><li><p>Nginx</p><p>使用像Certbot这样的外部组件进行证书续订</p></li><li><p>HAProxy</p><p>使用像Certbot这样的外部组件进行证书续订。</p></li><li><p>Kubernetes with an Ingress Controller like Nginx</p><p>具有用于证书续订的外部组件,如证书管理器。</p></li><li><p>Handled internally by a cloud provider as part of their services (read below👇)</p></li></ul><p>另一种选择是,你可以使用云服务来完成更多的工作,包括设置HTTPS。它可能会有一些限制或向你收取更高的费用,等等。但在这种情况下,你不必自己设置TLS终止代理。</p><p>在接下来的章节中,我将向你展示一些具体的例子。</p><p>接下来要考虑的概念都是关于运行实际API的程序(例如Uvicorn)。</p><h2 id="2-程序和进程"><a href="#2-程序和进程" class="headerlink" title="2. 程序和进程"></a>2. 程序和进程</h2><p>我们将经常谈论运行中的 “进程”,因此,明确它的含义,以及与 “程序 “一词的区别是很有帮助的。</p><h3 id="2-1-什么是程序?"><a href="#2-1-什么是程序?" class="headerlink" title="2.1 什么是程序?"></a>2.1 什么是程序?</h3><p><strong>程序</strong>这个词通常被用来描述很多东西。</p><ul><li>你写的代码,Python文件。</li><li>可以被操作系统执行的文件,例如:python、python.exe或uvicorn。</li><li>一个特定的程序,当它在操作系统上运行时,使用CPU,并在内存上存储东西。这也被称为一个进程</li></ul><h3 id="2-2-什么是进程"><a href="#2-2-什么是进程" class="headerlink" title="2.2 什么是进程"></a>2.2 什么是进程</h3><p><strong>进程</strong>这个词通常用得比较具体,它具体指的是操作系统正在执行和管理的东西(如上面最后一点)。</p><ul><li><p><strong>在操作系统上运行的特定程序</strong></p><p>这不是指文件,也不是指代码,而是特指正在被操作系统执行和管理的东西。</p></li><li><p>任何程序,任何代码,只有在它被执行时才能做事情。因此,当有一个进程在运行时。</p><p>这个进程可以被你终止(或 “杀死”),也可以被操作系统终止。一旦停止运行/被执行,进程就不能再做事情了。</p></li><li><p>你在电脑上运行的每一个应用程序背后都有一些进程,每个正在运行的程序,每个窗口,等等。而且,当一台电脑开机时,通常有许多进程在同时运行。</p></li><li><p>同一程序可能有多个进程在同时运行。</p></li></ul><p>如果你查看操作系统中的 “任务管理器 “或 “系统监视器”(或类似工具),你将能够看到许多正在运行的进程。</p><p>而且你可能会看到有多个进程在运行同一个浏览器程序(Firefox、Chrome、Edge等)。它们通常在每个标签上运行一个进程,再加上一些其他的额外进程。</p><p>现在,我们了解了术语进程和程序之间的区别,让我们继续讨论部署。</p><h2 id="3-在启动时运行"><a href="#3-在启动时运行" class="headerlink" title="3. 在启动时运行"></a>3. 在启动时运行</h2><p>在大多数情况下,当你创建Web API时,你希望它<strong>始终运行、不被中断</strong>,这样你的客户就可以一直访问它。当然,除非你有特定的理由,希望它只在某些情况下运行,但大多数情况下,你希望它能一直运行,并且可用。</p><h3 id="3-1-在一个远程服务器中"><a href="#3-1-在一个远程服务器中" class="headerlink" title="3.1 在一个远程服务器中"></a>3.1 在一个远程服务器中</h3><p>当使用一个远程服务器(云服务器,虚拟机等)时,你可以做的最简单的事情是手动运行Uvicorn(或类似的),就像在本地开发时一样。</p><p>但是,如果你与服务器的连接丢失,运行中的进程可能会死亡。</p><p>如果服务器被重新启动(例如在更新或从云提供商迁移之后),你可能不会注意到它。正因为如此,你甚至不知道你必须手动重启这个进程。所以,你的API就会一直死机。😱</p><h3 id="3-2-启动时自动运行"><a href="#3-2-启动时自动运行" class="headerlink" title="3.2 启动时自动运行"></a>3.2 启动时自动运行</h3><p>一般来说,你可能希望服务器程序(如Uvicorn)在服务器启动时自动启动,并且不需要任何人工干预,让一个进程始终与你的API一起运行(如Uvicorn运行你的FastAPI应用程序)。</p><h3 id="3-3-独立的程序"><a href="#3-3-独立的程序" class="headerlink" title="3.3 独立的程序"></a>3.3 独立的程序</h3><p>为了实现这一点,你通常会有一个单独的程序,以确保你的应用程序在启动时运行。在许多情况下,它还会确保其他组件或应用程序也被运行,例如,数据库。</p><h3 id="3-4-一些工具示例"><a href="#3-4-一些工具示例" class="headerlink" title="3.4 一些工具示例"></a>3.4 一些工具示例</h3><ul><li>Docker</li><li>Kubernetes</li><li>Docker Compose</li><li>Docker in Swarm Mode</li><li>Systemd</li><li>Supervisor</li><li>Handled internally by a cloud provider as part of their services</li><li>Others…</li></ul><p>我将在接下来的章节中给你更具体的例子。</p><h2 id="4-重启"><a href="#4-重启" class="headerlink" title="4. 重启"></a>4. 重启</h2><p>与确保你的应用程序在启动时运行类似,你可能也想确保它在失败后被重新启动。</p><h3 id="4-1-我们会犯错"><a href="#4-1-我们会犯错" class="headerlink" title="4.1 我们会犯错"></a>4.1 我们会犯错</h3><p>作为人类,我们一直在犯错。软件几乎总是有隐藏在不同地方的错误。🐛</p><p>而我们作为开发者,在发现这些错误和实现新的功能时,不断改进代码(也可能增加新的错误😅)。</p><h3 id="4-2-自动处理的小错误"><a href="#4-2-自动处理的小错误" class="headerlink" title="4.2 自动处理的小错误"></a>4.2 自动处理的小错误</h3><p>当使用FastAPI构建Web API时,如果我们的代码中有错误,FastAPI通常会将其包含到触发错误的单个请求中。🛡。</p><p>客户端将收到该请求的<500>内部服务器错误,但应用程序将继续为下一个请求工作,而不是完全崩溃。</p><h3 id="4-3-更大的错误-崩溃"><a href="#4-3-更大的错误-崩溃" class="headerlink" title="4.3 更大的错误 - 崩溃"></a>4.3 更大的错误 - 崩溃</h3><p>尽管如此,我们可能会在某些情况下编写一些代码,使整个应用程序崩溃,从而导致Uvicorn和Python崩溃。💥</p><p>然而,你肯定不希望应用程序因为某个地方的错误而一直死机,而是希望它至少在其他没有中断的路由操作中继续运行。</p><h3 id="4-4-崩溃后重新启动"><a href="#4-4-崩溃后重新启动" class="headerlink" title="4.4 崩溃后重新启动"></a>4.4 崩溃后重新启动</h3><p>但是,在那些有非常严重的错误导致运行进程崩溃的情况下,你会希望有一个外部组件负责重启进程,至少重启几次……</p><blockquote><p>……尽管如果整个应用程序立即崩溃,那么永远重启它可能是没有意义的。但在这些情况下,你可能会在开发过程中注意到它,或者至少在部署后马上注意到。</p><p>所以,让我们把注意力集中在主要的情况上,在未来的一些特定情况下,它可能完全崩溃,而重启它仍然是有意义的。</p></blockquote><p>你可能想让负责重启你的应用程序的东西作为一个外部组件,因为到那时,带有Uvicorn和Python的同一个应用程序已经崩溃了,它们除了趴下没有什么可以做的了。</p><h3 id="4-5-一些工具示例"><a href="#4-5-一些工具示例" class="headerlink" title="4.5 一些工具示例"></a>4.5 一些工具示例</h3><p>在大多数情况下,用于启动时运行程序的工具也被用来处理自动重新启动。 例如,这可以通过以下方式处理:</p><ul><li>Docker</li><li>Kubernetes</li><li>Docker Compose</li><li>Docker in Swarm Mode</li><li>Systemd</li><li>Supervisor</li><li>Handled internally by a cloud provider as part of their services</li><li>Others…</li></ul><h2 id="5-复制-进程和内存"><a href="#5-复制-进程和内存" class="headerlink" title="5. 复制 - 进程和内存"></a>5. 复制 - 进程和内存</h2><p>对于一个FastAPI应用程序,使用像Uvicorn这样的服务器程序,在一个进程中运行一次就可以同时为多个客户端提供服务。</p><p>但在很多情况下,你会希望同时运行几个工作进程。</p><h3 id="5-1-多个进程–Workers"><a href="#5-1-多个进程–Workers" class="headerlink" title="5.1 多个进程–Workers"></a>5.1 多个进程–Workers</h3><p>如果你的客户端多于单个进程所能处理的数量(例如,如果虚拟机不太大),并且服务器的CPU中有多个核心,那么你可以让多个进程同时运行相同的应用程序,并在它们之间分配所有请求。</p><p>当你运行同一个API程序的多个进程时,它们通常被称为Workers。</p><h3 id="5-2-工作进程和端口"><a href="#5-2-工作进程和端口" class="headerlink" title="5.2 工作进程和端口"></a>5.2 工作进程和端口</h3><p>还记得在关于HTTPS的文档中,一个服务器中只有一个进程可以在一个端口和IP地址的组合上进行监听吗?</p><p>这仍然是事实。</p><p>因此,为了能够在同一时间有多个进程,必须有一个单一的进程在一个端口上监听,然后以某种方式将通信传输给其他每个工作进程。</p><h3 id="5-3-每个进程的内存"><a href="#5-3-每个进程的内存" class="headerlink" title="5.3 每个进程的内存"></a>5.3 每个进程的内存</h3><p>现在,当程序在内存中加载东西时,例如,在一个变量中加载机器学习模型,或者在一个变量中加载一个大文件的内容,所有这些都会消耗服务器的一点内存(RAM)。</p><p>而多个进程通常不共享任何内存。这意味着,每个运行中的进程都有自己的东西、变量和内存。而如果你的代码中消耗了大量的内存,每个进程都会消耗等量的内存。</p><h3 id="5-4-服务器内存"><a href="#5-4-服务器内存" class="headerlink" title="5.4 服务器内存"></a>5.4 服务器内存</h3><p>例如,如果你的代码加载了一个大小为1GB的机器学习模型,当你用你的API运行一个进程时,它将至少消耗1GB的内存。而如果你启动4个进程(4个工人),每个进程将消耗1GB的内存。因此,总的来说,你的API将消耗4GB的RAM。</p><p>而如果你的远程服务器或虚拟机只有3GB的内存,试图加载超过4GB的内存将导致问题。🚨</p><h3 id="5-5-多进程的一个例子"><a href="#5-5-多进程的一个例子" class="headerlink" title="5.5 多进程的一个例子"></a>5.5 多进程的一个例子</h3><p>在这个例子中,有一个启动和控制两个<strong>工作进程</strong>的<strong>管理器进程</strong>。</p><p>这个管理进程可能是在IP端口上监听的那个进程。它将把所有的通信传输给工作进程。</p><p>这些工作进程将运行你的应用程序,它们将执行主要计算以接收请求并返回响应,并且将加载所有变量到RAM中。</p><p><img src="/images/deployments_concept/deployments_concepts.svg" alt="deployments_concepts"></p><p>当然,在同一台机器上,除了你的应用程序外,可能还有其他进程在运行。</p><p>一个有趣的细节是,每个进程所使用的CPU的百分比可以随时间变化很大,但内存(RAM)通常或多或少保持稳定。</p><p>如果你有一个API,每次都做相当数量的计算,而且你有很多客户端请求,那么CPU的利用率也可能是稳定的(而不是不断地快速上升和下降)。</p><h3 id="5-6-一些工具示例"><a href="#5-6-一些工具示例" class="headerlink" title="5.6 一些工具示例"></a>5.6 一些工具示例</h3><p>可以有几种方法来实现这一点,我会在未来的章节中告诉你更多关于具体的策略,例如在谈论Docker和容器时。</p><p>需要考虑的主要制约因素是,必须有一个单一的组件来处理公共IP中的端口。然后,它必须有一种方法将通信传输给复制的进程/工作。</p><p>这里有一些可能的组合和策略:</p><ul><li><strong>Gunicorn</strong> managing <strong>Uvicorn workers</strong></li></ul><p>Gunicorn将是监听IP和端口的进程管理器,复制过程将由多个Uvicorn工作进程完成</p><ul><li><strong>Uvicorn</strong> managing <strong>Uvicorn workers</strong></li></ul><p>一个Uvicorn进程管理器将监听IP和端口,它将启动多个Uvicorn工作进程</p><ul><li><p><strong>Kubernetes</strong> and other distributed <strong>container systems</strong></p><p> Kubernetes层的一些东西会监听IP和端口。复制的方式是有多个容器,每个容器有一个Uvicorn进程在运行</p></li><li><p><strong>Cloud services</strong> that handle this for your</p><p>云服务将可能为你处理复制问题。它可能会让你定义一个要运行的进程或要使用的容器镜像,在任何情况下,它很可能是一个单一的Uvicorn进程,而云服务将负责复制它。</p></li></ul><h2 id="6-开始前的步骤"><a href="#6-开始前的步骤" class="headerlink" title="6. 开始前的步骤"></a>6. 开始前的步骤</h2><p>在很多情况下,你想在启动应用程序之前执行一些步骤。</p><p>例如,你可能想运行数据库迁移。</p><p>但在大多数情况下,你只想执行一次这些步骤。</p><p>所以,你希望在启动应用程序之前,有一个单一的进程来执行之前的那些步骤。</p><p>而且你必须确保是一个单一的进程来运行这些先前的步骤,即使之后你为应用程序本身启动了多个进程(多个工人)。如果这些步骤是由多个进程运行的,它们就会通过平行运行来重复工作,如果这些步骤是一些微妙的东西,比如数据库迁移,它们可能会导致彼此冲突。</p><p>当然,有些情况下,多次运行前面的步骤是没有问题的,在这种情况下,处理起来就容易多了。</p><blockquote><p>此外,请记住,根据你的设置,在某些情况下,你甚至可能不需要在开始应用之前的任何步骤。</p><p>在这种情况下,你就不必担心这些了。🤷</p></blockquote><h3 id="6-1-策略示例"><a href="#6-1-策略示例" class="headerlink" title="6.1 策略示例"></a>6.1 策略示例</h3><p>这在很大程度上取决于你部署系统的方式,它可能会与你启动程序的方式、处理重启等相联系。</p><p>这里有一些可能的想法。</p><ul><li>在Kubernetes中的一个 “初始容器”,在你的应用容器之前运行。</li><li>一个bash脚本,运行前面的步骤,然后启动你的应用程序。<br>你仍然需要一种方法来启动/重启该bash脚本,检测错误,等等</li></ul><h2 id="7-资源利用"><a href="#7-资源利用" class="headerlink" title="7. 资源利用"></a>7. 资源利用</h2><p>你的服务器是一种资源,你可以通过你的程序、CPU的计算时间和可用的RAM内存来<strong>消耗/利用</strong>这些资源。</p><p>你想消耗/利用多少系统资源?可能很容易想到 “不多”,但在现实中,你可能想在不崩溃的情况下尽可能多地消耗。</p><p>如果你为3台服务器付费,但你只使用了它们的一点点内存和CPU,你可能在浪费钱💸,也可能在浪费服务器的电力🌎,等等。</p><p>在这种情况下,如果只有2台服务器,并使用更高比例的资源(CPU、内存、磁盘、网络带宽等)可能会更好。</p><p>另一方面,如果你有2台服务器,并且100%使用它们的CPU和内存,在某些时候,一个进程会要求更多的内存,服务器将不得不使用磁盘作为 “内存”(可能慢几千倍),甚至崩溃。或者一个进程可能需要做一些计算,将不得不等待,直到CPU再次空闲。</p><p>在这种情况下,最好是多找一台服务器,在上面运行一些进程,以便它们都有足够的内存和CPU时间。</p><p>还有一种情况是,由于某种原因,你的API的使用量激增了。也许在网上疯传,或者一些其他服务商或机器人开始使用它。在这些情况下,你可能希望有额外的资源来保证安全。</p><p>你可以把一个任意的数字作为目标,例如,资源利用率在50%到90%之间的东西。重点是,这些可能是你需要衡量和调整部署的主要内容。</p><p>你可以使用简单的工具,如HTOP来查看你的服务器所使用的CPU和RAM,或者每个进程所使用的数量。或者你可以使用更复杂的监控工具,这些工具可能分布在各个服务器上,等等。</p><h2 id="8-回顾"><a href="#8-回顾" class="headerlink" title="8. 回顾"></a>8. 回顾</h2><p>你已经阅读了一些主要的概念,在决定如何部署你的应用程序时,你可能需要记住这些概念。</p><ul><li>Security - HTTPS</li><li>Running on startup</li><li>Restarts</li><li>Replication (the number of processes running)</li><li>Memory</li><li>Previous steps before starting</li></ul><p>理解这些想法以及如何应用它们,会让你在配置和调整你的部署时,有一定的直觉来做出对应的决定。🤓</p>]]></content>
<categories>
<category>文章分类</category>
</categories>
<tags>
<tag>Python</tag>
<tag>Gunicorn</tag>
<tag>Deployment</tag>
</tags>
</entry>
<entry>
<title>Python GNSS、地理坐标以及投影坐标之间的转换</title>
<link href="/2021/04/19/python-gnss-to-coord/"/>
<url>/2021/04/19/python-gnss-to-coord/</url>
<content type="html"><![CDATA[<p>最近捣鼓了一些室外机器人的路线,位置信息来源于GPS接收器拿到的信息,之前接收项目的时候他们采用了相对坐标散点图画路径,现在想基于经纬度利用现成的高德地图接口实现,在提取数据的过程中发现了一些问题,记录下来。</p><h2 id="1-GNSS"><a href="#1-GNSS" class="headerlink" title="1. GNSS"></a>1. GNSS</h2><p>卫星导航系统(Global Navigation Satellite System,GNSS)是覆盖全球的自主地利空间定位的卫星系统,允许小巧的电子接收器确定它的所在位置(经度、纬度和高度),并且经由卫星广播沿着视线方向传送的时间信号精确到10米的范围内。接收机计算的精确时间以及位置,可以作为科学实验的参考。</p><p>截至2020年6月,只有美国的全球定位系统 (GPS;共由24颗卫星组成)、俄罗斯的格洛纳斯系统(GLONASS)和中国的北斗卫星导航系统(BDS)[1]覆盖全球。欧洲联盟的伽利略定位系统则为在初期部署阶段的全球导航卫星系统,预定最早到2020年才能够充分的运作[2]。一些国家,包括法国、日本和印度[3],都在发展区域导航系统。</p><p>每个覆盖全球的系统通常都是由20-30颗卫星组成的卫星集群,以中地球轨道分布在几个轨道平面上。实际的系统各自不同,但是使用的轨道倾斜都大于50°,和轨道周期大约都是12小时(高度大约20,000千米(12,000英里))。</p><p><img src="/images/gnss/different_gnss.png" alt="不同的GNSS"></p><p>GNSS的工作原理分很多部分,比如:<strong>GNSS接收器是如何工作的?</strong>;<strong>GPS 接收器如何知道我在哪里?</strong>;<strong>GPS信号</strong>;<strong>计算出到卫星的距离</strong>等等</p><p>这并不是本篇文章所要探讨的,具体可以看<a href="https://www.oxts.com/zh/what-is-gnss/">GNSS一系列相关问题</a></p><h2 id="2-经纬度"><a href="#2-经纬度" class="headerlink" title="2. 经纬度"></a>2. 经纬度</h2><p>经纬度这个概念想必不用我说了吧。其实我还真忘记了,太难了~ </p><p>小学是没地理这门课的,那肯定就是<a href="https://wenku.baidu.com/view/8c74bc66a7e9856a561252d380eb6294dd8822dc.html">初中知识</a>。</p><p>这边主要讲讲经纬度的三种方式:</p><p>经纬度格式分为三种:度、度-分、度-份-秒</p><p>1.) ddd.ddddd °【度 . 度 格式】的十进制小数部分(5位)</p><p>2.) ddd°mm.mmm’ 【度 . 分 . 分 格式】的十进制小数部分(3位)</p><p>3.) ddd°mm’ss’’ 【度 . 分 . 秒 格式】</p><p>Google 使用的是第三种格式 度°分’秒’’</p><p>度分转换:<br>将度分单位数据转换为度单位数据<br>度=度+分/60<br>例如:<br>经度 = 116°20.12’<br>纬度 = 39°12.34’<br>经度 = 116 + 20.12 / 60 = 116.33533°<br>纬度 = 39 + 12.34 / 60 = 39.20567°</p><p>度分秒转换:<br>将度分秒单位数据转换为度单位数据<br>度 = 度 + 分 / 60 + 秒 / 60 / 60<br>例如:<br>经度 = 116°20’43”<br>纬度 = 39°12’37”<br>经度 = 116 + 20 / 60 + 43 / 60 / 60 = 116.34528°<br>纬度 = 39 + 12 / 60 + 37 / 60 / 60 = 39.21028°</p><h2 id="3-GPGGA信息转换为经纬度"><a href="#3-GPGGA信息转换为经纬度" class="headerlink" title="3. GPGGA信息转换为经纬度"></a>3. GPGGA信息转换为经纬度</h2><p>NMEA-0183是美国国家海洋电子协会(National Marine Electronics Association)为海用电子设备制定的标准格式。目前业已成了GPS导航设备统一的RTCM(Radio Technical Commission for Maritime services )标准协议。</p><p>NMEA0183的六种输出协议:GPGGA、GPGLL、GPGSA、GPGSV、GPRMC、GPVTG。</p><p>而我们这里要解析的是**$GPGGA(GPS定位信息)** </p><p>pynmea2 是一个用来处理 NMEA 0183 协议的第三方模块,pynmea2 模块兼容 Python 2 和 Python 3,能够解析 GSA、GGA、GSV、RMC、VTG、GLL 等 NMEA 0183 协议定义的各类数据,功能强大。该模块目前以 MIT 协议开源并托管在 <a href="https://github.com/Knio/pynmea2">Github</a> 网站上。</p><p>注意,解析字符串中 NMEA 0183 协议的数据,可以使用 <code>pynmea2.parse(data, check=False)</code> 方法,其中的 <code>check</code> 参数指定是否对消息中的检校字段进行检查。</p><figure class="highlight python"><table><tr><td class="gutter"><div class="code-wrapper"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></div></td><td class="code"><pre><code class="hljs python"><span class="hljs-meta">>>> </span><span class="hljs-keyword">import</span> pynmea2<br><span class="hljs-meta">>>> </span>msg = pynmea2.parse(<span class="hljs-string">"$GPGGA,184353.07,1929.045,S,02410.506,E,1,04,2.6,100.00,M,-33.9,M,,0000*6D"</span>)<br><span class="hljs-meta">>>> </span>msg<br><GGA(timestamp=datetime.time(<span class="hljs-number">18</span>, <span class="hljs-number">43</span>, <span class="hljs-number">53</span>), lat=<span class="hljs-string">'1929.045'</span>, lat_dir=<span class="hljs-string">'S'</span>, lon=<span class="hljs-string">'02410.506'</span>, lon_dir=<span class="hljs-string">'E'</span>, gps_qual=<span class="hljs-string">'1'</span>, num_sats=<span class="hljs-string">'04'</span>, horizontal_dil=<span class="hljs-string">'2.6'</span>, altitude=<span class="hljs-number">100.0</span>, altitude_units=<span class="hljs-string">'M'</span>, geo_sep=<span class="hljs-string">'-33.9'</span>, geo_sep_units=<span class="hljs-string">'M'</span>, age_gps_data=<span class="hljs-string">''</span>, ref_station_id=<span class="hljs-string">'0000'</span>)><br><br><span class="hljs-meta">>>> </span>msg.timestamp<br>datetime.time(<span class="hljs-number">18</span>, <span class="hljs-number">43</span>, <span class="hljs-number">53</span>)<br><span class="hljs-meta">>>> </span>msg.lat<br><span class="hljs-string">'1929.045'</span><br><span class="hljs-meta">>>> </span>msg.lat_dir<br><span class="hljs-string">'S'</span><br><span class="hljs-meta">>>> </span>msg.lon<br><span class="hljs-string">'02410.506'</span><br><span class="hljs-meta">>>> </span>msg.lon_dir<br><span class="hljs-string">'E'</span><br></code></pre></td></tr></table></figure><blockquote><p> 这边的经纬度属性以python浮点数 (DD, “decimal degrees”)存在而不是DDDMM.MMMM (“Degrees, minutes, seconds”),但<strong>latitude_minutes</strong><code>, </code><strong>latitude_seconds,</strong> <strong>longitude_minutes</strong>, 和 <strong>longitude_seconds</strong>这些属性能轻松创建不同格式的位置字符串</p></blockquote><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-meta">>>> </span>msg.latitude<br>-<span class="hljs-number">19.4840833333</span><br><span class="hljs-meta">>>> </span>msg.longitude<br><span class="hljs-number">24.1751</span><br><span class="hljs-meta">>>> </span><span class="hljs-string">'%02d°%07.4f′'</span> % (msg.latitude, msg.latitude_minutes)<br><span class="hljs-string">'-19°29.0450′'</span><br><span class="hljs-meta">>>> </span><span class="hljs-string">'%02d°%02d′%07.4f″'</span> % (msg.latitude, msg.latitude_minutes, msg.latitude_seconds)<br><span class="hljs-string">"-19°29′02.7000″"</span><br></code></pre></td></tr></table></figure><h2 id="4-地理坐标系"><a href="#4-地理坐标系" class="headerlink" title="4. 地理坐标系"></a>4. 地理坐标系</h2><p>地理坐标系又可分为 <a href="https://links.jianshu.com/go?to=https://baike.baidu.com/item/%E5%8F%82%E5%BF%83%E5%9D%90%E6%A0%87%E7%B3%BB">参心坐标系</a> 和 <a href="https://links.jianshu.com/go?to=https://baike.baidu.com/item/%E5%9C%B0%E5%BF%83%E5%9D%90%E6%A0%87%E7%B3%BB">地心坐标系</a>,常见的参心坐标系<a href="https://links.jianshu.com/go?to=https://baike.baidu.com/item/%E5%8C%97%E4%BA%AC54%E5%9D%90%E6%A0%87%E7%B3%BB">北京54</a>、<a href="https://links.jianshu.com/go?to=https://baike.baidu.com/item/%E8%A5%BF%E5%AE%8980%E5%9D%90%E6%A0%87%E7%B3%BB">西安80</a>,常见的地心坐标系有WGS84、GCJ-02、BD-09、GCS2000,这篇文章就简单讲讲<strong>地心坐标系</strong>。</p><h3 id="4-1-地心坐标系分类"><a href="#4-1-地心坐标系分类" class="headerlink" title="4.1 地心坐标系分类"></a>4.1 地心坐标系分类</h3><p>通过上面的Python的GPGGA信息解析转换为经纬度之后,我想验证设备的精确度,当我把经纬度信息放在Google earth的时候,位置很准确,但当我把它放到Google map进行查询时,却发现位置有较大偏差。当时我就纳闷极了,同样是Google怎么位置还不一样,后来研究了一番,发现我进入的Google map是大陆的,使用的坐标系不一样,需要进行转换。</p><p>主流地图在各个地区使用的坐标系:</p><table><thead><tr><th align="center">地图</th><th align="center">大陆/港/澳</th><th align="center">台湾省</th><th align="center">海外</th></tr></thead><tbody><tr><td align="center">高德</td><td align="center">GCJ-02</td><td align="center">WGS84</td><td align="center">WGS84</td></tr><tr><td align="center">Google</td><td align="center">GCJ-02</td><td align="center">WGS84</td><td align="center">WGS84</td></tr><tr><td align="center">百度</td><td align="center">BD-09 / GCJ-02</td><td align="center">BD-09 / GCJ-02</td><td align="center">WGS84</td></tr></tbody></table><p>而我们通过GPS拿到的GPGGA信息在解析到的经纬度是属于WGS84坐标系。WGS84是为 GPS 全球定位系统建立的坐标系统,是世界上第一个统一的地心坐标系,因此也被称为<strong>大地坐标系</strong>、<strong>原始坐标系</strong>。一般通过GPS记录仪记录下来的经纬度,就是基于WGS84坐标系的数据。</p><h3 id="4-2-坐标系转换"><a href="#4-2-坐标系转换" class="headerlink" title="4.2 坐标系转换"></a>4.2 坐标系转换</h3><p>国测局规定:互联网地图在国内必须至少使用 GCJ02 进行首次加密,不允许直接使用 WGS84 坐标下的地理数据,同时任何坐标系均不可转换为 WGS84 坐标。因此不存在将 GCJ-02 坐标转换为 WGS84 坐标的官方转换方法。</p><p>各大地图开发平台其实都有其官方的坐标系转换的接口,我这就说说Python的,直接使用<a href="https://github.com/sshuair/coord-convert">coord-convert</a>库即可</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> coord_convert.transform <span class="hljs-keyword">import</span> wgs2gcj, wgs2bd, gcj2wgs, gcj2bd, bd2wgs, bd2gcj <br>lon, lat = <span class="hljs-number">120</span>, <span class="hljs-number">40</span><br>gcj_lon, gcj_lat = wgs2gcj(lon, lat)<br>bd_lon, bd_lat = wgs2bd(lon, lat)<br><span class="hljs-built_in">print</span>(gcj_lon, gcj_lat) <span class="hljs-comment"># the result should be: 120.00567568355486 40.0013047896019</span><br></code></pre></td></tr></table></figure><p>相关函数说明如下:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs bash">wgs2gcj : convert WGS-84 to GCJ-02<br>wgs2bd : convert WGS-84 to DB-09<br>gcj2wgs : convert GCJ-02 to WGS-84<br>gcj2bd : convert GCJ-02 to BD-09<br>bd2wgs : convert BD-09 to WGS-84<br>bd2gcj : convert BD-09 to GCJ-02<br></code></pre></td></tr></table></figure><h2 id="5-投影坐标系"><a href="#5-投影坐标系" class="headerlink" title="5. 投影坐标系"></a>5. 投影坐标系</h2><p>投影坐标系在二维平面中进行定义。与地理坐标系不同,在二维空间范围内,投影坐标系的长度、角度和面积恒定。投影坐标系始终基于地理坐标系,而后者则是基于球体或旋转椭球体的。</p><p>在投影坐标系中,通过格网上的 x,y 坐标来标识位置,其原点位于格网中心。每个位置均具有两个值,这两个值是相对于该中心位置的坐标。一个指定其水平位置,另一个指定其垂直位置。这两个值称为 x 坐标和 y 坐标。采用此标记法,原点坐标是 x = 0 和 y = 0。</p><h3 id="5-1-地图投影"><a href="#5-1-地图投影" class="headerlink" title="5.1 地图投影"></a>5.1 地图投影</h3><p>无论将地球视为球体还是旋转椭球体,都必须变换其三维曲面以创建平面地图图幅。此数学变换通常称作地图投影。理解地图投影如何改变空间属性的一种简便方法就是观察光穿过地球投射到表面(称为投影曲面)上。想像一下,地球表面是透明的,其上绘有经纬网。用一张纸包裹地球。位于地心处的光会将经纬网投影到一张纸上。现在,可以展开这张纸并将其铺平。纸张上的经纬网形状与地球上的形状不同。地图投影使经纬网发生了变形。</p><p>地图投影使用数学公式将地球上的球面坐标与平面坐标关联起来。</p><h3 id="5-2-墨卡托投影"><a href="#5-2-墨卡托投影" class="headerlink" title="5.2 墨卡托投影"></a>5.2 墨卡托投影</h3><p>墨卡托投影(Mercator Projection),又称麦卡托投影、正轴等角圆柱投影,是一种等角的圆柱形地图投影法。本投影法得名于法兰德斯出身的地理学家杰拉杜斯·墨卡托,他于1569年发表长202公分、宽124公分以此方式绘制的世界地图。在以此投影法绘制的地图上,经纬线于任何位置皆垂直相交,使世界地图可以绘制在一个长方形上。由于可显示任两点间的正确方位,航海用途的海图、航路图大都以此方式绘制。在该投影中线型比例尺在图中任意一点周围都保持不变,从而可以保持大陆轮廓投影后的角度和形状不变(即等角);但墨卡托投影会使面积产生变形,极点的比例甚至达到了无穷大。</p><h3 id="5-3-墨卡托投影范围"><a href="#5-3-墨卡托投影范围" class="headerlink" title="5.3 墨卡托投影范围"></a>5.3 墨卡托投影范围</h3><p>由于墨卡托投影在两极附近是趋于无限值得,因此它并没完整展现了整个世界,地图上最高纬度是85.05度。为了简化计算,采用球形映射,而不是椭球体形状。虽然采用墨卡托投影只是为了方便展示地图,需要知道的是,这种映射会给Y轴方向带来0.33%的误差。</p><p><img src="/images/gnss/mercator.jpg" alt="墨卡托投影范围"></p><p>X轴:由于赤道半径为6378137米,则赤道周长为2<em>PI</em>r = 20037508.3427892,因此X轴的取值范围:[-20037508.3427892,20037508.3427892]。</p><p>Y轴:当纬度φ接近两极,即90°时,Y值趋向于无穷。因此通常把Y轴的取值范围也限定在[-20037508.3427892,20037508.3427892]之间。</p><p>范围:因此在墨卡托投影坐标系(米)下的坐标范围是:最小为(-20037508.3427892, -20037508.3427892 )到最大坐标为(20037508.3427892, 20037508.3427892)。</p><p><img src="/images/gnss/mercator.png" alt="墨卡托投影网格"></p><h3 id="5-4-经纬度与墨卡托投影转换"><a href="#5-4-经纬度与墨卡托投影转换" class="headerlink" title="5.4 经纬度与墨卡托投影转换"></a>5.4 经纬度与墨卡托投影转换</h3><p>在Python中我们可以使用<code>pyproj</code>库来实现转换。<a href="https://pyproj4.github.io/pyproj/stable/"><code>pyproj</code></a>是一个地图投影和坐标变换库,它可以将经度、纬度转换为原生地图投影的x,y坐标,反之亦然。</p><p>网上找的好多都是已经被废弃的函数,这边说下最新的,基于3.0版本的,从GPGGA解析到的经纬度转换为墨卡托平面坐标:</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> pyproj <span class="hljs-keyword">import</span> Transformer<br><span class="hljs-comment">#如果希望轴的顺序总是按 x、y 或 lon、lat 的顺序排列,你可以在创建转换器时使用 always_xy 选项。</span><br><span class="hljs-comment"># 转换为伪墨卡托坐标,,也叫web墨卡托坐标,用于谷歌地图等</span><br>transformer1 = Transformer.from_crs(<span class="hljs-string">"EPSG:4326"</span>, <span class="hljs-string">"EPSG:3857"</span>, always_xy=<span class="hljs-literal">True</span>)<br><span class="hljs-comment"># 转换成中国地区的通用横轴墨卡托坐标(UTM投影),用于计算等</span><br>transformer2 = Transformer.from_crs(<span class="hljs-number">4326</span>, <span class="hljs-number">32650</span>, always_xy=<span class="hljs-literal">True</span>)<br><br>transformer1.transform(<span class="hljs-number">120.7</span>, <span class="hljs-number">31.3</span>)<br>(<span class="hljs-number">13436262.538748119</span>, <span class="hljs-number">3671771.4487971356</span>)<br>transformer1.transform(<span class="hljs-number">120.7</span>, <span class="hljs-number">31.3</span>)<br>(<span class="hljs-number">852227.2443889615</span>, <span class="hljs-number">3468763.8018454057</span>)<br></code></pre></td></tr></table></figure><blockquote><p>在国际上,每个坐标系统都会被分配一个 <a href="https://epsg.io/">EPSG</a> 代码,EPSG:4326 就是 WGS84 的代码。GPS是基于WGS84的</p></blockquote><h3 id="相关资源:"><a href="#相关资源:" class="headerlink" title="相关资源:"></a>相关资源:</h3><ul><li><a href="https://tool.lu/coordinate/">在线坐标转换</a></li><li><a href="https://lbs.amap.com/">高德开放平台</a></li></ul>]]></content>
<categories>
<category>文章分类</category>
</categories>
<tags>
<tag>Python</tag>
<tag>GPS</tag>
</tags>
</entry>
<entry>
<title>ASGI翻译系列(三):使用 ASGI 和 HTTP</title>
<link href="/2021/03/13/asgi3/"/>
<url>/2021/03/13/asgi3/</url>
<content type="html"><![CDATA[<p>在上一篇文章中,我们研究了 ASGI 接口的基本结构。现在我们将进一步了解 HTTP 请求的消息结构,并看看我们如何使用 Starlette 包提供的一些数据结构来处理 ASGI 中的 HTTP 请求。</p><p>在任何 ASGI 应用程序中发生的第一件事情是,它用“scope”字典来实例化,该字典提供有关传入请求的一些初始信息。</p><p>下面是范围字典如何查找简单 HTTP 请求的示例。</p><figure class="highlight python"><table><tr><td class="gutter"><div class="code-wrapper"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></div></td><td class="code"><pre><code class="hljs python"><span class="hljs-meta">>>> </span>scope = {<br> <span class="hljs-string">"type"</span>: <span class="hljs-string">"http"</span>,<br> <span class="hljs-string">"http_version"</span>: <span class="hljs-string">"1.1"</span>,<br> <span class="hljs-string">"method"</span>: <span class="hljs-string">"GET"</span>,<br> <span class="hljs-string">"scheme"</span>: <span class="hljs-string">"https"</span>,<br> <span class="hljs-string">"path"</span>: <span class="hljs-string">"/"</span>,<br> <span class="hljs-string">"query_string"</span>: <span class="hljs-string">b"search=red+blue&maximum_price=20"</span>,<br> <span class="hljs-string">"headers"</span>: [<br> (<span class="hljs-string">b"host"</span>, <span class="hljs-string">b"www.example.org"</span>),<br> (<span class="hljs-string">b"accept"</span>, <span class="hljs-string">b"application/json"</span>)<br> ],<br> <span class="hljs-string">"client"</span>: (<span class="hljs-string">"134.56.78.4"</span>, <span class="hljs-number">1453</span>),<br> <span class="hljs-string">"server"</span>: (<span class="hljs-string">"www.example.org"</span>, <span class="hljs-number">443</span>)<br>}<br></code></pre></td></tr></table></figure><p>scope字典与 WSGI 的“environ”字典非常相似。事实上,ASGI 规范描述了<a href="https://asgi.readthedocs.io/en/latest/specs/www.html#wsgi-compatibility">如何在两者之间进行映射</a>.。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs python">environ = {<br> <span class="hljs-string">"REQUEST_METHOD"</span>: <span class="hljs-string">"GET"</span>,<br> <span class="hljs-string">"SCRIPT_NAME"</span>: <span class="hljs-string">""</span>,<br> <span class="hljs-string">"PATH_INFO"</span>: <span class="hljs-string">"/"</span>,<br> <span class="hljs-string">"QUERY_STRING"</span>: <span class="hljs-string">"search=red+blue&maximum_price=20"</span>,<br> <span class="hljs-string">"SERVER_NAME"</span>: <span class="hljs-string">"www.example.org"</span>,<br> <span class="hljs-string">"SERVER_PORT"</span>: <span class="hljs-number">443</span>,<br> <span class="hljs-string">"REMOTE_HOST"</span>: <span class="hljs-string">"134.56.78.4"</span>,<br> <span class="hljs-string">"REMOTE_PORT"</span>: <span class="hljs-number">1453</span>,<br> <span class="hljs-string">"SERVER_PROTOCOL"</span>: <span class="hljs-string">"HTTP/1.1"</span>,<br> <span class="hljs-string">"HTTP_HOST"</span>: <span class="hljs-string">"www.example.org"</span>,<br> <span class="hljs-string">"HTTP_ACCEPT"</span>: <span class="hljs-string">"application/json"</span>,<br>}<br></code></pre></td></tr></table></figure><blockquote><p>译者注:原文代码的使用的ASGI2协议,当时接口采用的是双重调用,在2019年3月20日,ASGI3.0发布,改进了调用风格,虽然向下兼容,但是原文的例子有些还是没法用了,所以我这边做了更新统一采用ASGI3写法并适当进行增删改。</p><p>具体可以看<a href="https://www.aeracode.org/2019/03/20/asgi-30/">ASGI3.0</a></p></blockquote><h2 id="1-scope的类型"><a href="#1-scope的类型" class="headerlink" title="1. scope的类型"></a>1. scope的类型</h2><p>ASGI 和 WSGI 之间的一个根本区别是 ASGI 是一个更通用的消息传递接口,而 WSGI 是严格的请求/响应接口。</p><p>在任何 ASGI 的scope中必须始终存在的一个元素是“ type”键,它用于指示协议类型。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs python">scope = {<br> <span class="hljs-string">"type"</span>: <span class="hljs-string">"http"</span>, <span class="hljs-comment"># Deal with an incoming HTTP request.</span><br> ...<br></code></pre></td></tr></table></figure><h2 id="2-Request-URL"><a href="#2-Request-URL" class="headerlink" title="2. Request URL"></a>2. Request URL</h2><p>可以根据<code> scheme</code>、<code> server</code>、<code> path</code> 和 <code>query _ string</code> 中包含的信息构造传入请求的完整 URL。</p><p>由于我们通常不需要使用原始的ASGI信息进行工作,因此拥有一些可重用的工具对我们很有用,我们可以使用这些工具为我们提取一些细节。</p><p><a href="https://www.starlette.io/">Starlette框架</a>被特意设计为零依赖库,因此可以将其用作各种 ASGI 应用程序或其他更高级别框架的基础。</p><p>它提供了一组用于 ASGI 的基本数据结构。例如:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs bash">>>> from starlette.datastructures import URL<br>>>> url = URL(scope=scope)<br>>>> url<br>URL(<span class="hljs-string">'https://www.example.org/?search=red+blue&maximum_price=20'</span>)<br>>>> str(url)<br><span class="hljs-string">'https://www.example.org/?search=red+blue&maximum_price=20'</span><br></code></pre></td></tr></table></figure><p>通过使用URL实例,你可以像使用标准库的<code>urlparse</code>一样检查URL上的各个组成部分:</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-meta">>>> </span>url.scheme<br><span class="hljs-string">'https'</span><br></code></pre></td></tr></table></figure><p>你也可以修改 URL 的组件,并返回一个新的实例:</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-meta">>>> </span>url.replace(hostname=<span class="hljs-string">'www.example.com'</span>)<br>URL(<span class="hljs-string">'https://www.example.com/?search=red&maximum_price=20'</span>)<br></code></pre></td></tr></table></figure><h2 id="3-Request-headers"><a href="#3-Request-headers" class="headerlink" title="3. Request headers"></a>3. Request headers</h2><p>ASGI 中的 HTTP 头被授权为一个字节对列表,表示头的名称和值。由于 HTTP 标头是不区分大小写的,因此 ASGI 强制要求按字节分类的标头表示必须严格采用小写格式。</p><p>HTTP 标头还可以包含具有相同标头名称的多个实例,例如与 <code>Set-Cookie</code>一起使用。</p><p>Starlette 提供了一个不变的、不区分大小写的、多 dict 来处理来自 ASGI 的 HTTP 请求头。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-meta">>>> </span><span class="hljs-keyword">from</span> starlette.datastructures <span class="hljs-keyword">import</span> Headers<br><span class="hljs-meta">>>> </span>headers = Headers(scope=scope)<br><span class="hljs-meta">>>> </span>headers<br>Headers({<span class="hljs-string">'host'</span>: <span class="hljs-string">'www.example.org'</span>, <span class="hljs-string">'accept'</span>: <span class="hljs-string">'application/json'</span>})<br></code></pre></td></tr></table></figure><p>实例化 <code>header</code> 数据结构是一种廉价的操作,与直接迭代逐字节对相比,它为检查请求 header 提供了一个更方便的接口。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs bash">>>> headers[<span class="hljs-string">'Accept'</span>]<br><span class="hljs-string">'application/json'</span><br></code></pre></td></tr></table></figure><p>对于响应头,Starlette 提供了一个 <code>MutableHeaders</code> 数据结构,这个数据结构是等效的,但可以设置或删除头值。</p><h2 id="4-Request-query-params"><a href="#4-Request-query-params" class="headerlink" title="4. Request query params"></a>4. Request query params</h2><p>我们需要处理的 URL 的一个特殊部分是请求查询参数,例如<code>? search = red + blue & maximum _ price = 20</code>。</p><p>为了处理这些问题,可以使用 <code>QueryParams</code> 类,它在参数上提供了一个不变的多字典实现。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-meta">>>> </span><span class="hljs-keyword">from</span> starlette.datastructures <span class="hljs-keyword">import</span> QueryParams<br><span class="hljs-meta">>>> </span>params = QueryParams(scope=scope)<br><span class="hljs-meta">>>> </span>params<br>QueryParams(query_string=<span class="hljs-string">'search=red+blue&maximum_price=20'</span>)<br><span class="hljs-meta">>>> </span>params[<span class="hljs-string">'search'</span>]<br><span class="hljs-string">'red blue'</span><br></code></pre></td></tr></table></figure><h2 id="5-Request-body"><a href="#5-Request-body" class="headerlink" title="5. Request body"></a>5. Request body</h2><p>有关传入请求的大部分信息都存储在“scope”中,并在 ASGI 应用程序实例化时显示。但是,对于<strong>请求主体</strong>,这是不可能的。</p><p>为了访问请求体,我们必须从“ receive”通道获取消息流。以下是我们如何在流中获取单个消息的方法:</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs python">message = <span class="hljs-keyword">await</span> receive()<br></code></pre></td></tr></table></figure><p>以下是 HTTP 请求正文消息结构的一个示例:</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs python">{<br> <span class="hljs-string">'type'</span>: <span class="hljs-string">'http.request.body'</span>,<br> <span class="hljs-string">'body'</span>: <span class="hljs-string">b'{"example": "Some JSON data"}'</span>,<br> <span class="hljs-string">'more_body'</span>: <span class="hljs-literal">False</span><br>}<br></code></pre></td></tr></table></figure><p>如果你直接使用 ASGI,请按照以下模式使用HTTP请求正文的整个流:</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs python">body = <span class="hljs-string">b''</span><br>more_body = <span class="hljs-literal">True</span><br><span class="hljs-keyword">while</span> more_body:<br> message = <span class="hljs-keyword">await</span> receive()<br> body += message.get(<span class="hljs-string">'body'</span>, <span class="hljs-string">b''</span>)<br> more_body = message.get(<span class="hljs-string">'more_body'</span>, <span class="hljs-literal">False</span>)<br></code></pre></td></tr></table></figure><p>Starlette提供了一种轻量级的方式来在请求界面中同时封装“scope”和“receive”通道,从而提供了更简单的方法来访问请求主体:</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs python">request = Request(scope, receive=receive)<br>body = <span class="hljs-keyword">await</span> request.body()<br></code></pre></td></tr></table></figure><p>或者,要获取JSON解析的表示形式,请执行以下操作:</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs python">request = Request(scope, receive=receive)<br>body = <span class="hljs-keyword">await</span> request.json()<br></code></pre></td></tr></table></figure><p>对于大型请求,你可能不希望一次就把整个请求体消耗到内存中。</p><p>从 Python 3.6开始,<a href="https://www.python.org/dev/peps/pep-0525/">异步生成器语法</a>得到了支持,它允许我们提供一个很好的简单的API来流式处理请求体…</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs python">request = Request(scope, receive=receive)<br><br><span class="hljs-keyword">async</span> <span class="hljs-keyword">for</span> chunk <span class="hljs-keyword">in</span> request.stream():<br> ... <span class="hljs-comment"># Do something with "chunk"</span><br></code></pre></td></tr></table></figure><p>我们可以将其与请求解析结合起来,以便优雅地处理表单数据。</p><p>在处理 HTTP 多部分请求时,您通常希望确保请求解析能够将任何文件上传内容流到磁盘上的临时文件中,而不必首先将所有数据加载到内存中。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs python">request = Request(scope, receive=receive)<br><br><span class="hljs-comment"># Any upload files in the request body will be streamed into temporary files.</span><br>form = <span class="hljs-keyword">await</span> request.form()<br></code></pre></td></tr></table></figure><h2 id="6-总结"><a href="#6-总结" class="headerlink" title="6. 总结"></a>6. 总结</h2><p>我们已经了解了如何为HTTP请求构造ASGI的“scope”消息,以及消息主体是如何通过 “receive “通道进行流式传输的。</p><p>我们还使用了<a href="https://www.starlette.io/">Starlette</a>提供的一些基本数据结构,以便在更高层次上使用ASGI。</p>]]></content>
<categories>
<category>Python</category>
</categories>
<tags>
<tag>Python</tag>
<tag>Web</tag>
</tags>
</entry>
<entry>
<title>ASGI翻译系列(二):使用 ASGI 和 HTTP</title>
<link href="/2021/03/09/asgi2/"/>
<url>/2021/03/09/asgi2/</url>
<content type="html"><![CDATA[<p>我们之前的文章介绍了 ASGI 协议,并介绍了为什么拥有一个标准化的低级server/application接口是有用的,以及 Python 社区超越现有 WSGI 服务器并开始采用 ASGI 的一些动机。</p><p>在这篇文章中,我们将开始看看 ASGI 的构建快,并演示我们如何开始使用它们来编写 web 服务。</p><p>作为一个应用程序开发者,你通常不会在低级别的地方使用ASGI,因为框架通常会提供一个更高级别的接口来工作。</p><blockquote><p>译者注:原文代码的使用的ASGI2协议,当时接口采用的是双重调用,在2019年3月20日,ASGI3.0发布,改进了调用风格,虽然向下兼容,但是原文的例子有些还是没法用了,所以我这边做了更新统一采用ASGI3写法并适当进行增删改。</p><p>具体可以看<a href="https://www.aeracode.org/2019/03/20/asgi-30/">ASGI3.0</a></p></blockquote><h2 id="1-ASGI应用"><a href="#1-ASGI应用" class="headerlink" title="1. ASGI应用"></a>1. ASGI应用</h2><p><del>ASGI的结构是一对可调用的接口。</del></p><p><del>第一个API调用是一个常规函数调用,它是为了建立一个新的有状态上下文。</del></p><p><del>第二个API调用是一个异步调用,它提供了一对通信通道,服务器和客户端通过这个通道互相发送信息。</del></p><p><del>下面是基本结构的样子:</del></p><figure class="highlight python"><table><tr><td class="gutter"><div class="code-wrapper"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></div></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">asgi_application</span>(<span class="hljs-params">scope</span>):<br> <span class="hljs-comment"># Perform any initial state setup.</span><br> ...<br><br> <span class="hljs-keyword">async</span> <span class="hljs-keyword">def</span> <span class="hljs-title function_">asgi_instance</span>(<span class="hljs-params">receive, send</span>):<br> <span class="hljs-comment"># This is where the application performs any actual network I/O.</span><br> ...<br><br> <span class="hljs-keyword">return</span> asgi_instance<br></code></pre></td></tr></table></figure><p>上面的例子是ASGI2双重调用的风格,新的 ASGI3.0应用程序看起来是这样的:</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">async</span> <span class="hljs-keyword">def</span> <span class="hljs-title function_">application</span>(<span class="hljs-params">scope, receive, send</span>):<br> ...<br></code></pre></td></tr></table></figure><p>让我们来看看这些接口的参数:</p><h3 id="1-1-Scope"><a href="#1-1-Scope" class="headerlink" title="1.1 Scope"></a>1.1 Scope</h3><p>一个信息字典,用来设置应用程序的状态。</p><p>ASGI可以用于各种接口,而不仅仅是HTTP,所以这个字典中最重要的键是 “type “键,它用来确定设置的是什么样的消息接口。</p><p>下面是一个简单的HTTP GET请求 <code>https://www.example.org/</code>的scoop的例子:</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs python">{<br> <span class="hljs-string">"type"</span>: <span class="hljs-string">"http"</span>,<br> <span class="hljs-string">"method"</span>: <span class="hljs-string">"GET"</span>,<br> <span class="hljs-string">"scheme"</span>: <span class="hljs-string">"https"</span>,<br> <span class="hljs-string">"server"</span>: (<span class="hljs-string">"www.example.org"</span>, <span class="hljs-number">80</span>),<br> <span class="hljs-string">"path"</span>: <span class="hljs-string">"/"</span>,<br> <span class="hljs-string">"headers"</span>: []<br>}<br></code></pre></td></tr></table></figure><h3 id="1-2-Send"><a href="#1-2-Send" class="headerlink" title="1.2 Send"></a>1.2 Send</h3><p>一个接受单个消息参数并返回 None 的异步函数。在 HTTP 的情况下,这个消息通道用于发送 HTTP 响应。</p><p>有两种类型的 HTTP 响应消息: 一种用于初始化发送响应,另一种用于发送响应正文。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">await</span> send({<br> <span class="hljs-string">"type"</span>: <span class="hljs-string">"http.response.start"</span>,<br> <span class="hljs-string">"status"</span>: <span class="hljs-number">200</span>,<br> <span class="hljs-string">"headers"</span>: [<br> [<span class="hljs-string">b"content-type"</span>, <span class="hljs-string">b"text/plain"</span>],<br> ],<br>})<br><span class="hljs-keyword">await</span> send({<br> <span class="hljs-string">"type"</span>: <span class="hljs-string">"http.response.body"</span>,<br> <span class="hljs-string">"body"</span>: <span class="hljs-string">b"Hello, world!"</span>,<br>})<br></code></pre></td></tr></table></figure><h3 id="1-3-Receive"><a href="#1-3-Receive" class="headerlink" title="1.3 Receive"></a>1.3 Receive</h3><p>一个不带参数的异步函数,该函数返回一条消息。在使用HTTP的情况下,此消息传递通道用于使用HTTP请求正文。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># Consume the entire HTTP request body into `body`.</span><br>body = <span class="hljs-string">b''</span><br>more_body = <span class="hljs-literal">True</span><br><span class="hljs-keyword">while</span> more_body:<br> message = <span class="hljs-keyword">await</span> receive()<br> <span class="hljs-keyword">assert</span> message[<span class="hljs-string">"type"</span>] == <span class="hljs-string">"http.request.body"</span><br> body += message.get(<span class="hljs-string">"body"</span>, <span class="hljs-string">b""</span>)<br> more_body = message.get(<span class="hljs-string">"more_body"</span>, <span class="hljs-literal">False</span>)<br></code></pre></td></tr></table></figure><h2 id="2-我们的第一个“-Hello,World-”应用程序"><a href="#2-我们的第一个“-Hello,World-”应用程序" class="headerlink" title="2. 我们的第一个“ Hello,World!”应用程序"></a>2. 我们的第一个“ Hello,World!”应用程序</h2><p>让我们把所有这些放在我们的第一个简单的 ASGI 应用程序中:</p><p><strong>example.py</strong></p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">async</span> <span class="hljs-keyword">def</span> <span class="hljs-title function_">app</span>(<span class="hljs-params">scope, receive, send</span>):<br> <span class="hljs-keyword">assert</span> scope[<span class="hljs-string">"type"</span>] == <span class="hljs-string">"http"</span><br><br> <span class="hljs-keyword">await</span> send({<br> <span class="hljs-string">"type"</span>: <span class="hljs-string">"http.response.start"</span>,<br> <span class="hljs-string">"status"</span>: <span class="hljs-number">200</span>,<br> <span class="hljs-string">"headers"</span>: [<br> [<span class="hljs-string">b"content-type"</span>, <span class="hljs-string">b"text/plain"</span>],<br> ],<br> })<br> <span class="hljs-keyword">await</span> send({<br> <span class="hljs-string">"type"</span>: <span class="hljs-string">"http.response.body"</span>,<br> <span class="hljs-string">"body"</span>: <span class="hljs-string">b"Hello, World!"</span>,<br> })<br></code></pre></td></tr></table></figure><p>你现在可以使用任何 ASGI 服务器运行该应用程序,包括 daphne、 uvicorn 或 hypercorn。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ pip3 install uvicorn<br>[...]<br>$ uvicorn example:app<br>INFO: Started server process [30074]<br>INFO: Waiting <span class="hljs-keyword">for</span> application startup.<br>INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)<br></code></pre></td></tr></table></figure><p>现在在你的网页浏览器中打开“ <a href="http://127.0.0.1:8000/">http://127.0.0.1:8000</a></p><p><img src="/images/asgi/hello-world.png"></p><p>也许现在还不是特别令人兴奋?尽管如此,它仍然是一整套功能的基础,而这些功能在 Python 现有的 WSGI 接口中是不可能实现的。</p><h2 id="3-构造-ASGI-应用程序的不同方法(适用于ASGI2)"><a href="#3-构造-ASGI-应用程序的不同方法(适用于ASGI2)" class="headerlink" title="3. 构造 ASGI 应用程序的不同方法(适用于ASGI2)"></a><del>3. 构造 ASGI 应用程序的不同方法</del>(适用于ASGI2)</h2><p>有多种方法可以构建一个 ASGI 应用程序。</p><h3 id="3-1-使用闭包将scope绑定到-ASGI-实例"><a href="#3-1-使用闭包将scope绑定到-ASGI-实例" class="headerlink" title="3.1 使用闭包将scope绑定到 ASGI 实例"></a>3.1 使用闭包将scope绑定到 ASGI 实例</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs pyton">def app(scope):<br> assert scope["type"] == "http"<br><br> async def asgi(receive, send):<br> ...<br><br> return asgi<br></code></pre></td></tr></table></figure><h3 id="3-2-使用-functools-partial-将scope绑定到-ASGI-实例"><a href="#3-2-使用-functools-partial-将scope绑定到-ASGI-实例" class="headerlink" title="3.2 使用 functools.partial 将scope绑定到 ASGI 实例"></a>3.2 使用 functools.partial 将scope绑定到 ASGI 实例</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">import</span> functools<br><br><br><span class="hljs-keyword">async</span> <span class="hljs-keyword">def</span> <span class="hljs-title function_">asgi_instance</span>(<span class="hljs-params">receive, send, scope</span>):<br> ...<br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">asgi_application</span>(<span class="hljs-params">scope</span>):<br> <span class="hljs-keyword">assert</span> scope[<span class="hljs-string">"type"</span>] == <span class="hljs-string">"http"</span><br> <span class="hljs-keyword">return</span> functools.partial(asgi_instance, scope=scope)<br></code></pre></td></tr></table></figure><h3 id="3-3-使用基于类的接口将scope绑定到一个-ASGI-实例"><a href="#3-3-使用基于类的接口将scope绑定到一个-ASGI-实例" class="headerlink" title="3.3 使用基于类的接口将scope绑定到一个 ASGI 实例"></a>3.3 使用基于类的接口将scope绑定到一个 ASGI 实例</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">class</span> <span class="hljs-title class_">ASGIApplication</span>:<br> <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params">self, scope</span>):<br> <span class="hljs-keyword">assert</span> scope[<span class="hljs-string">"type"</span>] == <span class="hljs-string">"http"</span><br> self.scope = scope<br><br> <span class="hljs-keyword">async</span> <span class="hljs-keyword">def</span> <span class="hljs-title function_">__call__</span>(<span class="hljs-params">self, receive, send</span>):<br> ...<br></code></pre></td></tr></table></figure><p>基于类的接口将在 ASGI 实现中非常常见,因为它实例化了一个对象,在单个请求/响应周期的生命周期中可以对其状态进行操作。</p><h2 id="4-更高层次的工作"><a href="#4-更高层次的工作" class="headerlink" title="4. 更高层次的工作"></a>4. 更高层次的工作</h2><p>虽然理解 ASGI 的工作原理很重要,但是你不希望大部分时间都在低层接口上工作。</p><p>Starlette 库提供了请求和响应类,可用于处理读取传入的HTTP请求和发送传出的响应的底层细节。</p><h3 id="4-1-HTTP-Requests"><a href="#4-1-HTTP-Requests" class="headerlink" title="4.1 HTTP Requests"></a>4.1 HTTP Requests</h3><p>Request 类接受一个 ASGI scope,也可以选择receive通道,并在请求上显示一个更高级别的接口。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> starlette.requests <span class="hljs-keyword">import</span> Request<br><br><br><span class="hljs-keyword">async</span> <span class="hljs-keyword">def</span> <span class="hljs-title function_">app</span>(<span class="hljs-params">scope, receive, send</span>):<br> <span class="hljs-keyword">assert</span> scope[<span class="hljs-string">"type"</span>] == <span class="hljs-string">"http"</span><br> request = Request(scope=scope, receive=receive)<br> ...<br></code></pre></td></tr></table></figure><p>request类使下列接口可用:</p><ul><li><code>request.method</code> - The HTTP method.</li><li><code>request.url</code> - A string-like interface that also gives you access to the parsed components of the URL. eg <code>request.url.path</code>.</li><li><code>request.query_params</code> - A multi-dict, containing the parsed URL query parameters.</li><li><code>request.headers</code> - A case-insensitive multi-dict, containing the HTTP headers.</li><li><code>request.cookies</code> - A dictionary of string values, representing all the cookie data included in the request.</li><li><code>async request.body()</code> - An asynchronous method for returning the request body as bytes.</li><li><code>async request.form()</code> - An asynchronous method for returning the request body parsed as HTML form data.</li><li><code>async request.json()</code> - An asynchronous method for returning the request body parsed as JSON data.</li><li><code>async request.stream()</code> - An asynchronous iterator for consuming the request stream chunk-by-chunk without reading everything into memory.</li></ul><h3 id="4-2-HTTP-Responses"><a href="#4-2-HTTP-Responses" class="headerlink" title="4.2 HTTP Responses"></a>4.2 HTTP Responses</h3><p>Starlette 包括各种处理发送回传出 HTTP 响应的 Response 类。</p><p>下面是一个同时使用请求和响应的示例:</p><p><strong>example.py</strong></p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> starlette.requests <span class="hljs-keyword">import</span> Request<br><span class="hljs-keyword">from</span> starlette.responses <span class="hljs-keyword">import</span> JSONResponse<br><br><span class="hljs-keyword">async</span> <span class="hljs-keyword">def</span> <span class="hljs-title function_">app</span>(<span class="hljs-params">scope, receive, send</span>):<br> <span class="hljs-keyword">assert</span> scope[<span class="hljs-string">"type"</span>] == <span class="hljs-string">"http"</span><br> request = Request(scope=scope, receive=receive)<br> response = JSONResponse({<br> <span class="hljs-string">"method"</span>: request.method,<br> <span class="hljs-string">"path"</span>: request.url.path,<br> <span class="hljs-string">"query_params"</span>: <span class="hljs-built_in">dict</span>(request.query_params),<br> })<br> <span class="hljs-keyword">await</span> response(scope, receive, send)<br></code></pre></td></tr></table></figure><p>响应实例与其他任何 ASGI 实例呈现相同的接口。</p><p>要真正发送响应,你可以用同样的方式来称呼它:</p><p><code>await response(scope, receive, send)</code></p><p>这是一个很好的属性,因为它意味着我们可以像使用 ASGI 应用程序的后半部分一样使用响应实例。</p><p>运行我们的应用程序:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ uvicorn example:app<br>INFO: Started server process [30074]<br>INFO: Waiting <span class="hljs-keyword">for</span> application startup.<br>INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)<br></code></pre></td></tr></table></figure><h2 id="5-总结"><a href="#5-总结" class="headerlink" title="5. 总结"></a>5. 总结</h2><p>我们已经掌握了第一个 ASGI“ Hello,World”应用程序。</p><p>虽然理解 ASGI 消息传递的基本原理很重要,但这不是我们开发的时候要花费时间的地方,因此我们也看到了如何开始将这些细节抽象到更高级别的请求/响应接口中。</p><p>我们讨论了以下术语,每当我们谈论使用ASGI的机制时,都需要这些术语:</p><ul><li><em>ASGI Application</em> - 满足 ASGI 接口的应用程序</li><li><em>Scope</em> - 用于实例化 ASGI 应用程序的信息字典</li><li><em>Receive</em>, <em>Send</em> - server/application消息传递发生的一对通道</li><li><em>Message</em> - 通过接收或发送通道发送的信息字典</li></ul><p>我们还开始使用<a href="https://www.starlette.io/">Starlette软件包</a>,该<a href="https://www.starlette.io/">软件包</a>为我们提供了在更高级别的界面上与ASGI一起使用所需的基本工具集。</p><p>在<a href="https://fantasyhh.github.io/2021/03/09/asgi3/">本系列的下一篇文章中,</a>我们将更详细地探讨ASGI HTTP消息传递。</p>]]></content>
<categories>
<category>Python</category>
</categories>
<tags>
<tag>Python</tag>
<tag>Web</tag>
</tags>
</entry>
<entry>
<title>ASGI翻译系列(一): 你好,ASGI</title>
<link href="/2021/03/08/asgi1/"/>
<url>/2021/03/08/asgi1/</url>
<content type="html"><![CDATA[<p>说说为什么要写这个系列。在新项目Python Web框架的选项中,我选择了我之前没有使用过的<strong>Fastapi</strong>,这是一次新的尝试,当然在这次用之前老早的时候我就看过这个框架,并被其吸引住了,感觉很有意思。至目前为止,框架也用了一两个月了,愈发觉得其typing+asynico的特性吊爆了,正如某些人所说,<strong>Fastapi</strong>绝对是Python Web框架上历程上的一个里程碑!当然这次的翻译系列中,不说<strong>Fastapi</strong>,先来说说与其息息相关的<strong>ASGI</strong></p><p><a href="https://www.encode.io/articles/hello-asgi">原文地址</a></p><blockquote><p>译者注:原文代码的使用的ASGI2协议,当时接口采用的是双重调用,在2019年3月20日,ASGI3.0发布,改进了调用风格,虽然向下兼容,但是原文的例子有些还是没法用了,所以我这边做了更新统一采用ASGI3写法并适当进行增删改。</p><p>具体可以看<a href="https://www.aeracode.org/2019/03/20/asgi-30/">ASGI3.0</a></p></blockquote><h2 id="1-前言"><a href="#1-前言" class="headerlink" title="1. 前言"></a>1. 前言</h2><p>本文介绍了新兴的 ASGI 标准,以及它为 Python Web 框架带来的好处。</p><p>当前 Python 环境的最新变化之一是关注异步编程模型,并在语言中引入了 async/await 语法。</p><p>异步模型使用轻量级的基于任务的切换,在runtime时管理显式的切换点和流控制。这与基于线程的并发相反,后者资源更加密集,依赖于隐式切换,由操作系统管理。</p><p>更多的现代runtime(如 Go 和 Node)是基于异步模型的。作为一种相对古老的语言,Python 不得不逐步适应它,这既带来了机遇,也带来了挑战。</p><p>其中一个挑战是 Python Web 框架如何适应异步模型的潜在好处。我们已经在最近涌入的高性能 Python Web 框架中看到了这一点。</p><blockquote><p>译者注:runtime 根据使用语境有两种含义,一个是单纯的字面意思,指程序运行的时候。另一个貌似是指支撑程序运行所需的环境,包括比如系统性的变量、其他系统级的辅助程序等。</p></blockquote><h2 id="2-WSGI-综述"><a href="#2-WSGI-综述" class="headerlink" title="2. WSGI 综述"></a>2. WSGI 综述</h2><p>长期以来,Python领域的Web框架,包括Django、Flask、Falcon、Pyramid和Bottle都是基于WSGI的框架。</p><p>WSGI是一个标准,它在Server实现和Application实现之间提供了一个形式化的接口,处理原始socket处理的琐碎细节。</p><p>在这里有一个形式化的接口是非常重要的,因为它在这两个方面之间提供了一个适当的分离,并允许服务器实现独立于框架或应用程序实现而发展。</p><p>Python有很多WSGI服务器,包括Gunicorn、uWSGI、Apache/mod_wsgi和Waitress。</p><p>我不会在本文中深入研究WSGI接口的具体细节,但我们可以快速看看如何运行一个基本“<strong>Hello, World</strong>” 的Web服务。</p><p><strong>example. py</strong>:</p><figure class="highlight python"><table><tr><td class="gutter"><div class="code-wrapper"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></div></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">hello_world</span>(<span class="hljs-params">environ, start_response</span>):<br> data = <span class="hljs-string">b"Hello, World!\n"</span><br> start_response(<span class="hljs-string">"200 OK"</span>, [<br> (<span class="hljs-string">"Content-Type"</span>, <span class="hljs-string">"text/plain"</span>),<br> ])<br> <span class="hljs-keyword">return</span> [data]<br></code></pre></td></tr></table></figure><p>现在我们可以在任何WSGI服务器上运行我们的hello world应用程序。</p><p><code>$ gunicorn example:hello_world</code><br>WSGI已经取得了巨大的成功,它几乎被普遍采用,这证明了Server/application接口的重要性,但它有几个限制,如果不进行重大改革,这些限制是不容易克服的。</p><p>首先,WSGI必须是一个基于线程或greenlet的接口,无法支持async/await模式。WSGI本质上无法利用异步编程的潜在优势。</p><p>还有一点,WSGI严格来说是一个HTTP接口,不能轻易地调整为提供对WebSockets或其他网络协议的支持。</p><h2 id="2-谈谈ASGI"><a href="#2-谈谈ASGI" class="headerlink" title="2. 谈谈ASGI"></a>2. 谈谈ASGI</h2><p>这就是 ASGI 的用武之地。</p><p>ASGI规范是一个从WSGI迭代下来的但又基本算是一个重新设计,它提供了一个异步 server/application接口,支持HTTP、HTTP/2和WebSockets。</p><p>作为对比,这里有一个ASGI的“<strong>Hello, World</strong>”的 Web服务例子。</p><p><strong>example.py</strong>:</p><p><del>原文采用的ASGI2双重调用的例子:</del></p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">class</span> <span class="hljs-title class_">HelloWorld</span>:<br> <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params">self, scope</span>):<br> <span class="hljs-keyword">pass</span><br><br> <span class="hljs-keyword">async</span> <span class="hljs-keyword">def</span> <span class="hljs-title function_">__call__</span>(<span class="hljs-params">self, receive, send</span>):<br> <span class="hljs-keyword">await</span> send({<br> <span class="hljs-string">'type'</span>: <span class="hljs-string">'http.response.start'</span>,<br> <span class="hljs-string">'status'</span>: <span class="hljs-number">200</span>,<br> <span class="hljs-string">'headers'</span>: [<br> [<span class="hljs-string">b'content-type'</span>, <span class="hljs-string">b'text/plain'</span>],<br> ]<br> })<br> <span class="hljs-keyword">await</span> send({<br> <span class="hljs-string">'type'</span>: <span class="hljs-string">'http.response.body'</span>,<br> <span class="hljs-string">'body'</span>: <span class="hljs-string">b'Hello, world!'</span>,<br> })<br></code></pre></td></tr></table></figure><p><code>$ uvicorn example:HelloWorld</code></p><p>同样,在本文中我不会去讨论规范的具体内容,但在接口中需要注意的两个关键点:</p><ul><li><code>__call__</code>方法中的async语法明确地将其标记为一个可能的任务切换点 ,并允许在该执行上下文中使用其他异步代码。</li><li><code>receive</code>和<code>send</code>参数,server和application之间的消息传递是通过这些参数进行的</li></ul><p><strong>而最新的ASGI3例子,更简单明了:</strong></p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">async</span> <span class="hljs-keyword">def</span> <span class="hljs-title function_">app</span>(<span class="hljs-params">scope, receive, send</span>):<br> <span class="hljs-keyword">await</span> send({<br> <span class="hljs-string">'type'</span>: <span class="hljs-string">'http.response.start'</span>,<br> <span class="hljs-string">'status'</span>: <span class="hljs-number">200</span>,<br> <span class="hljs-string">'headers'</span>: [<br> [<span class="hljs-string">b'content-type'</span>, <span class="hljs-string">b'text/plain'</span>],<br> ]<br> })<br> <span class="hljs-keyword">await</span> send({<br> <span class="hljs-string">'type'</span>: <span class="hljs-string">'http.response.body'</span>,<br> <span class="hljs-string">'body'</span>: <span class="hljs-string">b'Hello, world!'</span>,<br> })<br></code></pre></td></tr></table></figure><p><code>$ uvicorn example:app</code></p><p>这种基于async/await的接口与WSGI相比有很多优势。</p><h2 id="3-性能"><a href="#3-性能" class="headerlink" title="3. 性能"></a>3. 性能</h2><p>在代码主要是网络绑定而非计算绑定的情况下,异步代码能够获得比基于线程的并发更高的吞吐量。基于任务的并发的资源高效切换意味着成千上万的轻量级任务可以同时执行,而不需要基于线程切换的高开销。这意味着每个单一的服务器或实例能够支持更多的流量。</p><p>Python框架对于异步方面某些类别的用例来说是一个糟糕的候选者,而Node或Go等的异步能力则表现出色。</p><p>ASGI规范为Python提供了一个机会,使其能够在广泛的用例中达到生产力/性能的最佳状态,从编写大容量的代理服务器到将大规模的Web应用程序快速推向市场。</p><p>例如,这里是TechEmpower基准测试的最新结果,过滤到一些动态语言(Python、JavaScript、Ruby、PHP、Perl),全部使用Postgres后端。</p><p><img src="/images/asgi/asgi-performance.png"></p><p>随着 Starlette 和 Uvicorn 位居榜首,Python 在这些基准测试中的性能与 Node 相当。</p><h2 id="4-支持WebSockets和长效HTTP连接"><a href="#4-支持WebSockets和长效HTTP连接" class="headerlink" title="4. 支持WebSockets和长效HTTP连接"></a>4. 支持WebSockets和长效HTTP连接</h2><p>ASGI直接支持WebSockets,允许开发人员构建响应速度更快的Web应用程序,以及其他不适合HTTP的高度交互式服务。</p><p>它还可以用于支持 HTTP 长轮询或 HTTP 服务器发送事件(HTTP Server Sent Events) ,允许服务器到客户端的单向更新。</p><h2 id="5-进程中的后台任务"><a href="#5-进程中的后台任务" class="headerlink" title="5. 进程中的后台任务"></a>5. 进程中的后台任务</h2><p>ASGI提供的消息模型意味着它能够在HTTP响应发送后继续运行代码。这使得框架能够提供轻量级的进程内后台任务,而不需要支持完整的任务队列的基础设施。</p><h2 id="6-可扩展性"><a href="#6-可扩展性" class="headerlink" title="6. 可扩展性"></a>6. 可扩展性</h2><p>WSGI提出的接口是 “<strong>用HTTP请求调用这个函数,并等待HTTP响应返回</strong>”,而ASGI规范则提出了一个更广泛的可扩展接口,即 “<strong>这里有一对通道,服务器和应用程序可以在这两个通道之间进行通信</strong>”。</p><p>这种更广泛的模式有可能被扩展到HTTP或WebSockets之外的其他协议中</p><h2 id="7-我们现在在哪里?"><a href="#7-我们现在在哪里?" class="headerlink" title="7. 我们现在在哪里?"></a>7. 我们现在在哪里?</h2><p>有许多可用于生产的 ASGI 服务器:</p><p><strong>daphne</strong> - 最初的ASGI服务器。最初是为Django channels 开发的。<br><strong>uvicorn</strong> - 一个高性能的ASGI服务器,基于uvloop和httptools实现。<br><strong>hypercorn</strong> - 一个功能齐全的ASGI服务器,包括支持HTTP/2服务器推送等功能。</p><p>其中每一个都有一个已建立的生产使用的度量标准,所有这三个都有助于改进 ASGI 规范。</p><p>还有一些不同的ASGI框架。</p><p><strong>channels</strong> - Django为了支持WebSocket而生。在基于异步服务器的实现上提供了一个基于线程的应用上下文。<br><strong>starlette</strong> - Starlette被设计成一个功能齐全的Web框架,或者用作与和 ASGI 一起工作的工具包。一个高性能的框架,其功能集的范围类似于 Sanic 或 Werkzeug。<br><strong>quart</strong> - 一个Python ASGI网络微框架,具有与Flask相同的API。</p><p>ASGI还为现有的Web框架提供了一个可管理的升级路径,以提供异步支持,因为它很好地映射到WSGI上,同时增加了额外的功能。</p><p>Werkzeug/Flask的维护者以及Falcon团队都发出了积极的声音。Django团队也有考虑将ASGI支持引入核心框架。</p><p>还有一些基于异步的框架的维护者提供了初步的支持,包括 Sanic。</p><h2 id="8-继续这个系列"><a href="#8-继续这个系列" class="headerlink" title="8. 继续这个系列"></a>8. 继续这个系列</h2><p>我在这里的开发工作主要集中在 ASGI 服务器、 Uvicorn 和 ASGI 框架 Starlette 上。</p><p>在接下来的几周里,我将更详细地介绍如何使用 ASGI,包括如何使用它构建基于 websocket 的应用程序、 HTTP 代理服务器、 ASGI 中间件等等。</p><p>我们的<a href="https://fantasyhh.github.io/2021/03/08/asgi2/">下一篇文章</a>将开始更详细地介绍在 ASGI 中使用 HTTP。</p>]]></content>
<categories>
<category>Python</category>
</categories>
<tags>
<tag>Python</tag>
<tag>Web</tag>
</tags>
</entry>
<entry>
<title>Ubuntu20.04系统配置总结</title>
<link href="/2020/12/31/Ubuntu2004-Config/"/>
<url>/2020/12/31/Ubuntu2004-Config/</url>
<content type="html"><![CDATA[<p>用Ubuntu系统也已经好多年了,从14.04到16.04再到18.04,Ubuntu也从侧面见证了我从实习到第一份工作到十二月中旬离职的这五年经历。搬到新公司,直接上了Ubuntu20.04作为新的开始。N卡和Ubuntu驱动不兼容,直接把我搞毛了,搞了半天稀里糊涂把驱动问题解决之后,又装了搜来搜去装了大半天的软件,费时费劲,赶在新的一年开始之前进行一些配置记录,以后有新的软件安装也会更新下来。</p><p>其实主要参考的还是<a href="https://zhuanlan.zhihu.com/p/139305626">这篇文章</a>,其他不同的,着重记录下来。</p><h2 id="1-electron-ssr"><a href="#1-electron-ssr" class="headerlink" title="1. electron-ssr"></a>1. electron-ssr</h2><p><a href="https://github.com/geeeeeeeeek/electronic-wechat">electron-ssr</a>作为我常用的一个梯子,势必得在安装chrome之前搞完,但是这货基于python2,而Ubuntu20.04只有自带的Python3,所以需要<code>apt install python</code>来安装Python2来保证顺利运行。</p><h2 id="2-zsh,alise以及terminnator配置"><a href="#2-zsh,alise以及terminnator配置" class="headerlink" title="2. zsh,alise以及terminnator配置"></a>2. zsh,alise以及terminnator配置</h2><p>安装什么的就不说了,就是记录下自己的配置。<br><code>~/.zshrc</code>部分配置</p><figure class="highlight routeros"><table><tr><td class="gutter"><div class="code-wrapper"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></div></td><td class="code"><pre><code class="hljs routeros">plugins=(git<br> z<br> safe-paste<br> zsh-autosuggestions<br> colored-man-pages<br> sudo<br> history-substring-search<br> docker<br> docker-compose<br>)<br><br><br>alias <span class="hljs-attribute">ipy</span>=<span class="hljs-string">"~/quick_ipython.sh"</span><br>alias <span class="hljs-attribute">proxy-git</span>=<span class="hljs-string">"git -c http.proxy='socks5://127.0.0.1:1080'"</span><br>alias <span class="hljs-attribute">proxy-docker</span>=<span class="hljs-string">"echo https://3xpu0n3u.mirror.aliyuncs.com ; echo https://blog.csdn.net/liu865033503/article/details/95936640"</span><br><br>alias <span class="hljs-attribute">c</span>=<span class="hljs-string">'clear'</span><br>alias <span class="hljs-attribute">h</span>=<span class="hljs-string">'history'</span><br>alias <span class="hljs-attribute">ping</span>=<span class="hljs-string">'ping -c 5'</span><br>alias <span class="hljs-attribute">ports</span>=<span class="hljs-string">'netstat -tulanp'</span><br>alias <span class="hljs-attribute">untar</span>=<span class="hljs-string">'tar -zxvf'</span><br>alias <span class="hljs-attribute">venv</span>=<span class="hljs-string">'source venv/bin/activate'</span><br><br><br><span class="hljs-built_in">export</span> VIRTUAL_ENV_DISABLE_PROMPT=<br>setopt no_nomatch<br><br><span class="hljs-built_in">export</span> <span class="hljs-attribute">NVM_DIR</span>=~/.nvm<br>[ -s <span class="hljs-string">"<span class="hljs-variable">$NVM_DIR</span>/nvm.sh"</span> ] && . <span class="hljs-string">"<span class="hljs-variable">$NVM_DIR</span>/nvm.sh"</span><br></code></pre></td></tr></table></figure><p><code>~/.config/terminator/config</code>配置</p><figure class="highlight ini"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><code class="hljs ini"><span class="hljs-section">[global_config]</span><br><span class="hljs-section">[keybindings]</span><br><span class="hljs-section">[profiles]</span><br> <span class="hljs-section">[[default]]</span><br> <span class="hljs-attr">background_darkness</span> = <span class="hljs-number">0.92</span><br> <span class="hljs-attr">background_type</span> = transparent<br> <span class="hljs-attr">cursor_color</span> = <span class="hljs-string">"#3036ec"</span><br> <span class="hljs-attr">font</span> = Ubuntu Mo<span class="hljs-literal">no</span> <span class="hljs-number">15</span><br> <span class="hljs-attr">foreground_color</span> = <span class="hljs-string">"#00ff00"</span><br> <span class="hljs-attr">show_titlebar</span> = <span class="hljs-literal">False</span><br> <span class="hljs-attr">login_shell</span> = <span class="hljs-literal">True</span><br> <span class="hljs-attr">custom_command</span> = tmux<br> <span class="hljs-attr">use_system_font</span> = <span class="hljs-literal">False</span><br><span class="hljs-section">[layouts]</span><br> <span class="hljs-section">[[default]]</span><br> <span class="hljs-section">[[[child1]]]</span><br> <span class="hljs-attr">type</span> = Terminal<br> <span class="hljs-attr">parent</span> = window0<br> <span class="hljs-section">[[[window0]]]</span><br> <span class="hljs-attr">type</span> = Window<br> <span class="hljs-attr">parent</span> = <span class="hljs-string">""</span><br> <span class="hljs-attr">size</span> = <span class="hljs-number">1060</span>, <span class="hljs-number">700</span><br><span class="hljs-section">[plugins]</span><br></code></pre></td></tr></table></figure><h2 id="3-桌面美化"><a href="#3-桌面美化" class="headerlink" title="3. 桌面美化"></a>3. 桌面美化</h2><ol><li>安装 tweek<br><code>sudo apt install gnome-tweak-tool</code></li><li>让gnome 支持插件扩展<br><code>sudo apt install gnome-shell-extensions</code></li><li>chrome 浏览器扩展支持,可以使用浏览器安装插件<br><code>sudo apt install chrome-gnome-shell</code></li></ol><blockquote><p>这里注意下,桌面主题跟插件不一样的东西,主题包括一些应用图标桌面美化,而插件是桌面插件。比如有个<code>Dash to Dock</code>用来自定义 dock,我以为主题,其实是插件。插件直接可以用chrome插件安装,很方便</p></blockquote><p>我用的主题是<a href="https://github.com/vinceliuice/WhiteSur-gtk-theme">WhiteSur-gtk-theme</a></p><h2 id="4-一些常用软件的安装"><a href="#4-一些常用软件的安装" class="headerlink" title="4. 一些常用软件的安装"></a>4. 一些常用软件的安装</h2><ul><li>搜狗输入法<a href="https://pinyin.sogou.com/linux/help.php">安装指南</a></li><li>pycharm CE:直接在软件商店安装</li><li>wps:官网下载deb文件,然后安装</li><li>chrome:不展开</li><li>Markdown软件 <del>haroopad:官网下载deb文件,然后安装</del>,haroopad各种造成死机,已换成<a href="https://typora.io/#linux">typora</a></li><li>vscode:官网下载deb文件,然后安装</li><li>剪贴板Gpaste <code>sudo apt install gnome-shell-extensions-gpaste gpaste</code>。安装完成后,按下 Alt + F2 并输入 r 重新启动 Gnome Shell,然后按回车键</li><li>MQTTX <code>snap install mqttx</code></li><li><del>SQlite Browser <code>sudo apt install sqlitebrowser</code></del> <a href="https://github.com/dbeaver/dbeaver/releases">dbeaver开源的数据库客户端工具</a> </li><li><a href="https://ugetdm.com/downloads/">uGet</a>用于下载软件,uGet安装完成后,根据个人需要,可以安装和配置aria2。Aria2是一个命令行下载软件,配合uGet使用,效果更好</li><li>微信 docker安装,后面会有讲到</li><li>QQ音乐 官网直接下载deb文件然后安装</li><li>docker <code>sudo apt install docker.io</code> <code>sudo systemctl enable --now docker</code> <code>sudo usermod -aG docker $USER</code>。给完用户权限之后,要logout一下才生效</li></ul><h2 id="5-一些常用的docker容器"><a href="#5-一些常用的docker容器" class="headerlink" title="5. 一些常用的docker容器"></a>5. 一些常用的docker容器</h2><h3 id="5-1-postgreSQL"><a href="#5-1-postgreSQL" class="headerlink" title="5.1 postgreSQL"></a>5.1 postgreSQL</h3><p><code>docker run --name mypgsql -e POSTGRES_PASSWORD=password -e POSTGRES_USER=user -p 5432:5432 -d postgres:13</code></p><h3 id="5-2-pgadmin4-web-docker命令"><a href="#5-2-pgadmin4-web-docker命令" class="headerlink" title="5.2 pgadmin4 web docker命令"></a>5.2 pgadmin4 web docker命令</h3><p><code>docker run -d -p 5433:80 --name pgadmin4 -e [email protected] -e PGADMIN_DEFAULT_PASSWORD=123456 dpage/pgadmin4</code></p><blockquote><p>注意本地访问80端口后其实访问的是pgadmin4容器web地址的5433端口,如果连接服务器的时候再选择localhost地址,其实还是pgadmin4容器ip地址,需要将localhost改为postgreSQL容器地址的ip</p></blockquote><figure class="highlight livescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs livescript"><span class="hljs-string">\h:查看SQL命令的解释,比如\h</span> select。<br><span class="hljs-string">\?:查看psql命令列表。</span><br><span class="hljs-string">\l:列出所有数据库。</span><br><span class="hljs-string">\c</span> [database_name]:连接其他数据库。<br><span class="hljs-string">\d:列出当前数据库的所有表格。</span><br><span class="hljs-string">\d</span> [table_name]:列出某一张表格的结构。<br><span class="hljs-string">\du:列出所有用户。</span><br><span class="hljs-string">\e:打开文本编辑器。</span><br><span class="hljs-string">\conninfo:列出当前数据库和连接的信息。</span><br></code></pre></td></tr></table></figure><h3 id="5-3-redis"><a href="#5-3-redis" class="headerlink" title="5.3 redis"></a>5.3 redis</h3><p><code>docker run -d -p 6379:6379 --name myredis redis:rc-alpine --appendonly yes</code><br>服务器安装好了,我们继续在本地安装redis管理器<br><code>snap install redis-desktop-manage</code></p><p><a href="https://segmentfault.com/a/1190000022713251">redis-cli 常用命令</a></p><h3 id="5-4-mosquitto-支持websocket"><a href="#5-4-mosquitto-支持websocket" class="headerlink" title="5.4 mosquitto 支持websocket"></a>5.4 mosquitto 支持websocket</h3><p><code>docker run -it -d -p 1883:1883 -p 9001:9001 -v /home/sjh/mosquitto.conf:/mosquitto/config/mosquitto.conf -v /mosquitto/data -v /mosquitto/log eclipse-mosquitto</code></p><h3 id="5-5-wechat"><a href="#5-5-wechat" class="headerlink" title="5.5 wechat"></a>5.5 wechat</h3><p>关于工作上常用的微信,以前我都是网页版微信,但是发现实在是太垃圾了,后来发现了<a href="https://github.com/bestwu/docker-wechat">基于深度操作系统的微信</a>,也存在docker镜像一键部署,但是跑之前需要进行一些配置,可在官网查看</p><figure class="highlight routeros"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs routeros">docker <span class="hljs-built_in">run</span> -d --name wechat --device /dev/snd <span class="hljs-attribute">--ipc</span>=host \ <br>-v /tmp/.X11-unix:/tmp/.X11-unix \<br>-v <span class="hljs-variable">$HOME</span>/WeChatFiles:/WeChatFiles \<br>-e <span class="hljs-attribute">DISPLAY</span>=unix$DISPLAY \<br>-e <span class="hljs-attribute">XMODIFIERS</span>=@im=fxitx \<br>-e <span class="hljs-attribute">QT_IM_MODULE</span>=fcitx \<br>-e <span class="hljs-attribute">GTK_IM_MODULE</span>=fcitx \<br>-e <span class="hljs-attribute">AUDIO_GID</span>=`getent<span class="hljs-built_in"> group </span>audio | cut -d: -f3` \<br>-e <span class="hljs-attribute">GID</span>=`id -g` \<br>-e <span class="hljs-attribute">UID</span>=`id -u` \<br>bestwu/wechat<br></code></pre></td></tr></table></figure><h3 id="5-6-EMQX"><a href="#5-6-EMQX" class="headerlink" title="5.6 EMQX"></a>5.6 EMQX</h3><p>正式开发的时候,在mqtt broker方面,需要用到emqx代替mosquitto<br><code>docker run -d --name emqx -p 1883:1883 -p 8083:8083 -p 18083:18083 emqx/emqx</code></p><h3 id="5-7-portainer"><a href="#5-7-portainer" class="headerlink" title="5.7 portainer"></a>5.7 portainer</h3><p>docker容器管理工具portainer</p><p><code>docker volume create portainer_data</code></p><p><code>docker run -d -p 9000:9000 --name=portainer --restart=always -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer-ce</code></p>]]></content>
<categories>
<category>Linux</category>
</categories>
<tags>
<tag>Linux</tag>
<tag>Bash</tag>
</tags>
</entry>
<entry>
<title>谈谈系统级I/O</title>
<link href="/2020/12/08/unixIO/"/>
<url>/2020/12/08/unixIO/</url>
<content type="html"><![CDATA[<p>对于I/O每个程序员并不陌生,但从菜鸟入门阶再到进阶,每个阶段对I/O都会有新的认识见解,从最开始的<code>printf</code>函数到Linux中的管道再到多线程使用中读写I/O,网络I/O的概念,每个地方都感觉有I/O的身影,但模模糊糊,含义好像都不太一样,困惑也渐渐增多…</p><h2 id="1-Unix-I-x2F-O"><a href="#1-Unix-I-x2F-O" class="headerlink" title="1. Unix I/O"></a>1. Unix I/O</h2><h3 id="1-1-输入-x2F-输出"><a href="#1-1-输入-x2F-输出" class="headerlink" title="1.1 输入/输出"></a>1.1 输入/输出</h3><p>输入/输出(I/O)是在主存(DRAM)和外部设备(例如磁盘驱动器、终端、网络)之间复制数据的过程。输入操作是I/O设备复制数据到主存,而输出操作是从主存复制数据到I/O设备。<br><img src="/images/unixIO/io.jpeg"><br>图稍微配的有点糙,但是能很清晰的说明输入上段文字中所描述的关系,复制数据的过程在I/O总线中进行。</p><p><strong>在UNIX操作系统中,所有的外围设备(上图中显示的)都被看作是文件系统中的文件</strong>,一个Linux文件就是m个字节的序列:<code>BO,B1,B2...</code>。因此,<strong>所有的输入/输出都要通过对应的读文件或写文件完成</strong>。这种将设备优雅地映射为文件的方法,允许Linux内核引出一个简单、低级的应用接口口,称为为<code>Unix I/O</code>。也就是说,通过一个单一的接口就可以处理外围设备和程序之间的所有通信。</p><blockquote><p>文本文件(比如txt文件)与文件(上述的文件含义)要区分开</p></blockquote><blockquote><p>主存(L3高速缓存)的速度远远大于本地磁盘(本地二级存储)和web服务器(远程二级存储),由于这种巨大的速度差,我们在多线程中总是提到I/O瓶颈,I/O时间观,例如从SSD读1M连续数据真实延迟是1毫秒,但对CPU的感觉来说是一个月的漫长时间!而这个等待时间我们不能让CPU白白浪费,要搞点别的事,然后就是I/O模型的各种进化版,最后到异步的操作了。</p></blockquote><h3 id="1-2-操作文件的方式"><a href="#1-2-操作文件的方式" class="headerlink" title="1.2 操作文件的方式"></a>1.2 操作文件的方式</h3><p><strong>打开文件</strong>:因为大多数的输入/输出是通过键盘和显示器实现的,为了方便,UNIX做了特别的安排。一个应用程序通过要求内核打开相应的文件,内核将返回一个非负整数,称为<strong>描述符</strong>,记录打开文件的所有信息:标准输入(描述符0)、标准输出(描述符1)、标准错误(描述符2)。</p><p><strong>改变当前文件位置</strong>:内核保持一个文件的位置k,初始为0,表示从文件开始处偏移的字节数。通过seek操作。</p><p><strong>读写文件</strong>:读操作就是从文件拷贝n个字节到存储器,如果是从k处开始,就是拷贝k+n为止。文件的大小为m,如果k≥m就会触发(EOF),所有就不需要明确的EOF字符了。写操作就是从存储器拷贝n个字节到文件当前位置k处。</p><p><strong>关闭文件</strong>:通知内核关闭文件。内核释放文件打开时创建的数据结构,将描述符恢复到可以的描述符池中。无论一个进程因为哪种原因终止,内核都会关闭所有打开的文件并释放他们的内存资源。</p><h2 id="2-标准I-x2F-O函数"><a href="#2-标准I-x2F-O函数" class="headerlink" title="2. 标准I/O函数"></a>2. 标准I/O函数</h2><p>有了上面的一些概念,我们可以看看C语言中一些常见的容易混淆的函数。各种I/O函数都存放在<code>stdio.h</code>头文件中</p><h3 id="2-1-格式化输出"><a href="#2-1-格式化输出" class="headerlink" title="2.1 格式化输出"></a>2.1 格式化输出</h3><figure class="highlight c"><table><tr><td class="gutter"><div class="code-wrapper"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></div></td><td class="code"><pre><code class="hljs C"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdio.h></span></span><br><span class="hljs-comment">// 这里在显示器打印`hello,world`</span><br><span class="hljs-type">int</span> <span class="hljs-title function_">main</span><span class="hljs-params">()</span>{<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"hello,world\n"</span>);<br> <span class="hljs-comment">// 等同于 fprintf(stdout,"hello,world\n");</span><br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><p><code>printf(...)</code>函数其实是<code>fprint</code>的带默认参数版,等价于<code>fprint(stdout,...)</code><br><code>int printf(const char *format,...)</code> 对比<code>int fprintf(FILE *stream, const char *foramt,...)</code><br>因为所有外围设备(这里指显示器)都是文件,我们只要把后者函数的stream参数默认为标准输出stdout即可。所以我们也可以把这个stream参数用一个<code>FILE *</code>参数来代替,那就直接格式化输出到文件里,这个我们后面会继续讲到。</p><blockquote><p>流(stream)是与磁盘或者其他外围设备关联的数据的源或者目的地,UNIX中文本流和二进制流是相同的。打开一个流将把该流与一个文件或设备连接起来,关闭流将断开这种连接。打开一个文件将返回一个指向FILE类型对象的指针,该指针记录了控制该流的所有必要信息。我们可以将<code>文件指针</code>和<code>流</code>等同<br>A very important concept in C is the stream. In C, the stream is a common, logical interface to the various devices that comprise the computer.In its most common form, a stream is a logical interface to a file. As C defines the term “file”, it can refer to a disk file, the screen, the keyboard, a port, a file on tape, and so on.Although files differ in form and capabilities, all streams are the same. The stream provides a consistent interface and to the programmer one hardware device will look much like another.<br><a href="https://www.cquestions.com/2009/01/what-is-stream-in-c-programming.html">https://www.cquestions.com/2009/01/what-is-stream-in-c-programming.html</a></p></blockquote><p>此外,还有个<code>int sprintf(char *s, const char *foramt,...)</code>函数,与<code>printf</code>函数基本相同,但其输出将被写入到字符串s中,具体可以看<a href="http://c.biancheng.net/cpp/html/295.html">C语言sprintf()函数:将格式化的数据写入字符串</a>。为什么会有这个函数,反正把内存中的数据写到文本,显示器等文件中,当然我也可以直接不变位置还是写到内存中的另一个地方,所有才有了这个<code>char *s</code>。</p><h3 id="2-2-格式化输入"><a href="#2-2-格式化输入" class="headerlink" title="2.2 格式化输入"></a>2.2 格式化输入</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs C"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdio.h></span></span><br><span class="hljs-type">int</span> <span class="hljs-title function_">main</span><span class="hljs-params">()</span><br>{<br> <span class="hljs-type">float</span> num;<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Enter a number: "</span>);<br> <span class="hljs-comment">// %f 匹配浮点型数据</span><br> <span class="hljs-built_in">scanf</span>(<span class="hljs-string">"%f"</span>,&num);<br><span class="hljs-comment">// 等同于 fscanf(stdin,"%f",&num)</span><br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Value = %.2f"</span>, num);<br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><p>格式化输入这边的原理跟输出,不展开</p><h3 id="2-3-字符输入输出"><a href="#2-3-字符输入输出" class="headerlink" title="2.3 字符输入输出"></a>2.3 字符输入输出</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs C"><span class="hljs-meta">#<span class="hljs-keyword">include</span><span class="hljs-string"><stdio.h></span></span><br><span class="hljs-type">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">( )</span>{<br> <span class="hljs-type">int</span> c;<br> <span class="hljs-keyword">while</span>( (c=getchar()) != <span class="hljs-string">'\n'</span> ) <span class="hljs-comment">//从控制台流中读取字符,直到按回车键结束</span><br> <span class="hljs-built_in">printf</span> (<span class="hljs-string">"%c"</span>, c); <span class="hljs-comment">//输出读取内容</span><br>}<br></code></pre></td></tr></table></figure><p>这个的<code>getchar</code>函数等同于<code>getc(stdin)</code>,而相对的输出这边<code>putchar</code>函数等同于<code>putc(c,stdout)</code>。</p><h3 id="2-4-文件操作"><a href="#2-4-文件操作" class="headerlink" title="2.4 文件操作"></a>2.4 文件操作</h3><p>上面都是常见的标准输出到显示器,从键盘标准输入,这些其实都是文件操作函数,只是带有默认输入输出的函数。<br>那么,究其根源,处理与文件有关的通用函数来了!</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><code class="hljs C"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdio.h></span></span><br><br><span class="hljs-type">int</span> <span class="hljs-title function_">main</span><span class="hljs-params">()</span><br>{<br> FILE *fp ;<br> <span class="hljs-type">char</span> *s = <span class="hljs-string">"hello\n"</span>;<br> fp = fopen(<span class="hljs-string">"foo.txt"</span>, <span class="hljs-string">"w"</span>);<br> fwrite(s, <span class="hljs-keyword">sizeof</span>(s) , <span class="hljs-number">1</span>, fp );<br> <span class="hljs-built_in">fprintf</span>(fp, <span class="hljs-string">"call one number:%d\n"</span>, <span class="hljs-number">520</span>);<br> <span class="hljs-built_in">fputs</span>(<span class="hljs-string">"This is testing for fputs...\n"</span>, fp);<br> fclose(fp);<br>}<br></code></pre></td></tr></table></figure><p><code>FILE *fopen(const char *filename, const char *mode)</code><br><code>fopen</code>函数打开filename指定的文本文件,并返回一个与之相关联的流。如果打开操作失败,则返回NULL。访问模式mode就是常见的:<code>r</code>,<code>w</code>,<code>a</code>等,含义就不一一说了,<a href="http://c.biancheng.net/view/2054.html">网上很多</a></p><p>相对应的,<code>int fclose(FILE *stream)</code>函数讲所有未写入的数据写入流stream中,丢弃缓冲区中的所有未读输入数据,并释放自动分配的全部缓冲区,最后关闭流,若出错则返回EOF,否则返回0。</p><p>我们可以用<code>fwrite</code>直接把数据输出到流stream中,相应的是<code>fread</code></p><p>而<code>fprintf</code>是对输出进行格式化再写到stream流中,对应的是<code>fscanf</code></p><p><code>fputs</code>就是讲字符使出到流stream中,对应的是<code>fgetc</code></p><p>顺便提一下<code>int fflush(FILE *stream)</code>,对输出流来说,该函数将已写到缓冲区但尚未写入文件的所有数据写入到文件中。对输入流来说,其结果是未定义的。如果在写的过程中发生错误,则返回EOF,否则返回0。<code>fflush(NULL)</code>将清洗所有的输出流。</p><blockquote><p>刚开始学python也遇到缓冲区,print函数中flush参数的问题,以及缓冲策略。参考<a href="https://anyisalin.github.io/2017/10/13/python-file-buffer/">Python File Buffer</a>和<a href="https://blog.csdn.net/Granthoo/article/details/82880562">python的print(flush=True)实现动态loading……效果</a></p></blockquote><h2 id="3-UNIX-I-x2F-O函数"><a href="#3-UNIX-I-x2F-O函数" class="headerlink" title="3. UNIX I/O函数"></a>3. UNIX I/O函数</h2><p><img src="/images/unixIO/unixio.png"><br>上面说的标准I/O函数其实算是叫高级别的I/O函数,在Linux系统中,这些高级I/O函数都是通过使用由内核提供的系统级Unix I/O函数(较低级)来实现的。大多数时候,高级别I/O函数工作良好,没必要直接使用Unix I/O。那什么还要讲呢,两点,一是能帮助理解其他的系统概念,二是又是你除了Unix I/O函数以外别无选择。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs C"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><sys/types.h></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><sys/stat.h></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><fcntl.h></span></span><br><span class="hljs-type">int</span> <span class="hljs-title function_">open</span><span class="hljs-params">(<span class="hljs-type">char</span> *filename, <span class="hljs-type">int</span> flags, <span class="hljs-type">mode_t</span> mode)</span>;<br></code></pre></td></tr></table></figure><p>进程是通过<code>open</code>函数来打开一个已存在的文件或者创建一个新文件,返回一个描述符数字。返回的描述符总是进程中当前没有打开的最小描述符</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs C"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><unistd.h></span></span><br><span class="hljs-type">ssize_t</span> <span class="hljs-title function_">read</span><span class="hljs-params">(<span class="hljs-type">int</span> fd, <span class="hljs-type">void</span> *buf, <span class="hljs-type">size_t</span> n)</span>;<br><span class="hljs-type">ssize_t</span> <span class="hljs-title function_">write</span><span class="hljs-params">(<span class="hljs-type">int</span> fd, <span class="hljs-type">const</span> <span class="hljs-type">void</span> *buf, <span class="hljs-type">size_t</span> n)</span>;<br></code></pre></td></tr></table></figure><p>应用程序是通过分别调用<code>read</code>和<code>write</code>函数来执行输入和输出的。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><code class="hljs C"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><unistd.h></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><sys/types.h></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><sys/stat.h></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><fcntl.h></span></span><br><br><span class="hljs-type">int</span> <span class="hljs-title function_">main</span><span class="hljs-params">(<span class="hljs-type">void</span>)</span><br>{<br><span class="hljs-type">char</span> c;<br><span class="hljs-type">int</span> fd;<br>fd = open(<span class="hljs-string">"foo.txt"</span>,O_WRONLY,<span class="hljs-number">1</span>);<br><span class="hljs-keyword">while</span>(read(STDOUT_FILENO, &c, <span class="hljs-number">1</span>) != <span class="hljs-number">0</span>){<br> write(fd, &c, <span class="hljs-number">1</span>);<br>}<br><span class="hljs-built_in">exit</span>(<span class="hljs-number">0</span>);<br><br>}<br></code></pre></td></tr></table></figure><p>这里写了个例子,打开<code>foo.txt</code>,从标准输入(即键盘输入)中获取字符只要<code>read</code>函数读到的字节数不为0就把字符写进<code>foo.txt</code>中。</p><h2 id="4-更健壮的RIO包"><a href="#4-更健壮的RIO包" class="headerlink" title="4. 更健壮的RIO包"></a>4. 更健壮的RIO包</h2><p>RIO包更强壮一点,带有无缓冲的输入输出函数以及带缓冲的输入函数,但这不是我这篇文章的重点,有兴趣可以去网上找找。</p><h2 id="5-我该使用哪些I-x2F-O函数"><a href="#5-我该使用哪些I-x2F-O函数" class="headerlink" title="5. 我该使用哪些I/O函数"></a>5. 我该使用哪些I/O函数</h2><p>Unix I/O模型实在操作系统内核中实现的。应用程序可以通过诸如<code>open</code>、<code>close</code>、<code>lseek</code>、<code>read</code>、<code>write</code>、<code>stat</code>这样的函数来访问Unix I/O。较高级别的RIO和标准I/O函数都是基于Unix I/O函数来实现的。具体可以看第三节的那个图,一目了然</p><p>那么,选哪一个? 对我自己来说:只要有可能就使用标准I/O。对磁盘和终端设备来说,标准I/O函数是首选方法。大多数C程序员在其整个职业生涯只使用标准I/O!</p><h2 id="6-I-x2F-O重定向"><a href="#6-I-x2F-O重定向" class="headerlink" title="6. I/O重定向"></a>6. I/O重定向</h2><p>Linux shell提供了I/O重定向操作符,允许用户将磁盘文件和标准输入输出联系在一起。例如<code>ls > foo.fxt</code>,使用shell加载和执行ls程序,将标准输出重定向到磁盘文件<code>foo.txt</code>。一些更加复杂的重定向操作可以看<a href="https://blog.csdn.net/ithomer/article/details/9288353">Linux Shell 1>/dev/null 2>&1 含义</a></p><h2 id="7-总结"><a href="#7-总结" class="headerlink" title="7. 总结"></a>7. 总结</h2><p>断断续续花了两天时间做了这篇总结,回头想想起因是啥,回头<code>C程序设计语言</code>这本圣经时,以前只是看到结构体那一章就盖上书了,结果这次不仅翻到了后一章,还把附录翻了一遍,刚好手头有本<code>CSAPP</code>,越翻又糊涂又明白,遂总结了一下。</p><p>其实写到后面,发现,有些低级函数作为一般程序员压根用不到,还是得用高级函数,但是在这学习的过程中,很多相关概念才是最让人解惑,让自己理解整个过程以及其他系统概念。其实平常常用的文本文件读写,Python相关的函数封装地更简单,直接两行代码搞定。然而,所有编程语言都基于操作系统,当总结了相关概念之后,无论啥语言, 关于I/O根本的东西都是一样的。而自己偏爱使用Python所谓高级语言后,简单便捷,但所有的东西经过封装之后隐藏了幕后的细节,只是把语法当成工具,或许,工具就够了,又或许,还不够。</p><p>当了解了系统I/O之后,接下去,回到过去,跟着历史和大佬们一起,开发Linux I/O模型!!</p>]]></content>
<categories>
<category>Linux</category>
</categories>
<tags>
<tag>Linux</tag>
<tag>C</tag>
<tag>I/O</tag>
</tags>
</entry>
<entry>
<title>在esp32上使用micropython的一些心得</title>
<link href="/2020/09/16/esp32-micropython/"/>
<url>/2020/09/16/esp32-micropython/</url>
<content type="html"><![CDATA[<p>其实老早之前买了<strong>物联网Python开发实战</strong>这本书的时候就注意到了Pyboard(基于stm32),还买了个板子实践了下,但是感觉<code>micropython</code>这个东西有点冷门,像个半成品一样(狭隘了),就把板子丢一边,继续用Arduino个树莓派了。最近在寻找单片机的时候,发现了esp32以及<code>micropython</code>,发现好像有点厉害,某宝店铺什么的的热度都不错,就买回来些许测试一波,发现很好用,顺便记录一些关键点。</p><h2 id="1-什么是Micropython"><a href="#1-什么是Micropython" class="headerlink" title="1. 什么是Micropython"></a>1. 什么是Micropython</h2><h3 id="1-1-语言优势"><a href="#1-1-语言优势" class="headerlink" title="1.1 语言优势"></a>1.1 语言优势</h3><p>MicroPython是以Python语言为基础能用于MCU的编程语言系统集合,它是运行在一系列微控制器硬件平台上的系统的名称,类似于Arduino。是近年来开源社区中很是热门的项目之一,它功能强大,使用简单,是创客、DIY爱好者、工程师相当好的工具,适用于小学到大学各个年龄段同学们完成对Python语言的入门,掌握基础电路知识和理解编程思想及原理,同时也适合熟练使用python或其他高级语言(JAVA,.NET,PHP等)但不懂或不熟悉硬件知识的程序猿完成嵌入式开发,同时也可以使用在专业开发中。相比另一个创客神器Arduino,MicroPython使用更加简单、方便,入门更快,性能也更好,更加适合初学者。</p><p>相比于C语言上,切身的感受到,需要学习者花费更多的精力去理解引用,指针等等对于一个新手而言完全搞不清楚的概念。而且当我们使用C语言开发时,一个很小的功能就需要花费很多行代码去实现,吃尽了苦头且没有吃到过甜头</p><h3 id="1-2-MicroPython-VS-传统硬件开发"><a href="#1-2-MicroPython-VS-传统硬件开发" class="headerlink" title="1.2 MicroPython VS 传统硬件开发"></a>1.2 MicroPython VS 传统硬件开发</h3><p>对于传统的硬件开发而言,大多使用C/C++这类编译型语言。<br>这种开发方式往往需要以下几个步骤:1.编写代码 2.编译代码 3.烧录到芯片 4.运行程序</p><p>在这种模式下,当你发现自己的代码里有一行出了bug,即使是很微小的改动就能够修复,但你仍旧需要重新执行编译和烧录,才能够正确的运行。</p><p>而对于MicroPython而言,只要我们给芯片刷入了MicroPython固件,(结合我们上节所解释的解释器的概念,这就相当于我们为自己雇佣了一个私人翻译官,随时在芯片里等待着为我们翻译解释代码给芯片去执行。)之后的一切就简单得多。我们可以以两种方式来进行开发:</p><p><strong>进入交互式解释器 测试和运行代码</strong><br>我们可以进入交互式的解释器环境随时输入代码执行(进入这位翻译官的办公室,当面发号施 令),发现代码有误,可以立即更改。</p><p><strong>将代码保存到文件系统,让解释器开机之后按特定规则去执行</strong>。<br>我们还可以将代码整理好,保存到MicroPython的文件系统中(没错这就相当于这位翻译官的文件夹,你可以让这位翻译官按照你规定的顺序去执行这些文件中的代码)。MicroPython默认开机后从boot.py开始执行,然后开始执行main.py。</p><h2 id="2-板子选择以及固件烧录"><a href="#2-板子选择以及固件烧录" class="headerlink" title="2.板子选择以及固件烧录"></a>2.板子选择以及固件烧录</h2><h3 id="2-1-板子选择"><a href="#2-1-板子选择" class="headerlink" title="2.1 板子选择"></a>2.1 板子选择</h3><p>我选择了我国的乐鑫公司设计研发的NodeMCU-32S,高性能,低功耗,物美价廉,自带了WIFI和蓝牙模块</p><h3 id="2-2-固件烧录"><a href="#2-2-固件烧录" class="headerlink" title="2.2 固件烧录"></a>2.2 固件烧录</h3><p>安装esptool<br><code>pip install esptool</code><br>擦除整个闪存<br><code>esptool.py --chip esp32 --port /dev/ttyUSB0 erase_flash</code><br>烧入固件,固件从地址0x1000开始:<br><code>esptool.py --chip esp32 --port /dev/ttyUSB0 --baud 460800 write_flash -z 0x1000 esp32-20190125-v1.10.bin </code></p><h2 id="3-Micropython工作流程"><a href="#3-Micropython工作流程" class="headerlink" title="3. Micropython工作流程"></a>3. Micropython工作流程</h2><p>来到了开发中的工作流程,这是比较适合自己的方法,值得着重记录。<br>Pycharm中的micropython插件本来是所有任务一把梭,但是有时候REPL会失效,上传文件倒是很好,所以REPL有其他选择</p><p>因为我是在Ubuntu中开发,所以存在串口权限问题,在连上电脑时,必须<code>sudo usermod -a -G dialout $USER</code> 一劳永逸!</p><h3 id="3-1-REPL"><a href="#3-1-REPL" class="headerlink" title="3.1 REPL"></a>3.1 REPL</h3><p>交互式的Python会话,爽爆了,测试语法,,硬件啥的简单的一笔,再也不用烧录测试了。<br><code>rshell</code>中的repl虽然好,但是当单片机中本来跑着一个循环任务,然后你repl进去会强制中中断或者没反应,也不知道啥情况,怪怪的,所以我直接使用<br>串口终端picocom:<code>picocom -b 115200 /dev/ttyUSB0</code>,如要退出,先按Ctrl + A进入转义模式,再按Ctrl + Q即可正常退出。<br>在特定情况下也可以用WebREPL,允许你通过WiFi使用MicroPython的REPL,通过浏览器连接,使用了WebSockt通信协议。但我没尝试过,=。=,具体看文章末的链接</p><h3 id="3-2-代码部署"><a href="#3-2-代码部署" class="headerlink" title="3.2 代码部署"></a>3.2 代码部署</h3><p>Pycharm中的micropython插件自带的Flash上传文件很好用,但是有时候还想要看看单片机里面的文件系统,这里<code>rshell</code>就派上用场了,我们用<code>rshell -b 115200 -p /dev/ttyUSB0</code>进入串口文件系统,<code>ls /pyboard</code>或者<code>ls /flash</code>查看单片机中的文件,当然也可以进行复制编辑等</p><h2 id="4-资料补充"><a href="#4-资料补充" class="headerlink" title="4. 资料补充"></a>4. 资料补充</h2><p>一些<code>micropython</code>的用法就说了,很多地方可以看。推荐一些资料,看完这些,基本需求一般都能掌握了</p><ul><li><p><a href="https://realpython.com/micropython/#micropython-workflow">MicroPython: An Intro to Programming Hardware in Python</a></p></li><li><p><a href="http://docs.micropython.org/en/latest/pyboard/quickref.html">Micropython doc</a></p></li><li><p><a href="https://randomnerdtutorials.com/esp32-pinout-reference-gpios/">ESP32 Pinout Reference: Which GPIO pins should you use?</a></p></li><li><p><a href="https://github.com/dhylands/rshell">rshell doc</a></p></li><li><p><a href="http://www.1zlab.com/wiki/micropython-esp32/">MicroPython-ESP32基础教程</a></p></li><li><p><a href="https://blog.jetbrains.com/pycharm/2018/01/micropython-plugin-for-pycharm/"> MicroPython Plugin for PyCharm</a></p></li></ul>]]></content>
<categories>
<category>micropython</category>
</categories>
<tags>
<tag>esp32</tag>
<tag>micropython</tag>
</tags>
</entry>
<entry>
<title>Golang数据初始化,零值以及一些陷阱</title>
<link href="/2020/07/07/goInitialize/"/>
<url>/2020/07/07/goInitialize/</url>
<content type="html"><![CDATA[<blockquote><p>基础不牢,地动山摇</p></blockquote><p>不像Python这种动态语言,遇见<strong>对象</strong>就是一言不合的赋给变一个<strong>变量</strong>,然后查看;而Go语言是静态类型语言,因此变量是有明确类型的,编译器也会检查变量类型的正确性。</p><h2 id="1-变量的初始化"><a href="#1-变量的初始化" class="headerlink" title="1. 变量的初始化"></a>1. 变量的初始化</h2><h3 id="1-1-声明"><a href="#1-1-声明" class="headerlink" title="1.1 声明"></a>1.1 声明</h3><p>声明变量的一般形式是使用 var 关键字:<code>var name type</code>,其中,var 是声明变量的关键字,name 是变量名,type 是变量的类型。<br>也可以进行批量声明:</p><figure class="highlight go"><table><tr><td class="gutter"><div class="code-wrapper"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></div></td><td class="code"><pre><code class="hljs go"><span class="hljs-keyword">var</span> (<br> a <span class="hljs-type">int</span><br> b <span class="hljs-type">string</span><br> c []<span class="hljs-type">float32</span><br> d <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">()</span></span> <span class="hljs-type">bool</span><br> e <span class="hljs-keyword">struct</span> {<br> x <span class="hljs-type">int</span><br> }<br>)<br></code></pre></td></tr></table></figure><h3 id="1-2-初始化"><a href="#1-2-初始化" class="headerlink" title="1.2 初始化"></a>1.2 初始化</h3><p>变量初始化的标准格式<code>var 变量名 类型 = 表达式</code>,例如<code>var age int = 25</code>;在标准格式的基础上,将 int 省略后,编译器会尝试根据等号右边的表达式推导 age 变量的类型:<code>var age = 25</code>,这个很容易理解。</p><h3 id="1-3-短变量声明并初始化"><a href="#1-3-短变量声明并初始化" class="headerlink" title="1.3 短变量声明并初始化"></a>1.3 短变量声明并初始化</h3><p>var 的变量声明还有一种更为精简的写法,例如:<code>age := 100</code>,这是Go语言的推导声明写法,编译器会自动根据右值类型推断出左值的对应类型,但只能在函数中使用。</p><h2 id="2-Go类型默认的零值"><a href="#2-Go类型默认的零值" class="headerlink" title="2. Go类型默认的零值"></a>2. Go类型默认的零值</h2><p><strong>不带初始值的变量声明会被设置为它们的零值:</strong></p><ul><li>0 for all <strong>integer</strong> types</li><li>0.0 for <strong>floating point</strong> numbers</li><li>false for <strong>booleans</strong></li><li>“” for <strong>strings</strong></li><li>nil for <strong>interfaces, slices, channels, maps, pointers</strong> and <strong>functions</strong></li></ul><p>如果<strong>array</strong>或<strong>struct</strong>声明后未指定值,则其中的元素的字段将为零值。该初始化以递归方式完成:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-keyword">type</span> T <span class="hljs-keyword">struct</span> {<br> n <span class="hljs-type">int</span><br> f <span class="hljs-type">float64</span><br> next *T<br>}<br>fmt.Println([<span class="hljs-number">2</span>]T{}) <span class="hljs-comment">// [{0 0 <nil>} {0 0 <nil>}]</span><br></code></pre></td></tr></table></figure><h2 id="3-make和new的区别"><a href="#3-make和new的区别" class="headerlink" title="3. make和new的区别"></a>3. make和new的区别</h2><p>Go语言中new和make是内建的两个函数,主要用来创建分配类型内存。 在我们定义生成变量的时候,可能会觉得有点迷惑,其实他们的规则很简单。</p><h3 id="3-1-new"><a href="#3-1-new" class="headerlink" title="3.1 new"></a>3.1 new</h3><p><code>new(T)</code>为一个 T 类型新值分配空间并将此空间初始化为 T 的零值,返回的是新值的地址,也就是 T 类型的指针 *T,该指针指向 T 的新分配的零值。new要点:</p><ul><li>内置函数 new 分配空间</li><li>传递给new 函数的是一个类型,不是一个值</li><li>返回值是 指向这个新分配的零值的指针</li></ul><h3 id="3-2-make"><a href="#3-2-make" class="headerlink" title="3.2 make"></a>3.2 make</h3><p><code>make(T, args)</code> 返回的是初始化之后的 T 类型的值,这个新值并不是 T 类型的零值,也不是指针 *T,是经过初始化之后的 T 的引用。make 也是内建函数,你可以从<a href="http://golang.org/pkg/builtin/#make">官方文档这里</a>看到,它的函数原型 比 new 多了一个(长度)参数,返回值也不同.</p><p><strong>make 只能用于 slice,map,channel 三种类型</strong>, 并且只能是这三种对象。 和 new 一样,第一个参数是 类型,不是一个值. 但是make 的返回值就是这个类型(即使一个引用类型),而不是指针.具体的返回值,依赖具体传入的类型.</p><h3 id="3-3-两者区别"><a href="#3-3-两者区别" class="headerlink" title="3.3 两者区别"></a>3.3 两者区别</h3><p><code>new(T)</code> 返回 T 的指针 *T 并指向 T 的零值。<br><code>make(T)</code> 返回的初始化的 T,只能用于 slice,map,channel,要获得一个显式的指针,使用new进行分配,或者显式地使用一个变量的地址。<br>new 函数分配内存,make函数初始化。</p><p>具体可以看<a href="https://mojotv.cn/tutorial/golang-make-or-new">mojoyv的文章</a> 以及三月沙的<a href="https://sanyuesha.com/2017/07/26/go-make-and-new/">理解 Go make 和 new 的区别</a>,包含很多代码例子</p><h2 id="4-一些初始化相关的陷阱"><a href="#4-一些初始化相关的陷阱" class="headerlink" title="4. 一些初始化相关的陷阱"></a>4. 一些初始化相关的陷阱</h2><h3 id="4-1-在nil-map中赋值"><a href="#4-1-在nil-map中赋值" class="headerlink" title="4.1 在nil map中赋值"></a>4.1 在nil map中赋值</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs go"># <span class="hljs-type">error</span> code:<br><span class="hljs-keyword">var</span> m <span class="hljs-keyword">map</span>[<span class="hljs-type">string</span>]<span class="hljs-type">float64</span><br>m[<span class="hljs-string">"pi"</span>] = <span class="hljs-number">3.1416</span><br><br># OUTPUT: <span class="hljs-built_in">panic</span>: assignment to entry in <span class="hljs-literal">nil</span> <span class="hljs-keyword">map</span><br><br># we should:<br>m := <span class="hljs-built_in">make</span>(<span class="hljs-keyword">map</span>[<span class="hljs-type">string</span>]<span class="hljs-type">float64</span>)<br>m[<span class="hljs-string">"pi"</span>] = <span class="hljs-number">3.1416</span><br></code></pre></td></tr></table></figure><h3 id="4-2-无效的内存地址或nil指针取消引用"><a href="#4-2-无效的内存地址或nil指针取消引用" class="headerlink" title="4.2 无效的内存地址或nil指针取消引用"></a>4.2 无效的内存地址或nil指针取消引用</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-keyword">type</span> Point <span class="hljs-keyword">struct</span> {<br> X, Y <span class="hljs-type">float64</span><br>}<br><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(p *Point)</span></span> Abs() <span class="hljs-type">float64</span> {<br> <span class="hljs-keyword">return</span> math.Sqrt(p.X*p.X + p.Y*p.Y)<br>}<br><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {<br> <span class="hljs-keyword">var</span> p *Point<br> fmt.Println(p.Abs())<br>}<br></code></pre></td></tr></table></figure><p>上面代码会报错:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-built_in">panic</span>: runtime <span class="hljs-type">error</span>: invalid memory address or <span class="hljs-literal">nil</span> pointer dereference<br>[signal SIGSEGV: segmentation violation code=<span class="hljs-number">0xffffffff</span> addr=<span class="hljs-number">0x0</span> pc=<span class="hljs-number">0xd2c5a</span>]<br><br>goroutine <span class="hljs-number">1</span> [running]:<br>main.(*Point).Abs(...)<br>../main.<span class="hljs-keyword">go</span>:<span class="hljs-number">6</span><br>main.main()<br>../main.<span class="hljs-keyword">go</span>:<span class="hljs-number">11</span> +<span class="hljs-number">0x1a</span><br><br></code></pre></td></tr></table></figure><p>未初始化的指针是nil,我们没法使用它,有两种解决方案:<br>创建一个指针</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {<br> <span class="hljs-keyword">var</span> p *Point = <span class="hljs-built_in">new</span>(Point)<br> fmt.Println(p.Abs())<br>}<br></code></pre></td></tr></table></figure><p>或者直接跳过指针,用值接收者调用指针方法</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {<br> <span class="hljs-keyword">var</span> p Point <span class="hljs-comment">// has zero value Point{X:0, Y:0}</span><br> fmt.Println(p.Abs())<br>}<br></code></pre></td></tr></table></figure><h3 id="4-3-一个copy错误"><a href="#4-3-一个copy错误" class="headerlink" title="4.3 一个copy错误"></a>4.3 一个copy错误</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-keyword">var</span> src, dst []<span class="hljs-type">int</span><br>src = []<span class="hljs-type">int</span>{<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>}<br><span class="hljs-built_in">copy</span>(dst, src) <span class="hljs-comment">// Copy elements to dst from src.</span><br>fmt.Println(<span class="hljs-string">"dst:"</span>, dst)<br> <br># OUTPUT: dst: []<br></code></pre></td></tr></table></figure><p>很明显这里的src是一个slice的零值nil,要完整副本的COPY,必须分配容量足够大的目标切片:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-keyword">var</span> src, dst []<span class="hljs-type">int</span><br>src = []<span class="hljs-type">int</span>{<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>}<br>dst = <span class="hljs-built_in">make</span>([]<span class="hljs-type">int</span>, <span class="hljs-built_in">len</span>(src))<br>n := <span class="hljs-built_in">copy</span>(dst, src)<br>fmt.Println(<span class="hljs-string">"dst:"</span>, dst, <span class="hljs-string">"(copied"</span>, n, <span class="hljs-string">"numbers)"</span>)<br><br># OUTPUT: dst: [<span class="hljs-number">1</span> <span class="hljs-number">2</span> <span class="hljs-number">3</span>] (copied <span class="hljs-number">3</span> numbers)<br></code></pre></td></tr></table></figure>]]></content>
<categories>
<category>Go</category>
</categories>
<tags>
<tag>Go</tag>
</tags>
</entry>
<entry>
<title>Golang代码工具使用</title>
<link href="/2020/07/01/goTools/"/>
<url>/2020/07/01/goTools/</url>
<content type="html"><![CDATA[<blockquote><p>工欲善其事必先利其器</p></blockquote><p>在学习golang的过程中,经常遇到教程让代码遵循<code>gofmt</code>,<code>golint</code>,<code>govet等</code>,而我虽然有Goland的加持,但对其中的区别也是稀里糊涂,在这里做一个简单的总结,主要参考的官方的<a href="https://github.com/golang/go/wiki/CodeTools">CodeTools</a>专栏。感谢Google!</p><h2 id="1-什么是-Linting"><a href="#1-什么是-Linting" class="headerlink" title="1. 什么是 Linting?"></a>1. 什么是 Linting?</h2><p>Linting是自动检查源代码中是否存在编程错误和样式错误。这可以通过使用lint 工具(也称为<strong>linter</strong>)来完成。lint 工具是基本的静态代码分析器<br>术语linting最初来自于C语言的Unix实用程序。当然现在有许多可用于各种编程语言的代码linter</p><h3 id="1-1-为什么-Linting-重要"><a href="#1-1-为什么-Linting-重要" class="headerlink" title="1.1 为什么 Linting 重要?"></a>1.1 为什么 Linting 重要?</h3><p>Linting对于减少错误和提高代码的整体质量很重要。使用lint 工具可以通过早期发现错误来帮助加速开发并降低成本</p><h3 id="1-2-Lint-Tools-是如何工作的"><a href="#1-2-Lint-Tools-是如何工作的" class="headerlink" title="1.2 Lint Tools 是如何工作的"></a>1.2 Lint Tools 是如何工作的</h3><p>这是lint 工具通常如何适应开发过程的方式。</p><ol><li>编写代码</li><li>编译</li><li>用linter进行分析</li><li>查看该工具识别的错误</li><li>更改代码以解决错误</li><li>代码干净后,链接模块。</li><li>用linter对其进行分析</li><li>进行手动代码审查</li></ol><p>lint 编程是一种自动检查。它应该开发早期进行,在代码审查和测试之前。那是因为自动代码检查使代码审查和测试过程更加有效,使开发人员有时间专注于正确的事情。<br>关于lint其他一些方面的可以查阅<a href="https://www.perforce.com/blog/qac/what-lint-code-and-why-linting-important">这里</a></p><h2 id="2-Lint工具介绍"><a href="#2-Lint工具介绍" class="headerlink" title="2. Lint工具介绍"></a>2. Lint工具介绍</h2><ul><li><p><a href="https://golang.org/cmd/gofmt/"><code>gofmt - standard Go code formatter</code></a><br>它使用制表符进行缩进,并使用空格进行对齐。对齐方式使用的是编辑器固定宽度的字体</p></li><li><p><a href="https://github.com/golang/lint"><code>golint - Detects style mistakes in Go code</code></a><br>检测样式错误,比如函数的注释,变量的注释等等</p></li><li><p><a href="https://godoc.org/golang.org/x/tools/cmd/goimports"><code>goimports - Format code and fix your import statements</code></a><br> 更新Go的import行,添加缺少的行并删除未引用的行</p></li><li><p><a href="http://golang.org/cmd/vet/"><code>govet - Examine Go source code</code></a><br> 检查Go的源代码并报告可疑的部分,例如其参数与格式字符串不一致的Printf调用。vet使用的试探法不能保证所有报告都是真实的问题,但可以发现编译器未捕获的错误</p></li><li><p><a href="https://golangci-lint.run/"><code>golangci-lint - Bundle of gofmt, golint, govet and many other tools</code></a><br>一把梭,各种linter都有</p></li></ul><p>关于其中的一些区别:</p><blockquote><p>Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes.<br> Golint differs from govet. Govet is concerned with correctness, whereas golint is concerned with coding style. Golint is in use at Google, and it seeks to match the accepted style of the open source Go project</p></blockquote><h2 id="3-工具的选择"><a href="#3-工具的选择" class="headerlink" title="3. 工具的选择"></a>3. 工具的选择</h2><p>因为我用的Goland,IDE自带了<code>vet</code>,<code>fmt</code>,<code>import</code>,我已经养成了习惯,每完成更新一个就会手动format一下,当然也可以放置在<strong>File Watcher <strong>中自动格式化。之后每次的</strong>commit</strong>都lint一下,然后用<code>golangci-lint</code>查一下一些错误,比如<code>if isExist == True</code>检查改为<code>if isExist</code>,为初学者矫正很多写代码的错误习惯。</p><p>但是在<code>golint</code>官方有这么一段话:</p><blockquote><p>The suggestions made by golint are exactly that: suggestions. Golint is not perfect, and has both false positives and false negatives. Do not treat its output as a gold standard. We will not be adding pragmas or other knobs to suppress specific warnings, so do not expect or require code to be completely “lint-free”. In short, this tool is not, and will never be, trustworthy enough for its suggestions to be enforced automatically, for example as part of a build process. Golint makes suggestions for many of the mechanically checkable items listed in Effective Go and the CodeReviewComments wiki page.</p></blockquote><p>请不要将<code>golint</code>的输出建议视为黄金标准! 不用搞得像个强迫症患者(我稍微有点,哈哈)一样,一定要完全**”lint-free”**!</p>]]></content>
<categories>
<category>Go</category>
</categories>
<tags>
<tag>Go</tag>
</tags>
</entry>
<entry>
<title>Arduino温控PC风扇以及信息显示思路总结</title>
<link href="/2020/05/13/arduinoFanByTemp/"/>
<url>/2020/05/13/arduinoFanByTemp/</url>
<content type="html"><![CDATA[<p>温控风扇的一个总所周知的例子就是CPU上的散热风扇,风扇速度会随着CPU负载上升的同时增加,达到及时散热的目的,然而这次要捣鼓的东西也跟这个差不多但是是在放在大机柜里,四路温控风扇,并带有温度风扇速度显示,以及通过触摸显示屏设置上下温度阀值,我毫不犹豫选择了Arduino,这在油管上也有很多类似的项目,不过也杂而不全,翻来覆去,测试了各个部分然后确定了自己想要的方案。</p><h2 id="1-温度传感器选择"><a href="#1-温度传感器选择" class="headerlink" title="1. 温度传感器选择"></a>1. 温度传感器选择</h2><p><a href="https://arduino.nxez.com/2017/03/26/arduino-sensor-series-of-temperature-measurement.html">温度传感器有很多</a>,我选择了其中的<strong>热敏电阻</strong>和<strong>LM35</strong>进行了测试以及对比,有如下几点想说的:</p><ul><li>最开始我用的100k的热敏电阻和51k的电阻在3.3V下分压计算得到的温度和LM35基本差不多,我很满意,区别只是热敏电阻相对LM35来说浮动小很多</li><li>之后我热敏电阻在5V情况下分压计算温度发现跟之前有一两度的差距,我纠结了很久,反复查看分压得到的值,热敏电阻计算到的值跟之前就差了一点,但是代入公式温度有了一两度的差距,验证后的确如此,不纠结</li><li>LM35在跑了几天后不知道是线路问题还是啥直飙40+摄氏度,感觉不太可靠</li></ul><p>再加上项目要求,我选择了稳定性较高的热敏电阻,<strong>但是想要得到很高的测量精度,需要做很多优化工作,难度较大</strong>,好在对精度要求不高,机柜温度,八九不离十就行。热敏电阻的具体操作可以参考<a href="https://blog.csdn.net/Al_shawn/article/details/51287759">这里</a>,而LM35的在<a href="https://blog.hobbycomponents.com/?p=89">这边</a>,附带一个要用到的Arduino函数知识点<a href="https://www.quora.com/What-is-analogReference-function-in-Arduino-Why-and-when-it-should-be-used">What is analogReference() function in Arduino?</a></p><h2 id="2-PC风扇速度控制"><a href="#2-PC风扇速度控制" class="headerlink" title="2. PC风扇速度控制"></a>2. PC风扇速度控制</h2><p>我一开始拿到个<strong>3 pin的PC风扇(12伏特)来测试</strong>,但还有<strong>2 pin</strong>和<strong>4 pin</strong>,前期找到了一些资料来了解它们</p><ul><li><a href="http://m.elecfans.com/article/1104020.html">CPU风扇的工作原理</a></li><li><a href="https://forums.tomshardware.com/threads/fan-speed-control-how-to-2-pin-vs-3-pin-vs-4-pin.2200004/">Fan speed control how-to, 2-pin vs.3-pin vs. 4-pin</a></li><li><a href="https://forum.arduino.cc/index.php?topic=174093.0"> Control a 3-pin PC fan (Read 5239 times)</a></li></ul><p>具体操作我在油管上找到了篇绝佳的教程<a href="https://www.youtube.com/watch?v=Pw1kSS_FIKk&t=355s">Controlling fan speed with mosfet and Arduino</a>,我是完全照着这个来,完美,但是还是有几点要强调的:</p><ul><li>风扇速度可以通过PWM电压(无论几pin风扇)来控制也可以选择<strong>4 pin风扇</strong>的pwm线来控制,我选择了前者</li><li>视频中MOS管两端插了个电阻,当时有疑惑,后来得到了<a href="https://blog.csdn.net/luojing194/article/details/69397252">解决</a></li></ul><h2 id="3-风扇速度测量"><a href="#3-风扇速度测量" class="headerlink" title="3. 风扇速度测量"></a>3. 风扇速度测量</h2><p>我一开始简单地想在施加PWM电压控制风速的同时直接把占空比当做速度的百分比,这明显是合理的,且网上大多都是通过百分比来表示,相对于一串数字的RPM来说更加直观点<br>但是后来摸索来摸索去又进入了测量RPM的怪圈,因为我测试的是<strong>3 pin的风扇</strong>,有一根测速度的线在上面,这就很尴尬不测测不行了。<br>我同样在油管上找到了合适的教程<a href="https://www.youtube.com/watch?v=sWjd61ouRVY&list=LLhi4-nyxWBvXJnWZgVlRZmA&index=6&t=0s">How to measure Fan RPM with Arduino using hall effect sensor</a>以及一些<a href="https://zhuanlan.zhihu.com/p/27996069">霍尔效应的原理</a><br>风扇的RPM速度是拿到了,但是在整合程序的时候出现了两个问题:</p><ul><li>测量风扇速度必须在程序中腾出一秒来感应霍尔效应,那么在这一秒内Arduino等于阻塞了,但是又不好搞多线程导致程序有延迟</li><li>在使用PWM控制风扇的时候测量出来的霍尔效应是垃圾数值,不能用作于风扇速度,具体看<a href="https://forum.arduino.cc/index.php?topic=620421.0">这里</a>有这么段话**:One thing you need to be aware of is that when you are controlling a three pin fan via a MOSFET then you need to crank the PWM signal to 100% for a few milliseconds while reading the speed or else you will get garbage readings.**</li></ul><p>所以兜兜转转又回到了用百分比描述风扇速度</p><h2 id="4-显示屏选择"><a href="#4-显示屏选择" class="headerlink" title="4. 显示屏选择"></a>4. 显示屏选择</h2><p>Arduino显示屏我主要参考<a href="https://www.youtube.com/watch?v=E6quVf1_BIg&list=LLhi4-nyxWBvXJnWZgVlRZmA&index=4&t=73s">Top 5 Arduino Displays</a>,主流的显示屏都有讲到。但是我这边的要求是尺寸大一点带触摸功能最终我选择了<a href="https://item.taobao.com/item.htm?spm=a1z09.2.0.0.3e4a2e8dbwBdjH&id=610082128912&_u=evk2k025bb2">4寸TFT彩屏 480X320超高清液晶屏LCD触摸屏</a><br>,连接以及代码操作可以参考<a href="https://www.youtube.com/watch?v=9Ms59ofSJIY&list=LLhi4-nyxWBvXJnWZgVlRZmA&index=2&t=68">Arduino TFT LCD Touch Screen Tutorial</a>和<a href="https://www.youtube.com/watch?v=PAPW97X6IRM&list=LLhi4-nyxWBvXJnWZgVlRZmA&index=3&t=21s">Arduino Tutorial: 3.5” Color TFT display ILI9481 on Arduino Uno and Mega from Banggood.com</a>。其实中间过程比较坎坷,挑了好几次试验了蛮久才最终定下来,源于我对这方面只是的匮乏,不太懂,这里也强调几个点:</p><ul><li>选择shield显示屏,直白点说也就是可以直插到Arduino板子上,这样就免了接线烦恼直接用</li><li>选择<a href="https://github.com/prenticedavid/MCUFRIEND_kbv">MCUFRIEND_kbv</a>,里面有大量演示的例子以及代码让你校验这块屏幕,一些压感参数,这是我到最后才发现的。其中的代码优雅性也对自己有很大启发</li><li><code>MCUFRIEND_kbv/examples/TouchScreen_Calibr_native</code>是校验触摸屏,可以得到校准参数然后用到<code>examples/Touch_shield_new/</code>中,一路没问题那就没问题了,之后就开始自己的设计</li><li>一些相关函数可以参考<a href="http://adafruit.github.io/Adafruit-GFX-Library/html/class_adafruit___g_f_x.html#ab6e88c585d3ab6b4f95199361f224fc6">Adafruit GFX Library</a>,说到底<strong>MCUFRIEND_kbv</strong>是对<strong>Adafruit GFX</strong>的强大包装,自动识别驱动和尺寸,让小白一路畅通</li></ul><h2 id="5-图片和代码展示"><a href="#5-图片和代码展示" class="headerlink" title="5. 图片和代码展示"></a>5. 图片和代码展示</h2><p>代码当然不是最终版,很多还要修改调整的,但是核心的东西就不用变了,接线可以看上面的油管链接,电路图我是不会画的,=。=<br><img src="/images/fanbytemp/fan.jpeg"></p><figure class="highlight c"><table><tr><td class="gutter"><div class="code-wrapper"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br><span class="line">219</span><br><span class="line">220</span><br><span class="line">221</span><br></pre></div></td><td class="code"><pre><code class="hljs C"><span class="hljs-meta">#<span class="hljs-keyword">include</span><span class="hljs-string"><math.h></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><Adafruit_GFX.h></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><MCUFRIEND_kbv.h></span></span><br>MCUFRIEND_kbv tft; <span class="hljs-comment">// hard-wired for UNO shields anyway.</span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><TouchScreen.h></span></span><br><br><span class="hljs-comment">// Assign human-readable names to some common 16-bit color values:</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> BLACK 0x0000</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> BLUE 0x001F</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> RED 0xF800</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> GREEN 0x07E0</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> CYAN 0x07FF</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> MAGENTA 0xF81F</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> YELLOW 0xFFE0</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> WHITE 0xFFFF</span><br><br><span class="hljs-type">const</span> <span class="hljs-type">int</span> XP = <span class="hljs-number">8</span>, XM = A2, YP = A3, YM = <span class="hljs-number">9</span>; <span class="hljs-comment">//ID=0x7796</span><br><span class="hljs-type">const</span> <span class="hljs-type">int</span> TS_LEFT = <span class="hljs-number">922</span>, TS_RT = <span class="hljs-number">122</span>, TS_TOP = <span class="hljs-number">53</span>, TS_BOT = <span class="hljs-number">933</span>;<br><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> MINPRESSURE 200</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> MAXPRESSURE 1000</span><br><br><span class="hljs-comment">// 触摸屏实例化</span><br>TouchScreen ts = TouchScreen(XP, YP, XM, YM, <span class="hljs-number">300</span>);<br>TSPoint tp;<br><span class="hljs-comment">// 触摸不同区域的不同标志</span><br>byte touch;<br><br><span class="hljs-comment">// 第一行的初始坐标以及行间距</span><br><span class="hljs-type">const</span> <span class="hljs-type">uint16_t</span> line_x = <span class="hljs-number">8</span>, line_y = <span class="hljs-number">20</span>, line_distance = <span class="hljs-number">40</span>;<br><span class="hljs-comment">// + - 按钮的位置大下参数,一共四个按钮小方块,以左上角第一个作为起点,side_正方形按钮边长</span><br><span class="hljs-comment">// distance分别为了左右上下两个方块的间隔,symbol为符号到正方向边上的距离,intital_y表达式中的常量3代表与第一行之间的偏移量</span><br><span class="hljs-type">const</span> <span class="hljs-type">uint16_t</span> initial_x = <span class="hljs-number">300</span> , initial_y = line_y - <span class="hljs-number">3</span> , side = <span class="hljs-number">30</span>, x_distance = <span class="hljs-number">50</span>, y_distance = <span class="hljs-number">40</span> , symbol_x = <span class="hljs-number">7</span>, symbol_y = <span class="hljs-number">5</span>;<br><br><span class="hljs-type">int16_t</span> BOXSIZE;<br><span class="hljs-type">int16_t</span> PENRADIUS = <span class="hljs-number">1</span>;<br><span class="hljs-type">uint16_t</span> ID, oldcolor, currentcolor;<br><span class="hljs-type">const</span> byte Orientation = <span class="hljs-number">1</span>; <span class="hljs-comment">//PORTRAIT</span><br><br><span class="hljs-comment">//MOSFET Gate 控制电源PWM,选择PWM pin 44~46 for mega </span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> Gate 44</span><br><br><span class="hljs-comment">//MF52-100K 常温25摄氏度(298.15K)下阻值为100k欧</span><br><span class="hljs-type">const</span> <span class="hljs-type">float</span> voltagepower = <span class="hljs-number">5.0</span>;<br><span class="hljs-type">const</span> <span class="hljs-type">float</span> R = <span class="hljs-number">51</span>; <span class="hljs-comment">//采样电阻为51千欧</span><br><span class="hljs-type">const</span> <span class="hljs-type">int</span> B = <span class="hljs-number">3950</span>;<br><span class="hljs-type">const</span> <span class="hljs-type">double</span> T1 = <span class="hljs-number">273.15</span> + <span class="hljs-number">25</span>; <span class="hljs-comment">//常温</span><br><span class="hljs-type">const</span> <span class="hljs-type">double</span> R1 = <span class="hljs-number">100</span>; <span class="hljs-comment">//常温对应的阻值,注意单位是千欧</span><br><span class="hljs-type">const</span> byte analogid = <span class="hljs-number">8</span>; <span class="hljs-comment">//A8处读取电压值</span><br><br><span class="hljs-comment">// 风扇速度调节的温度范围</span><br>byte MinTemp = <span class="hljs-number">25</span>;<br>byte MaxTemp = <span class="hljs-number">32</span>;<br><span class="hljs-comment">// 风扇电压模拟值变量</span><br>byte analogvalue;<br><br><br><span class="hljs-type">void</span> <span class="hljs-title function_">show_threshold</span><span class="hljs-params">(<span class="hljs-type">void</span>)</span><br>{<br> <span class="hljs-comment">// 显示设置值</span><br> tft.setCursor(line_x, line_y);<br> tft.print(<span class="hljs-string">"MinTemp:"</span>); <span class="hljs-comment">// 第一行</span><br> tft.print(MinTemp);<br> tft.println(<span class="hljs-string">" \367C"</span>);<br> tft.setCursor(line_x, line_y + line_distance * <span class="hljs-number">1</span>); <span class="hljs-comment">// 第二行</span><br> tft.print(<span class="hljs-string">"MaxTemp:"</span>);<br> tft.print(MaxTemp);<br> tft.println(<span class="hljs-string">" \367C"</span>);<br>}<br><br><br><span class="hljs-type">void</span> <span class="hljs-title function_">showTempAndControlFan</span><span class="hljs-params">(<span class="hljs-type">void</span>)</span><br>{<br> <span class="hljs-comment">//获得A1处的电压值</span><br> <span class="hljs-type">double</span> digitalValue = analogRead(analogid);<br> <span class="hljs-type">double</span> voltageValue = (digitalValue / <span class="hljs-number">1023</span>) * <span class="hljs-number">5</span>;<br> <span class="hljs-comment">//通过分压比获得热敏电阻的阻值</span><br> <span class="hljs-type">double</span> Rt = ((voltagepower - voltageValue) * R) / voltageValue;<br> <span class="hljs-comment">//换算得到温度值</span><br> <span class="hljs-type">double</span> temp = ((T1 * B) / (B + T1 * <span class="hljs-built_in">log</span>(Rt / R1))) - <span class="hljs-number">273.15</span>;<br> <span class="hljs-comment">// 显示温度</span><br> tft.setCursor(line_x, line_y + line_distance * <span class="hljs-number">2</span>); <span class="hljs-comment">// 第三行</span><br> tft.print(<span class="hljs-string">"Current Temp: "</span>);<br> tft.print((<span class="hljs-type">int</span>)temp);<br> tft.print(<span class="hljs-string">" \367C"</span>);<br><br> <span class="hljs-comment">// 温度控制风扇</span><br> <span class="hljs-keyword">if</span> (temp >= MaxTemp) {<br> analogvalue = <span class="hljs-number">255</span>;<br> <span class="hljs-comment">//digitalWrite(buzzer, HIGH);</span><br> }<br> <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (temp >= MinTemp) {<br> <span class="hljs-comment">//根据温度MinTemp~MaxTemp调节风扇速度对应analog值128~255</span><br> analogvalue = <span class="hljs-built_in">map</span>(temp, MinTemp, MaxTemp, <span class="hljs-number">128</span>, <span class="hljs-number">255</span>);<br> <span class="hljs-comment">//digitalWrite(buzzer, LOW);</span><br> }<br> <span class="hljs-keyword">else</span> {<br> analogvalue = <span class="hljs-number">0</span>;<br> <span class="hljs-comment">//digitalWrite(buzzer, LOW);</span><br> }<br> analogWrite(Gate, analogvalue);<br>}<br><br><span class="hljs-type">void</span> <span class="hljs-title function_">show_fan</span><span class="hljs-params">(<span class="hljs-type">void</span>)</span><br>{<br> <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> i = <span class="hljs-number">1</span>; i <= <span class="hljs-number">4</span>; i++) {<br> tft.setCursor(line_x, line_y + line_distance * (<span class="hljs-number">2</span> + <span class="hljs-number">1</span> * i));<br> tft.print(<span class="hljs-string">"Fan"</span>);<br> tft.print(i);<br> tft.print(<span class="hljs-string">" Speed: "</span>);<br> <span class="hljs-keyword">if</span> (analogvalue == <span class="hljs-number">255</span>) {<br> tft.print(<span class="hljs-string">"100%"</span>);<br> }<br> <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (analogvalue == <span class="hljs-number">0</span>) {<br> tft.print(<span class="hljs-string">" 0%"</span>);<br> }<br> <span class="hljs-keyword">else</span> {<br> tft.print(<span class="hljs-string">" "</span>);<br> tft.print(analogvalue * <span class="hljs-number">100</span> / <span class="hljs-number">255</span>);<br> tft.print(<span class="hljs-string">"%"</span>);<br> }<br> }<br>}<br><br><br><span class="hljs-type">void</span> <span class="hljs-title function_">setup</span><span class="hljs-params">()</span> {<br> Serial.begin(<span class="hljs-number">9600</span>);<br> Serial.println(<span class="hljs-string">"start..."</span>);<br><br> tft.reset();<br> ID = tft.readID();<br> tft.begin(ID);<br> tft.setRotation(Orientation);<br> tft.fillScreen(BLACK);<br> tft.setTextColor(WHITE);<br> tft.setTextSize(<span class="hljs-number">2</span>);<br><br> <span class="hljs-comment">//https://forum.arduino.cc/index.php?topic=364055.0</span><br> <span class="hljs-comment">// 设置加减符号边框</span><br> tft.fillRect(initial_x, initial_y, side, side, MAGENTA);<br> tft.fillRect(initial_x + x_distance, initial_y, side, side, MAGENTA);<br> tft.fillRect(initial_x, initial_y + y_distance , side, side, MAGENTA);<br> tft.fillRect(initial_x + x_distance, initial_y + y_distance, side, side, MAGENTA);<br> <span class="hljs-comment">// 显示加减符号</span><br> tft.setCursor(initial_x + symbol_x, initial_y + symbol_y);<br> tft.print(<span class="hljs-string">"+"</span>);<br> tft.setCursor(initial_x + symbol_x + x_distance, initial_y + symbol_y);<br> tft.print(<span class="hljs-string">"-"</span>);<br> tft.setCursor(initial_x + symbol_x, initial_y + symbol_y + y_distance);<br> tft.print(<span class="hljs-string">"+"</span>);<br> tft.setCursor(initial_x + symbol_x + x_distance, initial_y + symbol_y + y_distance);<br> tft.print(<span class="hljs-string">"-"</span>);<br> tft.setTextColor(WHITE, BLACK);<br> Serial.println(<span class="hljs-string">"Screen is "</span> + String(tft.width()) + <span class="hljs-string">"x"</span> + String(tft.height()));<br>}<br><br><span class="hljs-type">void</span> <span class="hljs-title function_">loop</span><span class="hljs-params">()</span> {<br> show_threshold();<br> showTempAndControlFan();<br> show_fan();<br><br> <span class="hljs-type">uint16_t</span> xpos, ypos; <span class="hljs-comment">//screen coordinates</span><br> tp = ts.getPoint(); <span class="hljs-comment">//p.x, p.y are ADC values</span><br><br> <span class="hljs-comment">// if sharing pins, you'll need to fix the directions of the touchscreen pins</span><br> pinMode(XM, OUTPUT);<br> pinMode(YP, OUTPUT);<br> <span class="hljs-comment">// we have some minimum pressure we consider 'valid'</span><br> <span class="hljs-comment">// pressure of 0 means no pressing!</span><br><br> <span class="hljs-keyword">if</span> (tp.z > MINPRESSURE && tp.z < MAXPRESSURE) {<br> xpos = <span class="hljs-built_in">map</span>(tp.y, TS_TOP, TS_BOT, <span class="hljs-number">0</span>, tft.width());<br> ypos = <span class="hljs-built_in">map</span>(tp.x, TS_RT, TS_LEFT, <span class="hljs-number">0</span>, tft.height());<br> Serial.println(String(xpos) + <span class="hljs-string">","</span> + String(ypos));<br> <span class="hljs-keyword">if</span> (xpos > initial_x && xpos < initial_x + side) {<br> <span class="hljs-keyword">if</span> (ypos > initial_y && ypos < initial_y + side) {<br> touch = <span class="hljs-number">1</span>;<br> }<br> }<br> <span class="hljs-keyword">if</span> (xpos > initial_x + x_distance && xpos < initial_x + x_distance + side) {<br> <span class="hljs-keyword">if</span> (ypos > initial_y && ypos < initial_y + side) {<br> touch = <span class="hljs-number">2</span>;<br> }<br> }<br> <span class="hljs-keyword">if</span> (xpos > initial_x && xpos < initial_x + side) {<br> <span class="hljs-keyword">if</span> (ypos > initial_y + y_distance && ypos < initial_y + y_distance + side) {<br> touch = <span class="hljs-number">3</span>;<br> }<br> }<br> <span class="hljs-keyword">if</span> (xpos > initial_x + x_distance && xpos < initial_x + x_distance + side) {<br> <span class="hljs-keyword">if</span> (ypos > initial_y + y_distance && ypos < initial_y + y_distance + side) {<br> touch = <span class="hljs-number">4</span>;<br> }<br> }<br> }<br><br> <span class="hljs-keyword">if</span> (touch == <span class="hljs-number">1</span>) {<br> MinTemp++;<br> touch = <span class="hljs-number">0</span>;<br> delay(<span class="hljs-number">200</span>);<br> }<br><br> <span class="hljs-keyword">if</span> (touch == <span class="hljs-number">2</span>) {<br> MinTemp--;<br> touch = <span class="hljs-number">0</span>;<br> delay(<span class="hljs-number">200</span>);<br> }<br><br> <span class="hljs-keyword">if</span> (touch == <span class="hljs-number">3</span>) {<br> MaxTemp++;<br> touch = <span class="hljs-number">0</span>;<br> delay(<span class="hljs-number">200</span>);<br> }<br><br> <span class="hljs-keyword">if</span> (touch == <span class="hljs-number">4</span>) {<br> MaxTemp--;<br> touch = <span class="hljs-number">0</span>;<br> delay(<span class="hljs-number">200</span>);<br> }<br><br>}<br></code></pre></td></tr></table></figure>]]></content>
<categories>
<category>Arduino</category>
</categories>
<tags>
<tag>C</tag>
<tag>Arduino</tag>
</tags>
</entry>
<entry>
<title>Python rsyslog日志集中化</title>
<link href="/2020/04/02/python-rsyslog/"/>
<url>/2020/04/02/python-rsyslog/</url>
<content type="html"><![CDATA[<p>下位机十多台树莓派,就开始考虑将日志集中在一起方便调试查看,其实一开始我对这个不太熟悉,对集中化这个词不太敏感,只是想要把日志发送到同一个主机上,后来网上查了查,这叫日志集中化,名气最大的是ELK,日志一把梭<br>上面说的 ELK 都比较复杂,其实用一台服务器作为日志收集服务,使用 Linux 自带的 <code>rsyslog</code>即可,<code>Python</code> 日志库也自带了 <code>SysLogHandler</code>. 简单配置一下即可使用。</p><h2 id="1-什么是rsyslog"><a href="#1-什么是rsyslog" class="headerlink" title="1. 什么是rsyslog"></a>1. 什么是rsyslog</h2><h3 id="1-1-基本介绍"><a href="#1-1-基本介绍" class="headerlink" title="1.1 基本介绍"></a>1.1 基本介绍</h3><p>开始之前想说说什么是 <code>syslog</code>,在 <code>Linux</code>中,有很多后台程序都是以后台进程的形式存在运行,例如 <code>crontab/sshd/nginx</code> 等,有些是系统的,有些是我们自己添加的,但是,他们都有一些相同的特点:</p><ul><li>我们不能直接从标准输入给他们输入,也不能直接从标准输出获得他们的输出</li><li>他们通常在固定的位置有日志可以查看,这个位置通常在<code>/var/log/</code></li><li>…</li></ul><p>对于其中的第 2 个特点,大部分 Linux 后台进程都通过 <code>syslog</code>来实现。因为服务器中很多进程的调试和维护都需要一个稳定专业的日志系统,因此,Linux 提供了一个守护进程专门用来处理系统日志,而这个守护进程就是 <code>syslogd</code>,不过,现在的系统大都使用 <code>rsyslod</code>(<code>syslog</code> 升级版) 代替,提供扩展的过滤,消息的加密保护,各种配置选项,输入和输出模块,支持通过TCP或者UDP协议进行传输</p><h3 id="1-2-日志位置"><a href="#1-2-日志位置" class="headerlink" title="1.2 日志位置"></a>1.2 日志位置</h3><p>rsyslogd 在接收到日志之后,需要将日志输出到特定的日志文件中,默认情况下:<br>其中大部分日志消息会在<code>/var/log/rsyslog中</code>,接下来有各类的消息,</p><ul><li>认证信息会保存到<code>/var/log/auth</code>文件中</li><li>调试信息会保存到<code>/var/log/debug</code>文件中</li><li>普通信息会保存到<code>/var/log/messages</code>文件中</li><li>内核消息会保存到<code>/var/log/kern.log</code>文件中</li><li>邮件消息会保存到<code>/var/log/mail.log</code>文件中</li></ul><p>但是这些都是可以改变的,配置文件的位置是 <code>/etc/rsyslog.conf</code>,可以查看配置文件里<code>RULES</code>项的日志对一对上面的东西</p><h3 id="1-3-传输方式"><a href="#1-3-传输方式" class="headerlink" title="1.3 传输方式"></a>1.3 传输方式</h3><p><code>rsyslog</code>提供三个远程日志传输方式:</p><ul><li>UDP: 数据包传输可信度不高</li><li>TCP: 数据包传输可信度比较高</li><li>RELP: 数据包传输可信度最高,避免数据丢失,比较新的协议,目前应用较少</li></ul><p>关于TCP和UDP的传输方式,rsyslog官方推荐使用TCP传输方式</p><blockquote><p>In general, we suggest to use TCP syslog. It is way more reliable than UDP syslog and still pretty fast. The main reason is, that UDP might suffer of message loss. This happens when the syslog server must receive large bursts of messages. If the system buffer for UDP is full, all other messages will be dropped. With TCP, this will not happen. But sometimes it might be good to have a UDP server configured as well. That is, because some devices (like routers) are not able to send TCP syslog by design. In that case, you would need both syslog server types to have everything covered. If you need both syslog server types configured, please make sure they run on proper ports. By default UDP syslog is received on port 514. TCP syslog needs a different port because often the RPC service is using this port as well.</p></blockquote><h2 id="2-rsyslogc基础配置"><a href="#2-rsyslogc基础配置" class="headerlink" title="2. rsyslogc基础配置"></a>2. rsyslogc基础配置</h2><p>rsyslog主要配置文件在<code>/etc/rsyslog.conf</code>。这里你可以配置 <code>global directives</code>, <code>modules</code>, <code>rules</code> that consist of <code>filter</code> and <code>action</code> parts,也可以写一些注释。<br>这里我们主要来讲讲<code>rules</code>板块的配置,<code>rules</code>简单点说就是让系统日志进来的规则,就好比数据库先筛选一部分记录然后给这部分记录做一些动作,等于<code>table.filter().update()</code><br>以下内容整理来自<a href="https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/deployment_guide/s1-configuring_rsyslog_on_a_logging_server">redhat rsyslog配置</a></p><h3 id="2-1-Filters(过滤器)"><a href="#2-1-Filters(过滤器)" class="headerlink" title="2.1 Filters(过滤器)"></a>2.1 Filters(过滤器)</h3><p>**Facility/Priority-based filters **</p><p>FACILITY指定产生特定系统日志消息的子系统。例如,邮件子系统处理所有与邮件相关的系统日志消息。 FACILITY可以用以下关键字之一(或数字代码)表示:<code>kern (0), user (1), mail (2), daemon (3), auth (4), syslog (5), lpr (6), news (7), cron (8), authpriv (9), ftp (10), and local0 through local7 (16 - 23)</code></p><p>PRIORITY指定系统日志消息的优先级。可以通过以下关键字之一(或数字)来表示优先级:<code>debug (7), info (6), notice (5), warning (4), err (3), crit (2), alert (1), and emerg (0)</code></p><p> To select all kernel syslog messages with any priority, add the following text into the configuration file:</p><p>选择任何优先级的所有内核syslog消息:</p><figure class="highlight"><table><tr><td class="gutter"><div class="code-wrapper"><pre><span class="line">1</span><br></pre></div></td><td class="code"><pre><code class="hljs">kern.*`<br></code></pre></td></tr></table></figure><p>要选择优先级crit更高的所有邮件系统日志消息,请使用以下形式:</p><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs">mail.crit<br></code></pre></td></tr></table></figure><p>要选择除具有info或debug优先级的消息外的所有cron syslog消息,请以以下形式设置配置:</p><figure class="highlight erlang-repl"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs erlang-repl">cron.!info,!debug<br></code></pre></td></tr></table></figure><p>此外,还支持比较符过滤,比如:</p><figure class="highlight elixir"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs elixir"><span class="hljs-symbol">:msg</span>, contains, <span class="hljs-string">"error"</span><br><span class="hljs-symbol">:hostname</span>, isequal, <span class="hljs-string">"host1"</span><br><span class="hljs-symbol">:msg</span>, !regex, <span class="hljs-string">"fatal .* error"</span><br></code></pre></td></tr></table></figure><p>以及表达式过滤器:</p><figure class="highlight fortran"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs fortran"><span class="hljs-keyword">if</span> EXPRESSION <span class="hljs-keyword">then</span> <span class="hljs-keyword">ACTION</span> <span class="hljs-keyword">else</span> <span class="hljs-keyword">ACTION</span><br></code></pre></td></tr></table></figure><h3 id="2-2-Actions(动作)"><a href="#2-2-Actions(动作)" class="headerlink" title="2.2 Actions(动作)"></a>2.2 Actions(动作)</h3><h4 id="2-2-1-将syslog消息存入文件"><a href="#2-2-1-将syslog消息存入文件" class="headerlink" title="2.2.1 将syslog消息存入文件"></a>2.2.1 将syslog消息存入文件</h4><p>大多数操作指定将系统日志消息保存到哪个日志文件。这是通过在已定义的选择器之后指定文件(FILTER PATH)路径来完成的:</p><figure class="highlight arcade"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs arcade">cron.* <span class="hljs-regexp">/var/</span><span class="hljs-built_in">log</span>/cron.<span class="hljs-built_in">log</span><br></code></pre></td></tr></table></figure><p>默认情况下,每次生成系统日志消息时都会同步日志文件。使用破折号(-)作为指定的文件路径的前缀以省略同步:</p><figure class="highlight pgsql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs pgsql"><span class="hljs-keyword">FILTER</span> -<span class="hljs-type">PATH</span><br></code></pre></td></tr></table></figure><p>指定的文件路径可以是静态或动态的。如上例所示,静态文件由固定文件路径表示。动态文件路径可能会根据收到的消息而有所不同。动态文件路径由模板和问号(?)前缀表示,<code>FILTER ?DynamicFile </code></p><figure class="highlight routeros"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs routeros">template(<span class="hljs-attribute">name</span>=<span class="hljs-string">"DynFile"</span> <span class="hljs-attribute">type</span>=<span class="hljs-string">"string"</span> <span class="hljs-attribute">string</span>=<span class="hljs-string">"/var/log/line/%fromhost%.log"</span>)<br>local7.* ?DynFile<br></code></pre></td></tr></table></figure><p>动态模板是很大的一块内容,具体的不展开我用到的不多,就上面这个例子。详情可以看<a href="https://dulishu.top/linux-rsyslog-template/">rsyslog template</a>以及<a href="https://www.rsyslog.com/doc/master/configuration/properties.html">需要用到的属性</a></p><h4 id="2-2-2-通过网络发送syslog消息"><a href="#2-2-2-通过网络发送syslog消息" class="headerlink" title="2.2.2 通过网络发送syslog消息"></a>2.2.2 通过网络发送syslog消息</h4><p>rsyslog允许您通过网络发送和接收syslog消息。此功能使您可以在一台计算机上管理多个主机的系统日志消息。要将系统日志消息转发到远程计算机,请使用以下语法,<code>@[(zNUMBER)]HOST:[PORT]</code>,<code>@</code>代表udp,<code>@@</code>代表TCP</p><figure class="highlight elixir"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs elixir">*.* <span class="hljs-variable">@192</span>.<span class="hljs-number">168.0</span>.<span class="hljs-number">1</span><br>*.* <span class="hljs-variable">@@example</span>.<span class="hljs-symbol">com:</span><span class="hljs-number">6514</span><br>*.* @(z9)[<span class="hljs-number">2001</span><span class="hljs-symbol">:db8</span>::<span class="hljs-number">1</span>]<br><span class="hljs-title class_">Storing</span> syslog messages <span class="hljs-keyword">in</span> a database<br></code></pre></td></tr></table></figure><h4 id="2-2-3-其他传输方式"><a href="#2-2-3-其他传输方式" class="headerlink" title="2.2.3 其他传输方式"></a>2.2.3 其他传输方式</h4><ul><li>Output channels</li><li>Sending syslog messages to specific users</li><li>Executing a program</li><li>Storing syslog messages in a database<br> (<code>:PLUGIN:DB_HOST,DB_NAME,DB_USER,DB_PASSWORD;[TEMPLATE]</code>)</li><li>Discarding syslog messages (丢弃日志<code>local5.* stop</code>)</li></ul><h4 id="2-2-4-指定多个动作"><a href="#2-2-4-指定多个动作" class="headerlink" title="2.2.4 指定多个动作"></a>2.2.4 指定多个动作</h4><p>可以为每个选择器指定多个操作。要为一个选择器指定多个动作,请将每个动作写在单独的行上,并在其前面加上一个&字符</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs nginx"><span class="hljs-attribute">FILTER</span> ACTION<br>& ACTION<br>& ACTION<br><br>kern.=<span class="hljs-literal">crit</span> user1<br>&<span class="hljs-regexp"> ^test-program</span>;<span class="hljs-attribute">temp</span><br>& @<span class="hljs-number">192.168.0.1</span><br></code></pre></td></tr></table></figure><h3 id="2-3-Log-Rotation-(日志轮替)"><a href="#2-3-Log-Rotation-(日志轮替)" class="headerlink" title="2.3 Log Rotation (日志轮替)"></a>2.3 Log Rotation (日志轮替)</h3><p>我们可以在<code>/etc/logrotate.conf</code>配置文件中进行<strong>日志轮替</strong>的配置</p><figure class="highlight apache"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs apache"><span class="hljs-comment"># rotate log files weekly</span><br><span class="hljs-attribute">weekly</span><br><span class="hljs-comment"># keep 4 weeks worth of backlogs</span><br><span class="hljs-attribute">rotate</span> <span class="hljs-number">4</span><br><span class="hljs-comment"># uncomment this if you want your log files compressed</span><br><span class="hljs-attribute">compress</span><br></code></pre></td></tr></table></figure><h2 id="3-使用Python发送日志到rsyslog"><a href="#3-使用Python发送日志到rsyslog" class="headerlink" title="3. 使用Python发送日志到rsyslog"></a>3. 使用Python发送日志到rsyslog</h2><p>我们查阅一下 Python 文档中的 logging 模块的文档,可以发现又一个 handler 叫做:SysLogHandler,看一下参数,并不比 syslog 的原始函数简单,但是,我们可以忽略所有这些参数,而简单得控制日志输出:</p><figure class="highlight routeros"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs routeros">class logging.handlers.SysLogHandler(address=(<span class="hljs-string">'localhost'</span>, SYSLOG_UDP_PORT), <span class="hljs-attribute">facility</span>=LOG_USER, <span class="hljs-attribute">socktype</span>=socket.SOCK_DGRAM)<br></code></pre></td></tr></table></figure><p><code>address</code>: 前面说了 rsyslog 是一个套接字,这里可以制定套接字的地址,注意:这个可以是不在同一台机器,默认为(‘localhost’, 514)<br><code>facility</code>:这个参数的作用是告诉 rsyslog 日志的类型,从而可以让他根据不同的类型执行不同的操作,默认为LOG_USER,下面的例子我们使用<strong>自定义的日志设备local7</strong><br><code>socktype</code>:默认UDP,想换TCP用<code>socket.SOCK_STREAM</code><br>使用的话就和其他的 Handler 一致,简单得记一下:</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">import</span> logging<br><span class="hljs-keyword">from</span> logging.handlers <span class="hljs-keyword">import</span> SysLogHandler<br><br>_LOG_SERVER = (<span class="hljs-string">'192.168.123.222'</span>, <span class="hljs-number">514</span>)<br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">remote_logger</span>(<span class="hljs-params">name</span>):<br> logger = logging.getLogger(name)<br> logger.setLevel(logging.INFO)<br> handler1 = SysLogHandler(<br> address=_LOG_SERVER, facility=SysLogHandler.LOG_LOCAL7)<br> <span class="hljs-comment"># python logging 默认所有的日志都是输出到 stderr 中的,可以在supervisor的stderr_logfile指定文件中查看相关日志</span><br> <span class="hljs-comment"># 或者 stdout_handler = logging.StreamHandler(sys.stdout)</span><br> handler2 = StreamHandler()<br> formatter1 = logging.Formatter(<span class="hljs-string">'%(name)s - %(levelname)s - %(message)s'</span>)<br> formatter2 = logging.Formatter(<br> <span class="hljs-string">'%(asctime)s - %(name)s - %(levelname)s - %(message)s'</span>)<br> handler1.setFormatter(formatter1)<br> handler2.setFormatter(formatter2)<br> logger.addHandler(handler1)<br> logger.addHandler(handler2)<br> <span class="hljs-keyword">return</span> logger<br><br>logger = remote_logger(<span class="hljs-string">'test'</span>)<br>logger.info(<span class="hljs-string">'xxx'</span>)<br></code></pre></td></tr></table></figure><h2 id="4-记录Python使用TCP的一些坑"><a href="#4-记录Python使用TCP的一些坑" class="headerlink" title="4. 记录Python使用TCP的一些坑"></a>4. 记录Python使用TCP的一些坑</h2><p>鉴于上面所说的,官方推荐使用TCP,而在上面的例子中我们只要改下<code>socktype</code>的参数就行,但随后发现日志总是没法显示,要么就是好多行在一起,找了好多资料才发现其中原因,具体看<a href="https://stackoverflow.com/questions/40041697/pythons-sysloghandler-and-tcp">Python’s SyslogHandler and TCP</a>和<a href="https://stackoverflow.com/questions/52950147/sysloghandler-messages-grouped-on-one-line-on-remote-server">SysLogHandler messages grouped on one line on remote server</a>,概括来说就是<strong>TCP格式标准不支持</strong>,要自己动手额外打些补丁,比较简便的方法是<a href="https://community.graylog.org/t/graylog-2-not-displaying-tcp-syslog-messages/961/2">使用syslog_rfc5424_formatter库</a></p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">import</span> logging<br><span class="hljs-keyword">import</span> logging.handlers<br><span class="hljs-keyword">import</span> socket<br><span class="hljs-keyword">from</span> syslog_rfc5424_formatter <span class="hljs-keyword">import</span> RFC5424Formatter<br><br>my_logger = logging.getLogger(<span class="hljs-string">'MyLogger'</span>)<br>my_logger.setLevel(logging.DEBUG)<br><br>handler = logging.handlers.SysLogHandler(address = (<span class="hljs-string">'0.0.0.0'</span>,<span class="hljs-number">8514</span>),socktype=socket.SOCK_STREAM)<br><span class="hljs-comment"># some old syslog daemons expect a NUL terminator, but graylog will not work if a NUL terminator is appended.</span><br>handler.append_nul = <span class="hljs-literal">False</span><br>handler.setFormatter(RFC5424Formatter())<br>my_logger.addHandler(handler)<br><br>my_logger.debug(<span class="hljs-string">'wolfgang was here\n'</span>)<br></code></pre></td></tr></table></figure><blockquote><p>当然我们可以用一些方法曲线救国,我们用UDP写入到本地然后在用rsylog自带的TCP传输写入到远程,这个方法还可以,尽量保证数据准确性</p></blockquote><h2 id="5-在Docker中使用rsyslog"><a href="#5-在Docker中使用rsyslog" class="headerlink" title="5. 在Docker中使用rsyslog"></a>5. 在Docker中使用rsyslog</h2><p>当需要一个干干静静的日志服务器时,我们需要用一个docker来解决此事,仅14M的镜像大小,没有类似于<strong>mail</strong>,<strong>auth</strong>等其他杂的信息,<code>docker logs -f container_id</code>直接跟踪,多协议支持,安逸!<br>我参考的是dockerhub上的<code>rsyslog/syslog_appliance_alpine</code>,正常的话使用<code>docker run --name testsyslog -d -p 666:514/udp rsyslog/syslog_appliance_alpine rsyslog </code>打开容器监听666端口上的system日志,然而我们自定义的话使用LOCAL7类型日志,简单改动下,自己做了个<code>shijiahuan /rsyslog</code>,详细说明都有,不展开。</p><h2 id="6-总结"><a href="#6-总结" class="headerlink" title="6. 总结"></a>6. 总结</h2><p>在学习日志集中化过程中,也发现了<a href="https://www.ibm.com/developerworks/cn/opensource/os-cn-elk/index.html">ELK</a>,相信以后如果接触大型项目也会要学习到的,而几个小机器,用rsyslog绰绰有余~</p>]]></content>
<categories>
<category>Python</category>
</categories>
<tags>
<tag>Python</tag>
<tag>Linux</tag>
<tag>rsyslog</tag>
</tags>
</entry>
<entry>
<title>Linux的信号以及Python处理</title>
<link href="/2020/03/31/linux-signal-with-python/"/>
<url>/2020/03/31/linux-signal-with-python/</url>
<content type="html"><![CDATA[<p>初次接触信号这个概念实在gpiozero库文档中的这么一个专题,<code>How do I keep my script running?</code>,我最最开始是在<code>button.when_pressed = hello</code>用一个<code>while True</code>的无限循环保持脚本运行,后来发现有个个内置库的<code>signal.pause()</code>函数可以保持脚本运行,直到按下<strong>Ctrl+C</strong>程序才会退出,我当时仅仅认识到这个地步,没往下深究,直到后来一次mqtt没加loop循环之后自动断开,我后来就在想我用<code>pause</code>还是<code>loop_forever</code>呢? 接下来我就慢慢揭开了信号的序幕,也算当补了下操作系统的一些知识。</p><h2 id="1-信号基础"><a href="#1-信号基础" class="headerlink" title="1. 信号基础"></a>1. 信号基础</h2><p>Linux信号是一种较高层的软件形式的异常,它允许进程和内核中断其他进程。<br>一个信号就是一条小消息,它通知进程系统中发生了一个某种类型的事件,Linux系统上支持30种不同类型的信号,每种信号类型都对应某种系统事件,红笔标注为常见信号:<br><img src="/images/signal/signals.jpeg" alt="Linux信号"></p><blockquote><p>可以用<code>man 7 signal</code> 查看signal信息</p></blockquote><p>如果觉得上面专业的术语较为枯燥,可以查看<strong>Vamei</strong>大神(看了这篇文章才知道Vamei博主因为抑郁症去世,R.I.P.)的<a href="https://www.cnblogs.com/vamei/archive/2012/10/04/2711818.html">Linux信号基础</a>以及之前几篇关于进程的文章,通熟易懂</p><h2 id="2-信号发送"><a href="#2-信号发送" class="headerlink" title="2. 信号发送"></a>2. 信号发送</h2><h3 id="2-1-用-bin-kill发送信号"><a href="#2-1-用-bin-kill发送信号" class="headerlink" title="2.1 用/bin/kill发送信号"></a>2.1 用<code>/bin/kill</code>发送信号</h3><p>我之前一直用<code>kill -9 15213</code>强制杀掉某个pid进程,对这个数字9一直没啥反应,其实就是信号9,也就是SIGKILL,等同于<code>kill -SIGTSTP 15213</code></p><h3 id="2-2-从键盘发送信号"><a href="#2-2-从键盘发送信号" class="headerlink" title="2.2 从键盘发送信号"></a>2.2 从键盘发送信号</h3><p>键盘快捷键我已经在上面图中用红笔写出来,不展开</p><h3 id="2-3-用函数发送信号"><a href="#2-3-用函数发送信号" class="headerlink" title="2.3 用函数发送信号"></a>2.3 用函数发送信号</h3><p>Python的<code>os.kill()</code>函数: <code>os.kill(os.getpid(), signal.SIGUSR1)</code></p><h3 id="2-4-用alarm函数向它自己发送信号"><a href="#2-4-用alarm函数向它自己发送信号" class="headerlink" title="2.4 用alarm函数向它自己发送信号"></a>2.4 用alarm函数向它自己发送信号</h3><p><code>signal.alarm(n)</code>函数会安排内核在n秒后发送一个<strong>SIGALRM</strong>信号给调用进程。如果n是0,那么不会调度安排新的alarm</p><h2 id="3-接收以及处理信号"><a href="#3-接收以及处理信号" class="headerlink" title="3. 接收以及处理信号"></a>3. 接收以及处理信号</h2><p><img src="/images/signal/signal_handler.jpg" alt="Linux信号处理"></p><p>当进程决定执行信号的时候,有下面几种可能:</p><ul><li>无视(ignore)信号,信号被清除,进程本身不采取任何特殊的操作</li><li>默认(default)操作。每个信号对应有一定的默认操作。比如上面SIGCONT用于继续进程。</li><li>自定义操作。也叫做获取 (catch) 信号。执行进程中预设的对应于该信号的操作。</li></ul><p>进程会采取哪种操作,要根据该进程的程序设计。特别是获取信号的情况,程序往往会设置一些比较长而复杂的操作(通常将这些操作放到一个函数中)。</p><h3 id="3-1-Python-signal函数信号处理"><a href="#3-1-Python-signal函数信号处理" class="headerlink" title="3.1 Python signal函数信号处理"></a>3.1 Python signal函数信号处理</h3><p><strong>signal包定义了各个信号名及其对应的整数,核心是使用signal.signal()函数来预设(register)信号处理函数</strong>,<code>singnal.signal(signalnum, handler)</code>中signalnum为某个信号,handler为该信号的处理函数。我们在信号基础里提到,进程可以无视信号,可以采取默认操作,还可以自定义操作。当handler为<code>signal.SIG_IGN</code>时,信号被无视(ignore)。当handler为<code>singal.SIG_DFL</code>,进程采取默认操作(default)。当handler为一个函数名时,进程采取函数中定义的操作。</p><figure class="highlight python"><table><tr><td class="gutter"><div class="code-wrapper"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></div></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">import</span> signal<br><span class="hljs-comment"># Define signal handler function</span><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">myHandler</span>(<span class="hljs-params">signum, frame</span>):<br> <span class="hljs-built_in">print</span>(<span class="hljs-string">'I received: '</span>, signum)<br><br><span class="hljs-comment"># register signal.SIGTSTP's handler </span><br>signal.signal(signal.SIGTSTP, myHandler)<br>signal.pause()<br><span class="hljs-built_in">print</span>(<span class="hljs-string">'End of Signal Demo'</span>)<br></code></pre></td></tr></table></figure><blockquote><p>信号处理程序和主程序以及其他信号处理程序并发地进行,如果处理程序和主程序并发的访问同样的全局数据结构,那么结果可能就是不可预知的,而且经常是致命的!</p></blockquote><p>在主程序中,我们首先使用<code>signal.signal()</code>函数来预设信号处理函数。然后我们执行<code>signal.pause()</code>来让该进程暂停以等待信号,以等待信号。当信号SIGUSR1被传递给该进程时,进程从暂停中恢复,并根据预设,执行SIGTSTP的信号处理函数<code>myHandler()</code>。<code>myHandler</code>的两个参数一个用来识别信号(signum),另一个用来获得信号发生时,进程栈的状况(stack frame)。这两个参数都是由<code>signal.singnal()</code>函数来传递的。</p><p>上面的程序可以保存在一个文件中(比如<code>test.py</code>)。我们使用如下方法运行:<br><code>$python3 test.py</code></p><p>以便让进程运行。当程序运行到<code>signal.pause()</code>的时候,进程暂停并等待信号。此时,通过按下<strong>CTRL+Z</strong>向该进程发送SIGTSTP信号。我们可以看到,进程执行了<code>myHandle()</code>函数, 随后返回主程序,继续执行。(当然,也可以用<code>$ps</code>查询process ID, 再使用<code>$kill</code>来发出信号。)</p><p>(进程并不一定要使用<code>signal.pause()</code>暂停以等待信号,它也可以在进行工作中接受信号,比如将上面的<code>signal.pause()</code>改为一个需要长时间工作的循环。)</p><p>我们可以根据自己的需要更改<code>myHandler()</code>中的操作,以针对不同的信号实现个性化的处理。</p><h3 id="3-2-Python-alarm函数自发自收信号"><a href="#3-2-Python-alarm函数自发自收信号" class="headerlink" title="3.2 Python alarm函数自发自收信号"></a>3.2 Python alarm函数自发自收信号</h3><p>再来看个官网的例子,它使用<code>alarm()</code>函数来限制等待打开文件所花费的时间;这很有用,因为如果该文件作用于可能无法打开的串行设备,这通常会导致<code>os.open()</code>然后无限期挂起。解决的办法是在打开文件之前设置5秒警报。如果操作花费的时间太长,则会发送警报信号,并且处理程序将引发异常。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">import</span> signal, os<br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">handler</span>(<span class="hljs-params">signum, frame</span>):<br> <span class="hljs-built_in">print</span>(<span class="hljs-string">'Signal handler called with signal'</span>, signum)<br> <span class="hljs-keyword">raise</span> OSError(<span class="hljs-string">"Couldn't open device!"</span>)<br><br><span class="hljs-comment"># Set the signal handler and a 5-second alarm</span><br>signal.signal(signal.SIGALRM, handler)<br>signal.alarm(<span class="hljs-number">5</span>)<br><br><span class="hljs-comment"># This open() may hang indefinitely</span><br>fd = os.<span class="hljs-built_in">open</span>(<span class="hljs-string">'/dev/ttyS0'</span>, os.O_RDWR)<br><br>signal.alarm(<span class="hljs-number">0</span>) <span class="hljs-comment"># Disable the alarm</span><br></code></pre></td></tr></table></figure><h2 id="4-说说在C语言中的实现"><a href="#4-说说在C语言中的实现" class="headerlink" title="4. 说说在C语言中的实现"></a>4. 说说在C语言中的实现</h2><p>在<strong>深入理解计算机系统</strong>书中,讲解Linux信号用了shell以及C来进行演示,这是必须的,理解这些底层机制用C实在是太契合了,但是回过头来看,Python实现这些功能时是完全照着C来的,无论是函数名参数名,只要底层原理相通,编程语言的函数实现也大体一致,如果想看更多关于C的演示,可以看<a href="https://jin-yang.github.io/post/kernel-signal-introduce.html">Linux 信号机制</a>,很棒的教程!</p><p>但是,Python3的文档中明确指出,handler并不是运行在底层的C handler中的,所以不要希望捕捉同步错误例如SIGFPE与SIGSEGV。<br>同时新增了几个功能,大概用于阻塞signal这种,不过我还不太会用:<br><code>signal.pthread_kill(thread_id, signum)</code><br><code>signal.pthread_sigmask(how, mask)</code></p>]]></content>
<categories>
<category>Python</category>
</categories>
<tags>
<tag>Python</tag>
<tag>Linux</tag>
</tags>
</entry>
<entry>
<title>Invoke与Fabric注意事项详解教程</title>
<link href="/2020/02/28/invoke-fabric/"/>
<url>/2020/02/28/invoke-fabric/</url>
<content type="html"><![CDATA[<p>说到远程部署利器,那就不得不讲讲Fabric,Fabric 是一个 Python 的库,同时它也是一个命令行工具。它提供了丰富的同 SSH 交互的接口,可以用来在本地或远程机器上自动化、流水化地执行 Shell 命令。使用 fabric 提供的命令行工具,可以很方便地执行应用部署和系统管理等操作。因此它非常适合用来做应用的远程部署及系统维护<br>刚实习那会,也遇到这种需求,但是不知道Fabric,自己在那实现好半天搞不定,sudo的问题也一直在,paramiko库也看得头晕,最终不了了之,直到发现了Fabric,才发现人家发明的轮子真的香!</p><h2 id="1-Fabric前世今生"><a href="#1-Fabric前世今生" class="headerlink" title="1. Fabric前世今生"></a>1. Fabric前世今生</h2><p>我从一开始接触的就是Python3,之后因为远程打包部署接触到了Fabric,当时官网Fabric还不支持Python3,所以我用了民间的Fabric3,十分好用,对新手很友好。后来,发现Fabric支持Python3了,但是看了文档,看的稀里糊涂,看了别人的教程才成功连接到远程主机上,我就又撒手转向了民间的Fabric3。<br>所以我们先区分下 Fabric1,Fabric2,Fabric3,它们的python官网发布的地址:</p><p>安装方式分别是:</p><figure class="highlight shell"><table><tr><td class="gutter"><div class="code-wrapper"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></div></td><td class="code"><pre><code class="hljs shell"><span class="hljs-meta prompt_"># </span><span class="language-bash">https://pypi.org/project/Fabric/</span><br>pip install Fabric<br><span class="hljs-meta prompt_"></span><br><span class="hljs-meta prompt_"># </span><span class="language-bash">https://pypi.org/project/fabric2/</span><br>pip install fabric2<br><span class="hljs-meta prompt_"></span><br><span class="hljs-meta prompt_"># </span><span class="language-bash">https://pypi.org/project/Fabric3/</span><br>pip install fabric3<br></code></pre></td></tr></table></figure><p>Fabric1和Fabric2,在pypi中的页面,就是同一个东西:</p><ul><li>都是Fabric的最新版:Fabric 2.x,截至到20200227,安装出来的版本是:2.5</li><li>而官网之所以弄出来个Fabric2是因为:Fabric2和Fabric1相比,完全重写了,接口和功能都有很大改动,官网也不建议你继续用Fabric1,建议升级到Fabric2,最新版也早就支持Python 3.4+,和之前的Python2.7</li></ul><p>而Fabric3,是非官网的是当之前Fabric1还没有支持Python3时,别人去fork出来,加了Python 3的支持的现在好像基本上不维护了</p><p>总结就是:</p><blockquote><p>尽量不要用之前旧的版本的Fabric1了,如果还在用,建议升级到最新的Fabric2<br>不需要操心、忽略掉,所谓的、非官网的,现在已没价值的:Fabric3</p></blockquote><p><strong>因为要同时远程部署七八个树莓派,又要用到Fabric,趁着这次疫情在家,啃了一个星期的文档,蛮多人也觉得新版的Fabric比较难用,原因在于完全重写了,其实我们可以把这个库分开学习,分为Inovke和Paramiko,为什么重写之后要将Invoke独立出来,可以看这里<a href="http://www.pyinvoke.org/faq.html#invoke-split-from-fabric">Why was Invoke split off from the Fabric project?</a>,那接下去就分别讲讲!</strong></p><h2 id="2-Invoke使用"><a href="#2-Invoke使用" class="headerlink" title="2. Invoke使用"></a>2. Invoke使用</h2><p><strong>Invoke 实现CLI解析,任务组织和Shell命令执行(通用框架以及本地命令的特定实现</strong>),关于Shell命令的执行其实和<code>subprocess</code>库挺像的,当然Invoke不仅于此。</p><h3 id="2-1-基础使用"><a href="#2-1-基础使用" class="headerlink" title="2.1 基础使用"></a>2.1 基础使用</h3><p>关于基础教程我就不说了,看<a href="http://docs.pyinvoke.org/en/latest/getting-started.html">官网例子</a>一目了然,我在这里记录一些重点与自己的理解。</p><h3 id="2-2-配置文件"><a href="#2-2-配置文件" class="headerlink" title="2.2 配置文件"></a>2.2 配置文件</h3><p>关于invoke的配置方面我们大体看一下<a href="http://docs.pyinvoke.org/en/latest/concepts/configuration.html">配置继承</a><br>配置很多层,有内部默认,系统级别,用户级别,项目级别…甚至可以在<a href="http://docs.pyinvoke.org/en/latest/invoke.html">命令行运行</a>的时候我们再添加配置也是可行的。<br>我这里推荐使用<strong>用户级别</strong>的配置文件(<code> ~/.invoke.yaml</code>,也可以是json,py文件),刚开始我自己测试的时候用的<strong>项目级别</strong>的配置文件(<code>/home/user/myproject/invoke.yaml</code>),一个原因是我在windows下测试,没有所谓的<code> ~/.invoke.yaml</code>,还有个就是我想万一这个项目到别的机器上运行,我可能把隐藏的配置文件忘了,想显示放在项目里,结果这样就跳入了一个火坑,琢磨了我好久,原因在于:</p><blockquote><p>Project-level configuration file living next to your top level tasks.py. For example, if your run of Invoke loads /home/user/myproject/tasks.py (see our docs on the load process), this might be /home/user/myproject/invoke.yaml.</p></blockquote><p>项目级别的配置文件只有在你识别到tasks文件时才会调用同文件夹里的配置,其他方式你会没法识别到,比如你想单独用python3运行这个invoke脚本(把invoke当成subprocess那样)!下面会讲到相关例子。<br>其实,invoke本身没有啥配置修改,所以我也建议在执行<code>invoke ... task1 ...</code>时通过一些命令行参数来传递配置,省了配置文件的存在。</p><h3 id="2-3-导入集合"><a href="#2-3-导入集合" class="headerlink" title="2.3 导入集合"></a>2.3 导入集合</h3><p>Invoke执行模型的核心涉及一个或多个Collection对象,就是集合的意思。<br>默认调用<code>invoke</code>命令我们会查找同目录中的<code>tasks.py</code>或者<code>tasks</code>包,tasks就算是一个集合,具体我们可以看<a href="http://docs.pyinvoke.org/en/latest/concepts/loading.html">Loading collections</a>,所以有接下来一个例子我上面说的火坑。<br>假如我tasks名字被占用了…我想换个任务名字来让Invoke搜索到,比如叫mytasks,常见的设置方法有两种,一种是设置<code>tasks.collection_name</code> 配置选项,或使用 <code>--collection</code>。集合名应该是Python模块名称,而不是文件名(所以mytasks,不是mytasks.py或 mytasks/。)<br>用<code>--collection</code>没问题,问题在于设置<code>tasks.collection_name</code> 配置选项,我一开始是用的<strong>项目级别</strong>配置,则报错<code>Can't find any collection named 'tasks'!</code>,我明明设置文件里设置了,为什么还是在查找<code>tasks.py</code>,哪里有问题!问题在于上一节引用的英文里,<strong>项目级别</strong>配置是在同目录的<code>tasks.py</code>文件被找到后才会引入该配置,问题是一开始我都没有<code>tasks.py</code>我怎么引入配置文件,更别说里面的<code>tasks.collection_name</code> 配置选项了</p><h3 id="2-4-集合的命名空间"><a href="#2-4-集合的命名空间" class="headerlink" title="2.4 集合的命名空间"></a>2.4 集合的命名空间</h3><p>当任务很多,存在不同类别层级的时候我们就需要<a href="http://docs.pyinvoke.org/en/stable/concepts/namespaces.html">构建命名空间</a>(看<code>Importing modules as collections</code>部分就行),单纯的<code>tasks.py</code>满足不了我们了,使用<code>tasks</code>包,包里有多个python文件代表个各种任务,然后在<code>tasks/__init__.py</code>中做<code>导入模块作为集合</code>操作就行,不展开。</p><h3 id="2-5-上下文Context"><a href="#2-5-上下文Context" class="headerlink" title="2.5 上下文Context"></a>2.5 上下文Context</h3><p>上下文Context这个参数出现了,很神奇,就好像Flask中的Context一样神奇。</p><p>what exactly is this <code>context</code> arg anyway?</p><blockquote><p>A common problem task runners face is transmission of “global” data - values loaded from configuration files or other configuration vectors, given via CLI flags, generated in ‘setup’ tasks, etc.<br>Some libraries (such as Fabric 1.x) implement this via module-level attributes, which makes testing difficult and error prone, limits concurrency, and increases implementation complexity.<br>Invoke encapsulates state in explicit Context objects, handed to tasks when they execute . The context is the primary API endpoint, offering methods which honor the current state (such as Context.run) as well as access to that state itself.</p></blockquote><p>Context对象是在命令行解析过程中创建的(如果需要的话,可以手动创建),并用于与执行的任务共享解析器和配置状态,下面两种例子是一样的.<br>第一种通过命令行,过程中创建c,即为context,会自动导入文件配置:</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> invoke <span class="hljs-keyword">import</span> task<br><span class="hljs-meta">@task</span><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">show</span>(<span class="hljs-params">c</span>):<br><span class="hljs-built_in">print</span>(config)<br> c.run(<span class="hljs-string">"ls"</span>)<br><br>$ invoke show<br>...<br></code></pre></td></tr></table></figure><p>第二种手动创建,如果有配置我们需要手动添加配置而没法用文件配置,比如:</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> invoke <span class="hljs-keyword">import</span> Config,Context<br><span class="hljs-comment"># 默认配置</span><br>config = Config()<br><span class="hljs-comment"># 添加一些配置</span><br>config[<span class="hljs-string">'sudo'</span>] = {<span class="hljs-string">'password'</span>:<span class="hljs-string">'mypassoword'</span>}<br><span class="hljs-built_in">print</span>(config)<br><span class="hljs-comment"># 传入配置,如果没有额外配置可以省略不填</span><br>c = Context(config=config)<br>c.run(<span class="hljs-string">'ls'</span>)<br></code></pre></td></tr></table></figure><h3 id="2-6-sudo运行以及自动响应"><a href="#2-6-sudo运行以及自动响应" class="headerlink" title="2.6 sudo运行以及自动响应"></a>2.6 sudo运行以及自动响应</h3><p>最后我们说运行sudo命令时如何免密码以及与一运行一些其他命令时的提示互动.<br>我们可以先配置好文件:</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs json"><span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"tasks"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"auto_dash_names"</span><span class="hljs-punctuation">:</span> <span class="hljs-keyword">false</span><br> <span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"sudo"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br><span class="hljs-attr">"password"</span><span class="hljs-punctuation">:</span><span class="hljs-string">"mypassword"</span><br> <span class="hljs-attr">"prompt"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"[sudo] password:"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-punctuation">}</span><br><span class="hljs-punctuation">}</span><br></code></pre></td></tr></table></figure><p>然后在脚本里<code>c.sudo('rm test.txt')</code>就行.<br>或者我们简单点:直接在在函数里面加入<code>c.sudo('rm test.txt',user='baird',password='mypassword')</code><br>关于自动响应程序输出的,我们可以看<a href="http://docs.pyinvoke.org/en/stable/concepts/watchers.html">Automatically responding to program output</a></p><h2 id="3-Fabric使用"><a href="#3-Fabric使用" class="headerlink" title="3. Fabric使用"></a>3. Fabric使用</h2><p>Fabric与Invoke基本上属于继承创新的关系,很多都是相同操作,所以这一部分就不重复讲了.<br><strong>Fabric相对于Invoke来说增加了Paramiko库的功能</strong>,也就是说实现了低/中级别SSH功能-SSH和SFTP会话,密钥管理等.但作为用户我们很少会直接从Paramiko导入,而是Fabric把它融合进来了,我们在看教程前,先来看看SSH认证</p><h3 id="3-1-认证"><a href="#3-1-认证" class="headerlink" title="3.1 认证"></a>3.1 认证</h3><p>即使在<strong>原始</strong> OpenSSH客户端中,对远程服务器的身份验证也涉及多个潜在的秘密和配置来源。Fabric不仅支持其中的大多数,而且还具有自己的功能,下面是认证方式以及对应的配置:</p><ul><li>私人秘钥文件,通过<code>connect_kwargs.key_filename</code>配置</li><li>如果私钥文件通过密码保护,通过<code>connect_kwargs.passphrase</code>配置</li><li>私钥对象,通过<code>connect_kwargs.pkey</code>配置</li><li>SSH代理</li><li>密码,通过<code>connect_kwargs.password</code>配置或者<code>--prompt-for-login-password</code>手动提示输入</li><li>GSSAPI,不太懂,pass<br>上面的认证方式基本就全了,当然,每种认证方式的配置选择有很多,不止一种,很灵活,具体看<a href="http://docs.fabfile.org/en/2.5/concepts/authentication.html">Authentication</a>.<br><strong>其实我们不用管那么多啥认证方式,密码就完事了~,方便省略</strong></li></ul><h3 id="3-2-连接"><a href="#3-2-连接" class="headerlink" title="3.2 连接"></a>3.2 连接</h3><p>有了认证之后,就得到了一个连接(connection),用于SSH守护程序的连接,其中包含命令和文件传输的方法.这个connection其实跟context是一样的,一个远程一个本机,我们可以看下,在<code>fabfile.py</code>里代码如下:</p><figure class="highlight gradle"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs gradle"><span class="hljs-keyword">from</span> fabric <span class="hljs-keyword">import</span> <span class="hljs-keyword">task</span><br><br>@<span class="hljs-keyword">task</span><br><span class="hljs-keyword">def</span> show(c):<br> c.run(<span class="hljs-string">'ls'</span>)<br></code></pre></td></tr></table></figure><p>当我们用<code>fab -H host1 show</code>时是连接的host1再运行ls命令,而<code>fab show</code>是直接在本机运行ls命令,也就是Invoke的作用,当然我么需要一个配置文件,我用的是密码:</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs json"><span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"user"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"pi"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"connect_kwargs"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"password"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"mypassword"</span><br> <span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span><br><span class="hljs-punctuation">}</span><br></code></pre></td></tr></table></figure><p>以上是使用命令行调用,或者我们可以使用connection函数:</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> fabric <span class="hljs-keyword">import</span> Connection<br><br>c = Connection(<span class="hljs-string">'pi@host1'</span>,connect_kwargs={<span class="hljs-string">'password'</span>:<span class="hljs-string">'mypassword'</span>})<br>c.run(<span class="hljs-string">'ls'</span>)<br></code></pre></td></tr></table></figure><p>更多例子我们可以看<a href="http://docs.fabfile.org/en/2.5/getting-started.html">官方教程</a>,其实我最早看这上面例子时,我有点彷徨,没密码怎么连上去跑命令的,后来发现密码都是认证配置,连接并不重要,重要的是给你展示用法。</p><blockquote><p>从上面这个例子我们可以看出,可以用各种灵活的配置以及实现方式来创建一个远程连接。我的项目中有涉及远程部署多台机器, 我开始的想法是一把梭,创建所有机器的connection,然后上传上去,即我不需要-H来指定哪些机器,我已全部手动实例化,这就意味着用Fabric的Connection类实例化再加Facric中的Invoke功能。这是一种选择。后来发现,我应该采用一种更通用的方法,函数里我就执行相应动作,然后用-H指定主机,但是这样的话,比如我想同时部署多台机器,或者一半的机器,我还得在-H之后一个个输地址,这很耗时间,但好处就是一台台调试的时候更方便。所以我们还需外的任务,来根据条件快速输出-H后面的值做一个辅助工具,比如说输入1~7,那就自动把1~7七台机器的ip提示出来买,也算是曲线救国</p></blockquote><h3 id="3-3-配置"><a href="#3-3-配置" class="headerlink" title="3.3 配置"></a>3.3 配置</h3><p><a href="http://docs.fabfile.org/en/2.5/concepts/configuration.html">Fabric配置系统的核心</a>依赖于Invoke功能(与Fabric的其余大部分一样),只是配置文件名从<code>invoke</code>(不包括后缀)改为<code>fabfile</code>,然后是多了一些关于远程主机认证的配置,最常用的就是<code>connect_kwargs</code>,上面例子也出现过了,就不过多强调了</p><h2 id="4-总结"><a href="#4-总结" class="headerlink" title="4.总结"></a>4.总结</h2><p>这篇文章总结下来,花了我蛮多时间的,也算是打开了2020的新篇章。以为过程中一切都很顺利,结果写着写着就卡住了,打了自己”自以为很懂”的脸,也算有了更多收获,接下来更多加油!</p>]]></content>
<categories>
<category>Fabric</category>
</categories>
<tags>
<tag>Python</tag>
<tag>Linux</tag>
<tag>Fabric</tag>
<tag>Invoke</tag>
</tags>
</entry>
<entry>
<title>Python并发之异步I/O(async,await)</title>
<link href="/2020/02/13/python-asyncio/"/>
<url>/2020/02/13/python-asyncio/</url>
<content type="html"><![CDATA[<p>前面关于yield,yield from的语法说了好多,其实大多是铺垫,算是python在通往异步大道上的一些探索,但是将异步完整融入python整个过程并非都是顺利的,创建修改替换修改,反反复复,直到最新几版的python,趋于平稳,用其优雅特性来给人们展示异步,我们一起来看看!</p><h2 id="1-背景"><a href="#1-背景" class="headerlink" title="1. 背景"></a>1. 背景</h2><p>Python有很长一段的异步编程历史,特别是<code>twisted</code>,<code>gevent</code>和一些无堆栈的Python项目。<br>异步编程因为一些好的原因在这些年来越来越受关注。尽管相对于传统的线性风格更难一点,但是却是值得的:因为异步编程有更高的效率。<br>举个例子:在Http请求方面,Python异步协程可以提交请求然后去做队列中其他等待的任务,让它慢慢请求,而不是传统的一直等它请求到完成为止,这样的话会浪费更多的时间与资源。总之异步编程能让你的代码在处于等待资源状态时处理其他任务。<br>在Python3.4中,<code>asyncio</code>产生了。而在Python3.5中,有加入了对<code>async def</code> 和<code>await</code>新的语法支持。</p><h2 id="2-四个核心概念"><a href="#2-四个核心概念" class="headerlink" title="2. 四个核心概念"></a>2. 四个核心概念</h2><h3 id="2-1-Eventloop"><a href="#2-1-Eventloop" class="headerlink" title="2.1 Eventloop"></a>2.1 Eventloop</h3><p>Eventloop 可以说是 asyncio 应用的核心,是中央总控。Eventloop 实例提供了注册、取消和执行任务和回调的方法。<br>把一些异步函数 (就是任务,Task,一会就会说到) 注册到这个事件循环上,事件循环会循环执行这些函数 (但同时只能执行一个),当执行到某个函数时,如果它正在等待 I/O 返回,事件循环会暂停它的执行去执行其他的函数;当某个函数完成 I/O 后会恢复,下次循环到它的时候继续执行。因此,这些异步函数可以协同 (Cooperative) 运行:这就是事件循环的目标。</p><h3 id="2-2-Coroutine"><a href="#2-2-Coroutine" class="headerlink" title="2.2 Coroutine"></a>2.2 Coroutine</h3><p>协程 (Coroutine) 本质上是一个函数,特点是在代码块中可以将执行权交给其他协程:</p><figure class="highlight python"><table><tr><td class="gutter"><div class="code-wrapper"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></div></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">import</span> asyncio<br><br><span class="hljs-comment"># 在Python 3.4中, 创建一个协程我们用asyncio.coroutine装饰器,但现在基本弃用</span><br><span class="hljs-keyword">async</span> <span class="hljs-keyword">def</span> <span class="hljs-title function_">a</span>():<br> <span class="hljs-built_in">print</span>(<span class="hljs-string">'Suspending a'</span>)<br> <span class="hljs-keyword">await</span> asyncio.sleep(<span class="hljs-number">0</span>)<br> <span class="hljs-built_in">print</span>(<span class="hljs-string">'Resuming a'</span>)<br> <br><span class="hljs-keyword">async</span> <span class="hljs-keyword">def</span> <span class="hljs-title function_">b</span>():<br> <span class="hljs-built_in">print</span>(<span class="hljs-string">'In b'</span>)<br><br><span class="hljs-keyword">async</span> <span class="hljs-keyword">def</span> <span class="hljs-title function_">main</span>():<br> <span class="hljs-keyword">await</span> asyncio.gather(a(), b())<br><br><span class="hljs-comment">#asyncio.run 是 Python 3.7 新加的接口,要不然你得这么写:</span><br><span class="hljs-comment">#loop = asyncio.get_event_loop()</span><br><span class="hljs-comment">#loop.run_until_complete(main())</span><br><span class="hljs-comment">#loop.close()</span><br><br><span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">'__main__'</span>:<br> asyncio.run(main())<br><br>>>Suspending a<br>>>In b<br>>>Resuming a<br></code></pre></td></tr></table></figure><blockquote><p>asyncio.gather 用来并发运行任务,在这里表示协同的执行 a 和 b两个协程<br>在协程 a 中,有一句 await asyncio.sleep (0),await 表示调用协程,sleep 0 并不会真的 sleep(因为时间为 0),但是却可以把控制权交出去了。</p></blockquote><h3 id="2-3-Future"><a href="#2-3-Future" class="headerlink" title="2.3 Future"></a>2.3 Future</h3><p>接着说 Future,它代表了一个「未来」对象,异步操作结束后会把最终结果设置到这个 Future 对象上。Future 是对协程的封装,不过日常开发基本是不需要直接用这个底层 Future 类的</p><h3 id="2-4-Task"><a href="#2-4-Task" class="headerlink" title="2.4 Task"></a>2.4 Task</h3><p>Eventloop 除了支持协程,还支持注册 Future 和 Task2 种类型的对象,那为什么要存在 Future 和 Task 这 2 种类型呢?<br>Future 是协程的封装,Future 对象提供了很多任务方法 (如完成后的回调、取消、设置任务结果等等),但是开发者并不需要直接操作 Future 这种底层对象,而是用 Future 的子类 Task 协同的调度协程以实现并发。</p><p>Task 非常容易创建和使用:</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 或者用task = loop.create_task(a())</span><br>In : task = asyncio.ensure_future(a())<br><br>In : task<br>Out: <Task pending coro=<a() running at /Users/dongwm/mp/<span class="hljs-number">2019</span>-05-<span class="hljs-number">22</span>/coro1.py:<span class="hljs-number">4</span>>><br><br>In : task.done()<br>Out: <span class="hljs-literal">False</span><br><br>In : <span class="hljs-keyword">await</span> task<br>Suspending a<br>Resuming a<br><br>In : task<br>Out: <Task finished coro=<a() done, defined at /Users/dongwm/mp/<span class="hljs-number">2019</span>-05-<span class="hljs-number">22</span>/coro1.py:<span class="hljs-number">4</span>> result=<span class="hljs-literal">None</span>><br><br>In : task.done()<br>Out: <span class="hljs-literal">True</span><br></code></pre></td></tr></table></figure><p><strong>在这边的时候我卡了很长时间:future与Task的区别是什么????</strong></p><p>future在多线程说过,future表示终将发生的事情,而确定某件事会发生的唯一方式是执行的时间已经排定。(你不排定它,它就是个协程),那怎么排定?</p><p>BaseEventLoop.create_task(…) 或者 asyncio.ensure_future方法接收一个协程,排定它的运行时间,然后返回一个asyncio.Task 实例——也是 asyncio.Future 类的实例,因为 Task 是Future 的子类,用于包装协程。这与调用 Executor.submit(…) 方法创建Future实例是一个道理</p><p>这句话我读了好多遍,意思是不是说future跟task是同一样东西。对于event loop来说,一个包装了协程的future,就是循环中的一个task?我是这么理解的。</p><p>我们无法确定future啥时完成结束,但是总归结束(无论报错还是返回值)的,因为我们已经给它排定了时间。</p><h2 id="3-如何准确使用asyncio"><a href="#3-如何准确使用asyncio" class="headerlink" title="3. 如何准确使用asyncio"></a>3. 如何准确使用asyncio</h2><p>自从Python3.5加入了<code>async def</code> 和<code>await</code>新的语法,在往后Python大版本更新中一直对该部分的语法有更新修改,从前期的乱而杂,到最新的Python3.8语法渐渐稳定下来,优雅了很多,文档也条理了很多,其实要使用真正的语法还是要多看看<a href="https://docs.python.org/3/library/asyncio-task.html#coroutines">官网例子</a>。</p><p>初阶选手使用时,一般的<code>High-level APIs</code>完全可以满足需求,当理解了高阶API时,在去查看源代码,查看<code>Low-level APIs</code>,深层次的理解!<a href="https://www.dongwm.com/post/understand-asyncio-3/">这里有个较新的教程,分三个部分,很好!</a>,推荐阅读!</p>]]></content>
<categories>
<category>Python</category>
</categories>
<tags>
<tag>Python</tag>
<tag>asyncio</tag>
</tags>
</entry>
<entry>
<title>树莓派几种不同的PWM及其应用</title>
<link href="/2020/02/10/Raspberrypi-PWM/"/>
<url>/2020/02/10/Raspberrypi-PWM/</url>
<content type="html"><![CDATA[<p>树莓派上PWM其实应用的不多,很多例子都是简单地用代码控制小灯地闪亮黯淡过程,这在我之前刚接触硬件时充满懵懂的脑子面前显得十分新奇,后来慢慢懂了PWM,很多时候用在步进马达上,后来,慢慢过了半年后,我才发现一个问题,当树莓派有发热现象,传统地调用树莓派PWM功能时,步进马达总是怪怪的,转的不是很规律,有点性能跟不上的感觉。深入进去,发现PWM有多东西,这次就趁机总结一下,方便以后使用。</p><h2 id="1-树莓派上PWM的分类"><a href="#1-树莓派上PWM的分类" class="headerlink" title="1. 树莓派上PWM的分类"></a>1. 树莓派上PWM的分类</h2><h3 id="1-1-Fully-hardware-PWM-全硬件PWM"><a href="#1-1-Fully-hardware-PWM-全硬件PWM" class="headerlink" title="1.1 Fully hardware PWM (全硬件PWM)"></a>1.1 Fully hardware PWM (全硬件PWM)</h3><blockquote><p>这种类型的PWM由树莓派的PWM外设产生。脉冲的时序由PWM外设控制。它是最准确的,而且可以说是最灵活的。<br>它可以在GPIO 12/13/18/19上生成。但是,只有两个通道,因此一次只能生成两个不同的PWM流。GPIO 12/18在一个通道上,GPIO 13/19在另一个通道上。<br>适用于无抖动的伺服,无干扰的LED亮度控制,电机速度控制。</p></blockquote><h3 id="1-2-DMA-timed-PWM-(DMA定时PWM)"><a href="#1-2-DMA-timed-PWM-(DMA定时PWM)" class="headerlink" title="1.2 DMA timed PWM (DMA定时PWM)"></a>1.2 DMA timed PWM (DMA定时PWM)</h3><blockquote><p>这种类型的PWM由树莓派的DMA外设产生。<br>脉冲的时序由DMA控制。它的定时精度不如完全硬件的PWM精确,但比软件定时的PWM精确得多。取决于实现方式,它不如完全硬件的PWM灵活,例如,频率的数量要受限制得多,开和关之间的步数要受限制得多。<br>此类PWM可以在扩展头上的任何GPIO上生成。所有GPIO可能具有不同的设置。<br>适用于无抖动的伺服,无干扰的LED亮度控制,电机速度控制。</p></blockquote><h3 id="1-3-Software-Timed-PWM-(软件定时PWM)"><a href="#1-3-Software-Timed-PWM-(软件定时PWM)" class="headerlink" title="1.3 Software Timed PWM (软件定时PWM)"></a>1.3 Software Timed PWM (软件定时PWM)</h3><blockquote><p>这种类型的PWM由软件生成。<br>脉冲的时序由(Linux)调度程序控制。与完全硬件PWM或DMA定时PWM相比,它的定时精度明显降低。它比DMA定时PWM灵活得多,并且与完全硬件PWM一样灵活,例如,频率数量是无限的,开和关之间的步数是无限的。<br>此类PWM可以在扩展头上的任何GPIO上生成。所有GPIO可能具有不同的设置。时序精度将根据用于PWM的GPIO数量而变化。<br>不太适合伺服器,可以控制LED的亮度,但会出现毛刺,适合于电机速度控制。</p></blockquote><p>脉冲稳定性当然是:全硬件PWM >> DMA定时PWM >> 软件定时PWM</p><h2 id="2-树莓派上PWM的实现方式"><a href="#2-树莓派上PWM的实现方式" class="headerlink" title="2. 树莓派上PWM的实现方式"></a>2. 树莓派上PWM的实现方式</h2><h3 id="2-1-传统的实现方式"><a href="#2-1-传统的实现方式" class="headerlink" title="2.1 传统的实现方式"></a>2.1 传统的实现方式</h3><p>手动sleep来实现pwm功能,步进马达连接与传统实现可以看<a href="https://www.raspberrypi.org/forums/viewtopic.php?t=186016">这里</a></p><figure class="highlight python"><table><tr><td class="gutter"><div class="code-wrapper"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></div></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">while</span> <span class="hljs-literal">True</span>:<br><span class="hljs-built_in">print</span> (<span class="hljs-string">"Move Backward"</span>)<br><span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> <span class="hljs-built_in">range</span> (<span class="hljs-number">5</span>*<span class="hljs-number">200</span>):<br>GPIO.output(LinearActuatorDir, <span class="hljs-number">0</span>)<br>GPIO.output(LinearActuatorStepPin, <span class="hljs-number">1</span>)<br>time.sleep(LowSpeed)<br>GPIO.output(LinearActuatorStepPin, <span class="hljs-number">0</span>)<br>time.sleep(LowSpeed)<br><span class="hljs-built_in">print</span> (<span class="hljs-string">"Moving"</span>)<br> ...<br></code></pre></td></tr></table></figure><h3 id="2-2-神级武器gpiozero"><a href="#2-2-神级武器gpiozero" class="headerlink" title="2.2 神级武器gpiozero"></a>2.2 神级武器gpiozero</h3><p>这里我们可以用gpiozero中的<a href="https://gpiozero.readthedocs.io/en/stable/api_output.html#pwmoutputdevice">PWMOutputDevice类</a></p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 初始化完即运行,initial_value为占空比,frequency为频率</span><br>p = PWMOutputDevice(pin, *, active_high=<span class="hljs-literal">True</span>, initial_value=<span class="hljs-number">0</span>, frequency=<span class="hljs-number">100</span>, pin_factory=<span class="hljs-literal">None</span>)<br></code></pre></td></tr></table></figure><blockquote><p>注意,这里有一个点,<code>pin_factory=None</code>一般情况下默认使用的是<code>RPi.GPIO</code>,而<code>RPi.GPIO</code>默认的PWM是Software Timed PWM,所以可能稳定性不太好,所以我们可以使用<code>factory = PiGPIOFactory(host='127.0.0.1')</code>, <code>PiGPIO</code>支持DMA timed PWM,这个比较好</p></blockquote><h3 id="2-3-庞大的pigpio-(不推荐新手)"><a href="#2-3-庞大的pigpio-(不推荐新手)" class="headerlink" title="2.3 庞大的pigpio (不推荐新手)"></a>2.3 庞大的pigpio (不推荐新手)</h3><p>piggio python库中的set_PWM_dutycycle函数和hardware_PWM函数分别对应DMA,以及Fully hardware,这里不做过多阐述,自己也不是很懂 =。= ,有点尴尬,感觉相对于gpiozero稍微有点麻烦,可以看<a href="https://raspberrypi.stackexchange.com/questions/40243/pigpio-set-pwm-dutycycle-vs-hardware-pwm">两者区别</a>和<a href="http://abyz.me.uk/rpi/pigpio/python.html">pigpio文档</a></p>]]></content>
<categories>
<category>RaspberryPi</category>
</categories>
<tags>
<tag>Python</tag>
<tag>Linux</tag>
<tag>PWM</tag>
</tags>
</entry>
<entry>
<title>树莓派使用MCP3008模数转换器(ADC)</title>
<link href="/2020/01/20/ADC-MCP3008/"/>
<url>/2020/01/20/ADC-MCP3008/</url>
<content type="html"><![CDATA[<p>树莓派是一款出色的小型台式计算机,可用于控制数字输入和输出。但是,当你想读取模拟信号(例如从热敏电阻,电位器或许多其他类型的传感器获得的信号)时会怎么做?通过将小型的模数转换器(ADC)芯片连接到P树莓派,可以为程序打开模拟信号的世界!<br>我这边的目的是想测电瓶里面还剩多少电,通过数模转换器拿到电瓶此时输出的电压,再根据电瓶电压衰减曲线大概估算所剩百分比。</p><h2 id="1-方法总结"><a href="#1-方法总结" class="headerlink" title="1. 方法总结"></a>1. 方法总结</h2><ul><li><p><a href="https://www.raspberrypi-spy.co.uk/2013/10/analogue-sensors-on-the-raspberry-pi-using-an-mcp3008/">Analogue Sensors On The Raspberry Pi Using An MCP3008</a></p></li><li><p><a href="https://learn.adafruit.com/mcp3008-spi-adc/python-circuitpython">MCP3008 - 8-Channel 10-Bit ADC With SPI Interface</a></p></li><li><p><a href="https://gpiozero.readthedocs.io/en/stable/api_spi.html#mcp3208">gpiozero 15.2.8. MCP3208</a></p></li></ul><p>用上面的三种方法都能拿到最后所需要的转换值。第一种比较原生。推荐第二种跟第三种方法,原因下面讲。</p><h2 id="2-一些小细节"><a href="#2-一些小细节" class="headerlink" title="2. 一些小细节"></a>2. 一些小细节</h2><ul><li>市面上常用的模数转换器,一种是简单的MCP3008 模数转换器,可以读取8个10位精度的模拟输入通道;另外一种是相对更高级的ADS1x15系列可以读取4个12位至16位精度的通道,大部分情况前者就够用</li><li>当你使用MCP3008芯片时,务必使用第三种方法,<code>gpiozero MCP3008</code>函数,依旧是熟悉的味道,简单好用,三行代码就解决;当使用ADS1x15系列时,需要用第二种方法中的库<a href="https://learn.adafruit.com/raspberry-pi-analog-to-digital-converters">在这里往下翻翻</a></li><li>第二种方法中的<code>adafruit-circuitpython-mcp3xxx</code>库有个注意点,虽然MCP是10位精度的,也就是转换的最大值本应该是1023,但用这个方法的时候默认返回16位精度的值,为了与ADS1x15系列的借口保持统一,所以结果要么右移6位,要么就当成是16位精度也行。</li></ul><p>另外的,在<code>gpiozero</code>中,有下列方法实例化模数转换器:</p><figure class="highlight python"><table><tr><td class="gutter"><div class="code-wrapper"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></div></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> gpiozero <span class="hljs-keyword">import</span> MCP3008<br><br>MCP3008(channel=<span class="hljs-number">0</span>)<br>MCP3008(channel=<span class="hljs-number">0</span>, device=<span class="hljs-number">0</span>)<br>MCP3008(channel=<span class="hljs-number">0</span>, port=<span class="hljs-number">0</span>, device=<span class="hljs-number">0</span>)<br>MCP3008(channel=<span class="hljs-number">0</span>, select_pin=<span class="hljs-number">8</span>)<br>MCP3008(channel=<span class="hljs-number">0</span>, clock_pin=<span class="hljs-number">11</span>, mosi_pin=<span class="hljs-number">10</span>, miso_pin=<span class="hljs-number">9</span>, select_pin=<span class="hljs-number">8</span>)<br></code></pre></td></tr></table></figure><p>上面<code>device=0(默认) 或者device=1</code>,其实默认意味着MCP3008的<strong>cs</strong>引脚接的是<strong>GPIO08</strong>(也就是SPI0_CE0_N),所以<code>device=1</code>意味着接的<strong>GPIO07</strong></p><p>我一开始以为<strong>cs</strong>引脚只能接这两个,后来发现,所有通用<strong>IO</strong>引脚都能接,只要加个<code>select_pin</code>参数,简简单单,好用的。</p><p><strong>Arduino是自带模数转换功能,实在着急,先用个Arduino救救急,不展开~</strong></p>]]></content>
<categories>
<category>RaspberryPi</category>
</categories>
<tags>
<tag>Python</tag>
<tag>Linux</tag>
<tag>SPI</tag>
</tags>
</entry>
<entry>
<title>Python3 yield from 用法详解</title>
<link href="/2020/01/12/python-yield-from/"/>
<url>/2020/01/12/python-yield-from/</url>
<content type="html"><![CDATA[<p><code>yield from</code>是Python3.3以后全新的语言结构,它的作用比<code>yield</code>多得多,因此人们认为继续使用那个关键字多少会引起误解。在其他语言中,类似的结构使用<code>await</code>关键字,这个名称就好多了。当然,后面Python也改成了<code>await</code>,这个我们在结尾说,少废话,先看东西。</p><h2 id="1-替代内层for循环"><a href="#1-替代内层for循环" class="headerlink" title="1. 替代内层for循环"></a>1. 替代内层for循环</h2><p>如果生成器函数需要产出另一个生成器生成的值,传统的解决方法是使用嵌套的for循环:</p><figure class="highlight python"><table><tr><td class="gutter"><div class="code-wrapper"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></div></td><td class="code"><pre><code class="hljs python"><span class="hljs-meta">>>> </span><span class="hljs-keyword">def</span> <span class="hljs-title function_">chain</span>(<span class="hljs-params">*iterables</span>):<br>...<span class="hljs-keyword">for</span> it <span class="hljs-keyword">in</span> iterables:<br>...<span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> it:<br>...<span class="hljs-keyword">yield</span> i<br><span class="hljs-meta">>>> </span>s = <span class="hljs-string">'ABC'</span><br><span class="hljs-meta">>>> </span>t = <span class="hljs-built_in">tuple</span>(<span class="hljs-built_in">range</span>(<span class="hljs-number">3</span>))<br><span class="hljs-meta">>>> </span><span class="hljs-built_in">list</span>(chain(s, t))<br>[<span class="hljs-string">'A'</span>, <span class="hljs-string">'B'</span>, <span class="hljs-string">'C'</span>, <span class="hljs-number">0</span>, <span class="hljs-number">1</span>, <span class="hljs-number">2</span>]<br></code></pre></td></tr></table></figure><p>chain 生成器函数把操作依次交给接收到的各个可迭代对象处理。 </p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs python">Python3<span class="hljs-number">.3</span>之后引入了新语法:<br><span class="hljs-meta">>>> </span><span class="hljs-keyword">def</span> <span class="hljs-title function_">chain</span>(<span class="hljs-params">*iterables</span>):<br><span class="hljs-meta">... </span><span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> iterables:<br><span class="hljs-meta">... </span><span class="hljs-keyword">yield</span> <span class="hljs-keyword">from</span> i<br>...<br><span class="hljs-meta">>>> </span><span class="hljs-built_in">list</span>(chain(s, t))<br>[<span class="hljs-string">'A'</span>, <span class="hljs-string">'B'</span>, <span class="hljs-string">'C'</span>, <span class="hljs-number">0</span>, <span class="hljs-number">1</span>, <span class="hljs-number">2</span>]<br></code></pre></td></tr></table></figure><ul><li><code>yield from</code> 完全代替了内层的 for 循环。</li><li><code>yield from x</code> 表达式对 x 对象所做的第一件事是,调用 iter(x),从中获取迭代器。因此,x 可以是任何可迭代的对象。</li><li>在这个示例中使用 <code>yield from </code>代码读起来更顺畅,不过感觉更像是语法糖。</li></ul><p>上面这个例子看上去比较简单(传统意义上说因为我们只是for循环一次就完事,因为只嵌套了一层),我们再来看几个<code>yield from</code>的例子。<br><strong>例子1:我们有一个嵌套型的序列,想将它扁平化处理为一列单独的值。</strong></p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> collections <span class="hljs-keyword">import</span> Iterable<br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">flatten</span>(<span class="hljs-params">items, ignore_types=(<span class="hljs-params"><span class="hljs-built_in">str</span>, <span class="hljs-built_in">bytes</span></span>)</span>):<br><span class="hljs-keyword">for</span> x <span class="hljs-keyword">in</span> items:<br><span class="hljs-keyword">if</span> <span class="hljs-built_in">isinstance</span>(x, Iterable) <span class="hljs-keyword">and</span> <span class="hljs-keyword">not</span> <span class="hljs-built_in">isinstance</span>(x, ignore_types):<br><span class="hljs-keyword">yield</span> <span class="hljs-keyword">from</span> flatten(x)<br><span class="hljs-keyword">else</span>:<br><span class="hljs-keyword">yield</span> x<br><br>items = [<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, [<span class="hljs-number">3</span>, <span class="hljs-number">4</span>, [<span class="hljs-number">5</span>, <span class="hljs-number">6</span>], <span class="hljs-number">7</span>], <span class="hljs-number">8</span>]<br><span class="hljs-keyword">for</span> x <span class="hljs-keyword">in</span> flatten(items):<br><span class="hljs-built_in">print</span>(x)<br><span class="hljs-comment"># output:</span><br> <span class="hljs-number">1</span> <span class="hljs-number">2</span> <span class="hljs-number">3</span> <span class="hljs-number">4</span> <span class="hljs-number">5</span> <span class="hljs-number">6</span> <span class="hljs-number">7</span> <span class="hljs-number">8</span><br>-----------------------------------------------<br>items = [<span class="hljs-string">'Dave'</span>, <span class="hljs-string">'Paula'</span>, [<span class="hljs-string">'Thomas'</span>, <span class="hljs-string">'Lewis'</span>]]<br><span class="hljs-keyword">for</span> x <span class="hljs-keyword">in</span> flatten(items):<br><span class="hljs-built_in">print</span>(x)<br><br><span class="hljs-comment"># output:</span><br>Dave<br>Paula<br>Thomas<br>Lewis<br></code></pre></td></tr></table></figure><ul><li><code>collections.Iterable</code>是一个抽象基类,我们用<code>isinstance(x, Iterable)</code>检查某个元素是否是可迭代的.如果是的话,那么就用<code>yield from</code>将这个可迭代对象作为一种子例程进行递归。最终返回结果就是一个没有嵌套的单值序列了。</li><li>代码中额外的参数<code>ignore types </code>和检测语句<code>isinstance(x, ignore types)</code>用来将字符<br>串和字节排除在可迭代对象外,防止将它们再展开成单个的字符。</li><li>如果这里不用<code>yield from</code>的话,那么就需要另外一个for来嵌套,并不是一种优雅的操作</li></ul><p><strong>例子2:利用一个Node类来表示树结构</strong></p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Node</span>:<br><span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params">self, value</span>):<br>self._value = value<br>self._children = []<br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">__repr__</span>(<span class="hljs-params">self</span>):<br><span class="hljs-keyword">return</span> <span class="hljs-string">'Node({!r})'</span>.<span class="hljs-built_in">format</span>(self._value)<br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">add_child</span>(<span class="hljs-params">self, node</span>):<br>self._children.append(node)<br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">__iter__</span>(<span class="hljs-params">self</span>):<br><span class="hljs-keyword">return</span> <span class="hljs-built_in">iter</span>(self._children)<br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">depth_first</span>(<span class="hljs-params">self</span>):<br><span class="hljs-keyword">yield</span> self<br><span class="hljs-keyword">for</span> c <span class="hljs-keyword">in</span> self:<br><span class="hljs-keyword">yield</span> <span class="hljs-keyword">from</span> c.depth_first()<br><br><br><span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">'__main__'</span>:<br>root = Node(<span class="hljs-number">0</span>)<br>child1 = Node(<span class="hljs-number">1</span>)<br>child2 = Node(<span class="hljs-number">2</span>)<br>root.add_child(child1)<br>root.add_child(child2)<br>child1.add_child(Node(<span class="hljs-number">3</span>))<br>child1.add_child(Node(<span class="hljs-number">4</span>))<br>child2.add_child(Node(<span class="hljs-number">5</span>))<br><span class="hljs-keyword">for</span> ch <span class="hljs-keyword">in</span> root.depth_first():<br><span class="hljs-built_in">print</span>(ch)<br></code></pre></td></tr></table></figure><ul><li><code> __iter__</code>代表一个Pyton的迭代协议,返回一个迭代器对象,就能迭代了</li><li><code>depth_frist</code>返回一个生成器,仔细体会其中的<code>yield</code>与 <code>yield from</code>用法</li></ul><p>上面两个例子无论是树还是嵌套序列,都比较复杂,观察这里<code>yield from</code>跟的是什么,跟的是函数,生成器函数,而且都是在函数内递归。虽然我也不是理解的很透彻 =,= 。但现在应该知道,这是<code>yield from</code>一种常用的方法了(认真体会,手动滑稽)。</p><h2 id="2-打开双通道"><a href="#2-打开双通道" class="headerlink" title="2. 打开双通道"></a>2. 打开双通道</h2><p>如果 <code>yield from</code> 结构唯一的作用是替代产出值的嵌套 for 循环,这个结构很有可能不会添加到 Python 语言中。<code>yield from</code> 结构的本质作用无法通过简单的可迭代对象说明,而要发散思维,使用嵌套的生成器。<br><strong>yield from 的主要功能是打开双向通道,把最外层的调用方与最内层的子生成器连接起来,这样二者可以直接发送和产出值,还可以直接传入异常,而不用在位于中间的协程中添加大量处理异常的样板代码。有了这个结构,协程可以通过以前不可能的方式委托职责。</strong><br>这里有张详细图来说明三者关系:<a href="http://flupy.org/resources/yield-from.pdf" title="委派生成器,子生成器与调用方">http://flupy.org/resources/yield-from.pdf</a><br>例子就不展开了,有兴趣的童鞋可以去 <strong>Fluent Python</strong>这本书上 查看 <strong>示例16-17</strong>。并且结合示例图好好体会(我也有待好好体会)</p><h2 id="3-总结"><a href="#3-总结" class="headerlink" title="3. 总结"></a>3. 总结</h2><ul><li><p><code>yield from</code>伴随着Python3.4新加入的asyncio模块得以发扬光大, <code>asynico + yield from</code>双枪组合,但当时定义协程还是需要<code>@asyncio.coroutine</code>装饰器,之前都是我们手工切换协程,现在当声明函数为协程后,我们通过事件循环来调度协程</p></li><li><p>从Python 3.5开始引入了新的语法 <code>async</code> 和 <code>await</code> ,<code>yield from</code>换成了<code>await</code>(为了不与实现内层for循环的<code>yield from</code>误解?!),<code>@asyncio.coroutine</code>换成了<code>async</code>, <code>asynico + await</code>成了新的双枪组合,一直到未来… 从Python设计的角度来说,它们让协程表面上独立于生成器而存在,将细节都隐藏于<code>asyncio</code>模块之下,语法更Pythonic。</p></li><li><p>从Python3.5加入以来,<code>asynico</code>官方文档有点混乱,毕竟是新模块,语法也一直在变换中,到了Python 3.7,3.8才趋于稳定,文档也好像重写了,清晰明了,如果要细品<code>asynico</code>,那么不如看最新文档搞起来,能节省不少功夫!。</p></li></ul>]]></content>
<categories>
<category>Python</category>
</categories>
<tags>
<tag>Python</tag>
<tag>yield from</tag>
</tags>
</entry>
<entry>
<title>Python yield使用详解(二)</title>
<link href="/2020/01/06/python-yield-2/"/>
<url>/2020/01/06/python-yield-2/</url>
<content type="html"><![CDATA[<p>yield的第二部分主要是with模块的功能,即上下文管理器。我们可以用<code>contextmanager</code>装饰器加上<code>yield</code>语法代替传统的<code>__enter__</code>,<code>__exit__</code>魔法方法,让代码变得更Pythonic!</p><h2 id="1-上下文管理器和with块"><a href="#1-上下文管理器和with块" class="headerlink" title="1. 上下文管理器和with块"></a>1. 上下文管理器和with块</h2><h3 id="1-1-with表达式"><a href="#1-1-with表达式" class="headerlink" title="1.1 with表达式"></a>1.1 with表达式</h3><p>常见的with用法格式,控制代码块的<code>进入/退出</code>:</p><figure class="highlight python"><table><tr><td class="gutter"><div class="code-wrapper"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></div></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">with</span> <span class="hljs-built_in">open</span>(filename) <span class="hljs-keyword">as</span> f:<br> statement<br> statement<br> ...<br><span class="hljs-keyword">with</span> lock:<br> statement<br> statement<br> ...<br></code></pre></td></tr></table></figure><h3 id="1-2-定制你自己的上下文管理器"><a href="#1-2-定制你自己的上下文管理器" class="headerlink" title="1.2 定制你自己的上下文管理器"></a>1.2 定制你自己的上下文管理器</h3><p>一个计时器的例子:</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">import</span> time<br><span class="hljs-keyword">from</span> contextlib <span class="hljs-keyword">import</span> contextmanager<br><br><span class="hljs-meta">@contextmanager</span><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">timethis</span>(<span class="hljs-params">label</span>):<br> start = time.time()<br> <span class="hljs-keyword">try</span>:<br> <span class="hljs-keyword">yield</span> <br> <span class="hljs-keyword">finally</span>:<br> end = time.time()<br> <span class="hljs-built_in">print</span>(<span class="hljs-string">'%s: %0.3f'</span> % (label, end-start))<br><br><span class="hljs-comment">#Usage</span><br><span class="hljs-keyword">with</span> timethis(<span class="hljs-string">'counting'</span>):<br> n = <span class="hljs-number">1000000</span><br> <span class="hljs-keyword">while</span> n > <span class="hljs-number">0</span>:<br> n -= <span class="hljs-number">1</span><br><span class="hljs-comment">#Output</span><br>counting: <span class="hljs-number">0.156</span><br></code></pre></td></tr></table></figure><p>另外一个例子:临时文件夹</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">import</span> tempfile, shutil<br><span class="hljs-keyword">from</span> contextlib <span class="hljs-keyword">import</span> contextmanager<br><br><span class="hljs-meta">@contextmanager</span><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">tempdir</span>():<br> outdir = tempfile.mkdtemp()<br> <span class="hljs-keyword">try</span>:<br> <span class="hljs-keyword">yield</span> outdir<br> <span class="hljs-keyword">finally</span>:<br> shutil.rmtree(outdir)<br><br><span class="hljs-comment">#Example</span><br><span class="hljs-keyword">with</span> tempdir() <span class="hljs-keyword">as</span> dirname:<br> ...<br></code></pre></td></tr></table></figure><p><strong>等等!!!</strong>,<strong>这里的<code>yield outdir</code>是什么?</strong></p><ul><li>不是迭代</li><li>不是数据流</li><li>不是并发</li><li>那是什么???</li></ul><h2 id="2-深入上下文管理器"><a href="#2-深入上下文管理器" class="headerlink" title="2. 深入上下文管理器"></a>2. 深入上下文管理器</h2><p>现在可以对上面内容进行小结一下:</p><ul><li>上下文对象存在的目的是管理<code>with</code>语句,就像迭代器的存在是为了管理<code>for</code>语句</li><li>with语句的目的是简化t<code>try/finally</code>模式,这种模式用于保证一段代码运行完毕后执行某项操作。即使那段代码由于异常,<code>return</code>语句或<code>sys.exit()</code>调用而终止,也会执行指定操作</li></ul><p><strong>上下文管理器的内部实现:</strong><br><img src="/images/yield/yield2_1.png"></p><ul><li>上下文管理器协议包含__enter__和__exit__两个方法,<code>with</code>语句还是运行时,会在上下文管理器对象上调用__enter__方法。with语句结束后,会在上下文管理器对象上调用__exit__方法,以此扮演finally子句的角色</li></ul><p><strong>实现模板</strong></p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Manager</span>(<span class="hljs-title class_ inherited__">object</span>):<br> <span class="hljs-keyword">def</span> <span class="hljs-title function_">__enter__</span>(<span class="hljs-params">self</span>):<br> <span class="hljs-keyword">return</span> value<br> <span class="hljs-keyword">def</span> <span class="hljs-title function_">__exit__</span>(<span class="hljs-params">self, exc_type, val, tb</span>):<br> <span class="hljs-keyword">if</span> exc_type <span class="hljs-keyword">is</span> <span class="hljs-literal">None</span>:<br> <span class="hljs-keyword">return</span><br> <span class="hljs-keyword">else</span>:<br> <span class="hljs-comment"># Handle an exception (if you want)</span><br> <span class="hljs-keyword">return</span> <span class="hljs-literal">True</span> <span class="hljs-keyword">if</span> handled <span class="hljs-keyword">else</span> <span class="hljs-literal">False</span><br><br> <span class="hljs-comment"># Usage </span><br><span class="hljs-keyword">with</span> Manager() <span class="hljs-keyword">as</span> value:<br> statements<br> statements<br></code></pre></td></tr></table></figure><p><strong>实例</strong></p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">import</span> tempfile<br><span class="hljs-keyword">import</span> shutil<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">tempdir</span>(<span class="hljs-title class_ inherited__">object</span>):<br> <span class="hljs-keyword">def</span> <span class="hljs-title function_">__enter__</span>(<span class="hljs-params">self</span>):<br> self.dirname = tempfile.mkdtemp() <span class="hljs-comment">#生成临时文件夹</span><br> <span class="hljs-keyword">return</span> self.dirname<br> <span class="hljs-keyword">def</span> <span class="hljs-title function_">__exit__</span>(<span class="hljs-params">self, exc, val, tb</span>):<br> shutil.rmtree(self.dirname) <span class="hljs-comment">#删除文件夹</span><br><br><span class="hljs-comment"># Usage </span><br><span class="hljs-keyword">with</span> tempdir() <span class="hljs-keyword">as</span> dirname:<br>...<br><span class="hljs-comment"># with语句运行完毕后,会自动删除那个临时文件夹</span><br></code></pre></td></tr></table></figure><h2 id="3-更简洁的一种选择:利用-contextmanager装饰器"><a href="#3-更简洁的一种选择:利用-contextmanager装饰器" class="headerlink" title="3. 更简洁的一种选择:利用@contextmanager装饰器"></a>3. 更简洁的一种选择:利用@contextmanager装饰器</h2><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">import</span> tempfile, shutil<br><span class="hljs-keyword">from</span> contextlib <span class="hljs-keyword">import</span> contextmanager<br><br><span class="hljs-meta">@contextmanager</span><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">tempdir</span>():<br> dirname = tempfile.mkdtemp()<br> <span class="hljs-keyword">try</span>:<br> <span class="hljs-keyword">yield</span> dirname<br> <span class="hljs-keyword">finally</span>:<br> shutil.rmtree(dirname)<br> <span class="hljs-comment"># 跟上个例子相同的代码。</span><br></code></pre></td></tr></table></figure><h3 id="3-1-contextmanager装饰器运行原理"><a href="#3-1-contextmanager装饰器运行原理" class="headerlink" title="3.1 contextmanager装饰器运行原理"></a>3.1 contextmanager装饰器运行原理</h3><p>下图:<br><img src="/images/yield/yield2_2.png"></p><ul><li>思考剪刀处<code>yield</code>代码</li><li>将代码一分两半</li></ul><p><img src="/images/yield/yield2_3.png"></p><ul><li>每一半对应着上下文管理器协议</li><li><code>yield</code>是促成图中这一实现的魔法</li></ul><p>这里有一个注意点:使用<code>@contextmanager</code>装饰器时,要把yield语句放在<code>try/finally</code>语句中,这是无法避免的,因为我们永远不知道上下文管理器的用户会在with中做什么(会引发一些python解释器能捕获到的错误)。<br>当然,想要你如果想要更加深入的了解<code>@contextmanager</code>的内部代码实现,可以查看源代码,这里不展开了。</p><h2 id="4-总结"><a href="#4-总结" class="headerlink" title="4. 总结"></a>4. 总结</h2><ol><li><code>yield</code>表达式的另一个不同作用:上下文管理器</li><li>常用来重新定制控制流</li><li>也可以用<code>@contextmanager</code>装饰器来代替__enter__和__exit__两个方法。优雅且实用,把三个不同的Python特性结合到一起: 函数装饰器,生成器和<code>with</code>语句</li></ol>]]></content>
<categories>
<category>Python</category>
</categories>
<tags>
<tag>Python</tag>
<tag>yield</tag>
</tags>
</entry>
<entry>
<title>Python yield使用详解(一)</title>
<link href="/2020/01/02/python-yield-1/"/>
<url>/2020/01/02/python-yield-1/</url>
<content type="html"><![CDATA[<p>刚学到yield这个较为陌生的语法时,一头埋了进去,自认为较为全面的学习到了精髓。结果码了这么多代码好像也没用到过多少次这个关键字,直接扑街。记得工作经验只有一年的时候我出去面了个试,人家问我yield作用是什么,我很自信,一个劲地回答道协程协程,结果好像不太满意,人家一说生成器,奥,恍然大悟,竟然把最基本的语法忘了。所以现在看来,yield是否在协程异步方面有着不可代替的作用,必须出现它呢,那就从篇文章往下看。</p><h2 id="1-生成器"><a href="#1-生成器" class="headerlink" title="1. 生成器"></a>1. 生成器</h2><p><strong>yield语句可以作为生成器</strong></p><figure class="highlight python"><table><tr><td class="gutter"><div class="code-wrapper"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></div></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">countdown</span>(<span class="hljs-params">n</span>):<br> <span class="hljs-keyword">while</span> n > <span class="hljs-number">0</span>:<br> <span class="hljs-keyword">yield</span> n<br> n -= <span class="hljs-number">1</span><br><br><span class="hljs-comment"># 可以当迭代器来使用它</span><br><span class="hljs-keyword">for</span> x <span class="hljs-keyword">in</span> countdown(<span class="hljs-number">10</span>):<br> <span class="hljs-built_in">print</span>(<span class="hljs-string">'T-minus'</span>, x)<br><br><span class="hljs-comment"># 可以使用next()来产出值,当生成器函数return(结束)时,报错。</span><br><span class="hljs-meta">>>> </span>c = countdown(<span class="hljs-number">3</span>)<br><span class="hljs-meta">>>> </span>c<br><generator <span class="hljs-built_in">object</span> countdown at <span class="hljs-number">0x10064f900</span>><br><span class="hljs-meta">>>> </span><span class="hljs-built_in">next</span>(c)<br><span class="hljs-number">3</span><br><span class="hljs-meta">>>> </span><span class="hljs-built_in">next</span>(c)<br><span class="hljs-number">2</span><br><span class="hljs-meta">>>> </span><span class="hljs-built_in">next</span>(c)<br><span class="hljs-number">1</span><br><span class="hljs-meta">>>> </span><span class="hljs-built_in">next</span>(c)<br>Traceback (most recent call last):<br> File <span class="hljs-string">"<stdin>"</span>, line <span class="hljs-number">1</span>, <span class="hljs-keyword">in</span> ?<br>StopIteration<br></code></pre></td></tr></table></figure><p>这篇文章我着重讲yield作为<code>协程</code>的使用方法,作为生成器的话我一笔带过,想要仔细了解<code>迭代器</code>与<code>生成器</code>使用,我这里推荐个教程。<a href="https://foofish.net/iterators-vs-generators.html">完全理解Python迭代对象、迭代器、生成器</a> ,很棒,还有的话就是与生成器密切相关的itertools模块,可以了解下。但是我在讲<code>yield协程</code>之前我再给出一张图来说yield一个有趣的用法。 </p><p><strong>生成器类似于UNIX管道的作用</strong></p><p><img src="/images/yield/yield1_1.png"></p><p>这个process会有难以置信的作用,比如实现UNIX中grep的作用。不展开,以后肯定会用到它。</p><h2 id="2-生成器进化为协程"><a href="#2-生成器进化为协程" class="headerlink" title="2. 生成器进化为协程"></a>2. 生成器进化为协程</h2><h3 id="2-1-一个协程例子"><a href="#2-1-一个协程例子" class="headerlink" title="2.1 一个协程例子"></a>2.1 一个协程例子</h3><p>重头戏来了。<br>如果你想更多的使用yield,那么就是协程了。<strong>协程就不仅仅是产出值了,而是能消费发送给它的值。</strong><br>那么这里的例子就用协程实现上面的UNIX的grep作用</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">grep</span>(<span class="hljs-params">pattern</span>):<br> <span class="hljs-built_in">print</span>(<span class="hljs-string">"Looking for {}"</span>.<span class="hljs-built_in">format</span>(pattern))<br> <span class="hljs-keyword">while</span> <span class="hljs-literal">True</span>:<br> line = <span class="hljs-keyword">yield</span><br> <span class="hljs-keyword">if</span> pattern <span class="hljs-keyword">in</span> line:<br> <span class="hljs-built_in">print</span>(<span class="hljs-string">'{} : grep success '</span>.<span class="hljs-built_in">format</span>(line))<br><br><span class="hljs-meta">>>> </span>g=grep(<span class="hljs-string">'python'</span>)<br><span class="hljs-comment"># 还是个生成器</span><br><span class="hljs-meta">>>> </span>g<br><generator <span class="hljs-built_in">object</span> grep at <span class="hljs-number">0x7f17e86f3780</span>> <br><span class="hljs-comment"># 激活协程!只能用一次,也可以用g.send(None)来代替next(g)</span><br><span class="hljs-meta">>>> </span><span class="hljs-built_in">next</span>(g)<br>Looking <span class="hljs-keyword">for</span> python<br><span class="hljs-comment"># 使用.send(...)发送数据,发送的数据会成为生成器函数中yield表达式值,即变量line的值</span><br><span class="hljs-meta">>>> </span>g.send(<span class="hljs-string">"Yeah, but no, but yeah, but no"</span>)<br><br><span class="hljs-meta">>>> </span>g.send(<span class="hljs-string">"A series of tubes"</span>)<br><span class="hljs-comment"># 协程,协程,就是互相协作的程序,我发数据过去然后你协助我一下看看grep成功没</span><br><span class="hljs-meta">>>> </span>g.send(<span class="hljs-string">"python generators rock!"</span>)<br>python generators rock! : grep success <br><span class="hljs-comment"># 关闭</span><br><span class="hljs-meta">>>> </span>g.close()<br></code></pre></td></tr></table></figure><p>例子讲完了。有几个注意点:</p><ul><li>生成器用于生成供迭代的数据</li><li>协程是数据的消费者</li><li>为了避免脑袋炸裂,不能把两个概念混为一谈</li><li>协程与迭代无关</li><li>注意,虽然在协程值会使用yield产出值,但这与迭代无关</li></ul><h3 id="2-2-发送数据给协程"><a href="#2-2-发送数据给协程" class="headerlink" title="2.2 发送数据给协程"></a>2.2 发送数据给协程</h3><p><img src="/images/yield/yield1_2.png"></p><p>预激活,到yield处暂停。然后发送item值,协程继续了,协程中item接收到发送的那个值,然后到下一个yield再暂停。</p><h3 id="2-3-使用一个装饰器"><a href="#2-3-使用一个装饰器" class="headerlink" title="2.3 使用一个装饰器"></a>2.3 使用一个装饰器</h3><p>如果不预激(<code>primer</code>),那么协程没什么用,调用g.send(x)之前。记住一定要调用next(g)。为了简化协程用法,有时会使用一个预激装饰器,如下。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">coroutine</span>(<span class="hljs-params">func</span>):<br> <span class="hljs-keyword">def</span> <span class="hljs-title function_">primer</span>(<span class="hljs-params">*args,**kwargs</span>):<br> cr = func(*args,**kwargs)<br> <span class="hljs-built_in">next</span>(cr)<br> <span class="hljs-keyword">return</span> cr<br> <span class="hljs-keyword">return</span> primer<br><br><span class="hljs-meta">@coroutine</span><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">grep</span>(<span class="hljs-params">pattern</span>):<br> ... <br></code></pre></td></tr></table></figure><h3 id="2-4-关闭一个协程"><a href="#2-4-关闭一个协程" class="headerlink" title="2.4 关闭一个协程"></a>2.4 关闭一个协程</h3><ul><li>一个协程有可能永远运行下去</li><li>可以 .close()让它停下来<br>例子中已经体现,不展开。</li></ul><h3 id="2-5-捕捉close"><a href="#2-5-捕捉close" class="headerlink" title="2.5 捕捉close()"></a>2.5 捕捉close()</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">grep</span>(<span class="hljs-params">pattern</span>):<br> <span class="hljs-built_in">print</span>(<span class="hljs-string">"Looking for {}"</span>.<span class="hljs-built_in">format</span>(pattern))<br> <span class="hljs-keyword">try</span>:<br> <span class="hljs-keyword">while</span> <span class="hljs-literal">True</span>:<br> line = <span class="hljs-keyword">yield</span><br> <span class="hljs-keyword">if</span> pattern <span class="hljs-keyword">in</span> line:<br> <span class="hljs-built_in">print</span>(line)<br> <span class="hljs-keyword">except</span> GeneratorExit:<br> <span class="hljs-built_in">print</span>(<span class="hljs-string">"Going away. Goodbye"</span>)<br></code></pre></td></tr></table></figure><p>捕捉到.close()方法,然后会打印<code>"Going away. Goodbye"</code>。</p><h3 id="2-6-抛出异常"><a href="#2-6-抛出异常" class="headerlink" title="2.6 抛出异常"></a>2.6 抛出异常</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-meta">>>> </span>g = grep(<span class="hljs-string">"python"</span>)<br><span class="hljs-meta">>>> </span><span class="hljs-built_in">next</span>(g) <span class="hljs-comment"># Prime it</span><br>Looking <span class="hljs-keyword">for</span> python<br><span class="hljs-meta">>>> </span>g.send(<span class="hljs-string">"python generators rock!"</span>)<br>python generators rock! : grep success <br><span class="hljs-meta">>>> </span>g.throw(RuntimeError,<span class="hljs-string">"You're hosed"</span>)<br>Traceback (most recent call last):<br>.....<br>.....<br>RuntimeError: Yo<span class="hljs-string">u're hosed</span><br><span class="hljs-string">>>></span><br></code></pre></td></tr></table></figure><p>说明:</p><ul><li>在协程内部能抛出一个异常</li><li>异常发生于yield表达式</li><li>不慌,我们可以平常的方法处理它</li></ul><h3 id="2-7-生成器返回数值"><a href="#2-7-生成器返回数值" class="headerlink" title="2.7 生成器返回数值"></a>2.7 生成器返回数值</h3><p>鉴于上面的例子是一直run下去的,所以稍加修改:</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">grep</span>(<span class="hljs-params">pattern</span>):<br> <span class="hljs-built_in">print</span>(<span class="hljs-string">"Looking for {}"</span>.<span class="hljs-built_in">format</span>(pattern))<br> <span class="hljs-keyword">while</span> <span class="hljs-literal">True</span>:<br> line = <span class="hljs-keyword">yield</span><br> <span class="hljs-comment"># 当发送的数据为None时,跳出while循环</span><br> <span class="hljs-keyword">if</span> line <span class="hljs-keyword">is</span> <span class="hljs-literal">None</span>:<br> <span class="hljs-keyword">break</span><br> <span class="hljs-keyword">else</span>:<br> <span class="hljs-keyword">if</span> pattern <span class="hljs-keyword">in</span> line:<br> <span class="hljs-built_in">print</span>(<span class="hljs-string">'{} : grep success '</span>.<span class="hljs-built_in">format</span>(line))<br> <span class="hljs-keyword">return</span> <span class="hljs-string">'End'</span><br><br><span class="hljs-meta">>>> </span>..... 省略<br><span class="hljs-meta">>>> </span>g.send(<span class="hljs-literal">None</span>)<br>Traceback (most recent call last):<br> ...<br> ...<br>StopIteration: End<br><br><span class="hljs-comment"># 这里可以用try捕捉异常,异常对象的value属性保存着返回的值</span><br> <span class="hljs-keyword">try</span>:<br> g.send(<span class="hljs-literal">None</span>)<br> <span class="hljs-keyword">except</span> StopIteration <span class="hljs-keyword">as</span> exc:<br> result = exc.value<br><br><span class="hljs-meta">>>> </span>result<br><span class="hljs-comment">#End</span><br></code></pre></td></tr></table></figure><p> 图解如下<br><img src="/images/yield/yield1_3.png"></p><p> 说明:</p><ul><li>通过捕捉异常获取返回值</li><li>只支持python3</li></ul><h2 id="3-总结"><a href="#3-总结" class="headerlink" title="3. 总结"></a>3. 总结</h2><ul><li><code>yield</code>的基本用法已经差不多了,有两个方面:生成器与协程(理解协程的关键在于明白它在何处暂停发送出的数据传到了哪个变量)</li><li><code>yield</code>的另一方面的应用是上下文管理器下一节讲 </li><li><code>yield from</code>我这里暂时不讲,留到后面。<code>yield from</code>会在内部自动捕获<code>StopIteration</code>异常等</li></ul>]]></content>
<categories>
<category>Python</category>
</categories>
<tags>
<tag>Python</tag>
<tag>yield</tag>
</tags>
</entry>
<entry>
<title>操作系统中线程与进程概念解惑</title>
<link href="/2019/12/31/thread-and-process/"/>
<url>/2019/12/31/thread-and-process/</url>
<content type="html"><![CDATA[<p>这篇文章总结本来记录于两年前,是在写Python多线程时对一些概念的疑惑的解答,当初查阅了很多资料,对于操作系统层面的线程进程概念很模糊。而今,随着Python版本的更新,本身异步的新特性逐渐完善,在学习异步的时候难免与常用的多进程多线程进行比较来说明异步的优越性,把以前的文章拿出来,并且在现在自己的理解上查阅一些资料进行适当修改,达到温故而知新的效果。</p><p>看了一天的相关概念,很多涉及到操作系统与底层硬件层面,脑子有点晕,对自己所理解的东西清理一下并记录下来,有些不对的地方还请指出.</p><h2 id="1-线程与进程"><a href="#1-线程与进程" class="headerlink" title="1. 线程与进程"></a>1. 线程与进程</h2><p><strong>进程</strong>: </p><blockquote><p>我们都知道计算机的核心是CPU,它承担了所有的计算任务;而操作系统是计算机的管理者,它负责任务的调度、资源的分配和管理,统领整个计算机硬件;应用程序侧是具有某种功能的程序,程序是运行于操作系统之上的。 (为了缓解头脑胀痛, 斜体字大体过一遍即可)<br><em>进程是一个具有一定独立功能的程序在一个数据集上的一次动态执行的过程,是操作系统进行资源分配和调度的一个独立单位,是应用程序运行的载体。进程是一种抽象的概念,从来没有统一的标准定义。进程一般由程序、数据集合和进程控制块三部分组成。程序用于描述进程要完成的功能,是控制进程执行的指令集;数据集合是程序在执行时所需要的数据和工作区;程序控制块(Program Control Block,简称PCB),包含进程的描述信息和控制信息,是进程存在的唯一标志。</em></p></blockquote><p><strong>线程</strong>:</p><blockquote><p>在早期的操作系统中并没有线程的概念,在当时进程是任务调度的最小单位,每个进程有各自独立的一块内存,使得各个进程之间内存地址相互隔离。进程间的通信与同步完全袭来操作系统内核转发与支持。<br>后来,随着计算机的发展,对CPU的要求越来越高,进程之间的切换开销较大,已经无法满足越来越复杂的程序的要求了。于是就发明了线程,线程是程序执行中一个单一的顺序控制流程,是程序执行流的最小单元,是处理器调度和分派的基本单位。一个进程可以有一个或多个线程,各个线程之间共享程序的内存空间(也就是所在进程的内存空间)。一个标准的线程由线程ID、当前指令指针(PC)、寄存器和堆栈组成。而进程由内存空间(代码、数据、进程空间、打开的文件)和一个或多个线程组成。</p></blockquote><p><strong>进程与线程的区别</strong> </p><ol><li>线程是程序执行的最小单位,而进程是操作系统分配资源的最小单位 (这句话一定要理解); </li><li>一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线; </li><li>进程之间相互独立,但同一进程下的各个线程之间共享程序的内存空间(包括代码段、数据集、堆等)及一些进程级的资源(如打开文件和信号),某进程内的线程在其它进程不可见; </li><li>调度和切换:线程上下文切换比进程上下文切换要快得多。</li></ol><p><strong>线程与进程关系的示意图:</strong><br><img src="/images/thread_and_process/%E7%BA%BF%E7%A8%8B%E4%B8%8E%E8%BF%9B%E7%A8%8B%E5%85%B3%E7%B3%BB.png" alt="线程与进程关系"></p><p><strong>某个进程中单线程与多线程的关系:</strong><br><img src="/images/thread_and_process/%E5%8D%95%E7%BA%BF%E7%A8%8B%E4%B8%8E%E5%A4%9A%E7%BA%BF%E7%A8%8B.png" alt="单线程与多线程"></p><hr><p>从别处看到线程是进程的一个实体,是程序执行的最小单位(线程也被称为<strong>轻量级进程</strong>).<br>按照我的理解:QQ音乐正在运行,QQ音乐就是个进程,而这个进程中有多个线程在跑着(各有各的任务);我们平常编写的所有的Python程序,都是执行单任务的进程,也就是只有一个线程。<br>如果我们要同时执行多个任务怎么办:一种是启动多个进程,每个进程虽然只有一个线程,但多个进程可以一块执行多个任务;还有一种方法是启动一个进程,在一个进程内启动多个线程,这样,多个线程也可以一块执行多个任务(之后详细讲)。 </p><h2 id="2-线程与cpu核心数的关系"><a href="#2-线程与cpu核心数的关系" class="headerlink" title="2. 线程与cpu核心数的关系"></a>2. 线程与cpu核心数的关系</h2><p>现在都是多核的处理器,那么在多核处理器的情况下,线程是怎样执行呢?这就需要了解内核线程。 </p><blockquote><p>多核(心)处理器是指在一个处理器上集成多个运算核心从而提高计算能力,也就是有多个真正并行计算的处理核心,每一个处理核心对应一个内核线程。<strong>内核线程</strong>(Kernel Thread, KLT)就是直接由操作系统内核支持的线程,这种线程由内核来完成线程切换,内核通过操作调度器对线程进行调度,并负责将线程的任务映射到各个处理器上。一般一个处理核心对应一个内核线程,比如单核处理器对应一个内核线程,双核处理器对应两个内核线程,四核处理器对应四个内核线程。<br>现在的电脑一般是双核四线程、四核八线程,是采用超线程技术将一个物理处理核心模拟成两个逻辑处理核心,对应两个内核线程,所以在操作系统中看到的CPU数量是实际物理CPU数量的两倍,如你的电脑是双核四线程,打开“任务管理器\性能”可以看到4个CPU的监视器,四核八线程可以看到8个CPU的监视器。 </p></blockquote><p>上面的应该可以理解,但是有个注意点:<strong>程序一般不会直接去使用内核线程,而是去使用内核线程的一种高级接口——轻量级进程</strong>. 那为什么要说出上面那段文字呢? 我心中的困惑是:一般我们买的CPU包装盒上写的 <code>四核八线程</code>的”线程” 到底是什么线程?跟系统的线程是一个东东么?<a href="https://www.zhihu.com/question/274189552">看这里</a></p><p>由于每个轻量级进程都需要一个内核线程支持,因此只有先支持内核线程,才能有轻量级进程。<br>下图中:K代表<strong>内核线程</strong>,LWP代表<strong>轻量级线程</strong>,U代表 <strong>用户线程</strong> </p><p><img src="/images/thread_and_process/%E5%86%85%E6%A0%B8%E7%BA%BF%E7%A8%8B%E7%94%A8%E6%88%B7%E7%BA%BF%E7%A8%8B.jpg" alt="内核线程用户线程"></p><p>问题一:什么是用户线程?</p><blockquote><p>用户线程是完全建立在用户空间的线程库(比如python的threading库),用户线程的创建、调度、同步和销毁全又库函数在用户空间完成,不需要内核的帮助。因此这种线程是极其低消耗和高效的。</p></blockquote><p>问题二:请简单解释下图中流程?</p><blockquote><p>还是上面的例子,当你运行一个python脚本时,等于创建可一个进程,光有进程没卵用,必须有程序执行的最小单位嘛,所以同时系统会创建一个LWP,程序就靠它运行,因为是第一个线程,也称它为主线程。然后,脚本跑着跑着,需要创建新的线程运行其他任务时,主线程程序安排一个”创建线程“的口号(比如调用一个线程库)来调用产生U。<br><a href="https://www.zhihu.com/question/25532384">可以看这里</a>的回答,进程和线程都是一个时间段的描述,是CPU工作时间段的描述,不过是颗粒大小不同。</p></blockquote><p>问题二:系统中有时会有那CPU核心数的数量影响着什么嘛? </p><blockquote><p>(不是很确定),多核就是系统同时可以运行多个线程,比如双核可以<strong>同时</strong>执行两个线程。单核只能一次执行一个线程。<br>如果答案真如上面那样:那为什么能有百来个多线程同时存在?那么就可以引申到下面的话题了。</p></blockquote><h2 id="3-并发和并行"><a href="#3-并发和并行" class="headerlink" title="3. 并发和并行"></a>3. 并发和并行</h2><p><strong>并行(parallelism)</strong>:</p><blockquote><p>这个概念很好理解。所谓并行,就是同时执行的意思,无需过度解读(在日本跟台湾,翻译成平行,这就更好理解了)。判断程序是否处于并行的状态,就看同一时刻是否有超过一个“工作单位”在运行就好了。所以,单线程永远无法达到并行状态。<br>要达到并行状态,最简单的就是利用多线程和多进程。但是 Python 的多线程由于存在著名的 GIL,无法让两个线程真正“同时运行”,所以实际上是无法到达并行状态的。 </p></blockquote><p>那么对于上面的问题三,我们是否可以认为,一个四核八线程的CPU,最多允许八个线程同时执行?某个时间点就只有八个,然后下一时间,其他八个再执行=.=(不展开。有点晕)</p><p>在并行性开发时,不但可以考虑多进程并行执行,而且还可以开发出进程内多线程并行的程序,如下图。<br><img src="/images/thread_and_process/%E5%B9%B6%E8%A1%8C%E6%89%A7%E8%A1%8C%E5%AE%9E%E7%8E%B0%E6%96%B9%E6%B3%95.jpg" alt="并行执行实现方法"></p><hr><hr><hr><hr><p><strong>并发(concurrency)</strong>: </p><blockquote><p>要理解“并发”这个概念,必须得清楚,并发指的是程序的“结构”。当我们说这个程序是并发的,实际上,这句话应当表述成“这个程序采用了支持并发的设计”。好,既然并发指的是人为设计的结构,那么怎样的程序结构才叫做支持并发的设计<br><strong>正确的并发设计的标准是:使多个操作可以在重叠的时间段内进行(two tasks can start, run, and complete in overlapping time periods)。</strong> </p></blockquote><p>这句话的重点有两个。我们先看“(操作)在重叠的时间段内进行”这个概念。它是否就是我们前面说到的并行呢?是,也不是。并行,当然是在重叠的时间段内执行,但是另外一种执行模式,也属于在重叠时间段内进行。这就是<strong>协程</strong>(或者简单点多线程也可以,然后把task1,task2改成线程1,线程2)。<br>使用协程时,程序的执行看起来往往是这个样子:</p><p><img src="/images/thread_and_process/%E5%8D%8F%E7%A8%8B.jpg" alt="协程"></p><p><strong>task1, task2 是两段不同的代码,比如两个函数,其中黑色块代表某段代码正在执行。注意,这里从始至终,在任何一个时间点上都只有一段代码在执行,但是,由于 task1 和 task2 在重叠的时间段内执行,所以这是一个支持并发的设计。与并行不同,单核单线程能支持并发(特指协程)。</strong></p><p>那么,如何实现支持并发的设计?两个字:拆分。<br>之所以并发设计往往需要把流程拆开,是因为如果不拆分也就不可能在同一时间段进行多个任务了。这种拆分可以是平行的拆分,比如抽象成同类的任务,也可以是不平行的,比如分为多个步骤。 </p><p><strong>并发和并行的区别</strong>:<br>并行指物理上同时执行,并发指能够让多个任务在逻辑上交织执行的程序设计</p><h2 id="3-同步和异步理解"><a href="#3-同步和异步理解" class="headerlink" title="3. 同步和异步理解"></a>3. 同步和异步理解</h2><p>这里我就简单举个例子就好了:web网页,点一下发送邮件按钮,然后发送(发送过程5秒),发送成功之网页显示跳转成功。<br>同步:点了发送之后,慢慢等五秒,然后显示成功<br>异步:点了发送之后,直接显示成功,后台默默的还在发送,知道发送完毕。<br>关于阻塞啥的我这里就不提了,已经有些晕了。毕竟在python中有些集成的库已经将这些问题考虑进去了,不需要重复造轮子,具体可以看看<a href="https://cloud.tencent.com/developer/article/1187407">这篇文章前半部分</a></p><h2 id="5-python的相关模块"><a href="#5-python的相关模块" class="headerlink" title="5. python的相关模块"></a>5. python的相关模块</h2><ul><li><code>threading</code> 多线程库</li><li><code>concurrent.futures</code>中的<code>ThreadpoolExecutor</code> 线程池</li><li><code>concurrent.futures</code>中的<code>ProcesspoolExecutor</code> 进程池</li><li><code>subprocess</code> 子进程</li><li><code>yield</code>关键字 协程</li><li><code>async</code>和<code>await </code>(python3.5新加入) 协程库</li></ul><h2 id="6-其他我想要说的几个点"><a href="#6-其他我想要说的几个点" class="headerlink" title="6. 其他我想要说的几个点"></a>6. 其他我想要说的几个点</h2><ol><li><p>在多线程这方面我代码中用的比较少.有时偶尔用到,我会直接选用<code>ThreadpoolExecutor</code>类。这个接口抽象层级很高,多线程直接使用,无需关心任何实现细节,啥线程死锁等问题都不需要担心。 </p></li><li><p>多线程在单cpu中其实也是顺序执行的,不过系统可以帮你切换那个执行而已,其实并没有快(反而慢);多个cpu的话就可以在两个cpu中同时执行了。</p></li><li><p>接上面那一条,虽然python有GIL锁,启用多线程的时候只能用一个cpu核心,但python线程仍然适合I/O密集型应用:标准库中每个使用 C 语言编写的 I/O 函数都会释放 GIL,因此,当某个线程在等待 I/O 时, Python 调度程序会切换到另一个线程。 (意思就是说I/O操作的延迟大于过程中程序运行时间,我完全可以在延迟的时候,切换到其他线程,做其他操作,如下图情况C)<br><img src="/images/thread_and_process/%E5%8D%95%E7%BA%BF%E7%A8%8B%E5%A4%9A%E7%BA%BF%E7%A8%8B.jpg" alt="单线程多线程"></p></li><li><p><strong>多线程与协程的区别:协程执行效率极高,因为子程序切换(函数)不是线程切换,由程序自身控制,不像多线程那样要切换线程的开销。所以与多线程相比,线程的数量越多,协程性能的优势越明显。</strong></p></li></ol><p>相关链接:<br><a href="https://laike9m.com/blog/huan-zai-yi-huo-bing-fa-he-bing-xing,61/">https://laike9m.com/blog/huan-zai-yi-huo-bing-fa-he-bing-xing,61/</a><br><a href="https://lz5z.com/Python%E5%8D%8F%E7%A8%8B/">https://lz5z.com/Python%E5%8D%8F%E7%A8%8B/</a><br><a href="http://blog.csdn.net/luoweifu/article/details/46595285">http://blog.csdn.net/luoweifu/article/details/46595285</a><br><a href="http://yangcongchufang.com/%E9%AB%98%E7%BA%A7python%E7%BC%96%E7%A8%8B%E5%9F%BA%E7%A1%80/python-process-thread.html">http://yangcongchufang.com/%E9%AB%98%E7%BA%A7python%E7%BC%96%E7%A8%8B%E5%9F%BA%E7%A1%80/python-process-thread.html</a><br><a href="http://blog.csdn.net/gatieme/article/details/51481863">http://blog.csdn.net/gatieme/article/details/51481863</a><br><a href="http://www.cnblogs.com/caihuafeng/p/5438753.html">http://www.cnblogs.com/caihuafeng/p/5438753.html</a></p>]]></content>
<categories>
<category>Linux</category>
</categories>
<tags>
<tag>Linux</tag>
<tag>Thread</tag>
</tags>
</entry>
<entry>
<title>MQTT知识指南以及在Python的应用</title>
<link href="/2019/12/10/python-mqtt/"/>
<url>/2019/12/10/python-mqtt/</url>
<content type="html"><![CDATA[<p>谈到物联网就会谈到MQTT协议,之前一段时间我虽然在书上看到过,也是粗略一看也没深究,主要自己只是维度不够,项目中也没有用到,就不了了之了。最近老板找了个架构师准备用心好好搞一搞公司那个平台,提出新的架构,用到了MQTT协议,用心研究了一番,发现以前写了好多的功能都是MQTT自带的,花了好多时间重复造轮子。最近看了好多相关的东西,虽然蛮多深层次的东西都没有摸到,但也有些了解,故搜集了一些资料有序的记录下来,大部分内容来自<a href="http://www.steves-internet-guide.com/">Steve</a>以及<a href="https://mntolia.com/">Maulin Tolia</a>的博客,十分感谢。</p><h2 id="1-MQTT基础"><a href="#1-MQTT基础" class="headerlink" title="1. MQTT基础"></a>1. MQTT基础</h2><p>图文并茂的<a href="https://mntolia.com/fundamentals-mqtt/">MQTT基础学习</a>,后来又发现了个湾湾的<a href="https://swf.com.tw/?p=1002">MQTT教学</a>,同为两岸同胞,感觉切实际更好一点</p><p>其他补充:</p><h3 id="1-1-关于MQTT版本:"><a href="#1-1-关于MQTT版本:" class="headerlink" title="1.1 关于MQTT版本:"></a>1.1 关于MQTT版本:</h3><p>最初的MQT T是在1999年设计的,已经使用了很多年,并且基于TCP/IP网络设计。<br>MQTTv3.1.1是常用版本。</p><ul><li>MQTT v3.1.0 – 几乎与3.1.1没什么区别</li><li>MQTT v3.1.1 – 常用的版本,想看底层的MQTT协议数据包结构,可以看<a href="http://www.steves-internet-guide.com/mqtt-protocol-messages-overview/">这里</a></li><li>MQTT v5 – 当前受限使用,目前只有C语言客户端支持,<a href="http://www.steves-internet-guide.com/mqttv5/">新功能阐述</a>以及<a href="https://github.com/wialon/gmqtt">正在开发的Python客户端</a></li><li>MQTT-SN – 旨在通过UDP,ZigBee和其他传输进行工作,但当前并不流行</li></ul><h3 id="1-2-关于MQTT与HTTP"><a href="#1-2-关于MQTT与HTTP" class="headerlink" title="1.2 关于MQTT与HTTP"></a>1.2 关于MQTT与HTTP</h3><ul><li><a href="https://www.linkedin.com/pulse/internet-things-http-vs-websockets-mqtt-ronak-singh-cspo/">物联网:协议之战(HTTP,Websocket和MQTT)</a></li><li><a href="https://medium.com/mqtt-buddy/mqtt-vs-http-which-one-is-the-best-for-iot-c868169b3105">MQTT与HTTP:哪种是IoT的最佳选择?</a></li><li><a href="https://flespi.com/blog/http-vs-mqtt-performance-tests">HTTP与MQTT性能测试</a></li></ul><h3 id="1-3-基于websocket的MQTT"><a href="#1-3-基于websocket的MQTT" class="headerlink" title="1.3 基于websocket的MQTT"></a>1.3 基于websocket的MQTT</h3><p>Websockets允许直接在Web浏览器中接收MQTT数据,Javascript MQTT Client提供了对Web浏览器的MQTT websocket支持<br><a href="http://www.steves-internet-guide.com/mqtt-websockets/">Using MQTT Over WebSockets with Mosquitto</a><br><a href="https://www.thomaslaurenson.com/blog/2018/07/10/mqtt-web-application-using-javascript-and-websockets/">MQTT Web Application Using JavaScript and Paho MQTT Library</a></p><h3 id="1-4-MQTT客户端"><a href="#1-4-MQTT客户端" class="headerlink" title="1.4 MQTT客户端"></a>1.4 MQTT客户端</h3><p>对于MQTTv3.1.1,基本可以在所有系统上使用所有的编程语言,例子中最常见的是基本就是<code>Python</code>以及<code>Node.js</code></p><h3 id="1-5-常见的broker"><a href="#1-5-常见的broker" class="headerlink" title="1.5 常见的broker"></a>1.5 常见的broker</h3><p>第一个它是按mqtt broker地址公有私有来分,旨在区分哪些能直接测试用,哪些自己搭建:<br><a href="https://mntolia.com/10-free-public-private-mqtt-brokers-for-testing-prototyping/">10 Free Public & Private MQTT Brokers(For Testing & Production)</a><br>而第二种是根据自建需求对比不同的broker:<br><a href="http://www.bewindoweb.com/244.html">MQTT Broker的需求和各大Broker对比</a></p><h3 id="1-6-MQTT安全机制简介"><a href="#1-6-MQTT安全机制简介" class="headerlink" title="1.6 MQTT安全机制简介"></a>1.6 MQTT安全机制简介</h3><ul><li>Client ids</li><li>Usernames and passwords (Restricting Access to topics)</li><li>Client Certificates (Restricting Access to topics)</li></ul><p>具体看<a href="http://www.steves-internet-guide.com/mqtt-security-mechanisms/">Introduction to MQTT Security Mechanisms</a></p><h2 id="2-Python-MQTT使用"><a href="#2-Python-MQTT使用" class="headerlink" title="2. Python MQTT使用"></a>2. Python MQTT使用</h2><p>Python mqtt客户端有很多库,特意找了篇文章进行了比较:<a href="https://flespi.com/blog/benchmarking-popular-mqtt-json-implementations">Benchmarking popular MQTT + JSON implementations</a></p><p>一开始看的<a href="https://mntolia.com/mqtt-python-with-paho-mqtt-client/">MQTT Python With Paho-MQTT (Beginner’s Guide With Example)</a>,然后是一些基础功能的使用,persistend session , qos level, retained messeges, last will基本都能在<a href="https://mntolia.com/">Maulin Tolia</a>的博客找到,一目了然</p><blockquote><p>要深究基本用法的各个参数以及对象,就要先了解整个mqtt的过程以及一些机制,参数与机制一一对应,就比如client.connection方法中的keepalive参数,或者loop_forever与loop_start区别与mqtt自身心跳机制的关系</p></blockquote><p>这些进阶的内容可以在<a href="http://www.steves-internet-guide.com/">人气非常高的steve mqtt博客上</a>看到,比如<a href="http://www.steves-internet-guide.com/category/python-mqtt-projects/">Python MQTT实际应用以及项目专栏</a>,面面俱到,并且详细到啥都有~</p><p><strong>虽然篇幅不多,但贴了好多链接,你品,你品,你细细品,OVER ~</strong></p>]]></content>
<categories>
<category>MQTT</category>
</categories>
<tags>
<tag>Python</tag>
<tag>Linux</tag>
<tag>MQTT</tag>
</tags>
</entry>
<entry>
<title>Django admin后台常用设置汇总</title>
<link href="/2019/12/09/django-admin-action/"/>
<url>/2019/12/09/django-admin-action/</url>
<content type="html"><![CDATA[<p>一开始学Django的时候基本都有接触django admin管理员后台,但是之后的开发基本很少用,写API测试的时候要修好后台数据基本都是命令行或者数据库直接修改,完全没有接触到admin。但是最近方便公司订饭,写了个订饭系统,前后端分离,本想让前端根据登录员工判断是否是管理员角色,然后在页面上增加管理员操作元素,有点麻烦,。之后,我直接傻了,拍了下自己头,为啥不用django自带的管理员后台呢,好用方便快捷。</p><h2 id="1-信手拈来"><a href="#1-信手拈来" class="headerlink" title="1. 信手拈来"></a>1. 信手拈来</h2><p><a href="https://www.cnblogs.com/huchong/p/7894660.html">Django admin 一些有用的设置</a><br><a href="https://www.jianshu.com/p/69e6f9c97b48">如何修改django的app在admin中显示的名称</a></p><h2 id="2-柳暗花明"><a href="#2-柳暗花明" class="headerlink" title="2. 柳暗花明"></a>2. 柳暗花明</h2><p>在搜寻资料的过程中,反现了一本好书 <a href="https://books.agiliq.com/projects/django-admin-cookbook/en/latest/two_admin.html">django-admin-cookbook</a>,我把各个topic翻译过来,以便以后方便翻阅。</p><ol><li>如何更改“ Django admin”文本? (常用的site_header,site_title,index_title)</li><li>如何为Django 模型设置复数文本? (verbose_name_plural参数)</li><li>如何创建两个独立的管理站点? (继承AdminSite)</li><li>如何从“Django admin”删除默认app? (比如默认的Groups Users)</li><li>如何为“Django admin”添加一个log?</li><li>如何覆盖“Django admin”的页面模板?</li><li>如何在列表视图页面上显示计算字段? (其他Model可计算获得的字段)</li><li>如何在“Django admin”中优化查询? (<code>get_queryset</code>方法覆盖)</li><li>如何启用对计算字段的排序? (接第七条)</li><li>如何启用对计算字段的过滤? (同上)</li><li>如何显示计算出的布尔字段的“开”或“关”图标?(同上)</li><li>如何在“Django admin”添加其他操作? (actions)</li><li>如何从“Django admin”中导出CSV?</li><li>如何在“Django admin”删除“deleted”操作?</li><li>如何将自定义操作按钮(不是操作)添加到“Django admin”列表页面?</li><li>如何使用“Django admin”导入CSV?</li><li>如何将Django管理员限制为特定用户? (<code>is_staff=True</code>,<code>is_superuser=True</code>)</li><li>如何限制对Django管理员部分的访问? (<code>has_xxx_permission</code>等)</li><li>如何允许管理员只能创建一个对象? (同上)</li><li>如何删除模型的“添加”或者“删除”按钮? (同上)</li><li>如何让一位Django管理员编辑多个模型? (需要使用内联)</li><li>如何将一对一关系添加为管理员内联?(同上)</li><li>如何在“Django admin”中添加嵌套的内联? (同上)</li><li>如何为两个不同的模型创建一个“Django admin”?</li><li>如何在列表视图页面上显示更多行? (<code>list_per_page</code>)</li><li>如何禁用分页? (同上)</li><li>如何在“Django admin”中添加基于日期的过滤? (<code>date_hierarchy = 'added_on'</code>)</li><li>如何在列表视图页面上显示多对多或反转FK字段?</li><li>如何在”Django admin“中显示Imagefield中的图像?</li><li>保存时如何将模型与当前用户关联?</li><li>如何在”Django admin“中将字段标记为只读? (<code>readonly_fields</code>)</li><li>如何在”Django admin“中显示不可编辑的字段 (同上)</li><li>如何使字段在创建时可编辑,但在现有对象中只能读取? (<code>get_readonly_fields</code>)</li><li>如何在”django admin“中过滤FK下拉值?</li><li>如何使用带有大量对象的FK管理模型?</li><li>如何更改下拉菜单中的ForeignKey显示文本?</li><li>如何在”Django更改视图页面“中添加自定义按钮?</li><li>如何获取特定对象的”Django admin“ URL?</li><li>如何向”Django adimn“添加两次模型?</li><li>如何在”Django admin“覆盖保存行为? (<code>save_model</code>)</li><li>如何将数据库视图添加到“Django admin”?</li><li>如何在“Django admin”中设置应用程序和模型的顺序?</li></ol>]]></content>
<categories>
<category>Python</category>
</categories>
<tags>
<tag>Django</tag>
<tag>Python</tag>
</tags>
</entry>
<entry>
<title>Python -m 参数以及生僻运行命令汇总</title>
<link href="/2019/10/17/python-m-option/"/>
<url>/2019/10/17/python-m-option/</url>
<content type="html"><![CDATA[<p>最近在翻阅剖析Python源码的时候讲到了内部Python变量的实现,以及当运行Python命令时,隐藏在内部的一些过程,于是<code>python3 --help</code>之后对某些运行参数产生了一些疑问,还涉及到了包与模块的内容,略微生僻,特此整理记录下~</p><h2 id="1-大杂烩"><a href="#1-大杂烩" class="headerlink" title="1. 大杂烩"></a>1. 大杂烩</h2><blockquote><p>旨在由用户直接执行的包含Python代码的纯文本文件通常称为**脚本(script)<strong>,这是一个非正式术语,表示顶级程序文件。<br> 另一方面,包含设计用于从另一个Python文件导入和使用的Python代码的纯文本文件称为</strong>模块(modules)**。</p></blockquote><h3 id="1-1-使用importlib导入模块"><a href="#1-1-使用importlib导入模块" class="headerlink" title="1.1 使用importlib导入模块"></a>1.1 使用importlib导入模块</h3><figure class="highlight python"><table><tr><td class="gutter"><div class="code-wrapper"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></div></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">import</span> importlib<br>importlib.import_module(<span class="hljs-string">'math'</span>)<br></code></pre></td></tr></table></figure><p>当我们想要导入自己写的<code>test.py</code>中的函数hello时,我们可以这样曲线救国: 首先每个模块导入后是一个对象(object), 它里面的各个变量都是以属性(attr)的形式挂靠在里面, 所以我们可以使用getattr来找到制定的函数</p><p><code>hello = getattr(importlib.import_module('test'), 'hello')</code> 等价于<code>from test import hello</code></p><p>或者包中的某个模块:</p><p><code>ClassA = getattr(importlib.import_module('packege.module'), 'ClassA')</code>等价于<br><code>from packege.module import ClassA </code></p><h3 id="1-2-重新导入"><a href="#1-2-重新导入" class="headerlink" title="1.2 重新导入"></a>1.2 重新导入</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">import</span> math<br><span class="hljs-keyword">import</span> math <span class="hljs-comment"># Second import, which does nothing ,need to reload</span><br>importlib.reload(math)<br></code></pre></td></tr></table></figure><p>这里的注意点,<code>reload</code>函数是一个模块对象,而不是一个字符串</p><h3 id="1-3-使用-runpy-run-module-and-runpy-run-path-运行脚本"><a href="#1-3-使用-runpy-run-module-and-runpy-run-path-运行脚本" class="headerlink" title="1.3 使用 runpy.run_module() and runpy.run_path()运行脚本"></a>1.3 使用 runpy.run_module() and runpy.run_path()运行脚本</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">import</span> runpy<br>runpy.run_module(mod_name=<span class="hljs-string">'hello'</span>)<br>runpy.run_path(file_path=<span class="hljs-string">'hello.py'</span>)<br></code></pre></td></tr></table></figure><h3 id="1-4-使用exec-运行脚本"><a href="#1-4-使用exec-运行脚本" class="headerlink" title="1.4 使用exec()运行脚本"></a>1.4 使用exec()运行脚本</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-built_in">exec</span>(<span class="hljs-built_in">open</span>(<span class="hljs-string">'hello.py'</span>).read())<br></code></pre></td></tr></table></figure><h3 id="1-5-python-c参数"><a href="#1-5-python-c参数" class="headerlink" title="1.5 python -c参数"></a>1.5 python -c参数</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs python">python -c <span class="hljs-string">"import math;print(math.pi)"</span><br></code></pre></td></tr></table></figure><h2 id="2-Python-m-参数"><a href="#2-Python-m-参数" class="headerlink" title="2. Python -m 参数"></a>2. Python -m 参数</h2><blockquote><p>-m参数显示为run library module as a script,常见于 <code>Python3 -m http.server 8080</code></p></blockquote><p>因为<code>http</code>是<strong>标准库</strong>(已经存在于sys.path中),这也是包,我们用<code>http.server</code>来调用server文件并把它作为脚本来运行,运行其中<code>__name__ == "__main__"</code>部分</p><p>那假设我们是自己创建的的包呢,那也是同样的道理,我们<a href="https://www.cnblogs.com/xueweihan/p/5118222.html?utm_source=tuicool&utm_medium=referral">python自问自答:python -m参数?</a>,直接运行与-m运行的区别在于:<br><strong>直接启动脚本是把脚本所在的目录添加在sys.path中<br>-m模块启动脚本是把你输入命令的目录(也就是当前路径),放到了sys.path属性中</strong></p><h2 id="3-其他谈谈"><a href="#3-其他谈谈" class="headerlink" title="3. 其他谈谈"></a>3. 其他谈谈</h2><h3 id="3-1-当使用from-module-import-语句时,希望对从模块或包导出的符号进行精确控制时,定义一个变量-all-来明确地列出需要导出的内容(强烈反对使用-from-module-import-)"><a href="#3-1-当使用from-module-import-语句时,希望对从模块或包导出的符号进行精确控制时,定义一个变量-all-来明确地列出需要导出的内容(强烈反对使用-from-module-import-)" class="headerlink" title="3.1 当使用from module import * 语句时,希望对从模块或包导出的符号进行精确控制时,定义一个变量 all 来明确地列出需要导出的内容(强烈反对使用 from module import *)"></a>3.1 当使用<code>from module import *</code> 语句时,希望对从模块或包导出的符号进行精确控制时,定义一个变量 <strong>all</strong> 来明确地列出需要导出的内容(强烈反对使用 <code>from module import *</code>)</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># somemodule.py</span><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">spam</span>():<br> <span class="hljs-keyword">pass</span><br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">grok</span>():<br> <span class="hljs-keyword">pass</span><br><br>blah = <span class="hljs-number">42</span><br><span class="hljs-comment"># Only export 'spam' and 'grok'</span><br>__all__ = [<span class="hljs-string">'spam'</span>, <span class="hljs-string">'grok'</span>]<br></code></pre></td></tr></table></figure><h3 id="3-2-运行目录或压缩文件"><a href="#3-2-运行目录或压缩文件" class="headerlink" title="3.2 运行目录或压缩文件"></a>3.2 运行目录或压缩文件</h3><p>如果你的应用程序已经有多个文件,你可以把你的应用程序放进它自己的目录并添加一个__main__.py文件。 举个例子,你可以像这样创建目录:</p><div class="code-wrapper"><pre><code class="hljs">myapplication/ spam.py bar.py grok.py __main__.py</code></pre></div><p>如果<code>__main__.py</code>存在,你可以简单地在顶级目录运行Python解释器:<code>python3 myapplication</code></p>]]></content>
<categories>
<category>Python</category>
</categories>
<tags>
<tag>Python</tag>
</tags>
</entry>
<entry>
<title>C语言中extern和头文件以及静态动态库概念梳理</title>
<link href="/2019/10/11/extern-and-linker/"/>
<url>/2019/10/11/extern-and-linker/</url>
<content type="html"><![CDATA[<p>最近因为Arduino使用的较多,开始学起来了C语言,看了还多天,无非就是基本的数据类型,运算符,控制语句,简单得很,后来发现这仅仅是语法层面,C语言是除了汇编最为底层的语言了,要了解这门语言,就要从了解支撑起C的底层原理开始了解,编译原理,操作系统,计算机硬件。而这次遇到的C语言的库文件,就好比Python中的标准库与第三方库,怎么共享,中间又是个什么过程,一团黑。因为涉及内容太多太多,我还是偏向于找一些好的资料然后分主题整理下来,自己再串起来,解惑也。</p><h2 id="1-先说说声明与定义"><a href="#1-先说说声明与定义" class="headerlink" title="1. 先说说声明与定义"></a>1. 先说说声明与定义</h2><p>stackoverflow上有个很全面的回答,<a href="https://stackoverflow.com/questions/1410563/what-is-the-difference-between-a-definition-and-a-declaration">What is the difference between a definition and a declaration?</a></p><p>下面是我摘抄的一些简单概念:</p><p>定义(definition):表示创建变量或分配存储单元<br>声明(declaration):说明变量的性质,但并不分配存储单元<br><code>extern int i</code>; //是声明,不是定义,没有分配内存<br><code>int i</code>; //是定义<br>如果在声明的时候给变量赋值,那么就和去掉extern直接定义变量赋值是等价的</p><p><code>extern int a = 10</code>;<br><code>int a = 10</code>;//上述两条语句等价<br>谨记:声明可以多次,定义只能一次</p><h2 id="2-extern与static"><a href="#2-extern与static" class="headerlink" title="2. extern与static"></a>2. extern与static</h2><h3 id="2-1-extern"><a href="#2-1-extern" class="headerlink" title="2.1 extern"></a>2.1 extern</h3><p>当需要在外部文件导入函数或者变量时,我们可能很正常的找到了extern这个关键字,依然有篇经典的回答,<a href="https://www.cnblogs.com/lanhaicode/p/10633125.html">extern的使用详解(多文件编程)——C语言</a></p><p>对变量而言,如果你想在本源文件中使用另一个源文件的变量,就需要在使用前用extern声明该变量,或者在头文件中用extern声明该变量;</p><blockquote><p>不加extern也可以…源于某些不可描述的原因(可从上面那链接中找到原因)</p></blockquote><p>对函数而言,如果你想在本源文件中使用另一个源文件的函数,就需要在使用前用声明该变量,声明函数加不加extern都没关系,所以在头文件中函数可以不用加extern。</p><h3 id="2-2-static"><a href="#2-2-static" class="headerlink" title="2.2 static"></a>2.2 static</h3><p>对于static,我暂时用的不多,查看<code>C primer plus</code>书籍,我们看到static的三个用法</p><ul><li><strong>块作用域的静态变量</strong></li><li><strong>外部链接的静态变量</strong></li><li><strong>内部链接的静态变量</strong></li></ul><p>相关问答:<a href="https://stackoverflow.com/questions/572547/what-does-static-mean-in-c">What does “static” mean in C?</a></p><p>上面的讲解都附带了一些例子,我们来看一些<a href="https://medium.com/@shrmoud/static-vs-extern-a79e36f14812">更清晰的例子: static vs extern</a></p><h2 id="3-头文件"><a href="#3-头文件" class="headerlink" title="3. 头文件"></a>3. 头文件</h2><p>对于我自己创建头文件这件事,我的第一反应是项目多文件的时候导入外部函数啥的用的,后来我发现了<code>extern</code>这个关键字,我就开始疑问,我都可以直接导入外部变量与函数,我为啥还要用头文件这个东西? 后来发现自己还是<code>too young too simple!</code></p><blockquote><p>其实头文件是种约定,对计算机而言没什么作用,它只是在预编译时在#include的地方展开一下,没别的意义了,其实头文件主要是给别人看的。</p></blockquote><p>所以,按这么说,我在<code>test.h</code>文件中写着<code>extern int max(int a,int b)</code>(函数的extern可以省略),然后主文件中<code>incluede "test.h"</code>,等价于我直接<code>extern int max(int a,int b)</code>放到主文件中,这就达到了和我所产生的疑问一样的目的,然后头文件作用不仅仅这样。</p><p><strong>在以下场景中会使用头文件:</strong></p><ul><li>通过头文件来调用库功能。在很多场合,源代码不便(或不准)向用户公布,只要向用户提供头文件和二进制的库即可。用户只需要按照头文件中的接口声明来调用库功 能,而不必关心接口怎么实现的。</li><li>多文件编译。将稍大的项目分成几个文件实现,通过头文件将其他文件的函数声明引入到当前文件。</li><li>头文件能加强类型安全检查。如果某个接口被实现或被使用时,其方式与头文件中的声明不一致,编译器就会指出错误,这一简单的规则能大大减轻程序员调试、改错的负担。</li></ul><p>既然头文件是一种规定,那么写头文件时也有相当多的规则,<a href="https://reality0ne.com/how-to-struct-c-header-files/">如何组织好 C 的头文件</a>很有必要。</p><h2 id="4-编译系统"><a href="#4-编译系统" class="headerlink" title="4. 编译系统"></a>4. 编译系统</h2><p>对与一个简单的hello程序</p><figure class="highlight c"><table><tr><td class="gutter"><div class="code-wrapper"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></div></td><td class="code"><pre><code class="hljs C"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdio.h></span></span><br><br><span class="hljs-type">int</span> <span class="hljs-title function_">main</span><span class="hljs-params">()</span><br>{<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"hello, world\n"</span>);<br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br><br></code></pre></td></tr></table></figure><p><strong>当我们调用gcc编译时,过程是这样的:</strong></p><p><img src="/images/gcc_linker/gcc.jpg" alt="compilation system"><br>关于对每个部分的具体相关命令,可以看下<a href="http://youguanxinqing.xyz/index.php/archives/85/">这篇文章前半部分</a></p><h2 id="5-链接"><a href="#5-链接" class="headerlink" title="5. 链接"></a>5. 链接</h2><p><strong>接下来我们着重讲讲链接以及相关的内容:</strong></p><blockquote><p>链接(linking)是将各种代码和数据片段收集并组合成为一个单一文件的过程,这个文件可被加载(复制)到内存并执行。链接可以执行于编’译时(compile time),也就是在源代码被翻译成机器代码时;也可以执行于加载时(load time),也就是在程序被加载器(load-er)加载到内存并执行时;甚至执行于运行时(run time),也就是由应用程序来执行。在早期的计算机系统中,链接是手动执行的。在现代系统中,链接是由叫做链接器(linker)的程序自动执行的。<br>链接器的两个主要任务是<strong>符号解析(symbol resolution)与重定位(relocation)</strong></p></blockquote><h3 id="5-1-可重定位目标文件"><a href="#5-1-可重定位目标文件" class="headerlink" title="5.1 可重定位目标文件"></a>5.1 可重定位目标文件</h3><p>在上面编译过程中的链接之前,我们经过预处理,编译,汇编,得到了<code>.o</code>文件,<br>也就是可重定位目标文件,包含了二进制代码与数据(是一堆乱码)。</p><h3 id="5-2-函数库"><a href="#5-2-函数库" class="headerlink" title="5.2 函数库"></a>5.2 函数库</h3><p>这里说个函数库的概念,顾名思义:里边存放了一堆供程序员使用的函数。其实不但有函数名、函数对应的实现代码,还有链接过程中所需的重定位信息。函数库分为静态库(linux .a 文件 ,windows 为.lib文件)和动态库(.so,windows为.dll文件)文件。<br>当然,Linux 中也有标准的 C 函数库,里面有我们平常熟知的<code>printf</code>,<code>scanf</code>等标准c函数,都统一在libc.a与libc.so中,会存放在某个文件夹,我们可以通过<code>gcc --print-file-name=libc.a </code>查找<br>而用户,也可以根据自生需求,建立自己的用户函数库,这也是为什么我们上面说多个文件共享等等事情。</p><p>这里需要注意,由于函数库来自于 .o 文件,也就是说,是一堆二进制文件构成,你看不到里面的库函数代码。所以库函数该怎么用呢?这就体现了头文件中的重要性。所以 .h 文件与 .c 最好是要分开写。</p><h2 id="6-静态库"><a href="#6-静态库" class="headerlink" title="6. 静态库"></a>6. 静态库</h2><blockquote><p><strong>静态链接器(static linker</strong>)读取一组可重定位目标文件,将所有相关的目标模块打包成为一个单独的文件,称为<strong>静态库(static library)</strong>,也可称之为静态函数库,最后再和主函数的<code>.o</code>文件链接起来创建一个可执行目标。</p></blockquote><p>为什么会有静态库? <strong>在静态库之前,我们可以选择这么做?</strong></p><p>把所有<code>printf</code>标准函数放到<code>libc.o</code>中,然后<code>gcc main.c /usr/lib/libc.o</code> ,我们就可以用标准函数了。优点是我们将编译器的实现与标准函数实现分开了,但是缺点就是造成浪费,<code>libc.o</code>中我们有上百个函数,但我们平常的一个程序,我们可能仅仅用到<code>printf</code>等几个常用函数,全部导入进来,这样就造成空间浪费,还有就是对某个函数改变了,等重新编译整个源文件,开发维护复杂。</p><p>再者,我们可以为每个标准函数创建独立的<code>.o</code>文件,然后用:<br><code>gcc main.c /usr/lib/printf.o /usr/lib/scanf.o</code><br>这也是可行的,但是一看就知道太麻烦耗时</p><p>之后,静态库概念被提出来,以解决这些不同方法的缺点。相关的函数可以被编译为独立的目标模块,然后封装成一个单独的静态库文件。然后,应用程序可以通过在命令行上指定单独的文件名字来使用这些在库中定义的函数。比如,使用c标准库和数学库中函数的程序可以用形式如下的命令行來编译和链接:<br><code>gcc main.c /usr/lib/libm.a /usr/lib/libc.a</code><br>在链接时,链接器只复制被程序引用的目标模块,这就减少了磁盘跟内存大小。</p><blockquote><p>在Linux系统中,静态库以一种称为存档(archive)的特殊文件格式存放在磁盘中。存档文件是一组连接起来的可重定位目标文件的集合,有一个头部用来描述每个成员目标文件的大小和位置。存档文件名由后缀.a标识。</p></blockquote><p>简单举个例子来,有两个向量加法与乘法的<code>.c</code>文件,分别为<code>addvec.c</code>,<code>multvec.c</code>,还有个主文件<code>main.c</code></p><ul><li>我们先生成<code>.o</code>文件:<br><code>gcc -c addvec.c multvec.c</code></li><li>创建静态库:<br><code>ar rcs libvector.a addvec.o multvec.o</code></li><li>编译主文件:<br><code>gcc -c main.c</code></li><li>最后链接:<br><code>gcc -static -o a.out main.o ./libvector.a</code></li></ul><h2 id="7-动态库"><a href="#7-动态库" class="headerlink" title="7. 动态库"></a>7. 动态库</h2><p>然而,静态库仍然有一些明显的缺点。静态库和所有的软件一样,需要定期维护和更新。如果应用程序员想要使用一个库的最新版本,他们必须以某种方式了解到该库的更新情况,然后显式地将他们的程序与更新了的库重新链接。<br>另一个问题是几乎每个C程序都使用标准I/O函数,比如printf和scanf。在运行时,这些函数的代码会被复制到每个运行进程的文本段中。在一个运行上百个进程的典型系统上,这将是对稀缺的内存系统资源的极大浪费。<br>共享库(shared library)是致力于解决静态库缺陷的一个现代创新产物。共享库是一个目标模块,在运行或加载时,可以加载到任意的内存地址,并和一个在内存中的程序链接起來。这个过程称为动态链接(dynamic linking),是由一个叫做动态链接器(dynamic linker)的程序来执行的。共享库也称为共享目标(shared object),在Linux系统中通常用.so后缀来表示。微软的操作系统大鼠地使用了共享库,它们称为DLL(动态链接库)<br><strong>所有引用某库的可执行目标文件共享这个<code>.so</code>文件中的代码和数据,而不是像静态库的内容那样被复制和嵌入到引用它们的可执行文件中</strong></p><p>还是展示上面向量那个例子:</p><ul><li>构造共享库:<br><code>gcc -shared -fpic -o libvector.so addvec.c multvec.c</code></li><li>链接<br><code>gcc -o a.out main.c ./libvector.so</code></li></ul><blockquote><p>这样就创建了一个可执行目标文件a.out,而此文件的形式使得它在运行时可以和libvector.so链接。基本的思路是当创建可执行文件时,静态执行一些链接,然后在程序加载时,动态完成链接过程。认识到这一点是很重要的:此时,没有任何libvector.so的代码和数据节貞的被复制到可执行文件a.out中。反之,链接器复制了一些重定位和符号表信息,它们使得运行时可以解析对libvector.so中代码利数据的引用</p></blockquote><p><strong>所以静态库与动态库的区别:</strong></p><div class="code-wrapper"><pre><code class="hljs">静态库:1.链接时将程序放进进可执行程序2.会产生多分副本3.不依赖程序运行动态库:1.程序运行时,加载时才去动态库找函数2.多进程共享3.依赖程序运行</code></pre></div><p>还有个题外话,<a href="https://stackoverflow.com/questions/6906360/by-default-does-gcc-link-to-static-or-dynamic-standard-library">gcc链接标准库时默认是动态的还是静态的?</a></p><h2 id="8-总结"><a href="#8-总结" class="headerlink" title="8. 总结"></a>8. 总结</h2><p>花了两天,算是把相关的概念过程整理清楚了,有些资料第一次读没有读懂,回过头来再细读,豁然开朗!花了蛮多精力,不知道以后有没有机会用C做代码量多一点的项目,哈哈~</p>]]></content>
<categories>
<category>C</category>
</categories>
<tags>
<tag>Linux</tag>
<tag>C</tag>
</tags>
</entry>
<entry>
<title>浅谈Flask目录结构以及与Vue.js整合注意事项</title>
<link href="/2019/09/25/Flask-structrue-and-integrate-with-vue/"/>
<url>/2019/09/25/Flask-structrue-and-integrate-with-vue/</url>
<content type="html"><![CDATA[<p>没用Flask之前,我对Flask的只有一个概念,那就是**<code>Micro</code><strong>,官网对其的解释是这样的:</strong><code>Micro</code><strong>并不意味着你的整个Web应用程序必须只有单个Python文件(尽管可以),也不意味着Flask缺少功能。微框架中的</strong><code>Micro</code>**意味着Flask旨在使核心保持简单但可扩展。Flask不会为你做出很多决定,例如使用什么数据库,而且它所做的决定(例如使用哪种模板引擎)很容易更改。其他一切都由你决定,因此Flask可以满足你的所有需求,而无所不包。</p><h2 id="1-前言"><a href="#1-前言" class="headerlink" title="1. 前言"></a>1. 前言</h2><p>由于工作性质,要求写的小web应用都要涉及一些底层,不如串口通信,或者GPIO调用。而串口这种东西不像数据库连接一样,开几个都可以,也不能开了之后关然后再开(效率比数据库连接差多了)。所以最好写个全局的,开了一次就不用关了,代码量也不多,哪个简单用哪个,我就想到了用Flask。例如一个最小的应用就可以这么简单的写:</p><figure class="highlight python"><table><tr><td class="gutter"><div class="code-wrapper"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></div></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> flask <span class="hljs-keyword">import</span> Flask<br>app = Flask(__name__)<br><br><span class="hljs-meta">@app.route(<span class="hljs-params"><span class="hljs-string">'/'</span></span>)</span><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">hello_world</span>():<br> <span class="hljs-keyword">return</span> <span class="hljs-string">'Hello, World!'</span><br></code></pre></td></tr></table></figure><p>单个Python文件就好办了,然后打开串口,在路由中引用串口实例加入一些操作,就是个能用的小型Web应用了。</p><h2 id="2-目录结构安排一下"><a href="#2-目录结构安排一下" class="headerlink" title="2. 目录结构安排一下"></a>2. 目录结构安排一下</h2><p>即使一个项目再小也要有个规则安排,总不能真的单文件走天下。<br><a href="https://www.v2ex.com/t/467423">V2EX上有篇文章</a>探讨了Flask的目录结构,我看了上面的每个链接,果然**<code>Flask 其实很灵活,怎样都行,怎么舒服怎么来</code>**这句话很对。</p><p>我从一些教程或者github中综合了下,整理下我会采用的结构目录,旨在简单明了。</p><h4 id="单模块目录结构"><a href="#单模块目录结构" class="headerlink" title="单模块目录结构"></a>单模块目录结构</h4><p>当项目代码量少于几百行的时候我们可以采用单模块的目录结构,也就是把所有的代码都写在<code>app.py</code>里,然后把一些配置(比如我上面例子中的串口位置,波特率啥的)写到<code>config.py</code>里,比如这样:</p><figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs stylus">app<span class="hljs-selector-class">.py</span><br>config<span class="hljs-selector-class">.py</span><br>requirements<span class="hljs-selector-class">.txt</span><br>static/<br>templates/<br></code></pre></td></tr></table></figure><h4 id="包目录结构"><a href="#包目录结构" class="headerlink" title="包目录结构"></a>包目录结构</h4><p>对于包目录结构我们可以按照<a href="https://flask.palletsprojects.com/en/1.1.x/tutorial/layout/">官网推荐</a>的来:</p><figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><code class="hljs stylus">/home/user/Projects/flask-tutorial<br>├── flaskr/<br>│ ├── __init__<span class="hljs-selector-class">.py</span><br>│ ├── db<span class="hljs-selector-class">.py</span><br>│ ├── schema<span class="hljs-selector-class">.sql</span><br>│ ├── auth<span class="hljs-selector-class">.py</span><br>│ ├── blog<span class="hljs-selector-class">.py</span><br>│ ├── templates/<br>│ │ ├── base<span class="hljs-selector-class">.html</span><br>│ │ ├── auth/<br>│ │ │ ├── login<span class="hljs-selector-class">.html</span><br>│ │ │ └── register<span class="hljs-selector-class">.html</span><br>│ │ └── blog/<br>│ │ ├── create<span class="hljs-selector-class">.html</span><br>│ │ ├── index<span class="hljs-selector-class">.html</span><br>│ │ └── update<span class="hljs-selector-class">.html</span><br>│ └── static/<br>│ └── style<span class="hljs-selector-class">.css</span><br>├── tests/<br>│ ├── conftest<span class="hljs-selector-class">.py</span><br>│ ├── data<span class="hljs-selector-class">.sql</span><br>│ ├── test_factory<span class="hljs-selector-class">.py</span><br>│ ├── test_db<span class="hljs-selector-class">.py</span><br>│ ├── test_auth<span class="hljs-selector-class">.py</span><br>│ └── test_blog<span class="hljs-selector-class">.py</span><br>├── venv/<br>├── setup<span class="hljs-selector-class">.py</span><br>└── MANIFEST.<span class="hljs-keyword">in</span><br></code></pre></td></tr></table></figure><p>看似文件很多,但是基本的骨架还是像下面这样:</p><figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><code class="hljs stylus">config<span class="hljs-selector-class">.py</span><br>requirements<span class="hljs-selector-class">.txt</span><br>run<span class="hljs-selector-class">.py</span><br>instance/<br> config<span class="hljs-selector-class">.py</span><br>yourapp/<br> __init__<span class="hljs-selector-class">.py</span><br> views<span class="hljs-selector-class">.py</span><br> models<span class="hljs-selector-class">.py</span><br> forms<span class="hljs-selector-class">.py</span><br> static/<br> templates/<br></code></pre></td></tr></table></figure><p>然后我们可以在这上面进行很多衍生,加入蓝图等东西。</p><h4 id="更完整的目录结构"><a href="#更完整的目录结构" class="headerlink" title="更完整的目录结构"></a>更完整的目录结构</h4><p>更完善的目录结构我们可以加入<code>docker</code>,<code>makefile</code>等,我们可以参考有名的<a href="https://github.com/cookiecutter/cookiecutter/blob/master/README.md">cookiecutter项目模板</a>,很全面,考虑很周到,但是在自己的开发过程中用不太到,仅供参考。</p><h2 id="3-Flask与Vue-js整合问题"><a href="#3-Flask与Vue-js整合问题" class="headerlink" title="3. Flask与Vue.js整合问题"></a>3. Flask与Vue.js整合问题</h2><p>采用前后端分离开发的时候,需要整合前端vue的包,我们可以在实例化app的时候这么做:</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs python"><br><span class="hljs-keyword">from</span> flask <span class="hljs-keyword">import</span> Flask, render_template, jsonify<br><br><span class="hljs-comment"># Set up automatic serving of static Vue & frontend files and template folder for index.html.</span><br>app = Flask(__name__, static_folder=<span class="hljs-string">'./dist/static'</span>, template_folder=<span class="hljs-string">'./dist'</span>)<br><br><br><span class="hljs-comment"># Add your routes here:</span><br><span class="hljs-meta">@app.route(<span class="hljs-params"><span class="hljs-string">'/api/posts'</span></span>)</span><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">blog_posts</span>():<br> <span class="hljs-keyword">return</span> jsonify([])<br><br><br><span class="hljs-comment"># Make a "catch all route" so all requests match our index.html file. This lets us use the new history APIs in the browser.</span><br><span class="hljs-meta">@app.route(<span class="hljs-params"><span class="hljs-string">'/'</span>, defaults={<span class="hljs-string">'path'</span>: <span class="hljs-string">''</span>}</span>)</span><br><span class="hljs-meta">@app.route(<span class="hljs-params"><span class="hljs-string">'/<path:path>'</span></span>)</span><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">index</span>(<span class="hljs-params">path</span>):<br> <span class="hljs-keyword">return</span> render_template(<span class="hljs-string">'index.html'</span>)<br></code></pre></td></tr></table></figure><p>Vue这边也要设置,平时前后端分离开发,都是用axios到我开发的机器上拿数据,还会遇到跨域问题,但是整合的时候Vue这边打包前填什么服务器ip呢,我要是部署到别的机器ip肯定会变,ip肯定会变,总不能一个ip打一个不同的包。所以说我们需要在vue.config.js中这么配置:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-variable language_">module</span>.<span class="hljs-property">exports</span> = {<br> <span class="hljs-attr">assetsDir</span>: <span class="hljs-string">'static'</span>, <span class="hljs-comment">// For simple configuration of static files in Flask (the "static_folder='client/dist/static'" part in app.py)</span><br> <span class="hljs-attr">devServer</span>: {<br> <span class="hljs-attr">proxy</span>: <span class="hljs-string">'http://localhost:5000'</span> <span class="hljs-comment">// So that the client dev server can access your Flask routes</span><br> }<br>};<br></code></pre></td></tr></table></figure><p>这样就OK了,打一个包后续都能用了</p><h2 id="4-总结"><a href="#4-总结" class="headerlink" title="4. 总结"></a>4. 总结</h2><p>对于Flask目录结构真的是仁者见仁,智者见智,怎么舒服怎么来,但是对于刚开始用的人来说不要一开始就用人家的大结构目录,容易看的懵逼,项目都是从一开始的几个文件然后内容的增多到很多个包,中间有慢慢衍生的过程,想一开始就把代码套在大骨架上实属勉强。<br>Vue配置文件中<code>proxy</code>我只是知道个大概的作用,只是在网上找到了这个解决方案,如果不加,直接在<code>axios</code>中裸用<code>'http://localhost:5000'</code>,那么访问 到的地址就是打开浏览器访问页面时的那台机器的localhost资源,原因可能在于webpack4本身不知道<code>'http://localhost:5000'</code>这个地址,需要代理下!<br><br><br><br><br>参考资料:<br><a href="https://medium.com/unbabel/integrating-webpack-4-with-a-backend-framework-4a0e630d2a03">Integrating Webpack 4 with a backend framework</a></p><p><a href="https://stackabuse.com/single-page-apps-with-vue-js-and-flask-deployment/">Single Page Apps with Vue.js and Flask: Deployment</a></p><p><a href="https://vsupalov.com/combine-frontend-and-backend-development-servers/">Running Frontend and Backend Development Servers Together</a></p><p><a href="https://olav.it/2018/08/29/simple-spa-setup-with-vue-cli-3-and-flask/">Simple SPA setup with Vue CLI 3 and Flask</a></p><p><a href="https://olav.it/2018/08/29/simple-spa-setup-with-vue-cli-3-and-flask/">flask-vuejs-template</a></p>]]></content>
<categories>
<category>Flask</category>
</categories>
<tags>
<tag>Python</tag>
<tag>Vue</tag>
<tag>Flask</tag>
</tags>
</entry>
<entry>
<title>用Python在树莓派中使用MCP23017 I/O扩展模块</title>
<link href="/2019/09/20/MCP23017-raspberrypi-expander/"/>
<url>/2019/09/20/MCP23017-raspberrypi-expander/</url>
<content type="html"><![CDATA[<p><strong>摘自老外一段话,本来想翻译,有点拗口,直接搬运过来,作为背景:</strong></p><p>For all of my projects I have used the standard GPIO header pins as inputs and outputs. This gives you a total of 17 pins to play with but what if you need more?</p><p>The easiest way of getting more inputs and outputs is to use an “i/o port expander”. This is a device that allows you to control a number of ports using data you send to the device.</p><p>MCP23017 Example CircuitOther people have has lots of success using I2C devices so I decided to give one a try. I2C is a serial communications protocol which allows chips to swap data on the same “bus”. A port expander takes the data and controls the appropriate pins. This allows lots of sensors and devices to be controlled using only a few of the Pi’s GPIO pins.</p><h2 id="1-说说硬件"><a href="#1-说说硬件" class="headerlink" title="1. 说说硬件"></a>1. 说说硬件</h2><p>我买的是微雪电子上的MCP23017 I/O扩展模块,这是一款基于 I2C 接口控制的 I/O 扩展模块,可外扩 16Pin I/O 口,支持同时使用多达 8 个,即可扩至 128Pin I/O 口,兼容 3.3V 和 5V 电平。</p><p>产品参数与接线这里不多说,参考该产品的<a href="http://www.waveshare.net/w/upload/3/31/MCP23017-IO-Expansion-Board-user-manual-cn.pdf">用户手册</a>,当然我们要验证这个拓展模块有没有用的直接表现就是简单地在电路上面接一个小灯(<strong>假设接在引脚PAO上</strong>).</p><p>这个模块不仅可以拓展树莓派,还可以拓展Arduino与单片机,算是通用的,性价比不错.</p><h2 id="2-系统设置与硬件测试"><a href="#2-系统设置与硬件测试" class="headerlink" title="2. 系统设置与硬件测试"></a>2. 系统设置与硬件测试</h2><p>我们先要打开树莓派上的I2C接口,默认是不开的,这个简单,直接在配置里面打开接口就行,然后重启</p><ul><li><p>安装 i2c-tools 工具对器件地址进行确认<br><strong><code>sudo apt-get install i2c-tools</code></strong></p></li><li><p>查询已连接的 I2C 设备<br><strong><code>i2cdetect -y 1</code></strong></p></li></ul><p>将会打印出已连接设备的 I2C 器件地址信息:</p><figure class="highlight brainfuck"><table><tr><td class="gutter"><div class="code-wrapper"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></div></td><td class="code"><pre><code class="hljs brainfuck"><span class="hljs-comment">pi@raspberrypi:~ $ i2cdetect</span> <span class="hljs-literal">-</span><span class="hljs-comment">y 1</span><br> <span class="hljs-comment">0 1 2 3 4 5 6 7 8 9 a b c d e f</span><br><span class="hljs-comment">00:</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <br><span class="hljs-comment">10:</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <br><span class="hljs-comment">20:</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-comment">27</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <br><span class="hljs-comment">30:</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <br><span class="hljs-comment">40:</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <br><span class="hljs-comment">50:</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <br><span class="hljs-comment">60:</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <br><span class="hljs-comment">70:</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <span class="hljs-literal">--</span> <br></code></pre></td></tr></table></figure><blockquote><p>注意,这里不同厂家的拓展模块有可能是其他地址,比如0x20,原因在于我这个模块的A0,A1,A2在厂家出厂时就在内部接了3.3V,所以是0x27,这也是为什么能同时接八个该拓展模块的原因,A0,A1,A2,有接GND与3V3的选择,共有<code>2*2*2=8</code>不同的组合,具体可以看下面的表格</p></blockquote><p><img src="/images/MCP23017/address_pin.jpg" alt="MCP23017地址"></p><h2 id="3-命令行测试"><a href="#3-命令行测试" class="headerlink" title="3.命令行测试"></a>3.命令行测试</h2><p><strong>这里的命令行测试我并不是很懂,只是看到外文教程上以这样为例,而且我依葫芦画瓢成功了,写在这里只是为了快速测试 (LED引脚需接在PAO上)</strong></p><ul><li><p>首先我们将PA0-PA6设置为输出模式,将PA7设置为输入(二进制用10000000表示,十六进制用0x80表示):</p><blockquote><p><strong><code>sudo i2cset -y 1 0x27 0x00 0x80</code></strong></p></blockquote></li><li><p>将PA0设置为逻辑高点亮LED:</p><blockquote><p><strong><code>sudo i2cset -y 1 0x27 0x14 0x01</code></strong></p></blockquote></li><li><p>关闭小灯则可以用:</p><blockquote><p><strong><code>sudo i2cset -y 1 0x27 0x14 0x00</code></strong></p></blockquote></li></ul><h2 id="4-在Python脚本中使用拓展模块"><a href="#4-在Python脚本中使用拓展模块" class="headerlink" title="4. 在Python脚本中使用拓展模块"></a>4. 在Python脚本中使用拓展模块</h2><p>习惯了在树莓派中用<code>gpiozero</code>简单丝滑流畅地操控IO,不用关乎太多底层,实在爽的很. 而在这里,我们是I2C bus协议,我们一般是用到<code>smbus</code>库,但是还是有些底层(特别是拓展模块上各个引脚代表的寄存器地址好像有点难以理解)的有些费力,所以找阿找,找到了一个新库,依旧保持优雅!</p><h3 id="4-1-什么是CircuitPython"><a href="#4-1-什么是CircuitPython" class="headerlink" title="4.1 什么是CircuitPython?"></a>4.1 什么是CircuitPython?</h3><p><code>CircuitPython</code>基于<code>Python</code>,在<code>Python</code>上添加了硬件支持.<code>CircuitPython</code>旨在在微控制器板上运行。微控制器板是一块带有微控制器芯片的电路板,它本质上是一个多功能的一体机。你持有的电路板是微控制器板! <code>CircuitPython</code>可以在小型Linux板上运行,这里正好可以用我们的树莓派试试手.先安装库<code>sudo pip3 install adafruit-circuitpython-mcp230xx</code>,然后是代码演示。</p><h3 id="4-2-代码演示"><a href="#4-2-代码演示" class="headerlink" title="4.2 代码演示"></a>4.2 代码演示</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">import</span> time<br><span class="hljs-keyword">import</span> board<br><span class="hljs-keyword">import</span> busio<br><span class="hljs-keyword">from</span> digitalio <span class="hljs-keyword">import</span> Direction, Pull<br><span class="hljs-keyword">from</span> adafruit_mcp230xx.mcp23017 <span class="hljs-keyword">import</span> MCP23017<br><br><span class="hljs-comment"># Initialize the I2C bus:</span><br>i2c = busio.I2C(board.SCL, board.SDA)<br><br><span class="hljs-comment"># Initialize the MCP23017 chip on the bonnet</span><br>mcp = MCP23017(i2c,<span class="hljs-number">0x27</span>)<br><br><span class="hljs-comment">#0 to 15 for the GPIOA0...GPIOA7, GPIOB0...GPIOB7 pins (i.e. pin 12 is GPIOB4).</span><br>a0 = mcp.get_pin(<span class="hljs-number">0</span>)<br><br><span class="hljs-comment"># defalut input mode , switch to output pinmode</span><br>a0.switch_to_output()<br><br><span class="hljs-keyword">while</span> <span class="hljs-literal">True</span>:<br> a0.value = <span class="hljs-literal">True</span><br> time.sleep(<span class="hljs-number">2</span>)<br> a0.value = <span class="hljs-literal">False</span><br> time.sleep(<span class="hljs-number">2</span>)<br></code></pre></td></tr></table></figure><p>具体的代码解释都在注释上写这个了,很简单,不展开.想要查看更多的例子或者源代码可以看<a href="https://github.com/adafruit/Adafruit_CircuitPython_MCP230xx/blob/master/examples/mcp230xx_simpletest.py"><strong>官方库手册</strong></a></p><h2 id="5-总结"><a href="#5-总结" class="headerlink" title="5. 总结"></a>5. 总结</h2><p><strong>当树莓派GPIO口要连接较多设备,数量紧张时,可以用MCP23017拓展模块了来增加,最多可以加128个….<code>adafruit_mcp230xx.mcp23017</code>库也可以很Python地写出控制代码,但是一些监听事件要自己实现,不像<code>gpiozero</code>都有现成的轮子.当然我们可以转换下思路,把一些简单的output设备接在拓展模块上用来简单控制就行,把一些实现复杂的input设备接在树莓派自带的GPIO上,就可以用<code>gpiozero</code>库的高级内容了.</strong></p><br><p>一些资料整理:<br><a href="https://www.raspberrypi-spy.co.uk/2013/07/how-to-use-a-mcp23017-i2c-port-expander-with-the-raspberry-pi-part-1/#prettyPhoto">How To Use A MCP23017 I2C Port Expander With The Raspberry Pi – Part 1</a></p><p><a href="https://learn.adafruit.com/using-mcp23008-mcp23017-with-circuitpython/python-circuitpython">Using MCP23008 & MCP23017 with CircuitPython</a></p><p><a href="https://raspi.tv/2013/using-the-mcp23017-port-expander-with-wiringpi2-to-give-you-16-new-gpio-ports-part-3">Using the MCP23017 port expander with WiringPi2 to give you 16 new GPIO ports – part 3</a></p><p><a href="https://circuitpython.readthedocs.io/en/2.x/shared-bindings/index.html">Adafruit CircuitPython</a></p>]]></content>
<categories>
<category>RaspberryPi</category>
</categories>
<tags>
<tag>Python</tag>
<tag>Linux</tag>
<tag>I2C</tag>
</tags>
</entry>
<entry>
<title>Arduino Serial使用以及各种读写函数区别</title>
<link href="/2019/08/01/Arduino-Serial/"/>
<url>/2019/08/01/Arduino-Serial/</url>
<content type="html"><![CDATA[<p>之前有一篇文章总结了Python的串口使用,自认为已经深得要领。直到最近开始研究Arduino,用的串口传输数据,心里还是有很多问题,时常在运用相关函数的时候想起之前Python上的串口知识点,两者有着千丝万缕的关系。从头开始理一下思路,原理都是一样的,计算机只懂得二进制,而人类懂得ASCII文本,如何理解两者之间架起的这座桥梁甚是关键….</p><h2 id="1-前言"><a href="#1-前言" class="headerlink" title="1. 前言"></a>1. 前言</h2><p>常见的串口调试是将Arduino与PC相连,然后利用PC上的串口助手与其通信调试,但是有一个注意点是,调试Arduino串口时,用串口助手调试与跟程序语言(比如Python,Arduino语言)调试的传输的数据有区别,比如串口助手输入框中输入<code>97</code>时你可能输入的是两个字符<code>9</code>与<code>7</code>,而在用Arduino语法(Serial.write(97))写的时候就是代表ASCII的<code>a</code>,或者在Python中是<code>ser.write(b'a')</code>也可写成<code>ser.write([97])</code>),两者调试方法有明显的差别性。</p><h2 id="2-两个重要的写函数(write,print)"><a href="#2-两个重要的写函数(write,print)" class="headerlink" title="2. 两个重要的写函数(write,print)"></a>2. 两个重要的写函数(write,print)</h2><h3 id="2-1-Serial-write"><a href="#2-1-Serial-write" class="headerlink" title="2.1 Serial.write()"></a>2.1 Serial.write()</h3><p>串口中通信的一定是byte(字节),也就是八位Bit(二进制位),可以为一个字节,也可为多个字节;如果想发送代表数字的字符,应该使用print()函数(这个后面说);该函数返回有几个字节被发送了。</p><p><code>Serial.write()</code>一共有三种语法</p><ul><li><code>Serial.write(val)</code></li></ul><p>发送单个字节,比如发送97,那么等于发送十进制ASCII码值为97的字符,也就是字母a</p><ul><li><code>Serial.write(str)</code></li></ul><p>发送字符串作为一系列的字节,比如<code>int bytesSent = Serial.write(“hello”);</code></p><blockquote><p>注意,这里发送字符串一定要用双引号,我一开始习惯了Python的用法,单双引号看心情来用,直接只收到一个字节,问题很大</p></blockquote><ul><li><code>Serial.write(buf, len)</code></li></ul><p>发送一个长度为len的数组(一般为char类型数组),跟上面<code>Serial.write(str)</code>作用是一致的,只是传入参数不一样</p><h3 id="2-2-Serial-print"><a href="#2-2-Serial-print" class="headerlink" title="2.2 Serial.print()"></a>2.2 Serial.print()</h3><blockquote><p><strong>Prints data to the serial port as human-readable ASCII text.</strong></p></blockquote><p>println与print类似,只是多了换行符,这里只介绍print函数。<br>上面这行英文已经把它跟read()函数的关系分的很清楚了。这个命令可以有多种形式。每个数字都使用ASCII字符打印。浮点数类似地打印为ASCII数字,默认为两位小数。字节作为单个字符发送。字符和字符串按原样发送。例如:</p><figure class="highlight cpp"><table><tr><td class="gutter"><div class="code-wrapper"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></div></td><td class="code"><pre><code class="hljs cpp">Serial.<span class="hljs-built_in">print</span>(<span class="hljs-number">78</span>) gives <span class="hljs-string">"78"</span><br><br>Serial.<span class="hljs-built_in">print</span>(<span class="hljs-number">1.23456</span>) gives <span class="hljs-string">"1.23"</span><br><br>Serial.<span class="hljs-built_in">print</span>(<span class="hljs-string">'N'</span>) gives <span class="hljs-string">"N"</span><br><br>Serial.<span class="hljs-built_in">print</span>(<span class="hljs-string">"Hello world."</span>) gives <span class="hljs-string">"Hello world."</span><br><br></code></pre></td></tr></table></figure><p>还有第二个可选参数<code>format</code>是具体的格式,允许二进制八进制十六进制等,例如:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs cpp">Serial.<span class="hljs-built_in">print</span>(<span class="hljs-number">78</span>, BIN) gives <span class="hljs-string">"1001110"</span><br><br>Serial.<span class="hljs-built_in">print</span>(<span class="hljs-number">78</span>, OCT) gives <span class="hljs-string">"116"</span><br><br>Serial.<span class="hljs-built_in">print</span>(<span class="hljs-number">78</span>, DEC) gives <span class="hljs-string">"78"</span><br><br>Serial.<span class="hljs-built_in">print</span>(<span class="hljs-number">78</span>, HEX) gives <span class="hljs-string">"4E"</span><br><br>Serial.<span class="hljs-built_in">print</span>(<span class="hljs-number">1.23456</span>, <span class="hljs-number">0</span>) gives <span class="hljs-string">"1"</span><br><br>Serial.<span class="hljs-built_in">print</span>(<span class="hljs-number">1.23456</span>, <span class="hljs-number">2</span>) gives <span class="hljs-string">"1.23"</span><br><br>Serial.<span class="hljs-built_in">print</span>(<span class="hljs-number">1.23456</span>, <span class="hljs-number">4</span>) gives <span class="hljs-string">"1.2346"</span><br></code></pre></td></tr></table></figure><h3 id="2-3-Serial-write与Serial-print函数的区别"><a href="#2-3-Serial-write与Serial-print函数的区别" class="headerlink" title="2.3 Serial.write与Serial.print函数的区别"></a>2.3 Serial.write与Serial.print函数的区别</h3><blockquote><p>谢邀,我觉得print,printf是开发出来专门针对pc端显示的,write则是用来与串口设备通信的,当然在老手眼里怎么用都行 ——————from逼乎某大佬回答</p></blockquote><p>按照arduino官网reference的解释,<code>Serial. print()</code>是<strong>print data to the serial port as human-reading ASC II text</strong> ,<code>Serial. write()</code>是<strong>write binary data to the serial port</strong> ,一个转化为文本输出,一个是数据输出。</p><p>我在测试的时候刚开始并没有发现两者的区别。起初我认为传数据就该用<code>Serial.write</code>,而对于<code>Serial.print</code>这是在用串口助手的时候用,打印一些格式啥的,好利于调试辨认啥的等等(事实确实如此,但不仅于此)</p><p>于是我就将这两者的区别与Python中<code>sys.stdout.write</code>与内置的<code>print</code>函数作比较,前者不会写换行符然后是写到缓冲区,而<code>print</code>函数每次都有换行符,清空了缓冲区,然后就打印到屏幕上了,而且<code>print</code>函数自带了很多格式,可以跟<code>format</code>函数配合啥的,倒是跟Arduino的<code>print</code>有几分相似……但是其实这并不是重点~</p><blockquote><p>我认为Arduino中的<code>write</code>与<code>print</code>还是存在缓冲区这个区别的,比如是在<code>write</code>中设置了<code>timeout</code>等操作,达到了与<code>print</code>类似的操作,这里只是猜测下,不做深究</p></blockquote><p>重点来了! </p><p>我在这里<a href="https://www.zhihu.com/question/21307404">Arduino 的 Serial.write() 和 Serial.print() 的区别在哪里?</a>看到了答案?</p><ul><li><strong>在输出字符或字符串时,没有任何区别</strong></li><li><strong>在输出数值时,write会直接输出数据本身,而print会将其转化为可显示的ASCII字符(其实我觉得不妥,转换是串口助手来转换的,应该是算是说转换成可以传输的ASCII码值)</strong></li></ul><p>当时我的想法跟<code>amazing814</code>答主一样,而跟最高赞同者那位相反,后来发现我们概念理解错了<br>他“错误”的回答如下:</p><blockquote><p>1, print 出来的是真实数值,<br> 2, write出来的是ascii码表对应的值(或者是说”对应的图形”)</p></blockquote><p>错在了串口助手会自动将数据转换程ASCII文本! 反正,你要知道的是,串口发送或者读取到的数据都是字节!这一点用Python串口模块自己尝试一下就行!</p><p>所以<code>Serial.write</code>与<code>Serial.print</code>最大的区别就是传进函数中的数据,前者是真正的原始数据(int型或者说二进制位的数值),得到的是数据本身,而<code>使用print</code>函数的时候会以多个字节的形式向串口传递括号中数值,会将它看成一个字符串,传递其中每一个字符的ASCII码。例如你举的例子“78”会向串口传递“7”和“8”的ASCII码的值。</p><p>比如 Serial.write(78) ,发送的是<code>N</code>(换算成二进制就是01001110),得到的也是<code>N</code>的字节(即二进制位),虽然串口助手上显示的是<code>N</code><br>与Serial.print(78),发送的<code>7</code>和<code>8</code>两个字节,内部先把<code>7</code>,<code>8</code>转换成<code>55</code>,<code>56</code>的原始数据,在发送出去,得到也是<code>55</code>,<code>56</code></p><p>所以那位错误的答主,想说的其实并不是“真实”,而是“相同”! 真正的计算机数据是二进制位(为了方便,转换成十进制了,也就是上面的55,56)</p><p><strong>print主要用于”给人看”的地方. 电脑内部通讯, 如果程序内不需要用人眼再检查, 用 write 会比较好, 直接快捷.</strong></p><h2 id="3-Serial-read-相关函数"><a href="#3-Serial-read-相关函数" class="headerlink" title="3. Serial.read 相关函数"></a>3. Serial.read 相关函数</h2><h3 id="3-1-Serial-read"><a href="#3-1-Serial-read" class="headerlink" title="3.1 Serial.read()"></a>3.1 Serial.read()</h3><p>读取即将来的串口数据,但是只读取第一个字节,且这个字节的数据类型为int,即ASCII码值。</p><p>通常与<code>Serial.available()</code>一起用,例子如下:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-type">int</span> incomingByte = <span class="hljs-number">0</span>; <span class="hljs-comment">// for incoming serial data</span><br><br><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">setup</span><span class="hljs-params">()</span> </span>{<br> Serial.<span class="hljs-built_in">begin</span>(<span class="hljs-number">9600</span>); <span class="hljs-comment">// opens serial port, sets data rate to 9600 bps</span><br>}<br><br><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">loop</span><span class="hljs-params">()</span> </span>{<br> <span class="hljs-comment">// send data only when you receive data:</span><br> <span class="hljs-keyword">if</span> (Serial.<span class="hljs-built_in">available</span>() > <span class="hljs-number">0</span>) {<br> <span class="hljs-comment">// read the incoming byte:</span><br> incomingByte = Serial.<span class="hljs-built_in">read</span>();<br><br> <span class="hljs-comment">// say what you got:</span><br> Serial.<span class="hljs-built_in">print</span>(<span class="hljs-string">"I received: "</span>);<br> Serial.<span class="hljs-built_in">println</span>(incomingByte, DEC);<br> }<br>}<br></code></pre></td></tr></table></figure><p>串口发送数据与读到数据都为字节,所以该函数return回来int 型数据很正常,但是我们可以直接拿来跟字符比较或者用到某些话函数中,这得益于在C语言中单引号括起一个字符实际上代表一个整数,即ASCII码值</p><p>,从Arduino内置的例子就能看到:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">setup</span><span class="hljs-params">()</span> </span>{<br> <span class="hljs-comment">// Open serial communications and wait for port to open:</span><br> Serial.<span class="hljs-built_in">begin</span>(<span class="hljs-number">9600</span>);<br> <span class="hljs-keyword">while</span> (!Serial) {<br> ; <span class="hljs-comment">// wait for serial port to connect. Needed for native USB port only</span><br> }<br><br> <span class="hljs-comment">// send an intro:</span><br> Serial.<span class="hljs-built_in">println</span>(<span class="hljs-string">"send any byte and I'll tell you everything I can about it"</span>);<br> Serial.<span class="hljs-built_in">println</span>();<br>}<br><br><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">loop</span><span class="hljs-params">()</span> </span>{<br> <span class="hljs-comment">// get any incoming bytes:</span><br> <span class="hljs-keyword">if</span> (Serial.<span class="hljs-built_in">available</span>() > <span class="hljs-number">0</span>) {<br> <span class="hljs-type">int</span> thisChar = Serial.<span class="hljs-built_in">read</span>();<br><br> <span class="hljs-comment">// say what was sent:</span><br> Serial.<span class="hljs-built_in">print</span>(<span class="hljs-string">"You sent me: \'"</span>);<br> Serial.<span class="hljs-built_in">write</span>(thisChar);<br> Serial.<span class="hljs-built_in">print</span>(<span class="hljs-string">"\' ASCII Value: "</span>);<br> Serial.<span class="hljs-built_in">println</span>(thisChar);<br><br> <span class="hljs-comment">// analyze what was sent:</span><br> <span class="hljs-keyword">if</span> (<span class="hljs-built_in">isAlphaNumeric</span>(thisChar)) {<br> Serial.<span class="hljs-built_in">println</span>(<span class="hljs-string">"it's alphanumeric"</span>);<br> }<br> <span class="hljs-keyword">if</span> (<span class="hljs-built_in">isAlpha</span>(thisChar)) {<br> Serial.<span class="hljs-built_in">println</span>(<span class="hljs-string">"it's alphabetic"</span>);<br> }<br> <span class="hljs-keyword">if</span> (<span class="hljs-built_in">isAscii</span>(thisChar)) {<br> Serial.<span class="hljs-built_in">println</span>(<span class="hljs-string">"it's ASCII"</span>);<br> }<br> <span class="hljs-keyword">if</span> (<span class="hljs-built_in">isWhitespace</span>(thisChar)) {<br> Serial.<span class="hljs-built_in">println</span>(<span class="hljs-string">"it's whitespace"</span>);<br> }<br> <span class="hljs-keyword">if</span> (<span class="hljs-built_in">isControl</span>(thisChar)) {<br> Serial.<span class="hljs-built_in">println</span>(<span class="hljs-string">"it's a control character"</span>);<br> }<br> <span class="hljs-keyword">if</span> (<span class="hljs-built_in">isDigit</span>(thisChar)) {<br> Serial.<span class="hljs-built_in">println</span>(<span class="hljs-string">"it's a numeric digit"</span>);<br> }<br> <span class="hljs-keyword">if</span> (<span class="hljs-built_in">isGraph</span>(thisChar)) {<br> Serial.<span class="hljs-built_in">println</span>(<span class="hljs-string">"it's a printable character that's not whitespace"</span>);<br> }<br> <span class="hljs-keyword">if</span> (<span class="hljs-built_in">isLowerCase</span>(thisChar)) {<br> Serial.<span class="hljs-built_in">println</span>(<span class="hljs-string">"it's lower case"</span>);<br> }<br> <span class="hljs-keyword">if</span> (<span class="hljs-built_in">isPrintable</span>(thisChar)) {<br> Serial.<span class="hljs-built_in">println</span>(<span class="hljs-string">"it's printable"</span>);<br> }<br> <span class="hljs-keyword">if</span> (<span class="hljs-built_in">isPunct</span>(thisChar)) {<br> Serial.<span class="hljs-built_in">println</span>(<span class="hljs-string">"it's punctuation"</span>);<br> }<br> <span class="hljs-keyword">if</span> (<span class="hljs-built_in">isSpace</span>(thisChar)) {<br> Serial.<span class="hljs-built_in">println</span>(<span class="hljs-string">"it's a space character"</span>);<br> }<br> <span class="hljs-keyword">if</span> (<span class="hljs-built_in">isUpperCase</span>(thisChar)) {<br> Serial.<span class="hljs-built_in">println</span>(<span class="hljs-string">"it's upper case"</span>);<br> }<br> <span class="hljs-keyword">if</span> (<span class="hljs-built_in">isHexadecimalDigit</span>(thisChar)) {<br> Serial.<span class="hljs-built_in">println</span>(<span class="hljs-string">"it's a valid hexadecimaldigit (i.e. 0 - 9, a - F, or A - F)"</span>);<br> }<br><br> <span class="hljs-comment">// add some space and ask for another byte:</span><br> Serial.<span class="hljs-built_in">println</span>();<br> Serial.<span class="hljs-built_in">println</span>(<span class="hljs-string">"Give me another byte:"</span>);<br> Serial.<span class="hljs-built_in">println</span>();<br> }<br>}<br></code></pre></td></tr></table></figure><h3 id="3-2-Serial-readBytes-buffer-length"><a href="#3-2-Serial-readBytes-buffer-length" class="headerlink" title="3.2 Serial.readBytes(buffer, length)"></a>3.2 Serial.readBytes(buffer, length)</h3><p><code>Serial.readBytes</code>从串行端口读取字符到缓冲区,返回放入缓冲区的字节数,如果超时该函数将中断</p><p>这个函数我看到的用法是放在字符数组中,然后再利用数组的特性进行一些操作,例子如下:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs C"><span class="hljs-type">char</span> mystr[<span class="hljs-number">10</span>]; <span class="hljs-comment">//Initialized variable to store recieved data</span><br><br><span class="hljs-type">void</span> <span class="hljs-title function_">setup</span><span class="hljs-params">()</span> {<br> <span class="hljs-comment">// Begin the Serial at 9600 Baud</span><br> Serial.begin(<span class="hljs-number">9600</span>);<br>}<br><br><span class="hljs-type">void</span> <span class="hljs-title function_">loop</span><span class="hljs-params">()</span> {<br> Serial.readBytes(mystr,<span class="hljs-number">5</span>); <span class="hljs-comment">//Read the serial data and store in var</span><br> Serial.println(mystr); <span class="hljs-comment">//Print data on Serial Monitor</span><br> <span class="hljs-keyword">for</span> (byte i = <span class="hljs-number">0</span>; i < <span class="hljs-number">5</span>; i = i + <span class="hljs-number">1</span>) {<br> Serial.println(mystr[i]);<br> delay(<span class="hljs-number">1000</span>);<br>}<br></code></pre></td></tr></table></figure><h3 id="3-3-Serial-readString"><a href="#3-3-Serial-readString" class="headerlink" title="3.3 Serial.readString()"></a>3.3 Serial.readString()</h3><p><code>Serial.readString</code>将串行缓冲区中的字符读入String,如果超时该函数将中断</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><code class="hljs C">String a;<br><br><span class="hljs-type">void</span> <span class="hljs-title function_">setup</span><span class="hljs-params">()</span> {<br>Serial.begin(<span class="hljs-number">9600</span>); <span class="hljs-comment">// opens serial port, sets data rate to 9600 bps</span><br>}<br><span class="hljs-type">void</span> <span class="hljs-title function_">loop</span><span class="hljs-params">()</span> {<br><br> <span class="hljs-keyword">while</span>(Serial.available()) {<br> a= Serial.readString();<span class="hljs-comment">// read the incoming data as string</span><br> Serial.println(a);<br>}<br>}<br></code></pre></td></tr></table></figure><p>当时这个函数与上面的搞不太清楚,所以试了下。其实这个函数是读取发送过来的字符串,存到一个String类型的变量里,用来打印,或者处理(<code>Serial.write()无法字节发送String类型的变量</code>)再发出去等等,可以看一下String类型的变量很多个方法比如<code>toCharArray()</code>,<code>toInt()</code>之类的</p><h2 id="4-总结"><a href="#4-总结" class="headerlink" title="4. 总结"></a>4. 总结</h2><p>经历了两天的疑惑解惑过程,发现自己关于串口的基本功还是不够扎实,这个过程中需要自己不断反复地尝试与验证,剥开代码语言的皮囊,看到更下面真实的骨架,其实也挺开心的,故再花点时间整理在这里留作总结以及反思!</p>]]></content>
<categories>
<category>Arduino</category>
</categories>
<tags>
<tag>Serial</tag>
</tags>
</entry>
<entry>
<title>Python中关于键盘行为的方法总结</title>
<link href="/2019/07/25/python-about-keyboard/"/>
<url>/2019/07/25/python-about-keyboard/</url>
<content type="html"><![CDATA[<p>最近在倒腾AGV的时候,手动挪小车实在是太重太重了,之前写过控制小车转弯直行的命令,但没有优化,最近小车加了层铠甲实在太重了,于是想写一个类似于小时候玩赛车游戏一样的用手柄或者键盘控制的程序来控制它,中间查阅了相关Python控制键盘的一些第三方库,以及中间遇到的问题做一些整理</p><h2 id="1-前言"><a href="#1-前言" class="headerlink" title="1. 前言"></a>1. 前言</h2><p>我能想到的操控键盘的行为无非有两种,一种是监听键盘的按键,还有一种就是模拟键盘的输入,但我的情景里需要的是前者。</p><h2 id="2-pynput库"><a href="#2-pynput库" class="headerlink" title="2. pynput库"></a>2. pynput库</h2><p>这个库允许你控制和监视输入设备。 目前,支持鼠标和键盘输入和监视</p><h3 id="2-1-控制键盘示例"><a href="#2-1-控制键盘示例" class="headerlink" title="2.1 控制键盘示例"></a>2.1 控制键盘示例</h3><figure class="highlight python"><table><tr><td class="gutter"><div class="code-wrapper"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></div></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> pynput.keyboard <span class="hljs-keyword">import</span> Key, Controller<br><br>keyboard = Controller()<br><br><span class="hljs-comment"># Press and release space</span><br>keyboard.press(Key.space)<br>keyboard.release(Key.space)<br><br><span class="hljs-comment"># Type a lower case A; this will work even if no key on the</span><br><span class="hljs-comment"># physical keyboard is labelled 'A'</span><br>keyboard.press(<span class="hljs-string">'a'</span>)<br>keyboard.release(<span class="hljs-string">'a'</span>)<br><br><span class="hljs-comment"># Type two upper case As</span><br>keyboard.press(<span class="hljs-string">'A'</span>)<br>keyboard.release(<span class="hljs-string">'A'</span>)<br><span class="hljs-keyword">with</span> keyboard.pressed(Key.shift):<br> keyboard.press(<span class="hljs-string">'a'</span>)<br> keyboard.release(<span class="hljs-string">'a'</span>)<br><br><span class="hljs-comment"># Type 'Hello World' using the shortcut type method</span><br>keyboard.<span class="hljs-built_in">type</span>(<span class="hljs-string">'Hello World'</span>)<br></code></pre></td></tr></table></figure><h3 id="2-2-监听鼠标示例"><a href="#2-2-监听鼠标示例" class="headerlink" title="2.2 监听鼠标示例"></a>2.2 监听鼠标示例</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> pynput <span class="hljs-keyword">import</span> keyboard<br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">on_press</span>(<span class="hljs-params">key</span>):<br> <span class="hljs-keyword">try</span>:<br> <span class="hljs-built_in">print</span>(<span class="hljs-string">'alphanumeric key {0} pressed'</span>.<span class="hljs-built_in">format</span>(key.char))<br> <span class="hljs-keyword">except</span> AttributeError:<br> <span class="hljs-built_in">print</span>(<span class="hljs-string">'special key {0} pressed'</span>.<span class="hljs-built_in">format</span>(key))<br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">on_release</span>(<span class="hljs-params">key</span>):<br> <span class="hljs-built_in">print</span>(<span class="hljs-string">'{0} released'</span>.<span class="hljs-built_in">format</span>(key))<br> <span class="hljs-keyword">if</span> key == keyboard.Key.esc:<br> <span class="hljs-comment"># Stop listener</span><br> <span class="hljs-keyword">return</span> <span class="hljs-literal">False</span><br><br><span class="hljs-comment"># Collect events until released</span><br><span class="hljs-keyword">with</span> keyboard.Listener(<br> on_press=on_press,<br> on_release=on_release) <span class="hljs-keyword">as</span> listener:<br> listener.join()<br><br><span class="hljs-comment"># ...or, in a non-blocking fashion:</span><br>listener = mouse.Listener(<br> on_press=on_press,<br> on_release=on_release)<br>listener.start()<br></code></pre></td></tr></table></figure><p><strong>我对这个库的第一印象是简洁好用,监听部分的callback函数很优雅,很是喜欢,也简单测试成功了,于是我噗呲噗呲地开始写,然后把代码扔到树莓派上准备run一下,结果悲剧了,报错<code>Xlib.error.DisplayNameError: Bad display name ""</code>,网上说通过ssh使用这个库的时候会报错,没法正常监听,具体可以看<a href="https://github.com/moses-palmer/pynput/issues/6">这个issue</a>,虽然上面有着解决方法,但是我没有得到解决,只能忍痛弃之 (或者可以曲线救国,采用蓝牙键盘?没试过)</strong></p><h2 id="3-keyboard库"><a href="#3-keyboard库" class="headerlink" title="3. keyboard库"></a>3. keyboard库</h2><p><a href="https://github.com/boppreh/keyboard">keyboard库</a>在github上是有较多star的,内容非常多,功能十分强大,你能想到的基本都有….不过我看着头疼,当然,没有选择的另外一个原因是发现了另外一个小脚本来实现我所需要的键盘监听功能,这也是我待会要说的,如果要用其他关于键盘的行为操作,建议好好琢磨下这个库。</p><h2 id="4-神奇小脚本"><a href="#4-神奇小脚本" class="headerlink" title="4. 神奇小脚本"></a>4. 神奇小脚本</h2><p>网上看着看着,突然发现了国外大佬的这篇文章<a href="https://www.jonwitts.co.uk/archives/896">Detecting keyboard input in Python</a>,完美测试,感觉好极了!先贴代码:</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment">#!/usr/bin/python3</span><br> <br><span class="hljs-comment"># adapted from https://github.com/recantha/EduKit3-RC-Keyboard/blob/master/rc_keyboard.py</span><br> <br><span class="hljs-keyword">import</span> sys, termios, tty, os, time<br> <br><span class="hljs-keyword">def</span> <span class="hljs-title function_">getch</span>():<br> fd = sys.stdin.fileno()<br> old_settings = termios.tcgetattr(fd)<br> <span class="hljs-keyword">try</span>:<br> tty.setraw(sys.stdin.fileno())<br> ch = sys.stdin.read(<span class="hljs-number">1</span>)<br> <br> <span class="hljs-keyword">finally</span>:<br> termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)<br> <span class="hljs-keyword">return</span> ch<br> <br>button_delay = <span class="hljs-number">0.2</span><br> <br><span class="hljs-keyword">while</span> <span class="hljs-literal">True</span>:<br> char = getch()<br> <br> <span class="hljs-keyword">if</span> (char == <span class="hljs-string">"p"</span>):<br> <span class="hljs-built_in">print</span>(<span class="hljs-string">"Stop!"</span>)<br> exit(<span class="hljs-number">0</span>)<br> <br> <span class="hljs-keyword">if</span> (char == <span class="hljs-string">"a"</span>):<br> <span class="hljs-built_in">print</span>(<span class="hljs-string">"Left pressed"</span>)<br> time.sleep(button_delay)<br> <br> <span class="hljs-keyword">elif</span> (char == <span class="hljs-string">"d"</span>):<br> <span class="hljs-built_in">print</span>(<span class="hljs-string">"Right pressed"</span>)<br> time.sleep(button_delay)<br> <br> <span class="hljs-keyword">elif</span> (char == <span class="hljs-string">"w"</span>):<br> <span class="hljs-built_in">print</span>(<span class="hljs-string">"Up pressed"</span>)<br> time.sleep(button_delay)<br> <br> <span class="hljs-keyword">elif</span> (char == <span class="hljs-string">"s"</span>):<br> <span class="hljs-built_in">print</span>(<span class="hljs-string">"Down pressed"</span>)<br> time.sleep(button_delay)<br> <br> <span class="hljs-keyword">elif</span> (char == <span class="hljs-string">"1"</span>):<br> <span class="hljs-built_in">print</span>(<span class="hljs-string">"Number 1 pressed"</span>)<br> time.sleep(button_delay)<br></code></pre></td></tr></table></figure><p><strong>说实话其实<code>getch</code>这个函数我没太看懂,我也不想花时间研究它了,轮子已经造好了,正如这篇文章的作者一样,他也进行了一些搜索并找到了一些示例代码,但是没有一个完全符合要求,直到github上发现了<a href="https://github.com/recantha/EduKit3-RC-Keyboard/blob/master/rc_keyboard.py">control a robot with a bluetooth keyboard </a>这篇文章,也是感叹极客的给力!</strong></p><h2 id="5-碎碎念"><a href="#5-碎碎念" class="headerlink" title="5. 碎碎念"></a>5. 碎碎念</h2><p>回过头来整理时发现了更巧的事情,<a href="https://github.com/recantha/EduKit3-RC-Keyboard/blob/master/rc_keyboard.py">control a robot with a bluetooth keyboard </a>这篇文章也是用键盘来控制两个马达,控制前进后退(虽然马达类型不一样),真是太巧了,哈哈哈哈哈~</p>]]></content>
<categories>
<category>Python</category>
</categories>
<tags>
<tag>Python</tag>
</tags>
</entry>
<entry>
<title>Docker常用命令整理</title>
<link href="/2019/07/10/docker-common-commands/"/>
<url>/2019/07/10/docker-common-commands/</url>
<content type="html"><![CDATA[<p>因为工作需要接触到了Docker,发现这个东西是真的好用。容器带来的隔离效果对我这种强迫症患者太友好了。学一个新的框架或者数据库,docker搞起来,无论怎么糟蹋怎么设置,出现小问题啥的直接移除这个容器,再创个干净的新的重头再来,少了很多配置的烦恼,电脑上也不用装很多乱七八糟的东西!所以这篇文章就记录下最近自己整理的常用的docker命令,以便后续翻阅。</p><p><strong>由于国内网络环境的缘故,要使用国内源安装,安装过程看<a href="https://yeasy.gitbooks.io/docker_practice/install/ubuntu.html">这里的教程</a></strong></p><h2 id="1-操作容器"><a href="#1-操作容器" class="headerlink" title="1. 操作容器"></a>1. 操作容器</h2><ul><li><p>启动容器以后台方式运行(更通用的方式)<br><code>docker run --name container_name -v /path/to/hostdir:/mnt -d -p 5000:5000 image_name</code></p></li><li><p>在运行着的容器内部运行一条命令(比attach更好用)<br><code>docker exec -it <id/container_name> /bin/(bash|sh)</code></p></li><li><p>附着到正在运行的容器<br><code>docker attach <id/container_name></code></p></li><li><p>实时查看日志输出<br><code>docker logs -f <id/container_name> (类似 tail -f) (带上时间戳-t)</code></p></li><li><p>列出当前所有正在运行的container(类似的选项就不说了 docker ps –help)<br><code>docker ps</code></p></li><li><p>显示一个运行的容器里面的进程信息<br><code>docker top <id/container_name></code></p></li><li><p>显示容器统计信息(可选–no-stream,all等参数)<br><code> docker stats</code></p></li><li><p>查看容器内部详情细节<br><code>docker inspect <id/container_name></code></p></li><li><p>从容器内拷贝文件到主机上(从主机拷贝到容器里将cp后面两个参数调换)<br><code>docker cp <id/container_name>:/file/path/within/container /host/path/target</code></p></li><li><p>保存对容器的修改(commit) 当你对某一个容器做了修改之后(通过在容器中运行某一个命令),可以把对容器的修改保存下来,这样下次可以从保存后的最新状态运行该容器<br><code>docker commit <id/container_name> new_image_name</code></p></li><li><p>删除单个容器(可加-f选项)<br><code>docker rm <id/container_name></code></p></li><li><p>删除所有容器<br><code>docker rm $(docker ps -a -q)</code></p></li><li><p>查看容器内部详情细节<br><code>docker inspect -f '{range .NetworkSettings.Networks}{.IPAddress}{end}' container_name_or_id</code></p><blockquote><p>注意这里format之后不是一对{} 而是两对,这里有bug,所以先这么写着</p></blockquote></li><li><p>停止、启动、杀死、重启一个容器<br><code>docker stop|start|kill|restart <id/container_name></code></p></li></ul><h2 id="2-操作镜像"><a href="#2-操作镜像" class="headerlink" title="2. 操作镜像"></a>2. 操作镜像</h2><ul><li><p>列出镜像(可加-a ,-q选项)<br><code>docker images</code></p></li><li><p>从dockerhub检索image<br><code>docker search image_name</code></p></li><li><p>下载image<br><code>docker pull image_name</code></p></li><li><p>删除一个或者多个镜像(可加-f选项)<br><code>docker rmi image_name</code></p></li><li><p>显示一个镜像的历史<br><code>docker history image_name</code></p></li><li><p>发布docker镜像<br><code>docker image push my_repo/my_image:my_tag </code></p></li><li><p>删除一个或者多个镜像<br><code>docker rmi image_name</code></p></li><li><p>保存一个镜像到一个tar文件里(反过来就是解压)<br><code>docker save debian -o mydebian.tar</code><br><code>docker load -i mydebian.tar</code></p></li><li><p>给镜像打标签(标签指向源镜像,多生产一个)<br><code>docker tag centos:7 mycentos7:v1</code></p></li></ul><h2 id="3-其他"><a href="#3-其他" class="headerlink" title="3. 其他"></a>3. 其他</h2><ul><li><p>通过Dockerfile生成新的镜像<br><code>docker build -t new_image_name:tag .</code></p></li><li><p>显示系统范围信息<br><code>docker info</code></p></li><li><p>提供docker 版本信息<br><code>docker version</code></p></li><li><p>查看docker0的网络(宿主机上操作)<br><code>ip a show docker0</code></p></li><li><p>附着到容器内部查看其内部ip:<br><code>ip a show eth0</code></p></li><li><p> 登录到Docker registry.<br><code>docker login </code></p></li><li><p>删除所有未使用的容器,未使用的网络和悬空图像(也可单独删除)<br><code>docker system prune</code></p></li></ul><h2 id="4-念念不忘"><a href="#4-念念不忘" class="headerlink" title="4. 念念不忘"></a>4. 念念不忘</h2><ul><li><a href="https://gist.github.com/garystafford/f0bd5f696399d4d7df0f">国外友人自己整理的有用的Docker命令</a></li></ul>]]></content>
<categories>
<category>Docker</category>
</categories>
<tags>
<tag>Linux</tag>
<tag>Docker</tag>
</tags>
</entry>
<entry>
<title>PostgreSQL 常用命令记录</title>
<link href="/2019/07/10/postgresql-tutorial/"/>
<url>/2019/07/10/postgresql-tutorial/</url>
<content type="html"><![CDATA[<p>从之前看的<code>Two Scoops Press Two Scoops of Django 1.11</code>书籍或者是官网的教程,又或者最近在github查找django-docker人家的代码时看到的,都是用的PostgreSQL作为Django的数据库,平常都是用的Mysql,是不是验证了”国外大多用PostgresSQL,国内大多用Mysql“这句话,我对这个数据库比较陌生,但是既然官网都主推了,相对Mysql也有额外特色功能,SQL语句大多是通用的,不如把其他的基础使用语法记录一下,也算入门了。</p><h2 id="1-好的开始"><a href="#1-好的开始" class="headerlink" title="1. 好的开始"></a>1. 好的开始</h2><ul><li><p>创建一个新的PostgreSQL角色并输入密码,可选选项查看<a href="https://www.postgresql.org/docs/11/app-createuser.html">createuser文档</a><br><code>createuser -P -e dbuser</code></p><blockquote><p><code>-e</code>选项为创建完成过后的通知消息,下同</p></blockquote></li><li><p>创建数据库,可选选项查看<a href="https://www.postgresql.org/docs/11/app-createdb.html">createdb选项</a><br><code>createdb -e -O dbuser mydb</code></p></li><li><p>通过sql文件导入数据<br><code>psql mydb < mydb.sql</code></p></li><li><p>如果数据库存在则删除该数据库<br><code>dropdb --if-exists mydb</code></p></li><li><p>如果数据用户存在则删除该用户<br><code>dropuser --if-exists dbuser</code></p></li></ul><p>也可以用交互的方法</p><figure class="highlight bash"><table><tr><td class="gutter"><div class="code-wrapper"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></div></td><td class="code"><pre><code class="hljs bash"><span class="hljs-comment"># su postgres first login postgres DB</span><br>psql<br><span class="hljs-comment"># create user</span><br>CREATE USER dbuser WITH PASSWORD <span class="hljs-string">'password'</span>;<br><span class="hljs-comment"># create db</span><br>CREATE DATABASE exampledb OWNER dbuser;<br><span class="hljs-comment"># grant all privileges</span><br>GRANT ALL PRIVILEGES ON DATABASE exampledb to dbuser;<br><span class="hljs-comment"># login exampledb</span><br>psql -U dbuser -d exampledb -h 127.0.0.1 -p 5432<br><span class="hljs-comment"># if linux user == db user</span><br>psql exampledb<br><span class="hljs-comment"># psql exampledb < exampledb.sql</span><br></code></pre></td></tr></table></figure><ul><li>将PostgreSQL数据库导出到一个脚本文件<br><code>pg_dump</code></li><li>将所有的PostgreSQL数据库导出到一个脚本文件<br><code>pg_dumpall</code></li><li>从一个由pg_dump或pg_dumpall程序导出的脚本文件中恢复PostgreSQL数据库<br><code>pg_restore </code></li></ul><h2 id="2-快捷命令"><a href="#2-快捷命令" class="headerlink" title="2. 快捷命令"></a>2. 快捷命令</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs bash">\h:查看SQL命令的详细解释,例如 \h select<br>?:查看psql命令列表<br>\l:列出所有数据库<br>\c [database_name]:连接其他数据库<br>\d (table):列出数据库的所有表(某一具体表) <br>\dn:查看表架构<br>\di:查看索引<br>\<span class="hljs-built_in">du</span>:列出所有数据库用户<br>\conninfo:列出连接<br><span class="hljs-comment"># 更多请查看 \?</span><br></code></pre></td></tr></table></figure><h2 id="3-东张西望"><a href="#3-东张西望" class="headerlink" title="3. 东张西望"></a>3. 东张西望</h2><ul><li><a href="http://www.freeoa.net/osuport/db/postgresql-comm-used-cmd-refer_3072.html">PostgreSQL最常用命令参考</a></li><li><a href="https://blog.csdn.net/baidu_33387365/article/details/80883142">PostgreSQL:深入理解 template1 和 template0</a></li></ul>]]></content>
<categories>
<category>PostgreSQL</category>
</categories>
<tags>
<tag>Linux</tag>
<tag>SQL</tag>
</tags>
</entry>
<entry>
<title>Django+Gunicorn+Nginx配置整理</title>
<link href="/2019/07/08/django-gunicorn-nginx-config/"/>
<url>/2019/07/08/django-gunicorn-nginx-config/</url>
<content type="html"><![CDATA[<p>之前一篇文章我们梳理了WSGI与WSGI服务器的一些概念,这里主要整理下web应用部署时候的配置,包括Gunicorn以及Nginx的配置,方便以后翻阅</p><h2 id="1-Gunicorn使用及配置"><a href="#1-Gunicorn使用及配置" class="headerlink" title="1. Gunicorn使用及配置"></a>1. Gunicorn使用及配置</h2><blockquote><p>我们可以通过执行**<code>./manage.py runserver</code>**命令来运行我们的django应用程序。但我们知道它被称为开发服务器是有原因的,因为自带的开发服务器不健壮,安全问题,线程问题等等。那么,我们如何真正运行我们的应用呢?</p></blockquote><p><strong>Gunicorn,一个简单,轻便,快速的Python WSGI HTTP Server for UNIX。</strong></p><ul><li>绑定一个具体的端口<figure class="highlight python"><table><tr><td class="gutter"><div class="code-wrapper"><pre><span class="line">1</span><br></pre></div></td><td class="code"><pre><code class="hljs python">gunicorn --bind <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span>:<span class="hljs-number">8030</span> myproject.wsgi<br></code></pre></td></tr></table></figure></li><li>增加请求服务的worker数量<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs python">gunicorn --workers <span class="hljs-number">3</span> myproject.wsgi<br></code></pre></td></tr></table></figure></li><li>以守护进程模式来运行<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs python">gunicorn --daemon myproject.wsgi<br></code></pre></td></tr></table></figure></li><li>或者把上面三个组合起来这是常用的<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs python">gunicorn -D -b <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span>:<span class="hljs-number">8030</span> -w <span class="hljs-number">3</span> myproject.wsgi<br></code></pre></td></tr></table></figure></li><li>或者我更喜欢把配置写到配置文件里<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs python">gunicorn -c /path/to/config/gunicorn.conf.py myproject.wsgi<br></code></pre></td></tr></table></figure></li></ul><p><strong>gunicorn.conf.py</strong> 配置文件模板如下,具体可以去看<a href="http://docs.gunicorn.org/en/stable/">Gunicorn官方文档</a> :</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">import</span> logging<br><span class="hljs-keyword">import</span> logging.handlers<br><span class="hljs-keyword">from</span> logging.handlers <span class="hljs-keyword">import</span> WatchedFileHandler<br><span class="hljs-keyword">import</span> os<br><span class="hljs-keyword">import</span> multiprocessing<br><br>bind = <span class="hljs-string">"unix:/tmp/gunicorn.sock"</span> <span class="hljs-comment">#绑定的ip与端口</span><br>backlog = <span class="hljs-number">512</span> <span class="hljs-comment">#监听队列数量,64-2048</span><br><span class="hljs-comment">#chdir = '/home/test/server/bin' #gunicorn要切换到的目的工作目录</span><br>worker_class = <span class="hljs-string">'sync'</span> <span class="hljs-comment">#使用gevent模式,还可以使用sync 模式,默认的是sync模式</span><br>workers = <span class="hljs-number">4</span> <span class="hljs-comment"># multiprocessing.cpu_count() #进程数</span><br>threads = <span class="hljs-number">16</span> <span class="hljs-comment">#multiprocessing.cpu_count()*4 #指定每个进程开启的线程数</span><br>loglevel = <span class="hljs-string">'info'</span> <span class="hljs-comment">#日志级别,这个日志级别指的是错误日志的级别,而访问日志的级别>无法设置</span><br><span class="hljs-comment">#access_log_format = '%(t)s %(p)s %(h)s "%(r)s" %(s)s %(L)s %(b)s %(f)s" "%(a)s"'</span><br>access_log_format = <span class="hljs-string">'%(t)s %(p)s %(h)s "%(r)s" %(s)s'</span><br><br><span class="hljs-comment">#accesslog = "./gunicorn_access.log" #访问日志文件</span><br><span class="hljs-comment">#errorlog = "./gunicorn_error.log" #错误日志文件</span><br>accesslog = <span class="hljs-string">"-"</span> <span class="hljs-comment">#访问日志文件,"-" 表示标准输出</span><br>errorlog = <span class="hljs-string">"-"</span> <span class="hljs-comment">#错误日志文件,"-" 表示标准输出</span><br></code></pre></td></tr></table></figure><h2 id="2-一对好基友:Supervisor与Gunicorn"><a href="#2-一对好基友:Supervisor与Gunicorn" class="headerlink" title="2. 一对好基友:Supervisor与Gunicorn"></a>2. 一对好基友:Supervisor与Gunicorn</h2><p>我们可以用supervisor来管理Gunicorn服务,配置模板如下。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><code class="hljs python">[program:orderlunch]<br><br>command= pipenv run gunicorn -c gunicorn.conf.py OrderLunch.wsgi<br>directory=/home/pi/OrderLunchEnv/OrderLunch<br><br>user=pi<br>numprocs=<span class="hljs-number">1</span><br><br>stdout_logfile=/var/log/supervisor/orderlunch.log<br>stderr_logfile=/var/log/supervisor/orderlunch_error.log<br><br>autostart= true<br>autorestart=true<br><br>stopwaitsecs = <span class="hljs-number">600</span><br>killasgroup=true<br>priority=<span class="hljs-number">999</span><br><br><br><br></code></pre></td></tr></table></figure><h2 id="3-Gunicorn如何处理静态文件?"><a href="#3-Gunicorn如何处理静态文件?" class="headerlink" title="3. Gunicorn如何处理静态文件?"></a>3. Gunicorn如何处理静态文件?</h2><blockquote><p>在用Gunicorn跑Django的时候,比较郁闷的是静态文件的处理,即使在settings设置DEBUG=True,静态文件也不会正常显示.生产环境下一般不会裸跑Gunicorn,一般都会在前面放一个Nginx反代到Gunicorn,而静态文件直接交给Nginx处理.<br>但是如heroku,coding.net的演示平台这种PaaS就不能自己配置反向代理,怎么样设置wsgi才能正常处理静态文件呢.这里总结下处理这个问题的经验。</p></blockquote><p>以上问题来自<a href="https://zhu327.github.io/2015/09/29/gunicorn%E8%BF%90%E8%A1%8Cdjango%E6%97%B6%E9%9D%99%E6%80%81%E6%96%87%E4%BB%B6%E5%A4%84%E7%90%86/">Gunicorn运行Django时静态文件处理</a> </p><p>这里提到了两个方法来解决此问题:</p><ul><li><strong>1. 强制使用Django自带的静态文件处理器</strong></li></ul><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> django.conf <span class="hljs-keyword">import</span> settings<br><span class="hljs-keyword">from</span> django.conf.urls.static <span class="hljs-keyword">import</span> static<br><br>urlpatterns = [<br> <span class="hljs-comment"># ... the rest of your URLconf goes here ...</span><br>] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)<br></code></pre></td></tr></table></figure><ul><li><strong>2. 使用第三方库Whitenoise用来处理wsgi app静态文件</strong><br>这个方法比较好一点,用Whitenoise搭配Gunicorn,完全能在没有Nginx的情况下在生产环境中处理静态文件,配置非常简单,可以参考 <a href="http://whitenoise.evans.io/en/stable/">Whitenoise官方文档</a></li></ul><h2 id="4-Nginx与Gunicorn配置记录"><a href="#4-Nginx与Gunicorn配置记录" class="headerlink" title="4. Nginx与Gunicorn配置记录"></a>4. Nginx与Gunicorn配置记录</h2><p>网上的教程鱼龙混杂,配置各个都不太一样,但大差不差,有些能用有些不能用,踩了蛮多坑,把自己的第一次成功配置贴在下面,方便自己以后回来查看。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><code class="hljs shell">server {<br> listen 80;<br> server_name 0.0.0.0;<br> access_log /var/log/nginx/orderlunch_access_log;<br> error_log /var/log/nginx/orderlunch_error_log;<br> location = /favicon.ico { access_log off ; log_not_found off ; }<br> location / {<br> include proxy_params;<br> proxy_pass http://unix:/tmp/gunicorn.sock;<br> }<br> location /static {<br> alias /home/pi/OrderLunchEnv/OrderLunch/collected_static;<br> # 这里注意alias与root的区别<br> }<br>}<br><br></code></pre></td></tr></table></figure><p>这里gunicorn我用的是<code>.sock</code>,所以相应的nginx这边的proxy_pass也要用sock,当然也可以用,http+port的形式。<br>配置到能访问网址,不止是一个配置文件就能解决的,完整的过程看这边:<a href="http://rahmonov.me/posts/run-a-django-app-with-nginx-and-gunicorn/">run-a-django-app-with-nginx-and-gunicorn</a> 以及<a href="https://www.cnblogs.com/nanrou/p/7026802.html">初次部署django+gunicorn+nginx</a> ,用来看看一些概念和思路。</p><h2 id="5-更好的解决方式:Docker"><a href="#5-更好的解决方式:Docker" class="headerlink" title="5. 更好的解决方式:Docker"></a>5. 更好的解决方式:Docker</h2><p>研究的过程中,接触到了Docker。我们可以用pipenv保持python开发环境的一致,干净,但是当涉及到一些系统级别的服务时,比如redis作为消息队列 ,nginx作为代理服务器时,我们怎么做到隔离呢,那就是用Docker。这真的是太方便了!有时候因为Ubuntu系统版本的问题,或者之前设置过了这会想删除之前项目部署重新设置,可能存在一系列让人烦躁的事情,而Docker直接搞一个Nginx容器用来做反向代理,用一个redis容易作为消息队列的存储,干净的容器,不用了将容器直接关闭或者清除,也不用操心各个容器间的内部网络,都实现好了。<br>输入关键字docker django能在gihtub找到很多别人写的。我找到一个自己认为较好的,fork之后然后进行了一些修改(比如生成docker 镜像时,新增了国内下载源等),项目地址在这<a href="https://github.com/fantasyhh/docker-django">docker-django</a> ,可以先按照教程跑起来然后查看内部的实现原理,会发现docker真的乃一部署神器!</p><h2 id="6-总结"><a href="#6-总结" class="headerlink" title="6. 总结"></a>6. 总结</h2><p>这一配置篇与上一章的概念篇,把我最近关于对wsgi以及部署方面的一些概念进行了总结,也算解了心中一大困惑,部署是常见的事,记录下来,有规矩,才有方圆~</p>]]></content>
<categories>
<category>Python</category>
</categories>
<tags>
<tag>Django</tag>
<tag>Gunicorn</tag>
<tag>Nginx</tag>
</tags>
</entry>
<entry>
<title>梳理Python WSGI与WSGI服务器等概念</title>
<link href="/2019/06/27/python3-wsgi-concept/"/>
<url>/2019/06/27/python3-wsgi-concept/</url>
<content type="html"><![CDATA[<p>经常接触Django,问自己一个问题:Django项目中与setttings文件同目录的wsgi.py是干什么用的?这个问题熟悉而又陌生,wsgi.py这个文件可以说无时无刻不在我们眼皮底下出现,但又对它真正的功能作用知之甚少。更早的时候,将Django部署在Apache上会用到mod-wsgi,这是两者之间通信的媒介。忙碌中整理一些概念与教程来梳理这些概念,解惑也。</p><h2 id="1-首先弄清下面几个概念"><a href="#1-首先弄清下面几个概念" class="headerlink" title="1. 首先弄清下面几个概念"></a>1. 首先弄清下面几个概念</h2><p><strong>WSGI</strong>:全称是Web Server Gateway Interface,WSGI不是服务器,python模块,框架,API或者任何软件,只是一种规范,描述web server如何与web application通信的规范。server和application的规范在PEP 3333中有具体描述。要实现WSGI协议,必须同时实现web server和web application,当前运行在WSGI协议之上的web框架有Bottle, Flask, Django。</p><p><img src="/images/wsgi/wsgi.png" alt=" " title="wsgi"></p><blockquote><p>WSGI存在的目的有两个:</p></blockquote><ol><li>让Web服务器知道如何调用Python应用程序,并且把用户的请求告诉应用程序。</li><li>让Python应用程序知道用户的具体请求是什么,以及如何返回结果给Web服务器。</li></ol><p><strong>uwsgi</strong>:与WSGI一样是一种通信协议,<strong>是uWSGI服务器的独占协议</strong>,用于定义传输信息的类型(type of information),每一个uwsgi packet前4byte为传输信息类型的描述,与WSGI协议是两种东西,据说该协议是fcgi协议的10倍快。</p><p><strong>uWSGI</strong>:是一个web服务器,实现了WSGI协议、uwsgi协议、http协议等。</p><p><strong>WSGI协议主要包括server(或者gateway)和application(或者framework)两部分:</strong></p><ul><li><p>WSGI server负责从客户端接收请求,将request转发给application,将application返回的response返回给客户端;</p></li><li><p>WSGI application接收由server转发的request,处理请求,并将处理结果返回给server。application中可以包括多个栈式的中间件(middlewares),这些中间件需要同时实现server与application,因此可以在WSGI服务器与WSGI应用之间起调节作用:<strong>对服务器来说,中间件扮演应用程序,对应用程序来说,中间件扮演服务器</strong>。</p></li></ul><blockquote><p> WSGI Middleware(中间件)也是WSGI规范的一部分。上一章我们已经说明了WSGI的两个角色:server和application。那么middleware是一种运行在server和application中间的应用(一般都是Python应用)。middleware同时具备server和application角色,对于server来说,它是一个application;对于application来说,它是一个server。middleware并不修改server端和application端的规范,只是同时实现了这两个角色的功能而已。</p></blockquote><p><img src="/images/wsgi/middleware.png" alt="middleware"> </p><p>WSGI协议其实是定义了一种server与application解耦的规范,即可以有多个实现WSGI server的服务器,也可以有多个实现WSGI application的框架,那么就可以选择任意的server和application组合实现自己的web应用。例如<strong>uWSGI和Gunicorn</strong>都是实现了WSGI server协议的服务器,<strong>Django,Flask</strong>是实现了WSGI application协议的web框架,可以根据项目实际情况搭配使用,详细的我们后面说。</p><h2 id="2-WSGI实现原理以及”兄弟”CGI-FastCGI"><a href="#2-WSGI实现原理以及”兄弟”CGI-FastCGI" class="headerlink" title="2. WSGI实现原理以及”兄弟”CGI FastCGI?"></a>2. WSGI实现原理以及”兄弟”CGI FastCGI?</h2><p>WSGI原理的内部实现,说简单不简单说复杂却不复杂,这里不细说,我们可以**<a href="http://luckylau.tech/2017/02/28/python%E7%9A%84wsgi%E7%90%86%E8%A7%A3/">用简单实例来理解Python WSGI</a> **</p><p>那么<code>CGI</code> <code>FastCGI</code>又是什么?是不是跟WSGI有什么关系?我们可以翻阅下<a href="https://blog.callmewhy.com/2015/12/07/what-is-wsgi-and-cgi/">CGI FastCGI WSGI 学习笔记 </a> 就会豁然开朗了</p><h2 id="3-WSGI服务器的选择?"><a href="#3-WSGI服务器的选择?" class="headerlink" title="3. WSGI服务器的选择?"></a>3. WSGI服务器的选择?</h2><p>上面提到<strong>uWSGI和Gunicorn</strong>都是实现了WSGI server协议的服务器,还有我之前用过的部署在Apache上mod_wsgi, 那么对于这些东西,为什么需要一个?我应该选择哪一个?</p><blockquote><p><strong>django自带的web server目的是方便开发,不是能直接放到生产环境的,直接引用django的文档。It’s intended only for use while developing. (We’re in the business of making Web frameworks, not Web servers.)</strong></p></blockquote><blockquote><p> <strong>使用Gunicorn,除非你在Windows上部署,在这种情况下使用mod_wsgi。</strong></p></blockquote><p><img src="/images/wsgi/which_wsgi_server.png" alt="which_server"> </p><p>具体的解释我这里用这篇英文原文<a href="https://djangodeployment.com/2017/01/02/which-wsgi-server-should-i-use/">Which WSGI server should I use?</a> 翻译一下来回答。</p><p>如图所示(我们可以暂时忽略掉图中的Web Server,这是下一小节要说的),Web浏览器与Web服务器通信,Web服务器又与WSGI服务器通信。该WSGI服务器不会直接与你的Django项目通信,而是导入 Django项目。它是这么做的:</p><figure class="highlight python"><table><tr><td class="gutter"><div class="code-wrapper"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></div></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> django_project.wsgi <span class="hljs-keyword">import</span> application<br>application(args)<br></code></pre></td></tr></table></figure><p>因此,从操作系统的角度来看,你的Django项目将成为WSGI服务器的一部分; 这是同一个过程。调用**<code>application()</code>**的方式由WSGI规范标准化。这个函数的接口是标准化的,这使得你可以在许多不同的WSGI服务器(如Gunicorn,uWSGI或mod_wsgi)之间进行选择,以及为什么每个服务器都可以与许多Python应用程序框架(如Django或Flask)进行交互。</p><p>mod_wsgi仅适用于Apache,我更偏向于可与Apache或nginx一起使用的方法。这样的话,使更改Web服务器变得更容易。我也发现Gunicorn更容易设置和维护。</p><p>我使用了uWSGI几年,并且被它的功能所震撼。其中许多都复制了Apache或nginx或堆栈其他部分中已存在的功能,因此很少需要它们。它的文档有点混乱。开发人员自己承认:“我们尽力提供良好的文档,但这是一项艰苦的工作。对不起。“我记得每周都会遇到问题并且每次花费数小时来解决问题。</p><p>另一方面,Gunicorn正是你想要的恰到好处的功能。它很简单,工作正常。所以我推荐它,除非在你的特殊情况下有一个令人信服的理由使用其中一个。</p><p>uWSGI和Gunicorn没法在Windows中运行,因此如果你在Windows上部署那就使用Apache + mod_wsgi。</p><h2 id="4-既然有了WSGI-Server,为什么我们还需要Nginx(Web-Server)"><a href="#4-既然有了WSGI-Server,为什么我们还需要Nginx(Web-Server)" class="headerlink" title="4. 既然有了WSGI Server,为什么我们还需要Nginx(Web Server)?"></a>4. 既然有了WSGI Server,为什么我们还需要Nginx(Web Server)?</h2><blockquote><p>如果只有一个应用,不需要负载均衡;只提供api服务,没有静态文件;不需要额外的访问控制等功能这样是不是就不需要nginx等反向代理?答案是需要。nginx可以缓冲请求和响应。如果让Gunicorn直接提供服务,浏览器发起一个请求,鉴于浏览器和网络情况都是未知的,http请求的发起过程可能比较慢,而Gunicorn只能等待请求发起完成后,才去真正处理请求,处理完成后,等客户端完全接收请求后,才继续下一个。nginx缓存客户端发起的请求,直到收完整个请求,转发给Gunicorn,等Gunicorn处理完成后,拿到响应,再发给客户端,这个流程是nginx擅长处理,而Gunicorn不擅长处理的。因此将Gunicorn置于nginx后面,可以有效提高Gunicorn的处理能力。 ——摘自知乎<a href="https://www.zhihu.com/question/38528616">Nginx、Gunicorn在服务器中分别起什么作用?</a> 某回答</p></blockquote><p><img src="/images/wsgi/nginx+gunicorn.png" alt="nginx+gunicorn"></p><p>当我们为Gunicorn运行我们的django应用程序而开心时,然而,最后,我们看到管理面板的样式已经消失了。原因是Gunicorn是一个应用程序服务器,只运行应用程序(在我们的例子中是django app)和django,正如我们所知,<strong>除了开发之外,它不提供静态文件</strong>。Nginx来救援!它将是Gunicorn的反向代理。到底是什么反向代理?好问题!我们都知道VPN是什么,对吧?我们使用它们访问某些因某种原因被阻止的网站。在这种情况下,我们通过VPN访问该网站:我们 - > VPN - >某些网站。这种代理称为正向代理。至于反向代理,可以将它们视为强制代理。例如,用户正在尝试访问我们在gunicorn中运行的django应用程序。它认为它正在直接访问该应用程序。然而,真正的过程是它首先访问Nginx服务器,该服务器决定下一步该做什么,如果用户正在访问静态文件,Nginx服务器将自行提供服务。否则,它会将其重定向到Gunicorn。简单来说,<strong>http请求将由Gunicorn处理,而静态处理则由Nginx处理。这就是我们需要Nginx的原因</strong>。<br>除此之外,<strong>Nginx还提高了性能,可靠性,安全性和规模</strong>。</p><h2 id="5-总结"><a href="#5-总结" class="headerlink" title="5. 总结"></a>5. 总结</h2><p>这几天晚上睡觉前花了一点时间来对这些概念进行了一些整理,收获颇多。要想真正的成为一名Web开发,并不是简单的套用框架写写逻辑层,而是框架背后的原理与流程,这才是重中之重,了解了这些,所有的类似框架以后上手都能快半分。接下去,会对Gunicorn,Nginx的配置文件做个记录(这中间也踩了点坑),再往后就是Nginx与docker的理解与实践了,加油~</p>]]></content>